diff --git a/docs/REVISIONS-56-SERIES.TXT b/docs/REVISIONS-56-SERIES.TXT index 78e0084c5..dc9597740 100644 --- a/docs/REVISIONS-56-SERIES.TXT +++ b/docs/REVISIONS-56-SERIES.TXT @@ -472,4 +472,7 @@ Changed: Updated internal SQLite libs v3.17.0 to v3.18.0. 16-04-2017, Coruja Fixed: Poisoning spell not working correctly. -Fixed: Poisoning effect causing damage on items equipped on char. \ No newline at end of file +Fixed: Poisoning effect causing damage on items equipped on char. + +19-04-2017, Coruja +Changed: Huge improvements on internal chat system, now it works fine on both SA (new chat system) and pre-SA (old chat system) clients. \ No newline at end of file diff --git a/src/common/grayproto.h b/src/common/grayproto.h index 753cc91e0..c68896f8c 100644 --- a/src/common/grayproto.h +++ b/src/common/grayproto.h @@ -850,62 +850,96 @@ enum EXTCMD_TYPE enum CHATMSG_TYPE // Chat system messages. { - CHATMSG_NoError = -1, // Our return code for no error, but no message either - CHATMSG_Error, // 0x00 - Error - CHATMSG_AlreadyIgnoringMax, // 0x01 - You are already ignoring the maximum amount of people - CHATMSG_AlreadyIgnoringPlayer, // 0x02 - You are already ignoring - CHATMSG_NowIgnoring, // 0x03 - You are now ignoring - CHATMSG_NoLongerIgnoring, // 0x04 - You no longer ignoring - CHATMSG_NotIgnoring, // 0x05 - You are not ignoring - CHATMSG_NoLongerIgnoringAnyone, // 0x06 - You are no longer ignoring anyone - CHATMSG_InvalidConferenceName, // 0x07 - That is not a valid conference name - CHATMSG_AlreadyAConference, // 0x08 - There is already a conference of that name - CHATMSG_MustHaveOps, // 0x09 - You must have operator status to do this - CHATMSG_ConferenceRenamed, // 0x0A - Conference renamed to . - CHATMSG_MustBeInAConference, // 0x0B - You must be in a conference to do this. To join a conference, select one from the Conference menu - CHATMSG_NoPlayer, // 0x0C - There is no player named - CHATMSG_NoConference, // 0x0D - There is no conference named - CHATMSG_IncorrectPassword, // 0x0E - That is not the correct password - CHATMSG_PlayerIsIgnoring, // 0x0F - has chosen to ignore you. None of your messages to them will get through - CHATMSG_RevokedSpeaking, // 0x10 - The moderator of this conference has not given you speaking priveledges. - CHATMSG_ReceivingPrivate, // 0x11 - You can now receive private messages - CHATMSG_NoLongerReceivingPrivate, // 0x12 - You will no longer receive private messages. Those who send you a message will be notified that you are blocking incoming messages - CHATMSG_ShowingName, // 0x13 - You are now showing your character name to any players who inquire with the whois command - CHATMSG_NotShowingName, // 0x14 - You are no long showing your character name to any players who inquire with the whois command - CHATMSG_PlayerIsAnonymous, // 0x15 - is remaining anonymous - CHATMSG_PlayerNotReceivingPrivate, // 0x16 - has chosen to not receive private messages at the moment - CHATMSG_PlayerKnownAs, // 0x17 - is known in the lands of britania as . - CHATMSG_PlayerIsKicked, // 0x18 - has been kicked out of the conference - CHATMSG_ModeratorHasKicked, // 0x19 - , a conference moderator, has kicked you out of the conference - CHATMSG_AlreadyInConference, // 0x1A - You are already in the conference - CHATMSG_PlayerNoLongerModerator, // 0x1B - is no longer a conference moderator - CHATMSG_PlayerIsAModerator, // 0x1C - is now a conference moderator - CHATMSG_RemovedListModerators, // 0x1D - has removed you from the list of conference moderators. - CHATMSG_YouAreAModerator, // 0x1E - has made you a conference moderator - CHATMSG_PlayerNoSpeaking, // 0x1F - no longer has speaking priveledges in this conference. - CHATMSG_PlayerNowSpeaking, // 0x20 - now has speaking priveledges in this conference - CHATMSG_ModeratorRemovedSpeaking, // 0x21 - , a channel moderator, has removed your speaking priveledges for this conference. - CHATMSG_ModeratorGrantSpeaking, // 0x22 - , a channel moderator, has granted you speaking priveledges for this conference. - CHATMSG_SpeakingByDefault, // 0x23 - From now on, everyone in the conference will have speaking priviledges by default. - CHATMSG_ModeratorsSpeakDefault, // 0x24 - From now on, only moderators in this conference will have speaking priviledges by default. - CHATMSG_PlayerTalk, // 0x25 - : - CHATMSG_PlayerEmote, // 0x26 - ** - CHATMSG_PlayerPrivate, // 0x27 - []: - CHATMSG_PasswordChanged, // 0x28 - The password to the conference has been changed - CHATMSG_NoMorePlayersAllowed, // 0x29 - Sorry--the conference named is full and no more players are allowed in. - CHATMSG_Banning, // 0x2A - You are banning from this conference. - CHATMSG_ModeratorHasBanned, // 0x2B - , a conference moderator, has banned you from the conference. - CHATMSG_Banned, // 0x2C - You have been banned from this conference. - - CHATMSG_SendChannelName = 0x3E8, // Message to send a channel name and mode to client's channel list - CHATMSG_RemoveChannelName, // Message to remove a channel name from client's channel list - CHATMSG_GetChatName = 0x3EB, // Ask the client for a permanent chat name - CHATMSG_CloseChatWindow, // Close the chat system dialogs on the client??? - CHATMSG_OpenChatWindow, // Open the chat system dialogs on the client - CHATMSG_SendPlayerName, // Message to add a player out to client (prefixed with a "1" if they are the moderator or a "0" if not - CHATMSG_RemoveMember, // Message to remove a player from clients channel member list - CHATMSG_ClearMemberList, // This message clears the list of channel participants (for when leaving a channel) - CHATMSG_UpdateChannelBar, // This message changes the name in the channel name bar + // Messages (client <- server) + CHATMSG_AlreadyIgnoringMax = 0x1, // 0x01 - You are already ignoring the maximum number of people. + CHATMSG_AlreadyIgnoringPlayer, // 0x02 - You are already ignoring %1. + CHATMSG_NowIgnoring, // 0x03 - You are now ignoring %1. + CHATMSG_NoLongerIgnoring, // 0x04 - You are no longer ignoring %1. + CHATMSG_NotIgnoring, // 0x05 - You are not ignoring %1. + CHATMSG_NoLongerIgnoringAnyone, // 0x06 - You are no longer ignoring anyone. + CHATMSG_InvalidConferenceName, // 0x07 - That is not a valid conference name. + CHATMSG_DuplicatedConferenceName, // 0x08 - There is already a conference of that name. + CHATMSG_MustHaveOps, // 0x09 - You must have operator status to do this. + CHATMSG_ConferenceRenamed, // 0x0A - Conference %1 renamed to %2. + CHATMSG_MustBeInAConference, // 0x0B - You must be in a conference to do this. To join a conference, select one from the Conference menu. + CHATMSG_NoPlayer, // 0x0C - There is no player named '%1'. + CHATMSG_NoConference, // 0x0D - There is no conference named '%1'. + CHATMSG_IncorrectPassword, // 0x0E - That is not the correct password. + CHATMSG_PlayerIsIgnoring, // 0x0F - %1 has chosen to ignore you. None of your messages to them will get through. + CHATMSG_RevokedSpeaking, // 0x10 - The moderator of this conference has not given you speaking privileges. + CHATMSG_ReceivingPrivateMessages, // 0x11 - You can now receive private messages. + CHATMSG_NoLongerReceivingPrivateMessages, // 0x12 - You will no longer receive private messages. Those who send you a message will be notified that you are blocking incoming messages. + CHATMSG_ShowingName, // 0x13 - You are now showing your character name to any players who inquire with the whois command. + CHATMSG_NoLongerShowingName, // 0x14 - You are no longer showing your character name to any players who inquire with the whois command. + CHATMSG_PlayerIsAnonymous, // 0x15 - %1 is remaining anonymous. + CHATMSG_PlayerNotReceivingPrivateMessages, // 0x16 - %1 has chosen to not receive private messages at the moment. + CHATMSG_PlayerKnownAs, // 0x17 - %1 is known in the lands of Britannia as %2. + CHATMSG_PlayerKicked, // 0x18 - %1 has been kicked out of the conference. + CHATMSG_ModeratorHasKicked, // 0x19 - %1, a conference moderator, has kicked you out of the conference. + CHATMSG_AlreadyInConference, // 0x1A - You are already in the conference '%1'. + CHATMSG_PlayerNoLongerModerator, // 0x1B - %1 is no longer a conference moderator. + CHATMSG_PlayerIsModerator, // 0x1C - %1 is now a conference moderator. + CHATMSG_RemovedListModerators, // 0x1D - %1 has removed you from the list of conference moderators. + CHATMSG_YouAreAModerator, // 0x1E - %1 has made you a conference moderator. + CHATMSG_PlayerNoSpeaking, // 0x1F - %1 no longer has speaking privileges in this conference. + CHATMSG_PlayerNowSpeaking, // 0x20 - %1 now has speaking privileges in this conference. + CHATMSG_ModeratorHasRemovedSpeakPriv, // 0x21 - %1, a conference moderator, has removed your speaking privileges for this conference. + CHATMSG_ModeratorHasGrantedSpeakPriv, // 0x22 - %1, a conference moderator, has granted you speaking privileges in this conference. + CHATMSG_EveryoneSpeakingPrivByDefault, // 0x23 - From now on, everyone in the conference will have speaking privileges by default. + CHATMSG_ModeratorSpeakingPrivByDefault, // 0x24 - From now on, only moderators will have speaking privileges in this conference by default. + CHATMSG_PlayerMessage, // 0x25 - : + CHATMSG_PlayerEmote, // 0x26 - * * + CHATMSG_PlayerPrivateMessage, // 0x27 - []: + CHATMSG_PasswordChanged, // 0x28 - The password to the conference has been changed. + CHATMSG_ConferenceIsFull, // 0x29 - Sorry--the conference named '%1' is full and no more players are allowed in. + CHATMSG_Banning, // 0x2A - You are banning %1 from this conference. + CHATMSG_ModeratorHasBanned, // 0x2B - %1, a conference moderator, has banned you from the conference. + CHATMSG_Banned, // 0x2C - You have been banned from this conference. + CHATMSG_TrialAccOnlyHelpChannel = 0x3F5, // 0x3F5 - Trial accounts may only join the Help channel. + CHATMSG_TrialAccNoCustomChannel, // 0x3F6 - Trial accounts may not participate in custom channels. + + // Actions (client -> server) // OLD CHAT NEW CHAT + CHATACT_ChangeChannelPassword = 0x41, // x + CHATACT_LeaveChannel = 0x43, // x + CHATACT_LeaveChat = 0x58, // x + CHATACT_ChannelMessage = 0x61, // x x + CHATACT_JoinChannel = 0x62, // x x + CHATACT_CreateChannel = 0x63, // x x + CHATACT_RenameChannel = 0x64, // x + CHATACT_PrivateMessage = 0x65, // x + CHATACT_AddIgnore = 0x66, // x + CHATACT_RemoveIgnore = 0x67, // x + CHATACT_ToggleIgnore = 0x68, // x + CHATACT_AddVoice = 0x69, // x + CHATACT_RemoveVoice = 0x6A, // x + CHATACT_ToggleVoice = 0x6B, // x + CHATACT_AddModerator = 0x6C, // x + CHATACT_RemoveModerator = 0x6D, // x + CHATACT_ToggleModerator = 0x6E, // x + CHATACT_EnablePrivateMessages = 0x6F, // x + CHATACT_DisablePrivateMessages = 0x70, // x + CHATACT_TogglePrivateMessages = 0x71, // x + CHATACT_ShowCharacterName = 0x72, // x + CHATACT_HideCharacterName = 0x73, // x + CHATACT_ToggleCharacterName = 0x74, // x + CHATACT_WhoIs = 0x75, // x + CHATACT_Kick = 0x76, // x + CHATACT_EnableDefaultVoice = 0x77, // x + CHATACT_DisableDefaultVoice = 0x78, // x + CHATACT_ToggleDefaultVoice = 0x79, // x + CHATACT_EmoteMessage = 0x7A, // x + + // Commands (client <- server) // OLD CHAT NEW CHAT + CHATCMD_AddChannel = 0x3E8, // x x + CHATCMD_RemoveChannel, // x x + CHATCMD_SetChatName = 0x3EB, // x + CHATCMD_CloseChatWindow, // x + CHATCMD_OpenChatWindow, // x + CHATCMD_AddMemberToChannel, // x + CHATCMD_RemoveMemberFromChannel, // x + CHATCMD_ClearMembers, // x + CHATCMD_JoinedChannel, // x x + CHATCMD_LeftChannel = 0x3F4 // x }; enum INPVAL_STYLE // for the various styles for InpVal box. @@ -987,6 +1021,8 @@ enum NOTO_TYPE // Client versions (expansions) #define MINCLIVER_T2A 1253502 // minimum client to activate T2A packets (1.25.35b) +//#define MINCLIVER_R 2000000 // minimum client to activate R packets (2.0.0a) +//#define MINCLIVER_TD 3000000 // minimum client to activate TD packets (3.0.0a) #define MINCLIVER_LBR 3000702 // minimum client to activate LBR packets (3.0.7b) #define MINCLIVER_AOS 4000000 // minimum client to activate AOS packets (4.0.0a) #define MINCLIVER_SE 4000500 // minimum client to activate SE packets (4.0.5a) @@ -1003,11 +1039,14 @@ enum NOTO_TYPE #define MINCLIVER_STATUS_V6 7003000 // minimum client to receive v6 of 0x11 packet (7.0.30.0) // Client versions (behaviours) -#define MINCLIVER_CHECKWALKCODE 1260000 // minimum client to use walk crypt codes for fastwalk prevention (obsolete) +//#define MINCLIVER_CHECKWALKCODE 1260000 // minimum client to use walk crypt codes for fastwalk prevention (1.26.0a) +//#define MINCLIVER_CLILOCS 1260202 // minimum client to use clilocs (1.26.2b) #define MINCLIVER_PADCHARLIST 3000010 // minimum client to pad character list to at least 5 characters #define MINCLIVER_CLOSEDIALOG 4000400 // minimum client where close dialog does not trigger a client response #define MINCLIVER_NEWVERSIONING 5000605 // minimum client to use the new versioning format (after 5.0.6e it change to 5.0.6.5) #define MINCLIVER_CONTAINERGRID 6000107 // minimum client to use container grid index (6.0.1.7) +#define MINCLIVER_NEWCHATSYSTEM 7000401 // minimum client to use the new chat system (7.0.4.1) - classic client +#define MINCLIVER_NEWCHATSYSTEM_EC 4000400 // minimum client to use the new chat system (4.0.4.0) - enhanced client // Client versions (packets) #define MINCLIVER_STATLOCKS 4000100 // minimum client to receive 0xBF.0x19.0x02 packet @@ -1025,19 +1064,21 @@ enum NOTO_TYPE enum TALKMODE_TYPE // Modes we can talk/bark in. { - TALKMODE_SYSTEM = 0, // 0 = Normal system message - TALKMODE_PROMPT, // 1 = Display as system prompt - TALKMODE_EMOTE, // 2 = *smiles* at object (client shortcut: :+space) - TALKMODE_SAY, // 3 = A chacter speaking. - TALKMODE_OBJ, // 4 = At object - TALKMODE_NOTHING, // 5 = Does not display - TALKMODE_ITEM, // 6 = text labeling an item. Preceeded by "You see" - TALKMODE_NOSCROLL, // 7 = As a status msg. Does not scroll - TALKMODE_WHISPER, // 8 = Only those close can here. (client shortcut: ;+space) - TALKMODE_YELL, // 9 = Can be heard 2 screens away. (client shortcut: !+space) - TALKMODE_SPELL, // 10 = Used by spells - TALKMODE_GUILD = 0xd, // 13 = Used by guild chat (client shortcut: \) - TALKMODE_ALLIANCE, // 14 = Used by alliance chat (client shortcut: shift+\) + TALKMODE_SYSTEM = 0, // 0 = Normal system message + TALKMODE_PROMPT, // 1 = Display as system prompt + TALKMODE_EMOTE, // 2 = *smiles* at object (client shortcut: :+space) + TALKMODE_SAY, // 3 = A chacter speaking. + TALKMODE_OBJ, // 4 = At object + TALKMODE_NOTHING, // 5 = Does not display + TALKMODE_ITEM, // 6 = text labeling an item. Preceeded by "You see" + TALKMODE_NOSCROLL, // 7 = As a status msg. Does not scroll + TALKMODE_WHISPER, // 8 = Only those close can here. (client shortcut: ;+space) + TALKMODE_YELL, // 9 = Can be heard 2 screens away. (client shortcut: !+space) + TALKMODE_SPELL, // 10 = Used by spells WOP + TALKMODE_GUILD = 0xD, // 13 = Used by guild chat (client shortcut: \) + TALKMODE_ALLIANCE, // 14 = Used by alliance chat (client shortcut: shift+\) + TALKMODE_GM_COMMAND, // 15 = GM command input (client shortcut: .). Only available when using GM body (CREID_EQUIP_GM_ROBE) + TALKMODE_ENCODED = 0xC0, TALKMODE_BROADCAST = 0xFF }; diff --git a/src/graysvr/CChat.cpp b/src/graysvr/CChat.cpp index 1ac4ee7a9..170a78ef3 100644 --- a/src/graysvr/CChat.cpp +++ b/src/graysvr/CChat.cpp @@ -5,280 +5,394 @@ //////////////////////////////////////////////////////////////////////////// // -CChat -void CChat::EventMsg( CClient * pClient, const NCHAR * pszText, int len, CLanguageID lang ) // Text from a client +bool CChat::CreateChannel(LPCTSTR pszName, LPCTSTR pszPassword, CChatMember *pMember) { - ADDTOCALLSTACK("CChat::EventMsg"); + ADDTOCALLSTACK("CChat::CreateChannel"); + if ( !m_bAllowChannelCreation ) + { + CGString sName; + FormatName(sName, NULL, true); + pMember->SendChatMsg(CHATMSG_PlayerMessage, sName, "Conference creation is disabled."); + return false; + } + else if ( !IsValidName(pszName, false) ) + { + pMember->SendChatMsg(CHATMSG_InvalidConferenceName); + return false; + } + else if ( IsDuplicateChannelName(pszName) ) + { + pMember->SendChatMsg(CHATMSG_DuplicatedConferenceName); + return false; + } + + // Leave current channel + CChatChannel *pChannel = pMember->GetChannel(); + if ( pChannel ) + pChannel->RemoveMember(pMember); + + // Create and join new channel + pChannel = new CChatChannel(pszName, pszPassword); + m_Channels.InsertTail(pChannel); + pChannel->SetModerator(pMember->GetChatName()); + BroadcastAddChannel(pChannel); + return true; +} + +void CChat::DeleteChannel(CChatChannel *pChannel) +{ + ADDTOCALLSTACK("CChat::DeleteChannel"); + BroadcastRemoveChannel(pChannel); + delete pChannel; +} + +bool CChat::JoinChannel(CChatMember *pMember, LPCTSTR pszChannel, LPCTSTR pszPassword) +{ + ADDTOCALLSTACK("CChat::JoinChannel"); + ASSERT(pMember); + CClient *pMemberClient = pMember->GetClient(); + ASSERT(pMemberClient); + + CChatChannel *pNewChannel = FindChannel(pszChannel); + if ( !pNewChannel ) + { + pMemberClient->addChatSystemMessage(CHATMSG_NoConference, pszChannel); + return false; + } + pszChannel = pNewChannel->m_sName; // fix case-sensitive mismatch + + CChatChannel *pCurrentChannel = pMember->GetChannel(); + if ( pCurrentChannel && (pCurrentChannel == pNewChannel) ) + { + pMember->SendChatMsg(CHATMSG_AlreadyInConference, pszChannel); + return false; + } + + if ( (pszPassword != NULL) && (strcmp(static_cast(pNewChannel->m_sPassword), pszPassword) != 0) ) + { + pMemberClient->addChatSystemMessage(CHATMSG_IncorrectPassword); + return false; + } + + if ( pNewChannel->m_Members.GetCount() >= 100 ) + { + pMemberClient->addChatSystemMessage(CHATMSG_ConferenceIsFull, pszChannel); + return false; + } + + // Leave current channel (if any) + if ( pCurrentChannel ) + pCurrentChannel->RemoveMember(pMember); + + // Join the new channel + if ( !pNewChannel->AddMember(pMember) ) + return false; + + pMemberClient->addChatSystemMessage(CHATCMD_JoinedChannel, pszChannel); + if ( !pMemberClient->m_UseNewChatSystem ) + { + pNewChannel->FillMembersList(pMember); // fill the members list on this client + pNewChannel->SendMember(pMember); // send this member to all others clients + } + return true; +} + +void CChat::KillChannels() +{ + ADDTOCALLSTACK("CChat::KillChannels"); + CChatChannel *pChannel = GetFirstChannel(); + // Delete all channels + for ( ; pChannel != NULL; pChannel = pChannel->GetNext() ) + pChannel->KickAll(); + m_Channels.Empty(); +} + +void CChat::Action(CClient *pClient, const NCHAR *pszText, int len, CLanguageID lang) +{ + ADDTOCALLSTACK("CChat::Action"); // ARGS: // len = length of the pszText string in NCHAR's. - CChatChanMember *pMe = static_cast(pClient); + if ( !(g_Cfg.m_iFeatureT2A & FEATURE_T2A_CHAT) ) + return; + + CChatMember *pMe = static_cast(pClient); CChatChannel *pChannel = pMe->GetChannel(); - pMe->m_pClient = pClient; - TCHAR szText[MAX_TALK_BUFFER * 2]; - CvtNUNICODEToSystem( szText, sizeof(szText), pszText, len ); + TCHAR szFullText[MAX_TALK_BUFFER * 2]; + CvtNUNICODEToSystem(szFullText, sizeof(szFullText), pszText, len); - // The 1st character is a command byte, join channel, private message someone, etc etc - TCHAR * szMsg = szText+1; - switch ( szText[0] ) + TCHAR *szMsg = szFullText + 1; + switch ( szFullText[0] ) // the 1st character is a command byte (join channel, leave channel, etc) { - case 'a': // a = client typed a plain message in the text entry area - { - // Check for a chat command here - if (szMsg[0] == '/') + case CHATACT_ChangeChannelPassword: // client shortcut: /pw + { + if ( !pChannel ) + goto NoConference; + + pChannel->ChangePassword(pMe, szMsg); + break; + } + case CHATACT_LeaveChannel: + { + if ( !pChannel ) + goto NoConference; + + pChannel->RemoveMember(pMe); + break; + } + case CHATACT_LeaveChat: { - DoCommand(pMe, szMsg + 1); + if ( pChannel ) + pChannel->RemoveMember(pMe); break; } - if (!pChannel) + case CHATACT_ChannelMessage: { -not_in_a_channel: + if ( !pClient->m_UseNewChatSystem && (szMsg[0] == '/') ) // check for chat commands + { + DoCommand(pMe, szMsg + 1); + break; + } + if ( pChannel ) + { + pChannel->MemberTalk(pMe, pClient->m_UseNewChatSystem ? szFullText : szMsg, lang); + break; + } + NoConference: pMe->SendChatMsg(CHATMSG_MustBeInAConference); return; } - // Not a chat command, must be speech - pChannel->MemberTalk(pMe, szMsg, lang); - break; - }; - case 'A': // A = change the channel password - { - if (!pChannel) - goto not_in_a_channel; - pChannel->ChangePassword(pMe, szMsg); - break; - }; - case 'b': // b = client joining an existing channel - { - // Look for second double quote to separate channel from password - size_t i = 1; - for (; szMsg[i] != '\0'; i++) - if (szMsg[i] == '"') - break; - szMsg[i] = '\0'; - TCHAR * pszPassword = szMsg + i + 1; - if (pszPassword[0] == ' ') // skip leading space if any - pszPassword++; - JoinChannel( pMe, szMsg + 1, pszPassword); - break; - }; - case 'c': // c = client creating (and joining) new channel - { - TCHAR * pszPassword = NULL; - size_t iMsgLength = strlen(szMsg); - for (size_t i = 0; i < iMsgLength; i++) + case CHATACT_JoinChannel: // client shortcut: /conf + { + // Look for second double quote to separate channel from password + size_t i = 1; + for ( ; szMsg[i] != '\0'; i++ ) + { + if ( szMsg[i] == '"' ) + break; + } + szMsg[i] = '\0'; + TCHAR *pszPassword = szMsg + i + 1; + if ( pszPassword[0] == ' ' ) // skip whitespaces + pszPassword++; + JoinChannel(pMe, szMsg + 1, pszPassword); + break; + } + case CHATACT_CreateChannel: // client shortcut: /newconf { - if (szMsg[i] == '{') // OK, there's a password here + TCHAR *pszPassword = NULL; + size_t iMsgLength = strlen(szMsg); + for ( size_t i = 0; i < iMsgLength; i++ ) { - szMsg[i] = 0; - pszPassword = szMsg + i + 1; - size_t iPasswordLength = strlen(pszPassword); - for (i = 0; i < iPasswordLength; i++) + if ( szMsg[i] == '{' ) // there's a password here { - if (pszPassword[i] == '}') + szMsg[i] = 0; + pszPassword = szMsg + i + 1; + size_t iPasswordLength = strlen(pszPassword); + for ( i = 0; i < iPasswordLength; i++ ) { - pszPassword[i] = '\0'; - break; + if ( pszPassword[i] == '}' ) + { + pszPassword[i] = '\0'; + break; + } } + break; } - break; } + if ( CreateChannel(szMsg, pszPassword, pMe) ) + JoinChannel(pMe, szMsg, pszPassword); + break; } - if ( CreateChannel(szMsg, pszPassword, pMe) ) - JoinChannel(pMe, szMsg, pszPassword); - break; - }; - case 'd': // d = (/rename x) rename conference - { - if (!pChannel) - goto not_in_a_channel; - pMe->RenameChannel(szMsg); - break; - }; - case 'e': // e = Send a private message to .... - { - if (!pChannel) - goto not_in_a_channel; - TCHAR buffer[2048]; - strcpy(buffer, szMsg); - // Separate the recipient from the message (look for a space) - size_t i = 0; - size_t bufferLength = strlen(buffer); - for (; i < bufferLength; i++) + case CHATACT_RenameChannel: // client shortcut: /rename + { + if ( !pChannel ) + goto NoConference; + + pMe->RenameChannel(szMsg); + break; + } + case CHATACT_PrivateMessage: // client shortcut: /msg { - if (buffer[i] == ' ') + if ( !pChannel ) + goto NoConference; + + // Split the recipient from the message (look for a space) + TCHAR buffer[2048]; + strcpy(buffer, szMsg); + size_t bufferLength = strlen(buffer); + size_t i = 0; + for ( ; i < bufferLength; i++ ) { - buffer[i] = '\0'; - break; + if ( buffer[i] == ' ' ) + { + buffer[i] = '\0'; + break; + } } + pChannel->PrivateMessage(pMe, buffer, buffer + i + 1, lang); + break; } - pChannel->SendPrivateMessage(pMe, buffer, buffer+i+1); - break; - }; - case 'f': // f = (+ignore) ignore this person - { - pMe->Ignore(szMsg); - break; - }; - case 'g': // g = (-ignore) don't ignore this person - { - pMe->DontIgnore(szMsg); - break; - }; - case 'h': // h = toggle ignoring this person - { - pMe->ToggleIgnore(szMsg); - break; - }; - case 'i': // i = grant speaking privs to this person - { - if (!pChannel) - goto not_in_a_channel; - pChannel->GrantVoice(pMe, szMsg); - break; - }; - case 'j': // j = remove speaking privs from this person - { - if (!pChannel) - goto not_in_a_channel; - pChannel->RevokeVoice(pMe, szMsg); - break; - }; - case 'k': // k = (/voice) toggle voice status - { - if (!pChannel) - goto not_in_a_channel; - pChannel->ToggleVoice(pMe, szMsg); - break; - }; - case 'l': // l = grant moderator status to this person - { - if (!pChannel) - goto not_in_a_channel; - pChannel->GrantModerator(pMe, szMsg); - break; - }; - case 'm': // m = remove moderator status from this person - { - if (!pChannel) - goto not_in_a_channel; - pChannel->RevokeModerator(pMe, szMsg); - break; - }; - case 'n': // m = toggle the moderator status for this person - { - if (!pChannel) - goto not_in_a_channel; - pChannel->ToggleModerator(pMe, szMsg); - break; - } - case 'o': // o = turn on receiving private messages - { - pMe->SetReceiving(true); - break; - } - case 'p': // p = turn off receiving private messages - { - pMe->SetReceiving(false); - break; - } - case 'q': // q = toggle receiving messages - { - pMe->ToggleReceiving(); - break; - }; - case 'r': // r = (+showname) turn on showing character name - { - pMe->PermitWhoIs(); - break; - }; - case 's': // s = (-showname) turn off showing character name - { - pMe->ForbidWhoIs(); - break; - }; - case 't': // t = toggle showing character name - { - pMe->ToggleWhoIs(); - break; - }; - case 'u': // u = who is this player - { - if (!pChannel) - goto not_in_a_channel; - pChannel->WhoIs(pMe->GetChatName(), szMsg); - break; - }; - case 'v': // v = kick this person out of the conference - { - if (!pChannel) - goto not_in_a_channel; + case CHATACT_AddIgnore: // client shortcut: +ignore + { + pMe->AddIgnore(szMsg); + break; + } + case CHATACT_RemoveIgnore: // client shortcut: -ignore + { + pMe->RemoveIgnore(szMsg); + break; + } + case CHATACT_ToggleIgnore: // client shortcut: /ignore + { + pMe->ToggleIgnore(szMsg); + break; + } + case CHATACT_AddVoice: // client shortcut: +voice + { + if ( !pChannel ) + goto NoConference; - CChatChanMember * pMember = pChannel->FindMember(szMsg); - if (!pMember) + pChannel->AddVoice(pMe, szMsg); + break; + } + case CHATACT_RemoveVoice: // client shortcut: -voice { - pMe->SendChatMsg(CHATMSG_NoPlayer, szMsg); + if ( !pChannel ) + goto NoConference; + + pChannel->RemoveVoice(pMe, szMsg); break; } + case CHATACT_ToggleVoice: // client shortcut: /voice + { + if ( !pChannel ) + goto NoConference; - pChannel->KickMember(pMe, pMember); - // If noone is left, tell the chat system to - // delete it from memory (you can kick yourself) - if (pChannel->m_Members.GetCount() <= 0) // Kicked self + pChannel->ToggleVoice(pMe, szMsg); + break; + } + case CHATACT_AddModerator: // client shortcut: +ops { - DeleteChannel(pChannel); + if ( !pChannel ) + goto NoConference; + + pChannel->AddModerator(pMe, szMsg); + break; } - break; - }; - case 'X': // X = client quit chat - QuitChat(pClient); - break; - case 'w': // w = (+defaultvoice) make moderators be the only ones with a voice by default - if (!pChannel) - goto not_in_a_channel; - pChannel->DisableVoiceDefault(pMe->GetChatName()); - break; - case 'x': // x = (-defaultvoice) give everyone a voice by default - if (!pChannel) - goto not_in_a_channel; - pChannel->EnableVoiceDefault(pMe->GetChatName()); - break; - case 'y': // y = (/defaultvoice) toggle - if (!pChannel) - goto not_in_a_channel; - pChannel->ToggleVoiceDefault(pMe->GetChatName()); - break; - case 'z': // z = emote - if (!pChannel) - goto not_in_a_channel; - pChannel->Emote(pMe->GetChatName(), szMsg, lang ); - break; - }; -} + case CHATACT_RemoveModerator: // client shortcut: -ops + { + if ( !pChannel ) + goto NoConference; -void CChat::QuitChat(CChatChanMember * pClient) -{ - ADDTOCALLSTACK("CChat::QuitChat"); - // Are we in a channel now? - CChatChannel * pCurrentChannel = pClient->GetChannel(); - // Remove from old channel (if any) - if (pCurrentChannel) - { - // Remove myself from the channels list of members - pCurrentChannel->RemoveMember( pClient ); + pChannel->RemoveModerator(pMe, szMsg); + break; + } + case CHATACT_ToggleModerator: // client shortcut: /ops + { + if ( !pChannel ) + goto NoConference; + + pChannel->ToggleModerator(pMe, szMsg); + break; + } + case CHATACT_EnablePrivateMessages: // client shortcut: +receive + { + pMe->SetReceiving(true); + break; + } + case CHATACT_DisablePrivateMessages: // client shortcut: -receive + { + pMe->SetReceiving(false); + break; + } + case CHATACT_TogglePrivateMessages: // client shortcut: /receive + { + pMe->ToggleReceiving(); + break; + } + case CHATACT_ShowCharacterName: // client shortcut: +showname + { + pMe->ShowCharacterName(); + break; + } + case CHATACT_HideCharacterName: // client shortcut: -showname + { + pMe->HideCharacterName(); + break; + } + case CHATACT_ToggleCharacterName: // client shortcut: /showname + { + pMe->ToggleCharacterName(); + break; + } + case CHATACT_WhoIs: // client shortcut: /whois + { + if ( !pChannel ) + goto NoConference; + + pChannel->WhoIs(pMe->GetChatName(), szMsg); + break; + } + case CHATACT_Kick: // client shortcut: /kick + { + if ( !pChannel ) + goto NoConference; + + CChatMember *pMember = pChannel->FindMember(szMsg); + if ( pMember ) + pChannel->KickMember(pMe, pMember); + else + pMe->SendChatMsg(CHATMSG_NoPlayer, szMsg); + break; + } + case CHATACT_EnableDefaultVoice: // client shortcut: +defaultvoice + { + if ( !pChannel ) + goto NoConference; + + pChannel->EnableDefaultVoice(pMe->GetChatName()); + break; + } + case CHATACT_DisableDefaultVoice: // client shortcut: -defaultvoice + { + if ( !pChannel ) + goto NoConference; + + pChannel->DisableDefaultVoice(pMe->GetChatName()); + break; + } + case CHATACT_ToggleDefaultVoice: // client shortcut: /defaultvoice + { + if ( !pChannel ) + goto NoConference; - // Am I the last one here? Delete it from all other clients? - if (pCurrentChannel->m_Members.GetCount() <= 0) + pChannel->ToggleDefaultVoice(pMe->GetChatName()); + break; + } + case CHATACT_EmoteMessage: // client shortcut: /emote or /em { - // If noone is left, tell the chat system to delete it from memory - DeleteChannel(pCurrentChannel); + if ( !pChannel ) + goto NoConference; + + pChannel->Emote(pMe->GetChatName(), szMsg, lang); + break; } } - // Now tell the chat system you left +} - pClient->SetChatInactive(); +void CChat::QuitChat(CChatMember *pClient) +{ + ADDTOCALLSTACK("CChat::QuitChat"); + // Remove from old channel (if any) + + CChatChannel *pCurrentChannel = pClient->GetChannel(); + if ( pCurrentChannel ) + pCurrentChannel->RemoveMember(pClient); } -void CChat::DoCommand(CChatChanMember * pBy, LPCTSTR szMsg) +void CChat::DoCommand(CChatMember *pBy, LPCTSTR pszMsg) { ADDTOCALLSTACK("CChat::DoCommand"); static LPCTSTR const sm_szCmd_Chat[] = @@ -294,20 +408,20 @@ void CChat::DoCommand(CChatChanMember * pBy, LPCTSTR szMsg) "WHEREIS" }; - ASSERT(pBy != NULL); + ASSERT(pBy); ASSERT(szMsg != NULL); TCHAR buffer[2048]; ASSERT(strlen(szMsg) < COUNTOF(buffer)); - strcpy(buffer, szMsg); + strcpy(buffer, pszMsg); - TCHAR * pszCommand = buffer; - TCHAR * pszText = NULL; + TCHAR *pszCommand = buffer; + TCHAR *pszText = NULL; size_t iCommandLength = strlen(pszCommand); - for (size_t i = 0; i < iCommandLength; i++) + for ( size_t i = 0; i < iCommandLength; i++ ) { - ASSERT( iGetChannel(); - CClient * pByClient = pBy->m_pClient; - ASSERT(pByClient != NULL); + CChatChannel *pChannel = pBy->GetChannel(); + CClient *pByClient = pBy->GetClient(); + ASSERT(pByClient); - switch ( FindTableSorted( pszCommand, sm_szCmd_Chat, COUNTOF(sm_szCmd_Chat))) + switch ( FindTableSorted(pszCommand, sm_szCmd_Chat, COUNTOF(sm_szCmd_Chat)) ) { - case 0: // "ALLKICK" + case 0: // ALLKICK { - if (pChannel == NULL) + if ( !pChannel ) { pBy->SendChatMsg(CHATMSG_MustBeInAConference); return; } - - if (!pChannel->IsModerator(pBy->GetChatName())) + else if ( !pChannel->IsModerator(pBy->GetChatName()) ) { pBy->SendChatMsg(CHATMSG_MustHaveOps); return; } pChannel->KickAll(pBy); - DecorateName(sFrom, NULL, true); - pBy->SendChatMsg(CHATMSG_PlayerTalk, sFrom, "All members have been kicked!", ""); + FormatName(sFrom, NULL, true); + pBy->SendChatMsg(CHATMSG_PlayerMessage, sFrom, "All members have been kicked!"); return; } - case 1: // "BC" + case 1: // BC { - if ( ! pByClient->IsPriv( PRIV_GM )) + if ( !pByClient->IsPriv(PRIV_GM) ) { - need_gm_privs: - DecorateName(sFrom, NULL, true); - pBy->SendChatMsg(CHATMSG_PlayerTalk, sFrom, "You need to have GM privs to use this command."); + NeedGMPrivs: + FormatName(sFrom, NULL, true); + pBy->SendChatMsg(CHATMSG_PlayerMessage, sFrom, "You must have GM privileges to use this command."); return; } Broadcast(pBy, pszText); return; } - case 2: // "BCALL" + case 2: // BCALL { - if ( ! pByClient->IsPriv( PRIV_GM )) - goto need_gm_privs; + if ( !pByClient->IsPriv(PRIV_GM) ) + goto NeedGMPrivs; - Broadcast(pBy, pszText, "", true); + Broadcast(pBy, pszText, NULL, true); return; } - case 3: // "CHATSOK" + case 3: // CHATSOK { - if ( ! pByClient->IsPriv( PRIV_GM )) - goto need_gm_privs; + if ( !pByClient->IsPriv(PRIV_GM) ) + goto NeedGMPrivs; - if (!m_fChatsOK) + if ( !m_bAllowChannelCreation ) { - m_fChatsOK = true; + m_bAllowChannelCreation = true; Broadcast(NULL, "Conference creation is enabled."); } return; } - case 4: // "CLEARIGNORE" + case 4: // CLEARIGNORE { pBy->ClearIgnoreList(); return; } - case 5: // "KILLCHATS" + case 5: // KILLCHATS { - if ( ! pByClient->IsPriv( PRIV_GM )) - goto need_gm_privs; + if ( !pByClient->IsPriv(PRIV_GM) ) + goto NeedGMPrivs; KillChannels(); return; } - case 6: // "NOCHATS" + case 6: // NOCHATS { - if ( ! pByClient->IsPriv( PRIV_GM )) - goto need_gm_privs; + if ( !pByClient->IsPriv(PRIV_GM) ) + goto NeedGMPrivs; - if (m_fChatsOK) + if ( m_bAllowChannelCreation ) { Broadcast(NULL, "Conference creation is now disabled."); - m_fChatsOK = false; + m_bAllowChannelCreation = false; } return; } - case 7: // "SYSMSG" + case 7: // SYSMSG { - if ( ! pByClient->IsPriv( PRIV_GM )) - goto need_gm_privs; + if ( !pByClient->IsPriv(PRIV_GM) ) + goto NeedGMPrivs; - Broadcast(NULL, pszText, "", true); + Broadcast(NULL, pszText, NULL, true); return; } - case 8: // "WHEREIS" + case 8: // WHEREIS { WhereIs(pBy, pszText); return; @@ -416,933 +529,792 @@ void CChat::DoCommand(CChatChanMember * pBy, LPCTSTR szMsg) TCHAR *pszMsg = Str_GetTemp(); sprintf(pszMsg, "Unknown command: '%s'", pszCommand); - DecorateName(sFrom, NULL, true); - pBy->SendChatMsg(CHATMSG_PlayerTalk, sFrom, pszMsg); + FormatName(sFrom, NULL, true); + pBy->SendChatMsg(CHATMSG_PlayerMessage, sFrom, pszMsg); return; } } } -void CChat::KillChannels() -{ - ADDTOCALLSTACK("CChat::KillChannels"); - CChatChannel * pChannel = GetFirstChannel(); - // First /kick everyone - for ( ; pChannel != NULL; pChannel = pChannel->GetNext()) - pChannel->KickAll(); - m_Channels.Empty(); -} - -void CChat::WhereIs(CChatChanMember * pBy, LPCTSTR pszName ) const +void CChat::WhereIs(CChatMember *pBy, LPCTSTR pszName) { ADDTOCALLSTACK("CChat::WhereIs"); - + ClientIterator it; - for (CClient* pClient = it.next(); pClient != NULL; pClient = it.next()) + for ( CClient *pClient = it.next(); pClient != NULL; pClient = it.next() ) { - if ( ! strcmp( pClient->GetChatName(), pszName)) + if ( strcmpi(pClient->GetChatName(), pszName) != 0 ) continue; + pszName = pClient->GetChatName(); // fix case-sensitive mismatch TCHAR *pszMsg = Str_GetTemp(); - if (! pClient->IsChatActive() || !pClient->GetChannel()) + if ( !pClient->m_bChatActive || !pClient->GetChannel() ) sprintf(pszMsg, "%s is not currently in a conference.", pszName); else - sprintf(pszMsg, "%s is in conference '%s'.", pszName, pClient->GetChannel()->GetName()); + sprintf(pszMsg, "%s is in conference '%s'.", pszName, static_cast(pClient->GetChannel()->m_sName)); + CGString sFrom; - DecorateName(sFrom, NULL, true); - pBy->SendChatMsg(CHATMSG_PlayerTalk, sFrom, pszMsg); + FormatName(sFrom, NULL, true); + pBy->SendChatMsg(CHATMSG_PlayerMessage, sFrom, pszMsg); return; } pBy->SendChatMsg(CHATMSG_NoPlayer, pszName); } -void CChat::DeleteChannel(CChatChannel * pChannel) +void CChat::FormatName(CGString &sName, const CChatMember *pMember, bool bSystem) //static { - ADDTOCALLSTACK("CChat::DeleteChannel"); - SendDeleteChannel(pChannel); // tell everyone about it first. - delete pChannel; -} + ADDTOCALLSTACK("CChat::FormatName"); + // Format chat name with proper color + // 0 = Yellow (user) + // 1 = Purple (moderator) + // 2 = Blue (muted) + // 3 = Purple (unused?) + // 4 = White (me) + // 5 = Green (system) + + int iColor = 0; + if ( pMember ) + { + CChatChannel *pChannel = pMember->GetChannel(); + if ( pChannel ) + { + LPCTSTR pszName = const_cast(pMember)->GetChatName(); + if ( pChannel->IsModerator(pszName) ) + iColor = 1; + else if ( !pChannel->HasVoice(pszName) ) + iColor = 2; -void CChat::SendDeleteChannel(CChatChannel * pChannel) -{ - ADDTOCALLSTACK("CChat::SendDeleteChannel"); - // Send a delete channel name message to all clients using the chat system - - ClientIterator it; - for (CClient* pClient = it.next(); pClient != NULL; pClient = it.next()) - { - if ( ! pClient->IsChatActive()) - continue; - pClient->addChatSystemMessage(CHATMSG_RemoveChannelName, pChannel->GetName()); + sName.Format("%d%s", iColor, pszName); + return; + } } + + iColor = bSystem ? 5 : 4; + sName.Format("%d%s", iColor, "SYSTEM"); } -bool CChat::IsValidName( LPCTSTR pszName, bool fPlayer ) // static +bool CChat::IsValidName(LPCTSTR pszName, bool bPlayer) //static { ADDTOCALLSTACK("CChat::IsValidName"); // Channels can have spaces, but not player names - if (strlen(pszName) < 1) - return false; - if (strcmp(pszName, "SYSTEM") == 0) + + if ( (strlen(pszName) < 1) || g_Cfg.IsObscene(pszName) || (strcmpi(pszName, "SYSTEM") == 0) ) return false; size_t length = strlen(pszName); - for (size_t i = 0; i < length; i++) + for ( size_t i = 0; i < length; i++ ) { if ( pszName[i] == ' ' ) { - if (fPlayer) + if ( bPlayer ) return false; continue; } - if ( ! isalnum(pszName[i])) - return( false ); + if ( !isalnum(pszName[i]) ) + return false; } return true; } -void CChat::SendNewChannel(CChatChannel * pNewChannel) +void CChat::Broadcast(CChatMember *pFrom, LPCTSTR pszText, CLanguageID lang, bool bOverride) { - ADDTOCALLSTACK("CChat::SendNewChannel"); - // Send this new channel name to all clients using the chat system + ADDTOCALLSTACK("CChat::Broadcast"); + CGString sName; + FormatName(sName, pFrom, bOverride); + ClientIterator it; - for (CClient* pClient = it.next(); pClient != NULL; pClient = it.next()) + for ( CClient *pClient = it.next(); pClient != NULL; pClient = it.next() ) { - if ( ! pClient->IsChatActive()) + if ( !pClient->m_bChatActive ) continue; - pClient->addChatSystemMessage(CHATMSG_SendChannelName, pNewChannel->GetName(), pNewChannel->GetModeString()); + if ( bOverride || pClient->m_bReceiving ) + pClient->SendChatMsg(CHATMSG_PlayerMessage, sName, pszText, lang); } } -void CChat::DecorateName(CGString &sName, const CChatChanMember * pMember, bool fSystem) // static +void CChat::BroadcastAddChannel(CChatChannel *pChannel) { - ADDTOCALLSTACK("CChat::DecorateName"); - CChatChannel * pChannel = NULL; - if (pMember) - pChannel = pMember->GetChannel(); - // 0 = yellow - // 1 = purple - // 2 = blue - // 3 = purple - // 4 = white - // 5 = green - int iResult = 0; - if (!pMember || !pChannel) // Must be a system command if these are invalid - { - if (fSystem) - iResult = 5; - else - iResult = 4; - } - else if (pChannel->IsModerator(pMember->GetChatName())) - iResult = 1; - else if (!pChannel->HasVoice(pMember->GetChatName())) - iResult = 2; + ADDTOCALLSTACK("CChat::BroadcastAddChannel"); + // Send 'add channel' message to all clients - if (!pMember || !pChannel) - sName.Format("%i%s", iResult, "SYSTEM"); - else - sName.Format("%i%s", iResult, pMember->GetChatName()); + ClientIterator it; + for ( CClient *pClient = it.next(); pClient != NULL; pClient = it.next() ) + pClient->addChatSystemMessage(CHATCMD_AddChannel, static_cast(pChannel->m_sName), pClient->m_UseNewChatSystem ? NULL : pChannel->GetPasswordString()); } -void CChat::GenerateChatName(CGString &sName, const CClient * pClient) // static +void CChat::BroadcastRemoveChannel(CChatChannel *pChannel) { - if (pClient == NULL) - return; + ADDTOCALLSTACK("CChat::BroadcastRemoveChannel"); + // Send 'delete channel' message to all clients - // decide upon 'base' name - LPCTSTR pszName = NULL; - if (pClient->GetChar() != NULL) - pszName = pClient->GetChar()->GetName(); + ClientIterator it; + for ( CClient *pClient = it.next(); pClient != NULL; pClient = it.next() ) + pClient->addChatSystemMessage(CHATCMD_RemoveChannel, static_cast(pChannel->m_sName)); +} - if (pszName == NULL) - return; +//////////////////////////////////////////////////////////////////////////// +// -CChatChannel - // try the base name - CGString sTempName(pszName); - if (g_Accounts.Account_FindChat(sTempName.GetPtr()) != NULL) - { - sTempName.Empty(); +bool CChatChannel::AddMember(CChatMember *pMember) +{ + ADDTOCALLSTACK("CChatChannel::AddMember"); + pMember->SetChannel(this); + m_Members.Add(pMember); - // append (n) to the name to make it unique - for (unsigned int attempts = 100; attempts <= g_Accounts.Account_GetCount(); attempts++) - { - sTempName.Format("%s (%u)", pszName, attempts); - if (g_Accounts.Account_FindChat(static_cast(sTempName)) == NULL) - break; - - sTempName.Empty(); - } - } - - // copy name to output - sName.Copy(sTempName.GetPtr()); + // Check if only moderators have voice by default + LPCTSTR pszName = pMember->GetChatName(); + if ( !m_bDefaultVoice && !IsModerator(pszName) ) + SetVoice(pszName); + return true; } -void CChat::Broadcast(CChatChanMember *pFrom, LPCTSTR pszText, CLanguageID lang, bool fOverride) +void CChatChannel::RemoveMember(CChatMember *pMember) { - ADDTOCALLSTACK("CChat::Broadcast"); - ClientIterator it; - for (CClient *pClient = it.next(); pClient != NULL; pClient = it.next()) + ADDTOCALLSTACK("CChatChannel::RemoveMember"); + CClient *pClient = NULL; + for ( size_t i = 0; i < m_Members.GetCount(); i++ ) { - if (!pClient->IsChatActive()) + pClient = m_Members[i]->GetClient(); + if ( !pClient ) // auto-remove offline clients + { + m_Members[i]->SetChannel(NULL); + m_Members.RemoveAt(i); continue; - if (fOverride || pClient->IsReceivingAllowed()) + } + + if ( !pClient->m_UseNewChatSystem ) + pClient->addChatSystemMessage(CHATCMD_RemoveMemberFromChannel, pMember->GetChatName()); + + if ( m_Members[i] == pMember ) { - CGString sName; - DecorateName(sName, pFrom, fOverride); - pClient->SendChatMsg(CHATMSG_PlayerTalk, sName, pszText, lang); + pClient->addChatSystemMessage(pClient->m_UseNewChatSystem ? CHATCMD_LeftChannel : CHATCMD_ClearMembers, static_cast(m_sName)); + m_Members.RemoveAt(i); } } -} -bool CChat::CreateChannel(LPCTSTR pszName, LPCTSTR pszPassword, CChatChanMember * pMember) -{ - ADDTOCALLSTACK("CChat::CreateChannel"); - if (!m_fChatsOK) - { - CGString sName; - DecorateName(sName, NULL, true); - pMember->SendChatMsg(CHATMSG_PlayerTalk, sName, "Conference creation is disabled."); - return false; - } - else if (!IsValidName(pszName, false)) - { - pMember->SendChatMsg(CHATMSG_InvalidConferenceName); - return false; - } - else if (IsDuplicateChannelName(pszName)) - { - pMember->SendChatMsg(CHATMSG_AlreadyAConference); - return false; - } + // Delete the channel if there's no members left + if ( m_Members.GetCount() <= 0 ) + g_Serv.m_Chats.DeleteChannel(this); - CChatChannel * pChannel = new CChatChannel( pszName, pszPassword ); - m_Channels.InsertTail( pChannel ); - pChannel->SetModerator(pMember->GetChatName()); - // Send all clients with an open chat window the new channel name - SendNewChannel(pChannel); - return true; + pMember->SetChannel(NULL); } -bool CChat::JoinChannel(CChatChanMember * pMember, LPCTSTR pszChannel, LPCTSTR pszPassword) +void CChatChannel::KickMember(CChatMember *pByMember, CChatMember *pMember) { - ADDTOCALLSTACK("CChat::JoinChannel"); - ASSERT(pMember != NULL); - CClient * pMemberClient = pMember->m_pClient; - ASSERT(pMemberClient != NULL); + ADDTOCALLSTACK("CChatChannel::KickMember"); + ASSERT(pMember); - // Are we in a channel now? - CChatChannel * pCurrentChannel = pMember->GetChannel(); - if (pCurrentChannel != NULL) + LPCTSTR pszByName; + if ( pByMember ) { - // Is it the same channel as the one I'm already in? - if (strcmp(pszChannel, pCurrentChannel->GetName()) == 0) + pszByName = pByMember->GetChatName(); + if ( !IsModerator(pszByName) ) { - // Tell them and return - pMember->SendChatMsg(CHATMSG_AlreadyInConference, pszChannel); - return false; + pByMember->SendChatMsg(CHATMSG_MustHaveOps); + return; } } + else + pszByName = "SYSTEM"; - CChatChannel * pNewChannel = FindChannel(pszChannel); - if (pNewChannel == NULL) - { - pMemberClient->addChatSystemMessage(CHATMSG_NoConference, pszChannel ); - return false; - } - - // If there's a password, is it the correct one? - if ( pszPassword && (strcmp(pNewChannel->GetPassword(), pszPassword) != 0) ) - { - pMemberClient->addChatSystemMessage(CHATMSG_IncorrectPassword); - return false; - } + LPCTSTR pszName = pMember->GetChatName(); - // Leave the old channel 1st - // Remove from old channel (if any) - if (pCurrentChannel != NULL) + // Remove from moderators list + if ( IsModerator(pszName) ) { - // Remove myself from the channels list of members - pCurrentChannel->RemoveMember(pMember); - - // If noone is left, tell the chat system to delete it from memory - if (pCurrentChannel->m_Members.GetCount() <= 0) - { - // Am I the last one here? Delete it from all other clients? - DeleteChannel(pCurrentChannel); - } - - // Since we left, clear all members from our client that might be in our list from the channel we just left - pMemberClient->addChatSystemMessage(CHATMSG_ClearMemberList); + SetModerator(pszName, true); + SendMember(pMember); + Broadcast(CHATMSG_PlayerNoLongerModerator, pszName); + pMember->SendChatMsg(CHATMSG_RemovedListModerators, pszByName); } - // Now join a new channel - // Add all the members of the channel to the clients list of channel participants - pNewChannel->SendMembers(pMember); - - // Add ourself to the channels list of members - if (!pNewChannel->AddMember(pMember)) - return false; - - // Set the channel name title bar - pMemberClient->addChatSystemMessage(CHATMSG_UpdateChannelBar, pszChannel); + RemoveMember(pMember); + if ( !this ) // the channel got removed because there's no members left + return; - // Now send out my name to all clients in this channel - pNewChannel->SendThisMember(pMember); - return true; + Broadcast(CHATMSG_PlayerKicked, pszName); + pMember->SendChatMsg(CHATCMD_ClearMembers); + pMember->SendChatMsg(CHATMSG_ModeratorHasKicked, pszByName); } -//////////////////////////////////////////////////////////////////////////// -// -CChatChannel - -void CChatChannel::WhoIs(LPCTSTR pszBy, LPCTSTR pszMember) +void CChatChannel::KickAll(CChatMember *pMemberException) { - ADDTOCALLSTACK("CChatChannel::WhoIs"); - CChatChanMember * pBy = FindMember(pszBy); - CChatChanMember * pMember = FindMember(pszMember); - CChar * pChar = pMember? pMember->m_pClient->GetChar() : NULL; - if (!pMember || !pChar) - { - pBy->SendChatMsg(CHATMSG_NoPlayer, pszMember); - } - else if (pMember->GetWhoIs()) - { - pBy->SendChatMsg(CHATMSG_PlayerKnownAs, pszMember, pChar->GetName()); - } - else + ADDTOCALLSTACK("CChatChannel::KickAll"); + for ( size_t i = 0; i < m_Members.GetCount(); i++ ) { - pBy->SendChatMsg(CHATMSG_PlayerIsAnonymous, pszMember); + if ( m_Members[i] == pMemberException ) + continue; + KickMember(pMemberException, m_Members[i]); } } -void CChatChannel::Emote(LPCTSTR pszBy, LPCTSTR pszMsg, CLanguageID lang ) +bool CChatChannel::IsModerator(LPCTSTR pszMember) { - ADDTOCALLSTACK("CChatChannel::Emote"); - if (HasVoice(pszBy)) - Broadcast(CHATMSG_PlayerEmote, pszBy, pszMsg, lang ); - else - FindMember(pszBy)->SendChatMsg(CHATMSG_RevokedSpeaking); -} - -void CChatChannel::ToggleVoiceDefault(LPCTSTR pszBy) -{ - ADDTOCALLSTACK("CChatChannel::ToggleVoiceDefault"); - if (!IsModerator(pszBy)) + ADDTOCALLSTACK("CChatChannel::IsModerator"); + for ( size_t i = 0; i < m_Moderators.GetCount(); i++ ) { - FindMember(pszBy)->SendChatMsg(CHATMSG_MustHaveOps); - return; + if ( m_Moderators[i]->CompareNoCase(pszMember) == 0 ) + return true; } - if (GetVoiceDefault()) - Broadcast(CHATMSG_ModeratorsSpeakDefault, "", ""); - else - Broadcast(CHATMSG_SpeakingByDefault, "", ""); - SetVoiceDefault(!GetVoiceDefault()); + return false; } -void CChatChannel::DisableVoiceDefault(LPCTSTR pszBy) +void CChatChannel::SetModerator(LPCTSTR pszMember, bool bRemoveAccess) { - ADDTOCALLSTACK("CChatChannel::DisableVoiceDefault"); - if (GetVoiceDefault()) - ToggleVoiceDefault(pszBy); + ADDTOCALLSTACK("CChatChannel::SetModerator"); + // Check if they are already a moderator + for ( size_t i = 0; i < m_Moderators.GetCount(); i++ ) + { + if ( m_Moderators[i]->CompareNoCase(pszMember) == 0 ) + { + if ( bRemoveAccess ) + m_Moderators.DeleteAt(i); + return; + } + } + if ( !bRemoveAccess ) + m_Moderators.Add(new CGString(pszMember)); } -void CChatChannel::EnableVoiceDefault(LPCTSTR pszBy) +void CChatChannel::AddModerator(CChatMember *pByMember, LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::EnableVoiceDefault"); - if (!GetVoiceDefault()) - ToggleVoiceDefault(pszBy); -} + ADDTOCALLSTACK("CChatChannel::AddModerator"); -void CChatChannel::SendPrivateMessage(CChatChanMember * pFrom, LPCTSTR pszTo, LPCTSTR pszMsg) -{ - ADDTOCALLSTACK("CChatChannel::SendPrivateMessage"); - CChatChanMember * pTo = FindMember(pszTo); - if (!pTo) - { - pFrom->SendChatMsg(CHATMSG_NoPlayer, pszTo); - return; - } - if (!pTo->IsReceivingAllowed()) - { - pFrom->SendChatMsg(CHATMSG_PlayerNotReceivingPrivate, pszTo); - return; - } - // Can always send private messages to moderators (but only if they are receiving) - bool fHasVoice = HasVoice(pFrom->GetChatName()); - if ( !fHasVoice && !IsModerator(pszTo)) + LPCTSTR pszByName = pByMember->GetChatName(); + if ( !IsModerator(pszByName) ) { - pFrom->SendChatMsg(CHATMSG_RevokedSpeaking); + pByMember->SendChatMsg(CHATMSG_MustHaveOps); return; } - if (pTo->IsIgnoring(pFrom->GetChatName())) // See if ignoring you + CChatMember *pMember = FindMember(pszName); + if ( !pMember ) { - pFrom->SendChatMsg(CHATMSG_PlayerIsIgnoring, pszTo); + pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); return; } + pszName = pMember->GetChatName(); // fix case-sensitive mismatch + if ( IsModerator(pszName) ) + return; - CGString sName; - g_Serv.m_Chats.DecorateName(sName, pFrom); - // Echo to the sending client so they know the message went out - pFrom->SendChatMsg(CHATMSG_PlayerPrivate, sName, pszMsg); - // If the sending and receiving are different send it out to the receiver - if (pTo != pFrom) - pTo->SendChatMsg(CHATMSG_PlayerPrivate, sName, pszMsg); + SetModerator(pszName); + SendMember(pMember); // update name color + Broadcast(CHATMSG_PlayerIsModerator, pszName); + pMember->SendChatMsg(CHATMSG_YouAreAModerator, pszByName); } -void CChatChannel::RenameChannel(CChatChanMember * pBy, LPCTSTR pszName) +void CChatChannel::RemoveModerator(CChatMember *pByMember, LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::RenameChannel"); - // Ask the chat system if the new name is ok - if ( ! g_Serv.m_Chats.IsValidName( pszName, false )) - { - pBy->SendChatMsg(CHATMSG_InvalidConferenceName); - return; - } - // Ask the chat system if the new name is already taken - if ( g_Serv.m_Chats.IsDuplicateChannelName(pszName)) - { - pBy->SendChatMsg(CHATMSG_AlreadyAConference); - return; - } - // Tell the channel members our name changed - Broadcast(CHATMSG_ConferenceRenamed, GetName(), pszName); - // Delete the old name from all chat clients - g_Serv.m_Chats.SendDeleteChannel(this); - // Do the actual renaming - SetName(pszName); - // Update all channel members' current channel bar - Broadcast(CHATMSG_UpdateChannelBar, pszName, ""); - // Send out the new name to all chat clients so they can join - g_Serv.m_Chats.SendNewChannel(this); -} + ADDTOCALLSTACK("CChatChannel::RemoveModerator"); -void CChatChannel::KickAll(CChatChanMember * pMemberException) -{ - ADDTOCALLSTACK("CChatChannel::KickAll"); - for (size_t i = 0; i < m_Members.GetCount(); i++) + LPCTSTR pszByName = pByMember->GetChatName(); + if ( !IsModerator(pszByName) ) { - if ( m_Members[i] == pMemberException) // If it's not me, then kick them - continue; - KickMember( pMemberException, m_Members[i] ); + pByMember->SendChatMsg(CHATMSG_MustHaveOps); + return; } -} -void CChatChannel::RemoveMember(CChatChanMember * pMember) -{ - ADDTOCALLSTACK("CChatChannel::RemoveMember"); - for ( size_t i = 0; i < m_Members.GetCount(); ) + CChatMember *pMember = FindMember(pszName); + if ( !pMember ) { - // Tell the other clients in this channel (if any) you are leaving (including yourself) - CClient *pClient = m_Members[i]->m_pClient; - if (!pClient) // auto-remove offline clients - { - m_Members[i]->SetChannel(NULL); - m_Members.RemoveAt(i); - continue; - } - - pClient->addChatSystemMessage(CHATMSG_RemoveMember, pMember->GetChatName()); - if (m_Members[i] == pMember) // disjoin - { - m_Members.RemoveAt(i); - break; - } - - i++; + pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); + return; } + pszName = pMember->GetChatName(); // fix case-sensitive mismatch + if ( !IsModerator(pszName) ) + return; - // Update our persona - pMember->SetChannel(NULL); + SetModerator(pszName, true); + SendMember(pMember); // update name color + Broadcast(CHATMSG_PlayerNoLongerModerator, pszName); + pMember->SendChatMsg(CHATMSG_RemovedListModerators, pszByName); } -bool CChatChannel::IsModerator(LPCTSTR pszMember) const +void CChatChannel::ToggleModerator(CChatMember *pByMember, LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::IsModerator"); - for (size_t i = 0; i < m_Moderators.GetCount(); i++) - { - if (m_Moderators[i]->Compare(pszMember) == 0) - return true; - } - return false; + ADDTOCALLSTACK("CChatChannel::ToggleModerator"); + if ( !IsModerator(pszName) ) + AddModerator(pByMember, pszName); + else + RemoveModerator(pByMember, pszName); } -bool CChatChannel::HasVoice(LPCTSTR pszMember) const +bool CChatChannel::HasVoice(LPCTSTR pszMember) { ADDTOCALLSTACK("CChatChannel::HasVoice"); - for (size_t i = 0; i < m_NoVoices.GetCount(); i++) + if ( !m_bDefaultVoice && !IsModerator(pszMember) ) + return false; + for ( size_t i = 0; i < m_NoVoices.GetCount(); i++ ) { - if (m_NoVoices[i]->Compare(pszMember) == 0) + if ( m_NoVoices[i]->CompareNoCase(pszMember) == 0 ) return false; } return true; } -void CChatChannel::SetModerator(LPCTSTR pszMember, bool fFlag) +void CChatChannel::SetVoice(LPCTSTR pszName, bool bRemoveAccess) { - ADDTOCALLSTACK("CChatChannel::SetModerator"); - // See if they are already a moderator - for (size_t i = 0; i < m_Moderators.GetCount(); i++) + ADDTOCALLSTACK("CChatChannel::SetVoice"); + // Check if they have no voice already + for ( size_t i = 0; i < m_NoVoices.GetCount(); i++ ) { - if (m_Moderators[i]->Compare(pszMember) == 0) + if ( m_NoVoices[i]->CompareNoCase(pszName) == 0 ) { - if (fFlag == false) - m_Moderators.DeleteAt(i); + if ( !bRemoveAccess ) + m_NoVoices.DeleteAt(i); return; } } - if (fFlag) - { - m_Moderators.Add( new CGString(pszMember) ); - } + if ( bRemoveAccess ) + m_NoVoices.Add(new CGString(pszName)); } -void CChatChannel::KickMember(CChatChanMember *pByMember, CChatChanMember * pMember ) +void CChatChannel::AddVoice(CChatMember *pByMember, LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::KickMember"); - ASSERT( pMember ); + ADDTOCALLSTACK("CChatChannel::AddVoice"); - LPCTSTR pszByName; - if (pByMember) // If NULL, then an ADMIN or a GM did it + LPCTSTR pszByName = pByMember->GetChatName(); + if ( !IsModerator(pszByName) ) { - pszByName = pByMember->GetChatName(); - if (!IsModerator(pszByName)) - { - pByMember->SendChatMsg(CHATMSG_MustHaveOps); - return; - } + pByMember->SendChatMsg(CHATMSG_MustHaveOps); + return; } - else + + CChatMember *pMember = FindMember(pszName); + if ( !pMember ) { - pszByName = "SYSTEM"; + pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); + return; } + pszName = pMember->GetChatName(); // fix case-sensitive mismatch + if ( HasVoice(pszName) ) + return; - LPCTSTR pszName = pMember->GetChatName(); + SetVoice(pszName); + SendMember(pMember); // update name color + pMember->SendChatMsg(CHATMSG_ModeratorHasGrantedSpeakPriv, pszByName); + Broadcast(CHATMSG_PlayerNowSpeaking, pszName); +} + +void CChatChannel::RemoveVoice(CChatMember *pByMember, LPCTSTR pszName) +{ + ADDTOCALLSTACK("CChatChannel::RemoveVoice"); - // Kicking this person...remove from list of moderators first - if (IsModerator(pszName)) + LPCTSTR pszByName = pByMember->GetChatName(); + if ( !IsModerator(pszByName) ) { - SetModerator(pszName, false); - SendThisMember(pMember); - Broadcast(CHATMSG_PlayerNoLongerModerator, pszName, ""); - pMember->SendChatMsg(CHATMSG_RemovedListModerators, pszByName); + pByMember->SendChatMsg(CHATMSG_MustHaveOps); + return; } - // Now kick them - if (m_Members.GetCount() == 1) // If kicking yourself, send out to all clients in a chat that the channel is gone - g_Serv.m_Chats.SendDeleteChannel(this); - // Remove them from the channels list of members - RemoveMember(pMember); - // Tell the remain members about this - Broadcast(CHATMSG_PlayerIsKicked, pszName, ""); - // Now clear their channel member list - pMember->SendChatMsg(CHATMSG_ClearMemberList); - // And give them the bad news - pMember->SendChatMsg(CHATMSG_ModeratorHasKicked, pszByName); + CChatMember *pMember = FindMember(pszName); + if ( !pMember ) + { + pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); + return; + } + pszName = pMember->GetChatName(); // fix case-sensitive mismatch + if ( !HasVoice(pszName) ) + return; + + SetVoice(pszName, true); + SendMember(pMember); // update name color + pMember->SendChatMsg(CHATMSG_ModeratorHasRemovedSpeakPriv, pszByName); + Broadcast(CHATMSG_PlayerNoSpeaking, pszName); } -bool CChatChannel::AddMember(CChatChanMember * pMember) +void CChatChannel::ToggleVoice(CChatMember *pByMember, LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::AddMember"); - pMember->SetChannel(this); - m_Members.Add( pMember ); - // See if only moderators have a voice by default - LPCTSTR pszName = pMember->GetChatName(); - if (!GetVoiceDefault() && !IsModerator(pszName)) - // If only moderators have a voice by default, then add this member to the list of no voices - SetVoice(pszName, false); - // Set voice status - return true; + ADDTOCALLSTACK("CChatChannel::ToggleVoice"); + if ( !HasVoice(pszName) ) // this also returns true if this person is not in the channel + AddVoice(pByMember, pszName); + else + RemoveVoice(pByMember, pszName); } -void CChatChannel::SendMembers(CChatChanMember * pMember) +void CChatChannel::EnableDefaultVoice(LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::SendMembers"); - for (size_t i = 0; i < m_Members.GetCount(); i++) - { - CGString sName; - g_Serv.m_Chats.DecorateName(sName, m_Members[i]); - pMember->SendChatMsg(CHATMSG_SendPlayerName, sName); - } + ADDTOCALLSTACK("CChatChannel::EnableDefaultVoice"); + if ( !m_bDefaultVoice ) + ToggleDefaultVoice(pszName); } -void CChatChannel::SendThisMember(CChatChanMember * pMember, CChatChanMember * pToMember) +void CChatChannel::DisableDefaultVoice(LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::SendThisMember"); - TCHAR buffer[2048]; - sprintf(buffer, "%s%s", - (IsModerator(pMember->GetChatName()) == true) ? "1" : - (HasVoice(pMember->GetChatName()) == true) ? "0" : "2", pMember->GetChatName()); - // If no particular member is specified in pToMember, then send it out to all members - if (pToMember == NULL) - { - for (size_t i = 0; i < m_Members.GetCount(); i++) - { - // Don't send out members if they are ignored by someone - if (!m_Members[i]->IsIgnoring(pMember->GetChatName())) - m_Members[i]->SendChatMsg(CHATMSG_SendPlayerName, buffer); - } - } - else - { - // Don't send out members if they are ignored by someone - if (!pToMember->IsIgnoring(pMember->GetChatName())) - pToMember->SendChatMsg(CHATMSG_SendPlayerName, buffer); - } + ADDTOCALLSTACK("CChatChannel::DisableDefaultVoice"); + if ( m_bDefaultVoice ) + ToggleDefaultVoice(pszName); } -void CChatChannel::SetVoice(LPCTSTR pszName, bool fFlag) +void CChatChannel::ToggleDefaultVoice(LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChannel::SetVoice"); - // See if they have no voice already - for (size_t i = 0; i < m_NoVoices.GetCount(); i++) + ADDTOCALLSTACK("CChatChannel::ToggleDefaultVoice"); + if ( !IsModerator(pszName) ) { - if (m_NoVoices[i]->Compare(pszName) == 0) - { - if (fFlag == true) - m_NoVoices.DeleteAt(i); - return; - } - } - if (fFlag == false) - { - m_NoVoices.Add( new CGString(pszName) ); + FindMember(pszName)->SendChatMsg(CHATMSG_MustHaveOps); return; } + m_bDefaultVoice = !m_bDefaultVoice; + Broadcast(m_bDefaultVoice ? CHATMSG_EveryoneSpeakingPrivByDefault : CHATMSG_ModeratorSpeakingPrivByDefault); } -void CChatChannel::MemberTalk(CChatChanMember * pByMember, LPCTSTR pszText, CLanguageID lang ) +void CChatChannel::MemberTalk(CChatMember *pByMember, LPCTSTR pszText, CLanguageID lang) { ADDTOCALLSTACK("CChatChannel::MemberTalk"); - // Do I have a voice? - if (!HasVoice(pByMember->GetChatName())) + + LPCTSTR pszName = pByMember->GetChatName(); + if ( !HasVoice(pszName) ) { pByMember->SendChatMsg(CHATMSG_RevokedSpeaking); return; } - Broadcast(CHATMSG_PlayerTalk, pByMember->GetChatName(), pszText, lang ); + Broadcast(CHATMSG_PlayerMessage, pszName, pszText, lang); } -void CChatChannel::ChangePassword(CChatChanMember * pByMember, LPCTSTR pszPassword) +void CChatChannel::Emote(LPCTSTR pszBy, LPCTSTR pszMsg, CLanguageID lang) { - ADDTOCALLSTACK("CChatChannel::ChangePassword"); - if (!IsModerator(pByMember->GetChatName())) - { - pByMember->m_pClient->addChatSystemMessage(CHATMSG_MustHaveOps); - } + ADDTOCALLSTACK("CChatChannel::Emote"); + if ( HasVoice(pszBy) ) + Broadcast(CHATMSG_PlayerEmote, pszBy, pszMsg, lang); else - { - SetPassword(pszPassword); - g_Serv.m_Chats.SendNewChannel(pByMember->GetChannel()); - Broadcast(CHATMSG_PasswordChanged, "",""); - } + FindMember(pszBy)->SendChatMsg(CHATMSG_RevokedSpeaking); } -void CChatChannel::Broadcast(CHATMSG_TYPE iType, LPCTSTR pszName, LPCTSTR pszText, CLanguageID lang, bool fOverride ) +void CChatChannel::PrivateMessage(CChatMember *pFrom, LPCTSTR pszTo, LPCTSTR pszMsg, CLanguageID lang) { - ADDTOCALLSTACK("CChatChannel::Broadcast"); - CGString sName; - CChatChanMember *pSendingMember = FindMember(pszName); - - if (iType >= CHATMSG_PlayerTalk && iType <= CHATMSG_PlayerPrivate) // Only chat, emote, and privates get a color status number - g_Serv.m_Chats.DecorateName(sName, pSendingMember, fOverride); - else - sName = pszName; - - for (size_t i = 0; i < m_Members.GetCount(); i++) + ADDTOCALLSTACK("CChatChannel::PrivateMessage"); + CChatMember *pTo = FindMember(pszTo); + if ( !pTo ) { - // Check to see if the recipient is ignoring messages from the sender - // Just pass over it if it's a regular talk message - if (!m_Members[i]->IsIgnoring(pszName)) - { - m_Members[i]->SendChatMsg(iType, sName, pszText, lang ); - } - - // If it's a private message, then tell the sender the recipient is ignoring them - else if (iType == CHATMSG_PlayerPrivate) - { - pSendingMember->SendChatMsg(CHATMSG_PlayerIsIgnoring, m_Members[i]->GetChatName()); - } + pFrom->SendChatMsg(CHATMSG_NoPlayer, pszTo); + return; } -} - -void CChatChannel::GrantVoice(CChatChanMember * pByMember, LPCTSTR pszName) -{ - ADDTOCALLSTACK("CChatChannel::GrantVoice"); - if (!IsModerator(pByMember->GetChatName())) + if ( !pTo->m_bReceiving ) { - pByMember->SendChatMsg(CHATMSG_MustHaveOps); + pFrom->SendChatMsg(CHATMSG_PlayerNotReceivingPrivateMessages, pszTo); return; } - CChatChanMember * pMember = FindMember(pszName); - if (!pMember) + + // Members without voice can't send private messages on channel, but they still allowed to send private messages to moderators + if ( !HasVoice(pFrom->GetChatName()) && !IsModerator(pszTo) ) { - pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); + pFrom->SendChatMsg(CHATMSG_RevokedSpeaking); return; } - if (HasVoice(pszName)) + + if ( pTo->IsIgnoring(pFrom->GetChatName()) ) + { + pFrom->SendChatMsg(CHATMSG_PlayerIsIgnoring, pszTo); return; - SetVoice(pszName, true); - SendThisMember(pMember); // Update the color - pMember->SendChatMsg(CHATMSG_ModeratorGrantSpeaking, pByMember->GetChatName()); - Broadcast(CHATMSG_PlayerNowSpeaking, pszName, "", ""); + } + + CGString sName; + g_Serv.m_Chats.FormatName(sName, pFrom); + pFrom->SendChatMsg(CHATMSG_PlayerPrivateMessage, sName, pszMsg, lang); + if ( pTo != pFrom ) + pTo->SendChatMsg(CHATMSG_PlayerPrivateMessage, sName, pszMsg, lang); } -void CChatChannel::RevokeVoice(CChatChanMember * pByMember, LPCTSTR pszName) +void CChatChannel::WhoIs(LPCTSTR pszBy, LPCTSTR pszMember) { - ADDTOCALLSTACK("CChatChannel::RevokeVoice"); - if (!IsModerator(pByMember->GetChatName())) + ADDTOCALLSTACK("CChatChannel::WhoIs"); + CChatMember *pMemberBy = FindMember(pszBy); + CChatMember *pMemberTarg = FindMember(pszMember); + CChar *pCharTarg = (pMemberTarg && pMemberTarg->GetClient()) ? pMemberTarg->GetClient()->GetChar() : NULL; + + if ( !pCharTarg ) + pMemberBy->SendChatMsg(CHATMSG_NoPlayer, pszMember); + else if ( pMemberTarg->m_bAllowWhoIs ) + pMemberBy->SendChatMsg(CHATMSG_PlayerKnownAs, pszMember, pCharTarg->GetName()); + else + pMemberBy->SendChatMsg(CHATMSG_PlayerIsAnonymous, pszMember); +} + +void CChatChannel::RenameChannel(CChatMember *pByMember, LPCTSTR pszName) +{ + ADDTOCALLSTACK("CChatChannel::RenameChannel"); + + if ( !g_Serv.m_Chats.IsValidName(pszName, false) ) { - pByMember->SendChatMsg(CHATMSG_MustHaveOps); + pByMember->SendChatMsg(CHATMSG_InvalidConferenceName); return; } - CChatChanMember * pMember = FindMember(pszName); - if (!pMember) + if ( g_Serv.m_Chats.IsDuplicateChannelName(pszName) ) { - pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); + pByMember->SendChatMsg(CHATMSG_DuplicatedConferenceName); return; } - if (!HasVoice(pszName)) - return; - SetVoice(pszName, false); - SendThisMember(pMember); // Update the color - pMember->SendChatMsg(CHATMSG_ModeratorRemovedSpeaking, pByMember->GetChatName()); - Broadcast(CHATMSG_PlayerNoSpeaking, pszName, "", ""); + + Broadcast(CHATMSG_ConferenceRenamed, static_cast(m_sName), pszName); + g_Serv.m_Chats.BroadcastRemoveChannel(this); + m_sName = pszName; + Broadcast(CHATCMD_JoinedChannel, pszName); + g_Serv.m_Chats.BroadcastAddChannel(this); } -void CChatChannel::ToggleVoice(CChatChanMember * pByMember, LPCTSTR pszName) +void CChatChannel::ChangePassword(CChatMember *pByMember, LPCTSTR pszPassword) { - ADDTOCALLSTACK("CChatChannel::ToggleVoice"); - if (!HasVoice(pszName)) // (This also returns true if this person is not in the channel) - GrantVoice(pByMember, pszName); // this checks and reports on membership - else - RevokeVoice(pByMember, pszName); // this checks and reports on membership + ADDTOCALLSTACK("CChatChannel::ChangePassword"); + + if ( !IsModerator(pByMember->GetChatName()) ) + { + pByMember->SendChatMsg(CHATMSG_MustHaveOps); + return; + } + m_sPassword = pszPassword; + g_Serv.m_Chats.BroadcastAddChannel(pByMember->GetChannel()); + Broadcast(CHATMSG_PasswordChanged); } size_t CChatChannel::FindMemberIndex(LPCTSTR pszName) const { ADDTOCALLSTACK("CChatChannel::FindMemberIndex"); - for (size_t i = 0; i < m_Members.GetCount(); i++) + if ( pszName != NULL ) { - if ( strcmp( m_Members[i]->GetChatName(), pszName) == 0) - return i; + for ( size_t i = 0; i < m_Members.GetCount(); i++ ) + { + if ( strcmpi(m_Members[i]->GetChatName(), pszName) == 0 ) + return i; + } } return m_Members.BadIndex(); } -void CChatChannel::GrantModerator(CChatChanMember * pByMember, LPCTSTR pszName) +void CChatChannel::Broadcast(CHATMSG_TYPE iType, LPCTSTR pszName, LPCTSTR pszText, CLanguageID lang, bool bOverride) { - ADDTOCALLSTACK("CChatChannel::GrantModerator"); - if (!IsModerator(pByMember->GetChatName())) - { - pByMember->SendChatMsg(CHATMSG_MustHaveOps); - return; - } - CChatChanMember * pMember = FindMember(pszName); - if (!pMember) + ADDTOCALLSTACK("CChatChannel::Broadcast"); + + CChatMember *pSendingMember = FindMember(pszName); + CGString sName; + if ( (iType >= CHATMSG_PlayerMessage) && (iType <= CHATMSG_PlayerPrivateMessage) ) + g_Serv.m_Chats.FormatName(sName, pSendingMember, bOverride); + else + sName = pszName; + + for ( size_t i = 0; i < m_Members.GetCount(); i++ ) { - pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); - return; + if ( m_Members[i]->IsIgnoring(pszName) ) // don't receive messages from players being ignored + { + if ( pSendingMember && (iType == CHATMSG_PlayerPrivateMessage) ) // if it's a private message, tell the sender that he's being ignored + pSendingMember->SendChatMsg(CHATMSG_PlayerIsIgnoring, m_Members[i]->GetChatName()); + continue; + } + m_Members[i]->SendChatMsg(iType, sName, pszText, lang); } - if (IsModerator(pMember->GetChatName())) - return; - SetModerator(pszName, true); - SendThisMember(pMember); // Update the color - Broadcast(CHATMSG_PlayerIsAModerator, pMember->GetChatName(), "", ""); - pMember->SendChatMsg(CHATMSG_YouAreAModerator, pByMember->GetChatName()); } -void CChatChannel::RevokeModerator(CChatChanMember * pByMember, LPCTSTR pszName) +void CChatChannel::SendMember(CChatMember *pMember, CChatMember *pToMember) { - ADDTOCALLSTACK("CChatChannel::RevokeModerator"); - if (!IsModerator(pByMember->GetChatName())) + ADDTOCALLSTACK("CChatChannel::SendMember"); + CGString sName; + g_Serv.m_Chats.FormatName(sName, pMember); + + if ( pToMember ) { - pByMember->SendChatMsg(CHATMSG_MustHaveOps); - return; + // If pToMember is specified, send only to this member + if ( pToMember->IsIgnoring(pMember->GetChatName()) ) + return; + pToMember->SendChatMsg(CHATCMD_AddMemberToChannel, sName); } - CChatChanMember * pMember = FindMember(pszName); - if (!pMember) + else { - pByMember->SendChatMsg(CHATMSG_NoPlayer, pszName); - return; + // If no pToMember is specified, send to all members + for ( size_t i = 0; i < m_Members.GetCount(); i++ ) + { + if ( m_Members[i]->IsIgnoring(pMember->GetChatName()) ) + continue; + m_Members[i]->SendChatMsg(CHATCMD_AddMemberToChannel, sName); + } } - if (!IsModerator(pMember->GetChatName())) - return; - SetModerator(pszName, false); - SendThisMember(pMember); // Update the color - Broadcast(CHATMSG_PlayerNoLongerModerator, pMember->GetChatName(), "", ""); - pMember->SendChatMsg(CHATMSG_RemovedListModerators, pByMember->GetChatName()); } -void CChatChannel::ToggleModerator(CChatChanMember * pByMember, LPCTSTR pszName) +void CChatChannel::FillMembersList(CChatMember *pMember) { - ADDTOCALLSTACK("CChatChannel::ToggleModerator"); - if (!IsModerator(pszName)) - GrantModerator(pByMember, pszName); - else - RevokeModerator(pByMember, pszName); + ADDTOCALLSTACK("CChatChannel::FillMembersList"); + for ( size_t i = 0; i < m_Members.GetCount(); i++ ) + { + if ( pMember->IsIgnoring(m_Members[i]->GetChatName()) ) + continue; + CGString sName; + g_Serv.m_Chats.FormatName(sName, m_Members[i]); + pMember->SendChatMsg(CHATCMD_AddMemberToChannel, sName); + } } //////////////////////////////////////////////////////////////////////////// -// -CChatChanMember +// -CChatMember -CChatChanMember::~CChatChanMember() +CChatMember::~CChatMember() { - if ( IsChatActive()) // Are we chatting currently ? - { + if ( m_bChatActive ) g_Serv.m_Chats.QuitChat(this); - } } -void CChatChanMember::SetChatActive() +void CChatMember::addChatWindow() { - ADDTOCALLSTACK("CChatChanMember::SetChatActive"); - // called from Event_ChatButton - if ( IsChatActive() ) + ADDTOCALLSTACK("CChatMember::addChatWindow"); + // Called from Event_ChatButton + + CClient *pClient = GetClient(); + if ( !pClient || (!pClient->m_UseNewChatSystem && m_bChatActive) ) return; - if ( m_pClient ) - { - m_fChatActive = true; + // Open chat window (old chat system only) + // On new chat system this is not needed because the chat button is hardcoded on client-side, and + // PacketChatButton packet is sent by client after login complete only to get initial channel list + if ( !pClient->m_UseNewChatSystem ) + pClient->addChatSystemMessage(CHATCMD_OpenChatWindow, pClient->m_UseNewChatSystem ? NULL : GetChatName()); - // Tell the client to open the chat window dialog - m_pClient->addChatSystemMessage( CHATMSG_OpenChatWindow, GetChatName() ); + // Send channel names + CChatChannel *pChannel = g_Serv.m_Chats.GetFirstChannel(); + for ( ; pChannel != NULL; pChannel = pChannel->GetNext() ) + pClient->addChatSystemMessage(CHATCMD_AddChannel, static_cast(pChannel->m_sName), pClient->m_UseNewChatSystem ? NULL : pChannel->GetPasswordString()); +} - // Send all existing channel names to this client - const CChatChannel *pChannel = g_Serv.m_Chats.GetFirstChannel(); - for ( ; pChannel != NULL; pChannel = pChannel->GetNext() ) - m_pClient->addChatSystemMessage(CHATMSG_SendChannelName, pChannel->GetName(), pChannel->GetModeString()); - } +void CChatMember::SendChatMsg(CHATMSG_TYPE iType, LPCTSTR pszName1, LPCTSTR pszName2, CLanguageID lang) +{ + ADDTOCALLSTACK("CChatMember::SendChatMsg"); + GetClient()->addChatSystemMessage(iType, pszName1, pszName2, lang); } -void CChatChanMember::SetChatInactive() +void CChatMember::ToggleReceiving() { - ADDTOCALLSTACK("CChatChanMember::SetChatInactive"); - m_fChatActive = false; + ADDTOCALLSTACK("CChatMember::ToggleReceiving"); + m_bReceiving = !m_bReceiving; + SendChatMsg(m_bReceiving ? CHATMSG_ReceivingPrivateMessages : CHATMSG_NoLongerReceivingPrivateMessages); } -size_t CChatChanMember::FindIgnoringIndex(LPCTSTR pszName) const +void CChatMember::ShowCharacterName() { - ADDTOCALLSTACK("CChatChanMember::FindIgnoringIndex"); - for ( size_t i = 0; i < m_IgnoredMembers.GetCount(); i++) - { - if (m_IgnoredMembers[i]->Compare(pszName) == 0) - return i; - } - return m_IgnoredMembers.BadIndex(); + ADDTOCALLSTACK("CChatMember::ShowCharacterName"); + if ( m_bAllowWhoIs ) + return; + + m_bAllowWhoIs = true; + SendChatMsg(CHATMSG_ShowingName); } -void CChatChanMember::Ignore(LPCTSTR pszName) +void CChatMember::HideCharacterName() { - ADDTOCALLSTACK("CChatChanMember::Ignore"); - if (!IsIgnoring(pszName)) - ToggleIgnore(pszName); + ADDTOCALLSTACK("CChatMember::HideCharacterName"); + if ( !m_bAllowWhoIs ) + return; + + m_bAllowWhoIs = false; + SendChatMsg(CHATMSG_NoLongerShowingName); +} + +void CChatMember::ToggleCharacterName() +{ + ADDTOCALLSTACK("CChatMember::ToggleCharacterName"); + m_bAllowWhoIs = !m_bAllowWhoIs; + SendChatMsg(m_bAllowWhoIs ? CHATMSG_ShowingName : CHATMSG_NoLongerShowingName); +} + +void CChatMember::RenameChannel(LPCTSTR pszName) +{ + ADDTOCALLSTACK("CChatMember::RenameChannel"); + CChatChannel *pChannel = GetChannel(); + if ( !pChannel ) + SendChatMsg(CHATMSG_MustBeInAConference); + else if ( !pChannel->IsModerator(GetChatName()) ) + SendChatMsg(CHATMSG_MustHaveOps); else - SendChatMsg(CHATMSG_AlreadyIgnoringPlayer, pszName); + pChannel->RenameChannel(this, pszName); } -void CChatChanMember::DontIgnore(LPCTSTR pszName) +void CChatMember::AddIgnore(LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChanMember::DontIgnore"); - if (IsIgnoring(pszName)) - ToggleIgnore(pszName); + ADDTOCALLSTACK("CChatMember::AddIgnore"); + if ( IsIgnoring(pszName) ) + SendChatMsg(CHATMSG_AlreadyIgnoringPlayer, pszName); else + ToggleIgnore(pszName); +} + +void CChatMember::RemoveIgnore(LPCTSTR pszName) +{ + ADDTOCALLSTACK("CChatMember::RemoveIgnore"); + if ( !IsIgnoring(pszName) ) SendChatMsg(CHATMSG_NotIgnoring, pszName); + else + ToggleIgnore(pszName); } -void CChatChanMember::ToggleIgnore(LPCTSTR pszName) +void CChatMember::ToggleIgnore(LPCTSTR pszName) { - ADDTOCALLSTACK("CChatChanMember::ToggleIgnore"); - size_t i = FindIgnoringIndex( pszName ); + ADDTOCALLSTACK("CChatMember::ToggleIgnore"); + size_t i = FindIgnoringIndex(pszName); if ( i != m_IgnoredMembers.BadIndex() ) { - ASSERT( m_IgnoredMembers.IsValidIndex(i) ); + // Remove member from ignore list + ASSERT(m_IgnoredMembers.IsValidIndex(i)); m_IgnoredMembers.DeleteAt(i); - SendChatMsg(CHATMSG_NoLongerIgnoring, pszName); - // Resend the un ignored member to the client's local list of members (but only if they are currently in the same channel!) - if (m_pChannel) + // Show member name on members list again + if ( m_pChannel ) { - CChatChanMember * pMember = m_pChannel->FindMember(pszName); - if (pMember) - m_pChannel->SendThisMember(pMember, this); + CChatMember *pMember = m_pChannel->FindMember(pszName); + if ( pMember ) + m_pChannel->SendMember(pMember, this); } + return; } else { - CGString * name = new CGString(pszName); - m_IgnoredMembers.Add( name ); - SendChatMsg(CHATMSG_NowIgnoring, pszName); // This message also takes the ignored person off the clients local list of channel members - } -} + // Check if ignore list reached max limit + if ( m_IgnoredMembers.GetCount() >= 30 ) + { + SendChatMsg(CHATMSG_AlreadyIgnoringMax, pszName); + return; + } -void CChatChanMember::ClearIgnoreList() -{ - ADDTOCALLSTACK("CChatChanMember::ClearIgnoreList"); - for (size_t i = 0; i < m_IgnoredMembers.GetCount(); i++) - { - m_IgnoredMembers.DeleteAt(i); + // Add member on ignore list + CGString *sName = new CGString(pszName); + m_IgnoredMembers.Add(sName); + SendChatMsg(CHATMSG_NowIgnoring, pszName); // this message will also hide member name on members list } - SendChatMsg(CHATMSG_NoLongerIgnoringAnyone); -} - -void CChatChanMember::RenameChannel(LPCTSTR pszName) -{ - ADDTOCALLSTACK("CChatChanMember::RenameChannel"); - CChatChannel * pChannel = GetChannel(); - if (!pChannel) - SendChatMsg(CHATMSG_MustBeInAConference); - else if (!pChannel->IsModerator(pszName)) - SendChatMsg(CHATMSG_MustHaveOps); - else - pChannel->RenameChannel(this, pszName); } -void CChatChanMember::SendChatMsg(CHATMSG_TYPE iType, LPCTSTR pszName1, LPCTSTR pszName2, CLanguageID lang ) +void CChatMember::ClearIgnoreList() { - ADDTOCALLSTACK("CChatChanMember::SendChatMsg"); - m_pClient->addChatSystemMessage(iType, pszName1, pszName2, lang ); -} - -void CChatChanMember::ToggleReceiving() -{ - ADDTOCALLSTACK("CChatChanMember::ToggleReceiving"); - m_fReceiving = !m_fReceiving; - m_pClient->addChatSystemMessage((m_fReceiving) ? CHATMSG_ReceivingPrivate : CHATMSG_NoLongerReceivingPrivate); -} + ADDTOCALLSTACK("CChatMember::ClearIgnoreList"); + for ( size_t i = 0; i < m_IgnoredMembers.GetCount(); i++ ) + m_IgnoredMembers.DeleteAt(i); -void CChatChanMember::PermitWhoIs() -{ - ADDTOCALLSTACK("CChatChanMember::PermitWhoIs"); - if (GetWhoIs()) - return; - SetWhoIs(true); - SendChatMsg(CHATMSG_ShowingName); + SendChatMsg(CHATMSG_NoLongerIgnoringAnyone); } -void CChatChanMember::ForbidWhoIs() +size_t CChatMember::FindIgnoringIndex(LPCTSTR pszName) const { - ADDTOCALLSTACK("CChatChanMember::ForbidWhoIs"); - if (!GetWhoIs()) - return; - SetWhoIs(false); - SendChatMsg(CHATMSG_NotShowingName); + ADDTOCALLSTACK("CChatMember::FindIgnoringIndex"); + if ( pszName != NULL ) + { + for ( size_t i = 0; i < m_IgnoredMembers.GetCount(); i++ ) + { + if ( m_IgnoredMembers[i]->CompareNoCase(pszName) == 0 ) + return i; + } + } + return m_IgnoredMembers.BadIndex(); } -void CChatChanMember::ToggleWhoIs() +CClient *CChatMember::GetClient() { - ADDTOCALLSTACK("CChatChanMember::ToggleWhoIs"); - SetWhoIs(!GetWhoIs()); - SendChatMsg(GetWhoIs() ? CHATMSG_ShowingName : CHATMSG_NotShowingName); + ADDTOCALLSTACK("CChatMember::GetClient"); + return static_cast(this); } -LPCTSTR CChatChanMember::GetChatName() const +LPCTSTR CChatMember::GetChatName() { - ADDTOCALLSTACK("CChatChanMember::GetChatName"); - if ( !m_pClient || !m_pClient->m_pAccount ) - return NULL; - return m_pClient->m_pAccount->m_sChatName; + ADDTOCALLSTACK("CChatMember::GetChatName"); + CClient *pClient = GetClient(); + if ( pClient && pClient->m_UseNewChatSystem ) + { + CChar *pChar = pClient->GetChar(); + return pChar ? pChar->GetName() : ""; + } + return pClient->m_pAccount->m_sChatName; } diff --git a/src/graysvr/CClient.cpp b/src/graysvr/CClient.cpp index 5d793005e..fb785fad3 100644 --- a/src/graysvr/CClient.cpp +++ b/src/graysvr/CClient.cpp @@ -133,7 +133,7 @@ void CClient::CharDisconnect() int iLingerTime = g_Cfg.m_iClientLingerTime; // we are not a client anymore - if ( IsChatActive() ) + if ( m_bChatActive ) g_Serv.m_Chats.QuitChat(this); if ( m_pHouseDesign ) @@ -573,6 +573,7 @@ void CClient::UpdateCharacterListFlags() m_CharacterListFlags |= (0x400|0x200|0x20); m_TooltipEnabled = (m_CharacterListFlags & 0x20) ? true : false; m_ContainerGridEnabled = (m_NetState->isClientVersion(MINCLIVER_CONTAINERGRID) || m_NetState->isClientKR() || m_NetState->isClientEnhanced()); + m_UseNewChatSystem = (m_NetState->isClientVersion(MINCLIVER_NEWCHATSYSTEM) || m_NetState->isClientVersion(MASK_CLIENTTYPE_EC + MINCLIVER_NEWCHATSYSTEM_EC)); } //////////////////////////////////////////////////// diff --git a/src/graysvr/CClientEvent.cpp b/src/graysvr/CClientEvent.cpp index 35d7ce974..9870b23a6 100644 --- a/src/graysvr/CClientEvent.cpp +++ b/src/graysvr/CClientEvent.cpp @@ -29,67 +29,42 @@ LPCTSTR const CClient::sm_szCmd_Redirect[] = void CClient::Event_ChatButton(const NCHAR *pszName) { ADDTOCALLSTACK("CClient::Event_ChatButton"); - if ( !m_pChar ) - return; if ( IsTrigUsed(TRIGGER_USERCHATBUTTON) ) { - if ( m_pChar->OnTrigger(CTRIG_UserChatButton, m_pChar) == TRIGRET_RET_TRUE ) + if ( m_pChar && m_pChar->OnTrigger(CTRIG_UserChatButton, m_pChar) == TRIGRET_RET_TRUE ) return; } - // Dirty fix for classic clients with Injection using chat button to exploit movement packets. We set - // 'active trigger' to this, so we check it back on the packet to limit the amount of steps to do. - GetChar()->SetTriggerActive("UserChatButton"); - - ASSERT(m_pAccount); - if ( m_pAccount->m_sChatName.IsEmpty() ) + if ( !m_UseNewChatSystem && m_pAccount && m_pAccount->m_sChatName.IsEmpty() ) { - if ( m_NetState->isClientVersion(MINCLIVER_SA) || m_NetState->isClientKR() || m_NetState->isClientEnhanced() ) + // Client doesn't had sent any chat name to server yet, so ask for a permanent chat name (account based) + if ( (pszName == NULL) || (pszName[0] == 0) ) { - // Newest clients don't understand CHATMSG_GetChatName packet, so the name must be generated by the server. - // By default it will set chat name = char name. - CChat::GenerateChatName(m_pAccount->m_sChatName, this); - if ( m_pAccount->m_sChatName.IsEmpty() ) - { - addChatSystemMessage(CHATMSG_Error); - return; - } + addChatSystemMessage(CHATCMD_SetChatName); + return; } - else - { - // Check if the client sent any chat name - if ( (pszName == NULL) || (pszName[0] == 0) ) - { - // No data was sent, so ask for a permanent chat name (account based) - addChatSystemMessage(CHATMSG_GetChatName); - return; - } - // Make the chat name non-unicode and store it with the account stuff - TCHAR szChatName[MAX_NAME_SIZE * 2 + 2]; - CvtNUNICODEToSystem(szChatName, sizeof(szChatName), pszName, 128); + // Make the chat name non-unicode and store it on account + TCHAR szChatName[MAX_NAME_SIZE * 2 + 2]; + CvtNUNICODEToSystem(szChatName, sizeof(szChatName), pszName, 128); - if ( !CChat::IsValidName(szChatName, true) || g_Accounts.Account_FindChat(szChatName) ) // check if the name is illegal or duplicated - { - addChatSystemMessage(CHATMSG_Error); - addChatSystemMessage(CHATMSG_GetChatName); - return; - } - - m_pAccount->m_sChatName = szChatName; + if ( !CChat::IsValidName(szChatName, true) || g_Accounts.Account_FindChat(szChatName) ) + { + addChatSystemMessage(CHATCMD_SetChatName); + return; } + m_pAccount->m_sChatName = szChatName; } - SetChatActive(); - GetChar()->SetTriggerActive(""); + addChatWindow(); } // Client sent a text on chat void CClient::Event_ChatText(const NCHAR *pszText, int len, CLanguageID lang) { ADDTOCALLSTACK("CClient::Event_ChatText"); - g_Serv.m_Chats.EventMsg(this, pszText, len, lang); + g_Serv.m_Chats.Action(this, pszText, len, lang); } // Client selected an item hue diff --git a/src/graysvr/graysvr.h b/src/graysvr/graysvr.h index 5861e2b17..892e9fe39 100644 --- a/src/graysvr/graysvr.h +++ b/src/graysvr/graysvr.h @@ -389,244 +389,220 @@ class CGMPage : public CGObListRec, public CScriptObj class CChat; class CChatChannel; -class CChatChanMember +class CChatMember { // This is a member of the CClient. -private: - bool m_fChatActive; - bool m_fReceiving; - bool m_fAllowWhoIs; - CChatChannel * m_pChannel; // I can only be a member of one chan at a time. -public: - static const char *m_sClassName; - CGObArray m_IgnoredMembers; // Player's list of ignored members private: friend class CChatChannel; friend class CChat; - // friend CClient; - bool GetWhoIs() const { return m_fAllowWhoIs; } - void SetWhoIs(bool fAllowWhoIs) { m_fAllowWhoIs = fAllowWhoIs; } - bool IsReceivingAllowed() const { return m_fReceiving; } - LPCTSTR GetChatName() const; + bool m_bReceiving; + bool m_bAllowWhoIs; + CChatChannel *m_pChannel; - size_t FindIgnoringIndex( LPCTSTR pszName) const; + CChatMember(const CChatMember ©); + CChatMember &operator=(const CChatMember &other); -protected: - void SetChatActive(); - void SetChatInactive(); public: - CChatChanMember() + static const char *m_sClassName; + CGObArray m_IgnoredMembers; + bool m_bChatActive; + + CChatMember() { - m_fChatActive = false; + m_bChatActive = false; + m_bReceiving = true; + m_bAllowWhoIs = true; m_pChannel = NULL; - m_fReceiving = true; - m_fAllowWhoIs = true; } + virtual ~CChatMember(); - virtual ~CChatChanMember(); +protected: + void addChatWindow(); private: - CChatChanMember(const CChatChanMember& copy); - CChatChanMember& operator=(const CChatChanMember& other); - -public: - CClient *m_pClient; + void SendChatMsg(CHATMSG_TYPE iType, LPCTSTR pszName1 = NULL, LPCTSTR pszName2 = NULL, CLanguageID lang = 0); - bool IsChatActive() const + void SetReceiving(bool bToggle) { - return( m_fChatActive ); - } - - void SetReceiving(bool fOnOff) - { - if (m_fReceiving != fOnOff) + if ( m_bReceiving != bToggle ) ToggleReceiving(); } void ToggleReceiving(); - void PermitWhoIs(); - void ForbidWhoIs(); - void ToggleWhoIs(); + void ShowCharacterName(); + void HideCharacterName(); + void ToggleCharacterName(); - CChatChannel * GetChannel() const { return m_pChannel; } - void SetChannel(CChatChannel * pChannel) { m_pChannel = pChannel; } - void SendChatMsg( CHATMSG_TYPE iType, LPCTSTR pszName1 = NULL, LPCTSTR pszName2 = NULL, CLanguageID lang = 0 ); + CChatChannel *GetChannel() const { return m_pChannel; } + void SetChannel(CChatChannel *pChannel) + { + m_pChannel = pChannel; + m_bChatActive = (m_pChannel != NULL); + } void RenameChannel(LPCTSTR pszName); - void Ignore(LPCTSTR pszName); - void DontIgnore(LPCTSTR pszName); - void ToggleIgnore(LPCTSTR pszName); - void ClearIgnoreList(); bool IsIgnoring(LPCTSTR pszName) const { - return( FindIgnoringIndex( pszName ) != m_IgnoredMembers.BadIndex() ); + return (FindIgnoringIndex(pszName) != m_IgnoredMembers.BadIndex()); } + void AddIgnore(LPCTSTR pszName); + void RemoveIgnore(LPCTSTR pszName); + void ToggleIgnore(LPCTSTR pszName); + void ClearIgnoreList(); + size_t FindIgnoringIndex(LPCTSTR pszName) const; + + CClient *GetClient(); + LPCTSTR GetChatName(); }; class CChatChannel : public CGObListRec { // a number of clients can be attached to this chat channel. private: - friend class CChatChanMember; + friend class CChatMember; friend class CChat; CGString m_sName; CGString m_sPassword; - bool m_fVoiceDefault; // give others voice by default. + bool m_bDefaultVoice; // give others voice by default. + public: static const char *m_sClassName; - CGObArray m_NoVoices; // Current list of channel members with no voice - CGObArray m_Moderators; // Current list of channel's moderators (may or may not be currently in the channel) - CGPtrTypeArray m_Members; // Current list of members in this channel + CGObArray m_NoVoices; // current list of channel members with no voice (muted) + CGObArray m_Moderators; // current list of channel moderators (may or may not be currently in the channel) + CGPtrTypeArray m_Members; // current list of channel members + private: - void SetModerator(LPCTSTR pszName, bool fFlag = true); - void SetVoice(LPCTSTR pszName, bool fFlag = true); - void RenameChannel(CChatChanMember * pBy, LPCTSTR pszName); - size_t FindMemberIndex( LPCTSTR pszName ) const; + bool AddMember(CChatMember *pMember); + void RemoveMember(CChatMember *pMember); + + void KickMember(CChatMember *pByMember, CChatMember *pMember); + void KickAll(CChatMember *pMember = NULL); + + bool IsModerator(LPCTSTR pszName); + void SetModerator(LPCTSTR pszName, bool bRemoveAccess = false); + void AddModerator(CChatMember *pByMember, LPCTSTR pszName); + void RemoveModerator(CChatMember *pByMember, LPCTSTR pszName); + void ToggleModerator(CChatMember *pByMember, LPCTSTR pszName); + + bool HasVoice(LPCTSTR pszName); + void SetVoice(LPCTSTR pszName, bool bRemoveAccess = false); + void AddVoice(CChatMember *pByMember, LPCTSTR pszName); + void RemoveVoice(CChatMember *pByMember, LPCTSTR pszName); + void ToggleVoice(CChatMember *pByMember, LPCTSTR pszName); + + void EnableDefaultVoice(LPCTSTR pszName); + void DisableDefaultVoice(LPCTSTR pszName); + void ToggleDefaultVoice(LPCTSTR pszName); + + void MemberTalk(CChatMember *pByMember, LPCTSTR pszText, CLanguageID lang); + void Emote(LPCTSTR pszBy, LPCTSTR pszMsg, CLanguageID lang); + void PrivateMessage(CChatMember *pFrom, LPCTSTR pszTo, LPCTSTR pszMsg, CLanguageID lang); + + void WhoIs(LPCTSTR pszBy, LPCTSTR pszMember); + void RenameChannel(CChatMember *pByMember, LPCTSTR pszName); + + void ChangePassword(CChatMember *pByMember, LPCTSTR pszPassword); + LPCTSTR GetPasswordString() const + { + // (client needs this) "0" = not passworded, "1" = passworded + return m_sPassword.IsEmpty() ? "0" : "1"; + } public: explicit CChatChannel(LPCTSTR pszName, LPCTSTR pszPassword = NULL) { m_sName = pszName; m_sPassword = pszPassword; - m_fVoiceDefault = true; + m_bDefaultVoice = true; }; private: - CChatChannel(const CChatChannel& copy); - CChatChannel& operator=(const CChatChannel& other); + CChatChannel(const CChatChannel ©); + CChatChannel &operator=(const CChatChannel &other); -public: - CChatChannel* GetNext() const - { - return( static_cast ( CGObListRec::GetNext())); - } - LPCTSTR GetName() const - { - return( m_sName ); - } - LPCTSTR GetModeString() const + size_t FindMemberIndex(LPCTSTR pszName) const; + CChatChannel *GetNext() const { - // (client needs this) "0" = not passworded, "1" = passworded - return(( IsPassworded()) ? "1" : "0" ); + return static_cast(CGObListRec::GetNext()); } - LPCTSTR GetPassword() const + CChatMember *FindMember(LPCTSTR pszName) const { - return( m_sPassword ); - } - void SetPassword( LPCTSTR pszPassword) - { - m_sPassword = pszPassword; - return; - } - bool IsPassworded() const - { - return ( !m_sPassword.IsEmpty()); - } - - bool GetVoiceDefault() const { return m_fVoiceDefault; } - void SetVoiceDefault(bool fVoiceDefault) { m_fVoiceDefault = fVoiceDefault; } - void ToggleVoiceDefault(LPCTSTR pszBy); - void DisableVoiceDefault(LPCTSTR pszBy); - void EnableVoiceDefault(LPCTSTR pszBy); - void Emote(LPCTSTR pszBy, LPCTSTR pszMsg, CLanguageID lang = 0 ); - void WhoIs(LPCTSTR pszBy, LPCTSTR pszMember); - bool AddMember(CChatChanMember * pMember); - void KickMember( CChatChanMember *pByMember, CChatChanMember * pMember ); - void Broadcast(CHATMSG_TYPE iType, LPCTSTR pszName, LPCTSTR pszText, CLanguageID lang = 0, bool fOverride = false); - void SendThisMember(CChatChanMember * pMember, CChatChanMember * pToMember = NULL); - void SendMembers(CChatChanMember * pMember); - void RemoveMember(CChatChanMember * pMember); - CChatChanMember * FindMember(LPCTSTR pszName) const - { - size_t i = FindMemberIndex( pszName ); + size_t i = FindMemberIndex(pszName); if ( i == m_Members.BadIndex() ) return NULL; return m_Members[i]; } bool RemoveMember(LPCTSTR pszName) { - CChatChanMember * pMember = FindMember(pszName); - if ( pMember == NULL ) + CChatMember *pMember = FindMember(pszName); + if ( !pMember ) return false; RemoveMember(pMember); return true; } - void SetName(LPCTSTR pszName) - { - m_sName = pszName; - } - bool IsModerator(LPCTSTR pszName) const; - bool HasVoice(LPCTSTR pszName) const; - - void MemberTalk(CChatChanMember * pByMember, LPCTSTR pszText, CLanguageID lang ); - void ChangePassword(CChatChanMember * pByMember, LPCTSTR pszPassword); - void GrantVoice(CChatChanMember * pByMember, LPCTSTR pszName); - void RevokeVoice(CChatChanMember * pByMember, LPCTSTR pszName); - void ToggleVoice(CChatChanMember * pByMember, LPCTSTR pszName); - void GrantModerator(CChatChanMember * pByMember, LPCTSTR pszName); - void RevokeModerator(CChatChanMember * pByMember, LPCTSTR pszName); - void ToggleModerator(CChatChanMember * pByMember, LPCTSTR pszName); - void SendPrivateMessage(CChatChanMember * pFrom, LPCTSTR pszTo, LPCTSTR pszMsg); - void KickAll(CChatChanMember * pMember = NULL); + + void Broadcast(CHATMSG_TYPE iType, LPCTSTR pszName = NULL, LPCTSTR pszText = NULL, CLanguageID lang = 0, bool bOverride = false); + void SendMember(CChatMember *pMember, CChatMember *pToMember = NULL); + void FillMembersList(CChatMember *pMember); }; class CChat { // All the chat channels. private: - bool m_fChatsOK; // allowed to create new chats ? - CGObList m_Channels; // CChatChannel // List of chat channels. -private: - void DoCommand(CChatChanMember * pBy, LPCTSTR szMsg); - void DeleteChannel(CChatChannel * pChannel); - void WhereIs(CChatChanMember * pBy, LPCTSTR pszName) const; - void KillChannels(); - bool JoinChannel(CChatChanMember * pMember, LPCTSTR pszChannel, LPCTSTR pszPassword); - bool CreateChannel(LPCTSTR pszName, LPCTSTR pszPassword, CChatChanMember * pMember); - CChatChannel * FindChannel(LPCTSTR pszChannel) const - { - CChatChannel * pChannel = GetFirstChannel(); - for ( ; pChannel != NULL; pChannel = pChannel->GetNext()) - { - if (strcmp(pChannel->GetName(), pszChannel) == 0) - break; - } - return pChannel; - }; + bool m_bAllowChannelCreation; + + CChat(const CChat ©); + CChat &operator=(const CChat &other); + public: static const char *m_sClassName; + CGObList m_Channels; // CChatChannel // List of chat channels. + CChat() { - m_fChatsOK = true; + m_bAllowChannelCreation = true; } -private: - CChat(const CChat& copy); - CChat& operator=(const CChat& other); + bool CreateChannel(LPCTSTR pszName, LPCTSTR pszPassword, CChatMember *pMember); + void DeleteChannel(CChatChannel *pChannel); -public: - CChatChannel *GetFirstChannel() const - { - return static_cast(m_Channels.GetHead()); - } + bool JoinChannel(CChatMember *pMember, LPCTSTR pszChannel, LPCTSTR pszPassword); + void KillChannels(); - void EventMsg( CClient * pClient, const NCHAR * pszText, int len, CLanguageID lang ); // Text from a client + void Action(CClient *pClient, const NCHAR *pszText, int len, CLanguageID lang); + void QuitChat(CChatMember *pClient); - static bool IsValidName(LPCTSTR pszName, bool fPlayer); + void DoCommand(CChatMember *pBy, LPCTSTR pszMsg); + void WhereIs(CChatMember *pBy, LPCTSTR pszName); - void SendDeleteChannel(CChatChannel * pChannel); - void SendNewChannel(CChatChannel * pNewChannel); - bool IsDuplicateChannelName(const char * pszName) const + static void FormatName(CGString &sName, const CChatMember *pMember = NULL, bool bSystem = false); + static bool IsValidName(LPCTSTR pszName, bool bPlayer); + bool IsDuplicateChannelName(const char *pszName) const { - return FindChannel(pszName) != NULL; + return (FindChannel(pszName) != NULL); } - void Broadcast(CChatChanMember * pFrom, LPCTSTR pszText, CLanguageID lang = 0, bool fOverride = false); - void QuitChat(CChatChanMember * pClient); + void Broadcast(CChatMember *pFrom, LPCTSTR pszText, CLanguageID lang = 0, bool bOverride = false); + void BroadcastAddChannel(CChatChannel *pChannel); + void BroadcastRemoveChannel(CChatChannel *pChannel); + + CChatChannel *GetFirstChannel() const + { + return static_cast(m_Channels.GetHead()); + } - static void DecorateName(CGString & sName, const CChatChanMember * pMember = NULL, bool fSystem = false); - static void GenerateChatName(CGString & sName, const CClient * pClient); + CChatChannel *FindChannel(LPCTSTR pszChannel) const + { + CChatChannel *pChannel = GetFirstChannel(); + for ( ; pChannel != NULL; pChannel = pChannel->GetNext() ) + { + if ( strcmpi(static_cast(pChannel->m_sName), pszChannel) == 0 ) + break; + } + return pChannel; + }; }; class CDialogResponseArgs : public CScriptTriggerArgs @@ -955,7 +931,13 @@ enum BUFF_ICONS BI_BARAKODRAFTOFMIGHT, BI_URALITRANCETONIC, BI_SAKKHRAPROPHYLAXIS, - BI_QTY = BI_SAKKHRAPROPHYLAXIS + BI_HITSPARKSDEBUFF, + BI_SWARMDEBUFF, + BI_BROKENBONEDEBUFF, + BI_HITSPARKS, + BI_SWARM, + BI_BROKENBONE, + BI_QTY = BI_BROKENBONE }; // --------------------------------------------------------------------------------------------- @@ -1008,7 +990,7 @@ class NetworkInput; class NetworkOutput; #endif -class CClient : public CGObListRec, public CScriptObj, public CChatChanMember, public CTextConsole +class CClient : public CGObListRec, public CScriptObj, public CChatMember, public CTextConsole { // TCP/IP connection to the player or console. private: @@ -1052,6 +1034,7 @@ class CClient : public CGObListRec, public CScriptObj, public CChatChanMember, p DWORD m_CharacterListFlags; // character list features enabled on this client bool m_TooltipEnabled; // is tooltip feature enabled on this client? bool m_ContainerGridEnabled; // is container grid feature enabled on this client? + bool m_UseNewChatSystem; // is this client compatible with new SA+ chat system? CServTime m_timeLogin; // World clock of login time. "LASTCONNECTTIME" CServTime m_timeLastEvent; // Last time we got event from client. @@ -1222,7 +1205,7 @@ class CClient : public CGObListRec, public CScriptObj, public CChatChanMember, p void Event_Attack(CGrayUID uid); void Event_Book_Title(CGrayUID uid, LPCTSTR pszTitle, LPCTSTR pszAuthor); void Event_BugReport(const TCHAR *pszText, int len, BUGREPORT_TYPE type, CLanguageID lang = 0); - void Event_ChatButton(const NCHAR *pszName); // Client's chat button was pressed + void Event_ChatButton(const NCHAR *pszName = NULL); // Client's chat button was pressed void Event_ChatText(const NCHAR *pszText, int len, CLanguageID lang = 0); // Text from a client void Event_CombatMode(bool fWar); // Only for switching to combat mode bool Event_DoubleClick(CGrayUID uid, bool fMacro, bool fTestTouch, bool fScript = false); diff --git a/src/network/receive.cpp b/src/network/receive.cpp index 9c274204b..6b777cd71 100644 --- a/src/network/receive.cpp +++ b/src/network/receive.cpp @@ -2594,11 +2594,23 @@ bool PacketChatButton::onReceive(NetState* net) CClient* client = net->m_client; ASSERT(client); - skip(1); // 0x00 - NCHAR name[MAX_NAME_SIZE+1]; - readStringUNICODE(reinterpret_cast(name), COUNTOF(name)); + if ( !(g_Cfg.m_iFeatureT2A & FEATURE_T2A_CHAT) ) + return true; - client->Event_ChatButton(name); + if ( client->m_UseNewChatSystem ) + { + // On new chat system, the chat button is hardcoded on client-side and client will send + // this packet only a single time after login complete to get initial chat channel list + client->Event_ChatButton(); + } + else + { + // On old chat system, client will always send this packet when click on chat button + skip(1); // 0x0 + NCHAR chatname[MAX_NAME_SIZE * 2 + 2]; + readStringUNICODE(reinterpret_cast(chatname), COUNTOF(chatname)); + client->Event_ChatButton(chatname); + } return true; }