Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / client / src / client_chat_manager.cpp
blob1414c175d6e408431b7edac5092b3c7e2002686a
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2017 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 // Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
7 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
8 //
9 // This program is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU Affero General Public License as
11 // published by the Free Software Foundation, either version 3 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU Affero General Public License for more details.
19 // You should have received a copy of the GNU Affero General Public License
20 // along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "stdpch.h"
26 #include "game_share/generic_xml_msg_mngr.h"
27 #include "game_share/msg_client_server.h"
29 #include "client_chat_manager.h"
30 #include "net_manager.h"
31 #include "nel/gui/group_list.h"
32 #include "interface_v3/interface_manager.h"
33 #include "interface_v3/people_interraction.h"
34 #include "string_manager_client.h"
35 #include "entity_cl.h"
36 #include "nel/gui/action_handler.h"
37 #include "entities.h"
38 #include "nel/gui/group_editbox.h"
39 #include "permanent_ban.h"
40 #include "global.h"
41 #include "nel/gui/ctrl_text_button.h"
42 #include "nel/gui/group_tab.h"
43 #include "string_manager_client.h"
45 #include "game_share/generic_xml_msg_mngr.h"
46 #include "game_share/msg_client_server.h"
47 #include "game_share/chat_group.h"
48 #include "interface_v3/skill_manager.h"
50 #include "misc.h"
52 using namespace std;
53 using namespace NLMISC;
60 extern CGenericXmlMsgHeaderManager GenericMsgHeaderMngr;
61 extern CClientChatManager ChatMngr;
62 extern CLog g_log;
63 extern CEntityManager EntitiesMngr;
66 //#ifdef OLD_STRING_SYSTEM
68 //bool CNetworkString::getString (ucstring &result, CClientChatManager *mng) // OLD
69 //{
70 // result = StaticString + " / ";
71 // for (uint i = 0; i < Args.size(); i++)
72 // {
73 // result += " ";
74 // result += toString (Args[i]);
76 // CDynamicStringInfos *res = mng->getDynamicDB().getDynamicStringInfos ((uint32)Args[i]);
77 // if (res != NULL)
78 // {
79 // result += " ";
80 // result += res->Str;
81 // }
82 // }
84 // //nlinfo ("%s", result.c_str());
86 // return mng->getString (result, Args, StaticString);
87 //}
89 //void CNetworkString::setString (const ucstring &staticStringId, CClientChatManager *mng) // OLD
90 //{
91 // CBitMemStream bms;
92 // mng->getStaticDB().getInfos(staticStringId, StaticString, bms);
93 //}
95 ////-----------------------------------------------
96 //// add
97 ////
98 ////-----------------------------------------------
99 //uint32 CChatDynamicDatabase::add( uint32 index, ucstring& ucstr, vector<bool>& code ) // OLD
101 // nlinfo ("receive dynamic string association '%d' '%s'", index, ucstr.toString().c_str());
103 // map<ucstring, uint32>::iterator itIdx = _StringToIndex.find( ucstr ); // OLD
104 // if( itIdx == _StringToIndex.end() )
105 // {
106 // map<uint32,CDynamicStringInfos *>::iterator itStr = _Data.find( index );
107 // if( itStr == _Data.end() )
108 // {
109 // CDynamicStringInfos * dynInfosTmp = new CDynamicStringInfos();
110 // dynInfosTmp->Index = index;
111 // dynInfosTmp->Associated = true;
113 // // display the index number
114 // //dynInfosTmp->Str = toString((uint32)index);
115 // //dynInfosTmp->Str += " ";
116 // dynInfosTmp->Str = ucstr;
118 // if( !code.empty() )
119 // {
120 // _Huffman.add( ucstr, code );
121 // dynInfosTmp->IsHuffman = true;
122 // }
123 // else
124 // {
125 // dynInfosTmp->IsHuffman = false;
126 // }
128 // _Data.insert( make_pair(index,dynInfosTmp) );
129 // _StringToIndex.insert( make_pair(ucstr,index) );
131 // return index;
132 // }
133 // else
134 // {
135 // // we already insert a fake entry usign getInfos(), now we set the good value
137 // // display the index number
138 // //(*itStr).second->Str = toString((uint32)index);
139 // //(*itStr).second->Str += " ";
140 // (*itStr).second->Str = ucstr;
141 // (*itStr).second->Associated = true;
143 // if( !code.empty() )
144 // {
145 // _Huffman.add( ucstr, code );
146 // }
147 // return index;
148 // }
149 // }
150 // else
151 // {
152 // nlwarning("<CChatDynamicDatabase::add> the entry %s already exists",ucstr.toString().c_str());
153 // return 0;
154 // }
156 //} // add //
161 ////-----------------------------------------------
162 //// decodeString
163 ////
164 ////-----------------------------------------------
165 //void CChatDynamicDatabase::decodeString( ucstring& str, CBitMemStream& bms ) // OLD
167 // _Huffman.getId( str, bms );
169 //} // decodeString //
171 ////-----------------------------------------------
172 //// getDynamicStringInfos
173 ////
174 ////-----------------------------------------------
175 //CDynamicStringInfos * CChatDynamicDatabase::getDynamicStringInfos( uint32 index )
177 // if( index == 0 )
178 // {
179 // nldebug("<CChatDynamicDatabase::getInfos> The index 0 is not a valid index");
180 // return NULL;
181 // }
182 // map< uint32, CDynamicStringInfos *>::iterator itData = _Data.find( index );
183 // if( itData != _Data.end() )
184 // {
185 // return (*itData).second;
186 // }
187 // else
188 // {
189 // CDynamicStringInfos * infos = new CDynamicStringInfos();
190 // if( infos )
191 // {
192 // _Data.insert( make_pair(index,infos) );
193 // return infos;
194 // }
195 // else
196 // {
197 // nlwarning("<CChatDynamicDatabase::getInfos> new infos allocation failed for string %d",index);
198 // return NULL;
199 // }
200 // }
202 //} // getDynamicStringInfos //
207 ////-----------------------------------------------
208 //// ~CChatDynamicDatabase
209 ////
210 ////-----------------------------------------------
211 //CChatDynamicDatabase::~CChatDynamicDatabase()
213 // map< uint32, CDynamicStringInfos *>::iterator itData;
214 // for( itData = _Data.begin(); itData != _Data.end(); ++itData )
215 // {
216 // delete (*itData).second;
217 // }
219 //} // ~CChatDynamicDatabase //
221 //#endif
223 //-----------------------------------------------
224 // ctor
226 //-----------------------------------------------
227 CClientChatManager::CClientChatManager()
229 _ChatMode = (uint8) CChatGroup::nbChatMode;
230 _ChatDynamicChannelId = 0;
231 _NumTellPeople = 0;
232 _MaxNumTellPeople = 5;
234 // default to NULL
235 for(uint i=0;i<CChatGroup::MaxDynChanPerPlayer;i++)
237 _DynamicChannelNameLeaf[i]= NULL;
238 _DynamicChannelIdLeaf[i]= NULL;
239 _DynamicChannelIdCache[i]= DynamicChannelEmptyId;
243 //-------------------------------------------------------
244 const string *CClientChatManager::cycleLastTell()
246 if (_TellPeople.empty()) return NULL;
247 _TellPeople.push_front(_TellPeople.back());
248 _TellPeople.pop_back();
249 return &(_TellPeople.front());
252 //-------------------------------------------------------
253 void CClientChatManager::setTellListSize(uint numPeople)
255 if (numPeople <= _NumTellPeople)
257 for(uint k = 0; k < (_NumTellPeople - numPeople); ++k)
259 _TellPeople.pop_front();
261 _NumTellPeople = numPeople;
263 _MaxNumTellPeople = numPeople;
266 //-----------------------------------------------
267 // init
269 //-----------------------------------------------
270 void CClientChatManager::init( const string& /* staticDBFileName */ )
272 //#ifdef OLD_STRING_SYSTEM //
273 //// temp
274 // _StaticDB.load( staticDBFileName );
275 //#endif
277 } // init //
281 //-----------------------------------------------
282 // chat
284 //-----------------------------------------------
285 void CClientChatManager::chat( const string& strIn, bool isChatTeam )
287 // Truncate to 255 chars max (because of server restriction)
288 ucstring str= ucstring(strIn).substr(0,255); // FIXME: UTF-8 (serial)
290 // send str to IOS
291 CBitMemStream bms;
292 const char *msgType;
294 if (isChatTeam)
296 if (NLGUI::CDBManager::getInstance()->getDbProp("SERVER:GROUP:0:PRESENT")->getValueBool())
297 msgType = "STRING:CHAT_TEAM";
298 else
299 return; // don't display team chat message if there is no team chat
301 else
302 msgType = "STRING:CHAT";
304 if( GenericMsgHeaderMngr.pushNameToStream(msgType,bms) )
306 bms.serial( str ); // FIXME: UTF-8 (serial)
307 NetMngr.push( bms );
308 //nlinfo("impulseCallBack : %s %s sent", msgType.c_str(), str.toString().c_str());
310 else
312 nlwarning("<CClientChatManager::chat> unknown message name : %s", msgType);
315 if (UserEntity != NULL) UserEntity->setAFK(false);
317 } // chat //
320 //-----------------------------------------------
321 // tell
323 //-----------------------------------------------
324 void CClientChatManager::tell( const string& receiverIn, const string& strIn )
326 // Truncate to 255 chars max (because of server restriction)
327 string receiver= receiverIn.substr(0,255); // FIXME: UTF-8 (serial)
328 ucstring str= ucstring(strIn).substr(0,255); // FIXME: UTF-8 (serial)
330 // *** send str
331 CBitMemStream bms;
332 const char *msgType = "STRING:TELL";
333 if( GenericMsgHeaderMngr.pushNameToStream(msgType,bms) )
335 bms.serial( receiver );
336 bms.serial( str ); // FIXME: UTF-8
337 NetMngr.push( bms );
338 //nlinfo("impulseCallBack : %s %s %s sent", msgType.c_str(), receiver.c_str(), str.toString().c_str());
340 else
342 nlwarning("<CClientChatManager::tell> unknown message name : STRING:TELL");
346 // *** manage list of last telled people
347 // remove the telled people from list (if present)
348 std::list<string>::iterator it = _TellPeople.begin();
349 while(it != _TellPeople.end())
351 if (*it == receiver)
353 it = _TellPeople.erase(it);
354 nlassert(_NumTellPeople != 0);
355 -- _NumTellPeople;
357 else
359 ++it;
363 // readd to back of the list (most recent telled people)
364 _TellPeople.push_back(receiver);
365 ++ _NumTellPeople;
367 // if too much people, remove the older one
368 if (_NumTellPeople > _MaxNumTellPeople)
370 -- _NumTellPeople;
371 _TellPeople.pop_front();
374 // tell => the user is no more AFK.
375 if (UserEntity != NULL) UserEntity->setAFK(false);
377 } // tell //
381 //-----------------------------------------------
382 // filter
384 //-----------------------------------------------
385 void CClientChatManager::filter( uint8 filter )
387 CBitMemStream bms;
388 const char *msgType = "STRING:FILTER";
389 if( GenericMsgHeaderMngr.pushNameToStream(msgType,bms) )
391 bms.serial( filter );
392 NetMngr.push( bms );
393 //nlinfo("impulseCallBack : %s %d sent", msgType.c_str(), filter);
395 else
397 nlwarning("<CClientChatManager::filter> unknown message name : STRING:FILTER");
400 if (UserEntity != NULL) UserEntity->setAFK(false);
402 } // filter //
406 //-----------------------------------------------
407 // setChatMode
409 //-----------------------------------------------
410 void CClientChatManager::setChatMode(CChatGroup::TGroupType group, TChanID dynamicChannelId)
412 uint8 mode = group;
413 // mode really changed ?
414 if (mode == _ChatMode && dynamicChannelId==_ChatDynamicChannelId) return;
416 // Chat team don't need swap mode
417 if (group != CChatGroup::team)
419 CBitMemStream bms;
420 const char *msgType = "STRING:CHAT_MODE";
421 if( GenericMsgHeaderMngr.pushNameToStream(msgType,bms) )
423 bms.serial( mode );
424 bms.serial( dynamicChannelId );
425 NetMngr.push( bms );
426 //nlinfo("impulseCallBack : %s %d sent", msgType.c_str(), mode);
428 else
430 nlwarning("<CClientChatManager::setChatMode> unknown message name : STRING:CHAT_MODE");
434 // update cache
435 _ChatMode = mode;
436 _ChatDynamicChannelId = dynamicChannelId;
438 if (UserEntity != NULL) UserEntity->setAFK(false);
440 } // filter //
442 /// Reset the chat mode to force the client to resend it. Used during far TP.
443 void CClientChatManager::resetChatMode()
445 _ChatMode = (uint8)CChatGroup::nbChatMode;
449 // ***************************************************************************
450 void CClientChatManager::processTellString(NLMISC::CBitMemStream& bms, IChatDisplayer &chatDisplayer)
452 CChatMsg chatMsg;
454 // Serial. For tell message, there is no chat mode, coz we know we are in tell mode !
455 bms.serial (chatMsg.CompressedIndex);
456 bms.serial (chatMsg.SenderNameId);
457 bms.serial (chatMsg.Content); // FIXME: UTF-8 (serial)
459 if (PermanentlyBanned) return;
461 chatMsg.ChatMode = (uint8) CChatGroup::tell;
463 // If !complete, wait
464 string senderStr;
465 bool complete = true;
466 complete &= STRING_MANAGER::CStringManagerClient::instance()->getString(chatMsg.SenderNameId, senderStr);
467 if (!complete)
469 _ChatBuffer.push_back(CChatMsgNode(chatMsg, true));
470 nldebug("<impulseTell> Received TELL, put in buffer : waiting association");
471 return;
474 // display
475 string ucstr;
476 buildTellSentence(senderStr, chatMsg.Content.toUtf8(), ucstr);
477 chatDisplayer.displayTell(/*chatMsg.CompressedIndex, */ucstr, senderStr);
480 // ***************************************************************************
481 void CClientChatManager::processFarTellString(NLMISC::CBitMemStream& bms, IChatDisplayer &chatDisplayer)
483 CFarTellMsg farTellMsg;
485 // Serial. For far tell message, there is no chat mode nor sender index, and the sender is a string literal!
486 farTellMsg.serial(bms);
488 if (PermanentlyBanned) return;
490 // display
491 string ucstr;
492 buildTellSentence(farTellMsg.SenderName.toUtf8(), farTellMsg.Text.toUtf8(), ucstr);
493 chatDisplayer.displayTell(/*chatMsg.CompressedIndex, */ucstr, farTellMsg.SenderName.toUtf8());
497 // ***************************************************************************
498 void CClientChatManager::processChatString( NLMISC::CBitMemStream& bms, IChatDisplayer &chatDisplayer)
500 // before displaying anything, must ensure dynamic channels are up to date
501 // NB: only processChatString() have to do this. Other methods cannot be in dyn_chat mode
502 updateDynamicChatChannels(chatDisplayer);
504 // serial
505 CChatMsg chatMsg;
506 bms.serial( chatMsg );
507 CChatGroup::TGroupType type = static_cast<CChatGroup::TGroupType>(chatMsg.ChatMode);
508 string senderStr;
510 bool complete = true;
511 complete &= STRING_MANAGER::CStringManagerClient::instance()->getString(chatMsg.SenderNameId, senderStr);
513 if (type == CChatGroup::dyn_chat)
515 // retrieve the DBIndex from the dynamic chat id
516 sint32 dbIndex = ChatMngr.getDynamicChannelDbIndexFromId(chatMsg.DynChatChanID);
517 // if the client database is not yet up to date, put the chat message in buffer
518 if (dbIndex < 0)
519 complete = false;
522 // if !complete, wait
523 if (!complete)
525 _ChatBuffer.push_back(CChatMsgNode(chatMsg, false));
526 //nldebug("<impulseChat> Received CHAT, put in buffer : waiting association");
527 return;
530 // display
531 string ucstr;
532 buildChatSentence(chatMsg.CompressedIndex, senderStr, chatMsg.Content.toUtf8(), type, ucstr);
533 chatDisplayer.displayChat(chatMsg.CompressedIndex, ucstr, chatMsg.Content.toUtf8(), type, chatMsg.DynChatChanID, senderStr);
537 // ***************************************************************************
538 void CClientChatManager::processTellString2(NLMISC::CBitMemStream& bms, IChatDisplayer &chatDisplayer)
540 // serial
541 CChatMsg2 chatMsg;
542 chatMsg.ChatMode = CChatGroup::tell;
543 bms.serial(chatMsg.CompressedIndex);
544 bms.serial(chatMsg.SenderNameId);
545 bms.serial(chatMsg.PhraseId);
547 // if !complete, wait
548 string senderStr;
549 string rawMessage;
550 bool complete = true;
551 complete &= STRING_MANAGER::CStringManagerClient::instance()->getString(chatMsg.SenderNameId, senderStr);
552 complete &= STRING_MANAGER::CStringManagerClient::instance()->getDynString(chatMsg.PhraseId, rawMessage);
553 if (!complete)
555 _ChatBuffer.push_back(CChatMsgNode(chatMsg, true));
556 nldebug("<impulseTell> Received TELL, put in buffer : waiting association");
557 return;
560 // display
561 string ucstr;
562 buildTellSentence(senderStr, rawMessage, ucstr);
563 chatDisplayer.displayTell(/*chatMsg.CompressedIndex, */ucstr, senderStr);
567 // ***************************************************************************
568 void CClientChatManager::processChatString2(NLMISC::CBitMemStream& bms, IChatDisplayer &chatDisplayer)
570 CChatMsg2 chatMsg;
571 bms.serial( chatMsg );
572 if (PermanentlyBanned) return;
573 CChatGroup::TGroupType type = static_cast<CChatGroup::TGroupType>(chatMsg.ChatMode);
574 string senderStr;
575 string rawMessage;
577 // here, the type cannot be dyn_chat (no DynChatId in the message) => discard
578 if(type==CChatGroup::dyn_chat)
580 nlwarning("Client don't support dyn_chat with CChatMsg2 messages => '%x' aborted", chatMsg.PhraseId);
581 return;
584 // if !complete, wait
585 bool complete = true;
586 complete &= STRING_MANAGER::CStringManagerClient::instance()->getString(chatMsg.SenderNameId, senderStr);
587 complete &= STRING_MANAGER::CStringManagerClient::instance()->getDynString(chatMsg.PhraseId, rawMessage);
588 if (!complete)
590 _ChatBuffer.push_back(CChatMsgNode(chatMsg, false));
591 //nldebug("<impulseChat> Received CHAT, put in buffer : waiting association");
592 return;
595 rawMessage += " ";
596 rawMessage += chatMsg.CustomTxt.toUtf8();
598 // display
599 string ucstr;
600 buildChatSentence(chatMsg.CompressedIndex, senderStr, rawMessage, type, ucstr);
601 chatDisplayer.displayChat(chatMsg.CompressedIndex, ucstr, rawMessage, type, CEntityId::Unknown, senderStr);
604 // ***************************************************************************
605 void CClientChatManager::processChatStringWithNoSender( NLMISC::CBitMemStream& bms, CChatGroup::TGroupType type, IChatDisplayer &chatDisplayer)
607 nlassert(type!=CChatGroup::dyn_chat);
609 // serial
610 CChatMsg2 chatMsg;
611 uint32 phraseID;
612 bms.serial(phraseID);
613 if (PermanentlyBanned) return;
614 chatMsg.CompressedIndex = INVALID_DATASET_INDEX;
615 chatMsg.SenderNameId = 0;
616 chatMsg.ChatMode = type;
617 chatMsg.PhraseId = phraseID;
618 string ucstr;
620 // if !complete, wait
621 bool complete = STRING_MANAGER::CStringManagerClient::instance()->getDynString(chatMsg.PhraseId, ucstr);
622 if (!complete)
624 _ChatBuffer.push_back(CChatMsgNode(chatMsg, false));
625 //nldebug("<impulseDynString> Received CHAT, put in buffer : waiting association");
626 return;
629 // diplay
630 string senderName;
631 chatDisplayer.displayChat(INVALID_DATASET_INDEX, ucstr, ucstr, type, CEntityId::Unknown, senderName);
634 // ***************************************************************************
635 void CClientChatManager::flushBuffer(IChatDisplayer &chatDisplayer)
637 // before displaying anything, must ensure dynamic channels are up to date
638 updateDynamicChatChannels(chatDisplayer);
640 // **** Process waiting messages
642 list<CChatMsgNode>::iterator itMsg;
643 for( itMsg = _ChatBuffer.begin(); itMsg != _ChatBuffer.end(); )
645 CChatGroup::TGroupType type = static_cast<CChatGroup::TGroupType>(itMsg->ChatMode);
646 string sender, content;
648 // all strings received?
649 bool complete = true;
650 if (itMsg->SenderNameId != 0)
651 complete &= STRING_MANAGER::CStringManagerClient::instance()->getString(itMsg->SenderNameId, sender);
652 if(itMsg->UsePhraseId)
653 complete &= STRING_MANAGER::CStringManagerClient::instance()->getDynString(itMsg->PhraseId, content);
654 else
655 content= itMsg->Content.toUtf8();
657 if (type == CChatGroup::dyn_chat)
659 // retrieve the DBIndex from the dynamic chat id
660 sint32 dbIndex = ChatMngr.getDynamicChannelDbIndexFromId(itMsg->DynChatChanID);
661 // if the client database is not yet up to date, leave the chat message in buffer
662 if (dbIndex < 0)
663 complete = false;
666 // if complete, process
667 if (complete)
669 string ucstr;
670 if (itMsg->SenderNameId == 0)
672 ucstr = content;
674 else
676 if(itMsg->DisplayAsTell)
677 buildTellSentence(sender, content, ucstr);
678 else
679 buildChatSentence(itMsg->CompressedIndex, sender, content, type, ucstr);
682 // display
683 if(itMsg->DisplayAsTell)
684 chatDisplayer.displayTell(/*itMsg->CompressedIndex, */ucstr, sender);
685 else
686 chatDisplayer.displayChat(itMsg->CompressedIndex, ucstr, content, type, itMsg->DynChatChanID, sender);
688 list<CChatMsgNode>::iterator itTmp = itMsg++;
689 _ChatBuffer.erase( itTmp );
691 else
693 ++itMsg;
697 } // flushBuffer //
702 //-----------------------------------------------
703 // getString
705 //-----------------------------------------------
706 string CClientChatManager::getString( CBitMemStream& bms, string& ucstr )
709 // deal with parameters
710 uint32 dynParamIdx = 0;
711 bool dynParamSearch = true;
712 char chTmp[1024];
713 while( dynParamSearch )
715 // search if a parameter exists in the string
716 sprintf(chTmp,"$%d",dynParamIdx);
717 string ucstrTmp( chTmp );
718 string::size_type idx = ucstr.find(ucstrTmp);
720 // if there's a parameter in the string
721 if( idx != string::npos )
723 char c = (char)ucstr[idx+ucstrTmp.size()];
724 switch( c )
726 // parameter is an entry in the dynamic database
727 case 'e':
729 bool huff;
730 bms.serialBit(huff);
731 const string dynStr("???");
732 if( huff )
734 nldebug("<CClientChatManager::getString> receiving huffman dynamic parameter in static string");
735 // #ifdef OLD_STRING_SYSTEM
736 // _DynamicDB.decodeString( dynStr, bms );
737 // #endif
739 else
741 //if( (sint32)bms.length()*8 - bms.getPosInBit() >= 32 )
743 uint32 nameIndex;
744 bms.serial(nameIndex);
745 // #ifdef OLD_STRING_SYSTEM
746 // dynStr = _DynamicDB.getDynamicStringInfos(nameIndex)->Str;
747 // #endif
750 ucstr.replace( idx, ucstrTmp.size()+1, dynStr );
752 break;
754 // parameter is a string
755 case 's':
757 string dynStr;
758 bms.serial( dynStr );
759 ucstr.replace( idx, ucstrTmp.size()+1, dynStr );
761 break;
763 // parameter is an unsigned integer
764 case 'u':
766 uint32 nb;
767 bms.serial( nb );
768 ucstr.replace( idx, ucstrTmp.size()+1, toString(nb) );
770 break;
772 case 'u':
774 uint i = idx + strTmp.size() + 1;
775 string bitCountStr;
776 while( isdigit(str[i]) )
778 bitCountStr += str[i];
779 i++;
781 nlassert( !bitCountStr.empty() );
782 uint32 bitCount;
783 fromString(bitCountStr, bitCount);
784 nlassert( bitCount <= 64 );
785 uint64 nb;
786 bms.serial( nb, bitCount );
787 str.replace( idx, strTmp.size() + 1 + bitCountStr.size(), toString(nb) );
789 break;
791 // parameter is a signed integer
792 case 'i':
794 sint32 nb;
795 bms.serial( nb );
796 ucstr.replace( idx, ucstrTmp.size()+1, toString(nb) );
798 break;
800 case 'i':
802 uint i = idx + strTmp.size() + 1;
803 string bitCountStr;
804 while( isdigit(str[i]) )
806 bitCountStr += str[i];
807 i++;
809 nlassert( !bitCountStr.empty() );
810 uint32 bitCount;
811 fromString(bitCountStr, bitCount);
812 nlassert( bitCount <= 64 );
813 uint64 nb;
814 bms.serial( nb, bitCount );
815 str.replace( idx, strTmp.size() + 1 + bitCountStr.size(), toString(nb) );
817 break;
820 // parameter is a float
821 case 'f':
823 float nb;
824 bms.serial( nb );
825 ucstr.replace( idx, ucstrTmp.size()+1, toString(nb) );
827 break;
829 // parameter type is unknown
830 default :
832 nlwarning("<CClientChatManager::getString> The dynamic type %c is unknown",c);
835 dynParamIdx++;
837 else
839 dynParamSearch = false;
843 return ucstr;
845 } // getString //
847 //-----------------------------------------------
848 // getString
850 //-----------------------------------------------
851 bool CClientChatManager::getString( string &result, std::vector<uint64>& args, const string &ucstrbase )
853 result = ucstrbase;
855 bool finalString = true;
857 // deal with parameters
858 uint32 dynParamIdx = 0;
859 bool dynParamSearch = true;
860 char chTmp[1024];
861 while( dynParamSearch )
863 // search if a parameter exists in the string
864 sprintf(chTmp,"$%d",dynParamIdx);
865 string ucstrTmp( chTmp );
866 string::size_type idx = result.find(ucstrTmp);
868 // if there's a parameter in the string
869 if( idx != string::npos )
871 string rep;
872 rep = "???";
873 if (dynParamIdx >= args.size())
875 nlwarning ("Missing args for string '%s', only %d args, need arg %d", ucstrbase.c_str(), args.size(), dynParamIdx);
877 else
879 char c = (char)result[idx+ucstrTmp.size()];
880 switch( c )
882 // parameter is an entry in the dynamic database
883 case 'e':
885 // #ifdef OLD_STRING_SYSTEM
886 // CDynamicStringInfos *res = _DynamicDB.getDynamicStringInfos ((uint32)args[dynParamIdx]);
887 // if (!res->Associated)
888 // #endif
889 finalString = false;
890 // #ifdef OLD_STRING_SYSTEM
891 // rep = res->Str;
892 // #endif
894 break;
896 // parameter is a string
897 case 's':
899 nlwarning ("string param not implemented in the vector<uint64> decoding");
901 break;
903 // parameter is an unsigned integer
904 case 'u':
906 uint32 nb = (uint32) args[dynParamIdx];
907 rep = toString(nb);
909 break;
911 // parameter is a signed integer
912 case 'i':
914 sint32 nb = (sint32) args[dynParamIdx];
915 rep = toString(nb);
917 break;
919 // parameter is a float
920 case 'f':
922 float nb = *(float *) &(args[dynParamIdx]);
923 rep = toString(nb);
925 break;
927 // parameter type is unknown
928 default :
930 nlwarning("<CClientChatManager::getString> The dynamic type %c is unknown",c);
932 break;
935 result.replace( idx, ucstrTmp.size()+1, rep );
937 dynParamIdx++;
939 else
941 dynParamSearch = false;
945 return finalString;
947 } // getString //
949 // ***************************************************************************
950 void CClientChatManager::buildTellSentence(const string &sender, const string &msg, string &result)
952 // If no sender name was provided, show only the msg
953 if ( sender.empty() )
954 result = msg;
955 else
957 string name = CEntityCL::removeTitleAndShardFromName(sender);
958 string csr;
960 // special case where there is only a title, very rare case for some NPC
961 if (name.empty())
963 // we need the gender to display the correct title
964 CCharacterCL *entity = dynamic_cast<CCharacterCL*>(EntitiesMngr.getEntityByName(sender, true, true));
965 bool bWoman = entity && entity->getGender() == GSGENDER::female;
967 name = STRING_MANAGER::CStringManagerClient::getTitleLocalizedName(CEntityCL::getTitleFromName(sender), bWoman);
969 // Sometimes translation contains another title
970 string::size_type pos = name.find('$');
971 if (pos != string::npos)
973 name = STRING_MANAGER::CStringManagerClient::getTitleLocalizedName(CEntityCL::getTitleFromName(name), bWoman);
977 else
979 // Does the char have a CSR title?
980 csr = CHARACTER_TITLE::isCsrTitle(CEntityCL::getTitleFromName(sender)) ? "(CSR) " : "";
983 result = csr + name + " " + CI18N::get("tellsYou") + ": " + msg;
988 // ***************************************************************************
989 void CClientChatManager::buildChatSentence(TDataSetIndex /* compressedSenderIndex */, const string &sender, const string &msg, CChatGroup::TGroupType type, string &result)
991 // if its a tell, then use buildTellSentence
992 if(type==CChatGroup::tell)
994 buildTellSentence(sender, msg, result);
995 return;
998 // If no sender name was provided, show only the msg
999 if ( sender.empty() )
1001 result = msg;
1002 return;
1005 // get the category if any. Note, in some case (chat from other player), there is not categories
1006 // and we do not want getStringCategory to return 'SYS' category.
1007 string finalMsg;
1008 string catStr = getStringCategory(msg, finalMsg, false);
1009 string cat;
1010 if (!catStr.empty())
1011 cat = "&" + catStr + "&";
1013 if (!cat.empty())
1015 result = msg;
1016 return;
1019 // Format the sentence with the provided sender name
1020 string senderName = CEntityCL::removeTitleAndShardFromName(sender);
1022 string csr;
1023 // Does the char have a CSR title?
1024 csr = CHARACTER_TITLE::isCsrTitle(CEntityCL::getTitleFromName(sender)) ? "(CSR) " : "";
1026 if (UserEntity && senderName == UserEntity->getDisplayName())
1028 // The player talks
1029 switch(type)
1031 case CChatGroup::shout:
1032 result = cat + csr + CI18N::get("youShout") + ": " + finalMsg;
1033 break;
1034 default:
1035 result = cat + csr + CI18N::get("youSay") + ": " + finalMsg;
1036 break;
1039 else
1041 // Special case where there is only a title, very rare case for some NPC
1042 if (senderName.empty())
1044 CCharacterCL *entity = dynamic_cast<CCharacterCL*>(EntitiesMngr.getEntityByName(sender, true, true));
1045 // We need the gender to display the correct title
1046 bool bWoman = entity && entity->getGender() == GSGENDER::female;
1048 senderName = STRING_MANAGER::CStringManagerClient::getTitleLocalizedName(CEntityCL::getTitleFromName(sender), bWoman);
1050 // Sometimes translation contains another title
1051 string::size_type pos = senderName.find('$');
1052 if (pos != string::npos)
1054 senderName = STRING_MANAGER::CStringManagerClient::getTitleLocalizedName(CEntityCL::getTitleFromName(senderName), bWoman);
1059 senderName = STRING_MANAGER::CStringManagerClient::getLocalizedName(senderName);
1060 switch(type)
1062 case CChatGroup::shout:
1063 result = cat + csr + senderName + " " + CI18N::get("heShout") + ": " + finalMsg;
1064 break;
1065 default:
1066 result = cat + csr + senderName + " " + CI18N::get("heSays") + ": " + finalMsg;
1067 break;
1072 // ***************************************************************************
1073 void CClientChatManager::initInGame()
1075 CInterfaceManager *pIM= CInterfaceManager::getInstance();
1077 for(uint i=0;i<CChatGroup::MaxDynChanPerPlayer;i++)
1079 // default
1080 _DynamicChannelNameLeaf[i]= NULL;
1081 _DynamicChannelIdLeaf[i]= NULL;
1082 _DynamicChannelIdCache[i]= DynamicChannelEmptyId;
1083 // get
1084 CCDBNodeLeaf *name= NLGUI::CDBManager::getInstance()->getDbProp(toString("SERVER:DYN_CHAT:CHANNEL%d:NAME", i), false);
1085 CCDBNodeLeaf *id= NLGUI::CDBManager::getInstance()->getDbProp(toString("SERVER:DYN_CHAT:CHANNEL%d:ID", i), false);
1086 if(name && id)
1088 _DynamicChannelNameLeaf[i]= name;
1089 _DynamicChannelIdLeaf[i]= id;
1094 // ***************************************************************************
1095 void CClientChatManager::releaseInGame()
1097 for(uint i=0;i<CChatGroup::MaxDynChanPerPlayer;i++)
1099 _DynamicChannelNameLeaf[i]= NULL;
1100 _DynamicChannelIdLeaf[i]= NULL;
1101 _DynamicChannelIdCache[i]= DynamicChannelEmptyId;
1105 // ***************************************************************************
1106 TChanID CClientChatManager::getDynamicChannelIdFromDbIndex(uint32 dbIndex)
1108 if(dbIndex>=CChatGroup::MaxDynChanPerPlayer || _DynamicChannelIdLeaf[dbIndex]==NULL)
1109 return CEntityId::Unknown;
1110 else
1111 return TChanID(uint64(_DynamicChannelIdLeaf[dbIndex]->getValue64()));
1114 // ***************************************************************************
1115 sint32 CClientChatManager::getDynamicChannelDbIndexFromId(TChanID channelId)
1117 for(uint i=0;i<CChatGroup::MaxDynChanPerPlayer;i++)
1119 if(_DynamicChannelIdLeaf[i]!= NULL)
1121 if(uint64(_DynamicChannelIdLeaf[i]->getValue64()) == channelId.getRawId())
1122 return i;
1126 return -1;
1129 // ***************************************************************************
1130 bool CClientChatManager::isDynamicChannelExist(TChanID channelId)
1132 sint32 dbid= getDynamicChannelDbIndexFromId(channelId);
1133 return dbid>=0 && dbid<CChatGroup::MaxDynChanPerPlayer;
1136 // ***************************************************************************
1137 uint32 CClientChatManager::getDynamicChannelNameFromDbIndex(uint32 dbIndex)
1139 if(dbIndex>=CChatGroup::MaxDynChanPerPlayer || _DynamicChannelNameLeaf[dbIndex]==NULL)
1140 return 0;
1141 else
1142 return _DynamicChannelNameLeaf[dbIndex]->getValue32();
1146 // ***************************************************************************
1147 void CClientChatManager::updateDynamicChatChannels(IChatDisplayer &chatDisplayer)
1149 // For all channels
1150 for(uint i=0;i<CChatGroup::MaxDynChanPerPlayer;i++)
1152 // if the NAME is 0, force also an "empty id". because server may not release Id to 0 while NAME is.
1153 sint32 curActualChannelId= DynamicChannelEmptyId;
1154 if(_DynamicChannelIdLeaf[i] && _DynamicChannelNameLeaf[i])
1156 if(_DynamicChannelNameLeaf[i]->getValue32()!=0)
1157 curActualChannelId= _DynamicChannelIdLeaf[i]->getValue32();
1160 // if different from precend, clear the channel
1161 if(curActualChannelId !=(sint32)_DynamicChannelIdCache[i])
1163 _DynamicChannelIdCache[i]= curActualChannelId;
1164 // flush
1165 chatDisplayer.clearChannel(CChatGroup::dyn_chat, i);
1172 // ***************************************************************************
1174 class CHandlerTell : public IActionHandler
1176 void execute (CCtrlBase *pCaller, const string &sParams)
1178 string receiver = getParam (sParams, "player");
1179 string message;
1180 message = getParam (sParams, "text");
1182 if (receiver.empty() || message.empty())
1183 return;
1185 // Get the chat window (if any)
1186 CChatWindow *cw = NULL;
1187 CGroupEditBox *eb = pCaller?dynamic_cast<CGroupEditBox *>(pCaller):NULL;
1188 if (eb)
1189 cw = getChatWndMgr().getChatWindowFromCaller(eb);
1191 // Send the message.
1192 ChatMngr.tell(receiver, message);
1194 // display in the good window
1195 CInterfaceProperty prop;
1196 prop.readRGBA("UI:SAVE:CHAT:COLORS:SPEAKER"," ");
1197 string finalMsg;
1198 CChatWindow::encodeColorTag(prop.getRGBA(), finalMsg, false);
1200 string csr(CHARACTER_TITLE::isCsrTitle(UserEntity->getTitleRaw()) ? "(CSR) " : "");
1201 finalMsg += csr + CI18N::get("youTell") + ": ";
1202 prop.readRGBA("UI:SAVE:CHAT:COLORS:TELL"," ");
1203 CChatWindow::encodeColorTag(prop.getRGBA(), finalMsg, true);
1204 finalMsg += message;
1205 // display msg with good color
1206 // TDataSetIndex dsi; // not used ....
1207 PeopleInterraction.ChatInput.Tell.displayTellMessage(/*dsi, */finalMsg, receiver, prop.getRGBA());
1209 string s = CI18N::get("youTellPlayer");
1210 strFindReplace(s, "%name", receiver);
1211 strFindReplace(finalMsg, CI18N::get("youTell"), s);
1212 CInterfaceManager::getInstance()->log(finalMsg, CChatGroup::groupTypeToString(CChatGroup::tell));
1215 REGISTER_ACTION_HANDLER( CHandlerTell, "tell");
1217 // ***************************************************************************
1219 class CHandlerEnterTell : public IActionHandler
1221 void execute (CCtrlBase * /* pCaller */, const string &sParams)
1223 CInterfaceManager *im = CInterfaceManager::getInstance();
1224 string receiver = getParam (sParams, "player");
1225 if (receiver.empty())
1226 return;
1228 CChatGroupWindow *pCGW = PeopleInterraction.getChatGroupWindow();
1229 if (pCGW != NULL)
1231 CGroupContainer *pGC = pCGW->createFreeTeller(receiver);
1232 if (pGC != NULL)
1234 pGC->setActive(true);
1235 CGroupEditBox *eb = dynamic_cast<CGroupEditBox *>(pGC->getGroup("eb"));
1236 if (eb)
1238 CWidgetManager::getInstance()->setCaptureKeyboard(eb);
1244 REGISTER_ACTION_HANDLER( CHandlerEnterTell, "enter_tell");
1247 //-----------------------------------------------
1248 // updateChatModeAndButton
1250 //-----------------------------------------------
1251 void CClientChatManager::updateChatModeAndButton(uint mode, uint32 dynamicChannelDbIndex)
1253 // Check if USER chat is active
1254 bool userActive = false;
1255 CChatGroupWindow *pCGW = PeopleInterraction.getChatGroupWindow();
1256 if (pCGW)
1258 CInterfaceGroup* pIG = pCGW->getContainer()->getGroup("content:cb:user");
1259 if (pIG)
1260 userActive = pIG->getActive();
1263 CChatGroup::TGroupType m = (CChatGroup::TGroupType)mode;
1265 if (userActive)
1267 // Change the button of the user chat to the corresponding chat target
1268 if (pCGW)
1270 CCtrlTextButton *pUserBut = dynamic_cast<CCtrlTextButton*>(pCGW->getContainer()->getCtrl("content:but_user"));
1271 CCtrlTextButton *pEmoteBut = dynamic_cast<CCtrlTextButton*>(pCGW->getContainer()->getCtrl("content:but_emote"));
1272 CInterfaceGroup *pEditBox = dynamic_cast<CInterfaceGroup*>(pCGW->getContainer()->getGroup("content:ebw"));
1274 CInterfaceManager *pIM = CInterfaceManager::getInstance();
1275 const bool teamActive = NLGUI::CDBManager::getInstance()->getDbProp("SERVER:GROUP:0:PRESENT")->getValueBool();
1276 const bool guildActive = NLGUI::CDBManager::getInstance()->getDbProp("SERVER:GUILD:NAME")->getValueBool();
1278 if (m == CChatGroup::team && ! teamActive)
1279 m = PeopleInterraction.TheUserChat.Filter.getTargetGroup();
1281 if (m == CChatGroup::guild && ! guildActive)
1282 m = PeopleInterraction.TheUserChat.Filter.getTargetGroup();
1284 if (pUserBut)
1286 switch(m)
1288 default:
1289 case CChatGroup::arround:
1290 case CChatGroup::say: pUserBut->setHardText("uiFilterAround"); break;
1291 case CChatGroup::region: pUserBut->setHardText("uiFilterRegion"); break;
1292 case CChatGroup::universe: pUserBut->setHardText("uiFilterUniverse"); break;
1293 case CChatGroup::team: if (teamActive) pUserBut->setHardText("uiFilterTeam"); break;
1294 case CChatGroup::guild: if (guildActive) pUserBut->setHardText("uiFilterGuild"); break;
1295 case CChatGroup::dyn_chat:
1296 uint32 textId = ChatMngr.getDynamicChannelNameFromDbIndex(dynamicChannelDbIndex);
1297 string title;
1298 STRING_MANAGER::CStringManagerClient::instance()->getDynString(textId, title);
1299 if (title.empty())
1301 // Dyn channel does not exist, don't change
1302 m = PeopleInterraction.TheUserChat.Filter.getTargetGroup();
1303 dynamicChannelDbIndex = PeopleInterraction.TheUserChat.Filter.getTargetDynamicChannelDbIndex();
1305 else
1307 pUserBut->setHardText(title);
1309 break;
1312 pUserBut->setActive(true);
1313 pUserBut->getParent()->updateCoords();
1314 pUserBut->updateCoords();
1317 if (pEmoteBut)
1319 pEmoteBut->setActive(true);
1320 pEmoteBut->updateCoords();
1323 if (pEditBox && pUserBut && pEmoteBut)
1325 pEditBox->setW(-pUserBut->getWReal()-pEmoteBut->getWReal()-8);
1326 pEditBox->setX(pUserBut->getWReal()+4);
1329 PeopleInterraction.TheUserChat.Filter.setTargetGroup(m, dynamicChannelDbIndex);
1330 PeopleInterraction.ChatGroup.Filter.setTargetGroup(m, dynamicChannelDbIndex);
1335 // ***************************************************************************
1337 class CHandlerTalk : public IActionHandler
1339 void execute (CCtrlBase * /* pCaller */, const string &sParams)
1341 // Param
1342 uint mode;
1343 fromString(getParam (sParams, "mode"), mode);
1344 string text = getParam (sParams, "text");
1346 // Parse any tokens in the text
1347 if ( ! CInterfaceManager::parseTokens(text))
1349 return;
1352 // Find the base group
1353 if ((mode<CChatGroup::nbChatMode) && !text.empty())
1355 if(text[0] == '/')
1357 string str = text;
1358 string cmdWithArgs = str.substr(1);
1360 // Get the command name from the string, can contain spaces
1361 string cmd = cmdWithArgs.substr(0, cmdWithArgs.find(' '));
1362 if (cmdWithArgs.find('"') == 0)
1364 string::size_type pos = cmdWithArgs.find('"', 1);
1365 if (string::npos != pos)
1367 cmd = cmdWithArgs.substr(1, pos - 1);
1371 if ( NLMISC::ICommand::exists( cmd ) )
1373 NLMISC::ICommand::execute( cmdWithArgs, g_log );
1375 else
1377 CInterfaceManager *im = CInterfaceManager::getInstance();
1378 im->displaySystemInfo (cmd + ": " + CI18N::get ("uiCommandNotExists"));
1381 else
1383 if (mode == CChatGroup::dyn_chat)
1385 uint channel;
1386 fromString(getParam (sParams, "channel"), channel);
1387 if (channel < CChatGroup::MaxDynChanPerPlayer)
1389 PeopleInterraction.talkInDynamicChannel(channel, text);
1391 else
1393 nlwarning("/ah talk: invalid dyn_chat channel %d\n", channel);
1396 else
1398 ChatMngr.setChatMode((CChatGroup::TGroupType)mode);
1399 ChatMngr.chat(text, mode == CChatGroup::team);
1405 REGISTER_ACTION_HANDLER( CHandlerTalk, "talk");
1407 // ***************************************************************************
1409 class CHandlerEnterTalk : public IActionHandler
1411 void execute (CCtrlBase * /* pCaller */, const string &sParams)
1413 // Param
1414 uint mode;
1415 uint channel = 0;
1417 fromString(getParam (sParams, "mode"), mode);
1419 if (mode == CChatGroup::dyn_chat)
1421 fromString(getParam(sParams, "channel"), channel);
1423 if (channel >= CChatGroup::MaxDynChanPerPlayer)
1425 channel = 0;
1429 ChatMngr.updateChatModeAndButton(mode, channel);
1432 REGISTER_ACTION_HANDLER( CHandlerEnterTalk, "enter_talk");
1434 // ***************************************************************************
1436 class CHandlerTalkMessage : public IActionHandler
1438 void execute (CCtrlBase * /* pCaller */, const string &sParams)
1440 // Param
1441 string text = CI18N::get ("uiTalkMemMsg"+sParams);
1443 // Find the base group
1444 if (!text.empty())
1446 ChatMngr.setChatMode (CChatGroup::say);
1447 ChatMngr.chat(text);
1451 REGISTER_ACTION_HANDLER( CHandlerTalkMessage, "talk_message");
1453 // ***************************************************************************
1455 class CHandlerSwapChatMode : public IActionHandler
1457 void execute (CCtrlBase * /* pCaller */, const string &sParams)
1459 CInterfaceManager *pIM= CInterfaceManager::getInstance();
1460 bool updateCapture= getParam(sParams, "update_capture")=="1";
1462 CCDBNodeLeaf *node= NLGUI::CDBManager::getInstance()->getDbProp("UI:SAVE:CHAT:ENTER_DONT_QUIT_CB", false);
1463 if(node)
1465 // if "chatmode" is active
1466 if(node->getValue32())
1468 // leave it (enter quit CB)
1469 node->setValue32(0);
1470 // also leave Chat Focus (important if comes from command)
1471 if (updateCapture)
1472 CWidgetManager::getInstance()->setCaptureKeyboard(NULL);
1474 else
1476 // enter chat mode (enter dont quit CB)
1477 node->setValue32(1);
1478 // enter Chat focus if '/c' entered
1479 if (updateCapture && !CWidgetManager::getInstance()->getCaptureKeyboard())
1481 // reset to the old captured keyboard (should be the one that have launched the command)
1482 if(CWidgetManager::getInstance()->getOldCaptureKeyboard())
1483 CWidgetManager::getInstance()->setCaptureKeyboard(CWidgetManager::getInstance()->getOldCaptureKeyboard());
1489 REGISTER_ACTION_HANDLER( CHandlerSwapChatMode, "swap_chat_mode");