// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . //----------------------------------------------------------------------------- // includes //----------------------------------------------------------------------------- #include "gus_chat.h" #include "nel/misc/command.h" #include "nel/net/message.h" #include "nel/net/unified_network.h" #include "nel/net/service.h" #include "game_share/singleton_registry.h" #include "game_share/dyn_chat.h" #include "game_share/ryzom_entity_id.h" #include "game_share/synchronised_message.h" #include "game_share/ios_interface.h" #include "gus_mirror.h" #include "gus_client_manager.h" //----------------------------------------------------------------------------- // namespaces //----------------------------------------------------------------------------- using namespace std; using namespace NLMISC; using namespace NLNET; //----------------------------------------------------------------------------- // GUS namespace //----------------------------------------------------------------------------- namespace GUS { //----------------------------------------------------------------------------- // class CChatChannelImplementation //----------------------------------------------------------------------------- class CChatChannelImplementation: public CChatChannel { friend class CChatManagerImplementation; public: CChatChannelImplementation(const NLMISC::CSString &channelName); void openChannel(const NLMISC::CSString& channelTitle, uint32 historySize, bool noBroadcast, bool forwardInput, bool autoInsertPlayer); void closeChannel(); void addClient(GUS::TClientId clientId); void removeClient(GUS::TClientId clientId); void broadcastMessage(const ucstring& speakerName, const ucstring& txt); void broadcastMessage(const std::string& speakerNameUtf8, const std::string& txtUtf8); void sendMessage(GUS::TClientId clientId, const ucstring& speakerName,const ucstring& txt); void sendMessage(GUS::TClientId clientId, const std::string& speakerNameUtf8, const std::string& txtUtf8); void setChatCallback(IChatCallback *callback); const NLMISC::CSString& getChannelName() const; const NLMISC::CSString& getChannelTitle() const; void setChannelTitle(const NLMISC::CSString& title); private: IChatCallback* _ChatCallback; NLMISC::CSString _ChannelName; bool _ChannelOpen; TChanID _ChannelID; NLMISC::CSString _ChannelTitle; uint32 _HistorySize; bool _NoBroadcast; bool _ForwardInput; bool _AutoInsertPlayer; struct TClientValidation { GUS::TClientId ClientId; TGameCycle EntryDate; }; // List of client waiting 'validation' in the chat list _ClientToValidate; NLMISC_COMMAND_FRIEND(chatDisplayChannel); NLMISC_COMMAND_FRIEND(chatPlayerInput); }; //----------------------------------------------------------------------------- // class CChatManagerImplementation //----------------------------------------------------------------------------- class CChatManagerImplementation : public CChatManager, public CGusMirror::IMirrorModuleCallback, public CClientManager::IConnectionHandler, public IServiceSingleton { private: // prohibit instantiation with private ctor CChatManagerImplementation(); //----------------------------------------------------------------------------- // specialisation of CGusMirror::IMirrorModuleCallback void mirrorIsReady(CGusMirror *mirrorModule) {}; void serviceMirrorUp(CGusMirror *mirrorModule, const std::string &serviceName, NLNET::TServiceId serviceId); void serviceMirrorDown(CGusMirror *mirrorModule, const std::string &serviceName, NLNET::TServiceId serviceId) {}; void mirrorTickUpdate(CGusMirror *mirrorModule); void entityAdded(CGusMirror *mirrorModule, CMirroredDataSet *dataSet, const TDataSetRow &entityIndex) {}; void entityRemoved(CGusMirror *mirrorModule, CMirroredDataSet *dataSet, const TDataSetRow &entityIndex, const NLMISC::CEntityId *entityId) {}; void propertyChanged(CGusMirror *mirrorModule, CMirroredDataSet *dataSet, const TDataSetRow &entityIndex, TPropertyIndex propIndex) {}; //----------------------------------------------------------------------------- // specialisation of CClientManager::IConnectionHandler void connect(TClientId); void disconnect(TClientId); //----------------------------------------------------------------------------- // specialisation of IServiceSingleton void init(); public: //----------------------------------------------------------------------------- // specialisation of CChatManager TChatChannelPtr createChatChannel(const NLMISC::CSString &channelName); void removeChannel(CChatChannel *channel); TChatChannelPtr getChatChannel(const NLMISC::CSString& channelName); bool setChatChannelTitle(CChatChannel *channel,const NLMISC::CSString& channelTitle); //----------------------------------------------------------------------------- // propriatory public interface // get singleton instance static CChatManagerImplementation* getInstance(); uint32 generateChannelNumber(); static void cbDynChatForward( CMessage& msgin, const string &serviceName, NLNET::TServiceId serviceId ); private: //----------------------------------------------------------------------------- // private data typedef map > TChannels; TChannels _Channels; /// Channel numbering (incremented for each channel created) uint32 _CurrentChannelNumber; //----------------------------------------------------------------------------- // friendship for associated NLMISC_COMMANDs NLMISC_COMMAND_FRIEND(chatCreateChannel); NLMISC_COMMAND_FRIEND(chatRemoveChannel); NLMISC_COMMAND_FRIEND(chatSendMessage); NLMISC_COMMAND_FRIEND(chatDisplayChannel); NLMISC_COMMAND_FRIEND(chatPlayerInput); }; //----------------------------------------------------------------------------- // NeL service callbacks //----------------------------------------------------------------------------- TUnifiedCallbackItem ChatCbArray[]= { { "DYN_CHAT:FORWARD", CChatManagerImplementation::cbDynChatForward}, }; //----------------------------------------------------------------------------- // methods CChatManagerImplementation //----------------------------------------------------------------------------- CChatManagerImplementation::CChatManagerImplementation() : _CurrentChannelNumber(0) { } void CChatManagerImplementation::serviceMirrorUp(CGusMirror *mirrorModule, const std::string &serviceName, NLNET::TServiceId serviceId) { if (serviceName == "EGS") { // open all channel on the EGS server TChannels::iterator it(_Channels.begin()), last(_Channels.end()); for(; it != last; ++it) { CChatChannelImplementation *cci = it->second; cci->openChannel(cci->_ChannelTitle, cci->_HistorySize, cci->_NoBroadcast, cci->_ForwardInput, cci->_AutoInsertPlayer); } } } void CChatManagerImplementation::mirrorTickUpdate(CGusMirror *mirrorModule) { // for each channel, update waiting clients TGameCycle now = CTickEventHandler::getGameCycle(); TChannels::iterator it(_Channels.begin()), last(_Channels.end()); for (; it != last; ++it) { CChatChannelImplementation *cci = it->second; while( !cci->_ClientToValidate.empty() && (cci->_ClientToValidate.front().EntryDate < (now - 50))) { if (cci->_ChatCallback != NULL) cci->_ChatCallback->clientReadyInChannel(cci, cci->_ClientToValidate.front().ClientId); cci->_ClientToValidate.pop_front(); } } } void CChatManagerImplementation::connect(TClientId client) { // For now, we consider connecting all client in all chat channel TChannels::iterator it(_Channels.begin()), last(_Channels.end()); for (; it != last; ++it) { CChatChannelImplementation *cci = it->second; if (cci->_AutoInsertPlayer || (cci->_ChatCallback != NULL && cci->_ChatCallback->isClientAllowedInChatChannel(client, cci))) { cci->addClient(client); // // add the client into the chat // TChanID chan = cci->_ChannelID; // bool writeRight = true; // // CMessage msgout("DYN_CHAT:ADD_SESSION"); // msgout.serial(chan); // msgout.serial(client); // msgout.serial(writeRight); // // sendMessageViaMirror( "EGS", msgout); // // CChatChannelImplementation::TClientValidation cv; // cv.ClientId = client; // cv.EntryDate = CTickEventHandler::getGameCycle(); // // cci->_ClientToValidate.push_back(cv); } } } void CChatManagerImplementation::disconnect(TClientId) { // Nothing special to do } void CChatManagerImplementation::init() { NLNET::CUnifiedNetwork::getInstance()->addCallbackArray(ChatCbArray, sizeof(ChatCbArray) / sizeof(TUnifiedCallbackItem)); // Register a callback in client manager CClientManager::getInstance()->setConnectionCallback(this); // Register a callback in the gus mirror CGusMirror::getInstance()->registerModuleCallback(this); } CChatManagerImplementation* CChatManagerImplementation::getInstance() { static CChatManagerImplementation* ptr=NULL; if (ptr==NULL) ptr= new CChatManagerImplementation; return ptr; } TChatChannelPtr CChatManagerImplementation::createChatChannel(const NLMISC::CSString &channelName) { if (_Channels.find(channelName) != _Channels.end()) { nlwarning("A channel with name '%s' already exist, creation failed", channelName.c_str()); return NULL; } CChatChannelImplementation *cc = new CChatChannelImplementation(channelName); _Channels[channelName] = cc; return cc; } void CChatManagerImplementation::removeChannel(CChatChannel *channel) { TChannels::iterator it(_Channels.begin()), last(_Channels.end()); for (; it != last; ++it) { if (it->second == (CChatChannelImplementation*)channel) { _Channels.erase(it); break; } } } TChatChannelPtr CChatManagerImplementation::getChatChannel(const NLMISC::CSString& channelName) { if (_Channels.find(channelName) == _Channels.end()) { nlwarning("Chat Channel '%s' is unknown", channelName.c_str()); return TChatChannelPtr(); } return &*_Channels[channelName]; } bool CChatManagerImplementation::setChatChannelTitle(CChatChannel *channel,const NLMISC::CSString& channelTitle) { bool ok= true; CSString title= channelTitle; TChannels::iterator it(_Channels.begin()), last(_Channels.end()); for (; it != last; ++it) { if (it->second!=NULL && it->second!= (CChatChannelImplementation*)channel && (it->second->getChannelTitle()==title || it->second->getChannelName()==title) ) { // the channel title is already assigned to another channel ok= false; title= channel->getChannelName(); break; } } channel->setChannelTitle(title); return ok; } uint32 CChatManagerImplementation::generateChannelNumber() { return _CurrentChannelNumber++; } void CChatManagerImplementation::cbDynChatForward( CMessage& msgin, const string &serviceName, NLNET::TServiceId serviceId ) { CChatManagerImplementation *cmi = CChatManagerImplementation::getInstance(); // serial the player inputs TPlayerInputForward pif; msgin.serial(pif); CEntityId eid = CGusMirror::getInstance()->getDataSet("fe_temp")->getEntityId(pif.Sender); nldebug("Receiving input '%s' from player %s %s", pif.Content.toString().c_str(), eid.toString().c_str(), pif.Sender.toString().c_str()); // find the channel to callback the creator TChannels::iterator it(cmi->_Channels.begin()), last(cmi->_Channels.end()); for (; it != last; ++it) { CChatChannelImplementation *cc = it->second; if (cc->_ChannelID == pif.ChanID) { nldebug("Forwarding player %s input into channel '%s'", eid.toString().c_str(), cc->_ChannelName.c_str()); // ok, we find it ! if (cc->_ChatCallback != NULL) { cc->_ChatCallback->receiveMessage(pif.Sender, pif.Content); break; } } } } //----------------------------------------------------------------------------- // methods CChatManager //----------------------------------------------------------------------------- CChatManager* CChatManager::getInstance() { return CChatManagerImplementation::getInstance(); } //----------------------------------------------------------------------------- // methods CChatChannelImplementation //----------------------------------------------------------------------------- CChatChannelImplementation::CChatChannelImplementation(const CSString &channelName) :_ChannelOpen(false), _ChannelName(channelName) { } void CChatChannelImplementation::openChannel(const NLMISC::CSString& channelTitle, uint32 historySize, bool noBroadcast, bool forwardInput, bool autoInsertPlayer) { if (_ChannelOpen) return; // create the channel id uint32 number = CChatManagerImplementation::getInstance()->generateChannelNumber(); _ChannelID = CEntityId(RYZOMID::dynChatGroup, uint64(number)); _ChannelTitle = channelTitle; _HistorySize = historySize; _NoBroadcast = noBroadcast; _ForwardInput = forwardInput; _AutoInsertPlayer = autoInsertPlayer; CMessage msgout("DYN_CHAT:ADD_SERVICE_CHAN"); msgout.serial(_ChannelID); msgout.serial(_ChannelTitle); msgout.serial(_NoBroadcast); msgout.serial(_ForwardInput); sendMessageViaMirror( "EGS", msgout); if (_HistorySize != 0) { CMessage msgout("DYN_CHAT:SET_CHAN_HISTORY"); msgout.serial(_ChannelID); msgout.serial(_HistorySize); sendMessageViaMirror("EGS", msgout); } _ChannelOpen = true; // put all existing client into this new channel CMirroredDataSet *ds = CGusMirror::getInstance()->getDataSet("fe_temp"); TEntityIdToEntityIndexMap::const_iterator it(ds->entityBegin()), last(ds->entityEnd()); for (; it != last; ++it) { if (it->first.getType() == RYZOMID::player) { TDataSetRow clientID = ds->getDataSetRow(it->first); if (_AutoInsertPlayer || (_ChatCallback && _ChatCallback->isClientAllowedInChatChannel(clientID, this))) { addClient(clientID); // // We have a player here, open a session // // TChanID chan = _ChannelID; // bool writeRight = true; // // CMessage msgout("DYN_CHAT:ADD_SESSION"); // msgout.serial(chan); // msgout.serial(clientID); // msgout.serial(writeRight); // // sendMessageViaMirror( "EGS", msgout); // // TClientValidation cv; // cv.ClientId = clientID; // cv.EntryDate = CTickEventHandler::getGameCycle(); // // _ClientToValidate.push_back(cv); } } } } void CChatChannelImplementation::closeChannel() { if (!_ChannelOpen) return; CMessage msgout("DYN_CHAT:REMOVE_SERVICE_CHAN"); msgout.serial(_ChannelID); sendMessageViaMirror( "EGS", msgout); _ChannelOpen = false; } void CChatChannelImplementation::addClient(GUS::TClientId clientId) { TChanID chan = _ChannelID; bool writeRight = true; CMessage msgout("DYN_CHAT:ADD_SESSION"); msgout.serial(chan); msgout.serial(clientId); msgout.serial(writeRight); sendMessageViaMirror( "EGS", msgout); TClientValidation cv; cv.ClientId = clientId; cv.EntryDate = CTickEventHandler::getGameCycle(); _ClientToValidate.push_back(cv); } void CChatChannelImplementation::removeClient(GUS::TClientId clientId) { TChanID chan = _ChannelID; CMessage msgout("DYN_CHAT:REMOVE_SESSION"); msgout.serial(chan); msgout.serial(clientId); sendMessageViaMirror( "EGS", msgout); } void CChatChannelImplementation::broadcastMessage(const ucstring& speakerName, const ucstring& txt) { nldebug("Channel %s : broadcasting \"'%s' says '%s'\"", _ChannelTitle.c_str(), speakerName.toString().c_str(), txt.toString().c_str()); CMessage msgout("DYN_CHAT:SERVICE_CHAT"); msgout.serial(_ChannelID); msgout.serial(const_cast(speakerName)); msgout.serial(const_cast(txt)); sendMessageViaMirror( "IOS", msgout); } void CChatChannelImplementation::broadcastMessage(const std::string& speakerNameUtf8, const std::string& txtUtf8) { ucstring speakerName, txt; speakerName.fromUtf8(speakerNameUtf8); txt.fromUtf8(txtUtf8); broadcastMessage(speakerName, txt); } void CChatChannelImplementation::sendMessage(GUS::TClientId clientId, const ucstring& speakerName,const ucstring& txt) { CEntityId eid = CGusMirror::getInstance()->getDataSet("fe_temp")->getEntityId(clientId); nldebug("Channel %s : sending \"'%s' says '%s'\" to client %s", _ChannelTitle.c_str(), speakerName.toString().c_str(), txt.toString().c_str(), eid.toString().c_str()); CMessage msgout("DYN_CHAT:SERVICE_TELL"); msgout.serial(_ChannelID); msgout.serial(const_cast(speakerName)); msgout.serial(clientId); msgout.serial(const_cast(txt)); sendMessageViaMirror( "IOS", msgout); } void CChatChannelImplementation::sendMessage(GUS::TClientId clientId, const std::string& speakerNameUtf8, const std::string& txtUtf8) { ucstring speakerName, txt; speakerName.fromUtf8(speakerNameUtf8); txt.fromUtf8(txtUtf8); sendMessage(clientId, speakerName, txt); } void CChatChannelImplementation::setChatCallback(IChatCallback* callback) { _ChatCallback = callback; } const NLMISC::CSString& CChatChannelImplementation::getChannelName() const { return _ChannelName; } const NLMISC::CSString& CChatChannelImplementation::getChannelTitle() const { return _ChannelTitle; } void CChatChannelImplementation::setChannelTitle(const NLMISC::CSString& title) { _ChannelTitle= title; CIOSMsgSetPhrase(_ChannelName,_ChannelTitle).send(); } //----------------------------------------------------------------------------- // CChat instantiator //----------------------------------------------------------------------------- class CChatManagerImplementationInstantiator { public: CChatManagerImplementationInstantiator() { CChatManagerImplementation::getInstance(); } }; static CChatManagerImplementationInstantiator ChatManagerInstantiator; //----------------------------------------------------------------------------- // NLMISC_COMMAND commands //----------------------------------------------------------------------------- NLMISC_COMMAND(chatCreateChannel, "create a new chat channel", " ") { if (args.size() != 2) return false; CChatManagerImplementation *cmi = CChatManagerImplementation::getInstance(); TChatChannelPtr channel = cmi->createChatChannel(args[0]); if (channel != NULL) { channel->openChannel(args[1], 0, true, true, true); } else { nlwarning("Failed to create channel '%s'", args[1].c_str()); } return true; } NLMISC_COMMAND(chatRemoveChannel, "remove an existing chat channel", "") { if (args.size() != 1) return false; CChatManagerImplementation *cmi = CChatManagerImplementation::getInstance(); TChatChannelPtr channel = cmi->getChatChannel(args[0]); if (channel == NULL) { nlwarning ("The channel '%s' is unknow", args[0].c_str()); } else { channel->closeChannel(); cmi->removeChannel(channel); } return true; } NLMISC_COMMAND(chatSendMessage, "send a chat message in a channel", " ") { if (args.size() < 3) return false; CChatManagerImplementation *cmi = CChatManagerImplementation::getInstance(); TChatChannelPtr channel = cmi->getChatChannel(args[0]); if (channel == NULL) { nlwarning ("The channel '%s' is unknow", args[0].c_str()); } else { ucstring text; for (uint i=2; ibroadcastMessage(args[1], text); } return true; } NLMISC_COMMAND(chatDisplayChannel, "list all the chat channel and their states", "") { if (!args.empty()) return false; CChatManagerImplementation *cmi = CChatManagerImplementation::getInstance(); log.displayNL("Listing %u chat channel :", cmi->_Channels.size()); CChatManagerImplementation::TChannels::iterator it(cmi->_Channels.begin()), last(cmi->_Channels.end()); for (;it != last; ++it) { CChatChannelImplementation *cmi = it->second; log.displayNL(" Name '%s', Title '%s', ChanId %s, State '%s'", cmi->_ChannelName.c_str(), cmi->_ChannelTitle.c_str(), cmi->_ChannelID.toString().c_str(), cmi->_ChannelOpen ? "OPEN" : "CLOSED"); } return true; } NLMISC_COMMAND(chatPlayerInput, "simulate a player input in a channel", " +") { if (args.size() < 3) return false; CEntityId eid(args[0]); TDataSetRow index = CGusMirror::getInstance()->getDataSet("fe_temp")->getDataSetRow(eid); if (index == INVALID_DATASET_ROW) { log.displayNL("Unknow player '%s'", eid.toString().c_str()); return true; } // Retreive the channel TChatChannelPtr channel = CChatManager::getInstance()->getChatChannel(args[1]); if (channel == NULL) { log.displayNL("Unknown channel '%s'", args[1].c_str()); return true; } CChatChannelImplementation *cmi = static_cast(static_cast(channel)); // concatenate the message ucstring txt; for (uint i=2; i_ChannelID; pif.Content = txt; pif.Sender = index; CMessage msg("DYN_CHAT:FORWARD"); msg.serial(pif); msg.invert(); CChatManagerImplementation::getInstance()->cbDynChatForward(msg, IService::getInstance()->getServiceAliasName(), IService::getInstance()->getServiceId()); return true; } //----------------------------------------------------------------------------- // methods CChatBroadcastDisplayer //----------------------------------------------------------------------------- CChatBroadcastDisplayer::CChatBroadcastDisplayer(CChatChannel* channel): IDisplayer("gusChatBroadcast") { _Channel= channel; } void CChatBroadcastDisplayer::doDisplay(const NLMISC::CLog::TDisplayInfo& args,const char *message) { _Channel->broadcastMessage("*",message); } //----------------------------------------------------------------------------- // methods CChatDisplayer //----------------------------------------------------------------------------- CChatDisplayer::CChatDisplayer(CChatChannel* channel,TClientId clientId): IDisplayer("gusChat") { _Channel= channel; _ClientId= clientId; } void CChatDisplayer::doDisplay(const NLMISC::CLog::TDisplayInfo& args,const char *message) { _Channel->sendMessage(_ClientId,"*",message); } } //-----------------------------------------------------------------------------