diff --git a/apps/openmw-mp/Cell.cpp b/apps/openmw-mp/Cell.cpp index e74e4aa03..f3292f09f 100644 --- a/apps/openmw-mp/Cell.cpp +++ b/apps/openmw-mp/Cell.cpp @@ -117,10 +117,8 @@ void Cell::readActorList(unsigned char packetID, const mwmp::BaseActorList *newA bool Cell::containsActor(int refNumIndex, int mpNum) { - for (unsigned int i = 0; i < cellActorList.baseActors.size(); i++) + for (const auto &actor : cellActorList.baseActors) { - auto &actor = cellActorList.baseActors.at(i); - if (actor->refNumIndex == refNumIndex && actor->mpNum == mpNum) return true; } @@ -129,14 +127,12 @@ bool Cell::containsActor(int refNumIndex, int mpNum) mwmp::BaseActor *Cell::getActor(int refNumIndex, int mpNum) { - for (unsigned int i = 0; i < cellActorList.baseActors.size(); i++) + for (const auto &actor : cellActorList.baseActors) { - auto &actor = cellActorList.baseActors.at(i); - if (actor->refNumIndex == refNumIndex && actor->mpNum == mpNum) return actor.get(); } - return 0; + return nullptr; } void Cell::removeActors(const mwmp::BaseActorList *newActorList) diff --git a/apps/openmw-mp/Networking.cpp b/apps/openmw-mp/Networking.cpp index 1d37081f0..16b5fe162 100644 --- a/apps/openmw-mp/Networking.cpp +++ b/apps/openmw-mp/Networking.cpp @@ -57,6 +57,7 @@ Networking::Networking(RakNet::RakPeerInterface *peer) : mclient(nullptr) serverPassword = TES3MP_DEFAULT_PASSW; ProcessorInitializer(); + createChannel(); // create Default channel } Networking::~Networking() @@ -545,3 +546,41 @@ void Networking::postInit() } } } + +std::shared_ptr Networking::getChannel(unsigned id) +{ + auto it = chatChannels.find(id); + if (it != chatChannels.end()) + return it->second; + else + return nullptr; +} + +unsigned Networking::createChannel() +{ + static unsigned lastChatId = 0; + unsigned id = 0; + for(auto &channel : chatChannels) + { + if(channel.second == nullptr) + id = channel.first; + } + + if (id == 0) + id = lastChatId++; + + + chatChannels[id] = make_shared(); + return id; +} + +bool Networking::closeChannel(unsigned id) +{ + auto it = chatChannels.find(id); + if (it != chatChannels.end()) + { + it->second = nullptr; + return true; + } + return false; +} diff --git a/apps/openmw-mp/Networking.hpp b/apps/openmw-mp/Networking.hpp index b0174adff..64c5c7bc6 100644 --- a/apps/openmw-mp/Networking.hpp +++ b/apps/openmw-mp/Networking.hpp @@ -14,6 +14,14 @@ class MasterClient; namespace mwmp { + struct ChatChannel + { + ChatChannel() + { + } + std::vector> members; + }; + class Networking { public: @@ -63,6 +71,11 @@ namespace mwmp static Networking *getPtr(); void postInit(); + + std::shared_ptr getChannel(unsigned id); + unsigned createChannel(); + bool closeChannel(unsigned id); + private: LuaState luaState; PacketPreInit::PluginContainer getPluginListSample(); @@ -83,6 +96,8 @@ namespace mwmp bool running; int exitCode; PacketPreInit::PluginContainer samples; + + std::unordered_map> chatChannels; }; } diff --git a/apps/openmw-mp/Player.cpp b/apps/openmw-mp/Player.cpp index 9e37b6f82..5418e07cc 100644 --- a/apps/openmw-mp/Player.cpp +++ b/apps/openmw-mp/Player.cpp @@ -9,6 +9,8 @@ #include "Player.hpp" #include "Inventory.hpp" #include "Settings.hpp" +#include "Players.hpp" +#include "Script/EventController.hpp" using namespace std; @@ -39,7 +41,13 @@ void Player::Init(LuaState &lua) "getAvgPing", &Player::getAvgPing, "message", &Player::message, - "cleanChat", &Player::cleanChat, + "joinToChannel", &Player::joinToChannel, + "cleanChannel", &Player::cleanChannel, + "renameChannel", &Player::renameChannel, + "closeChannel", &Player::closeChannel, + "leaveChannel", &Player::leaveChannel, + "setChannel", &Player::setChannel, + "isChannelOpened", &Player::isChannelOpened, "pid", sol::readonly_property(&Player::id), "guid", sol::readonly_property(&Player::getGUID), @@ -85,6 +93,12 @@ void Player::Init(LuaState &lua) "setAuthority", &Player::setAuthority, "customData", &Player::customData ); + + lua.getState()->new_enum("ChannelAction", + "createChannel", 0, + "joinChannel", 1, + "closeChannel", 2, + "leftChannel", 3); } Player::Player(RakNet::RakNetGUID guid) : BasePlayer(guid), NetActor(), changedMap(false), cClass(this), @@ -310,9 +324,11 @@ void Player::ban() const netCtrl->banAddress(addr.ToString(false)); } -void Player::cleanChat() +void Player::cleanChannel(unsigned channelId) { - chatMessage.clear(); + chat.action = mwmp::Chat::Action::clear; + chat.channel = channelId; + chat.message.clear(); auto packet = mwmp::Networking::get().getPlayerPacketController(); packet->GetPacket(ID_CHAT_MESSAGE)->setPlayer(this); @@ -352,16 +368,142 @@ void Player::setCharGenStages(int currentStage, int endStage) packet->Send(false); } -void Player::message(const std::string &message, bool toAll) +void Player::message(unsigned channelId, const std::string &message, bool toAll) +{ + if (isChannelOpened(channelId)) + { + mwmp::Chat tmp; + tmp.action = mwmp::Chat::Action::print; + tmp.channel = channelId; + tmp.message = message; + chat = tmp; + + auto packet = mwmp::Networking::get().getPlayerPacketController()->GetPacket(ID_CHAT_MESSAGE); + packet->setPlayer(this); + + packet->Send(false); + if (toAll) + { + auto channel = mwmp::Networking::get().getChannel(channelId); + + for (auto it = channel->members.begin(); it != channel->members.end();) + { + if (auto member = it->lock()) + { + ++it; + if (member->guid == this->guid) + continue; + member->chat = tmp; + packet->setPlayer(member.get()); + packet->Send(false); + + } + else + it = channel->members.erase(it); + } + } + } +} + +bool Player::joinToChannel(unsigned channelId, const std::string &name) +{ + auto channel = mwmp::Networking::get().getChannel(channelId); + + if (channel == nullptr) // channel not found + return false; + + for (const auto &weakMember : channel->members) + { + if (auto member = weakMember.lock()) + { + if (member->guid == guid) + return false; // player already member of the channel + } + } + auto thisPl = Players::getPlayerByGUID(guid); + channel->members.push_back(thisPl); + + chat.action = mwmp::Chat::Action::addchannel; + chat.channel = channelId; + chat.message = name; + + auto packet = mwmp::Networking::get().getPlayerPacketController()->GetPacket(ID_CHAT_MESSAGE); + packet->setPlayer(this); + packet->Send(false); + + return true; +} + +void Player::renameChannel(unsigned channelId, const std::string &name) +{ + if (isChannelOpened(channelId)) + { + chat.action = mwmp::Chat::Action::renamechannel; + chat.channel = channelId; + chat.message = name; + + auto packet = mwmp::Networking::get().getPlayerPacketController()->GetPacket(ID_CHAT_MESSAGE); + packet->setPlayer(this); + + packet->Send(false); + } +} + +void Player::leaveChannel(unsigned channelId) { - chatMessage = message; + chat.action = mwmp::Chat::Action::closechannel; + chat.channel = channelId; + chat.message.clear(); auto packet = mwmp::Networking::get().getPlayerPacketController()->GetPacket(ID_CHAT_MESSAGE); packet->setPlayer(this); packet->Send(false); - if (toAll) - packet->Send(true); + + mwmp::Networking::get().getState().getEventCtrl().Call(this, channelId, 2); // leaved channel +} + +void Player::closeChannel(unsigned channelId) +{ + auto channel = mwmp::Networking::get().getChannel(channelId); + + for(auto &weakMember : channel->members) // kick members from channel before deleting channel + { + if(auto member = weakMember.lock()) + member->leaveChannel(channelId); + } + + if (!mwmp::Networking::get().closeChannel(channelId)) // cannot close channel + return; + + mwmp::Networking::get().getState().getEventCtrl().Call(this, channelId, 2); // channel closed +} + +void Player::setChannel(unsigned channelId) +{ + if (isChannelOpened(channelId)) + { + chat.action = mwmp::Chat::Action::setchannel; + chat.channel = channelId; + chat.message.clear(); + + auto packet = mwmp::Networking::get().getPlayerPacketController()->GetPacket(ID_CHAT_MESSAGE); + packet->setPlayer(this); + + packet->Send(false); + } +} + +bool Player::isChannelOpened(unsigned channelId) +{ + auto channel = mwmp::Networking::get().getChannel(channelId); + + auto it = std::find_if(channel->members.begin(), channel->members.end(), [this](const auto &weakMember){ + if(auto member = weakMember.lock()) + return member->guid == guid; + return false; + }); + return it != channel->members.end(); } int Player::getLevel() const diff --git a/apps/openmw-mp/Player.hpp b/apps/openmw-mp/Player.hpp index 909e8f16b..f285fbd5e 100644 --- a/apps/openmw-mp/Player.hpp +++ b/apps/openmw-mp/Player.hpp @@ -75,14 +75,21 @@ public: public: void kick() const; void ban() const; - void cleanChat(); + + void cleanChannel(unsigned channelId); + void message(unsigned channelId, const std::string &message, bool toAll = false); + bool joinToChannel(unsigned channelId, const std::string &name); + void renameChannel(unsigned channelId, const std::string &name); + void closeChannel(unsigned channelId); + void leaveChannel(unsigned channelId); + void setChannel(unsigned channelId); + bool isChannelOpened(unsigned channelId); int getAvgPing(); std::string getName(); void setName(const std::string &name); void setCharGenStages(int currentStage, int endStage); - void message(const std::string &message, bool toAll = false); int getGender() const; void setGender(int gender); std::string getRace() const; diff --git a/apps/openmw-mp/Players.cpp b/apps/openmw-mp/Players.cpp index 6f3979422..f00254ef1 100644 --- a/apps/openmw-mp/Players.cpp +++ b/apps/openmw-mp/Players.cpp @@ -38,17 +38,26 @@ std::shared_ptr Players::getPlayerByGUID(RakNet::RakNetGUID guid) const auto &ls = store.get(); auto it = ls.find(guid.g); if (it != ls.end()) + { + LOG_MESSAGE_SIMPLE(Log::LOG_TRACE, "%d references: %d", guid.g, it->use_count()); return *it; + } return nullptr; } +void deleter(Player *pl) +{ + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Player %lu deleted", pl->guid.g); + delete pl; +} + std::shared_ptr Players::addPlayer(RakNet::RakNetGUID guid) { const int maxConnections = 65535; - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Creating new player with guid %lu", guid.g); + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Creating new player with guid %lu", guid.g); - auto player = make_shared(guid); + auto player = shared_ptr(new Player(guid), deleter); unsigned short findPid = 0; const auto &ls = store.get(); @@ -71,25 +80,27 @@ std::shared_ptr Players::addPlayer(RakNet::RakNetGUID guid) void Players::deletePlayerByPID(int pid) { - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Marking player (pid %i) for deletion", pid); + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Marking player (pid %i) for deletion", pid); auto &ls = store.get(); auto it = ls.find(pid); if (it != ls.end()) { + (*it)->markedForDeletion = true; ls.erase(it); + LOG_APPEND(Log::LOG_TRACE, "- references: %d", it->use_count()); } } void Players::deletePlayerByGUID(RakNet::RakNetGUID guid) { - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Marking player (guid %lu) for deletion", guid.g); + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Marking player (guid %lu) for deletion", guid.g); auto &ls = store.get(); auto it = ls.find(guid.g); if (it != ls.end()) { - LOG_APPEND(Log::LOG_INFO, "- references: %d", it->use_count()); + (*it)->markedForDeletion = true; ls.erase(it); - LOG_APPEND(Log::LOG_INFO, "- references: %d", it->use_count()); + LOG_APPEND(Log::LOG_TRACE, "- references: %d", it->use_count()); } } diff --git a/apps/openmw-mp/Script/CommandController.cpp b/apps/openmw-mp/Script/CommandController.cpp index 22c095ae2..673c094ba 100644 --- a/apps/openmw-mp/Script/CommandController.cpp +++ b/apps/openmw-mp/Script/CommandController.cpp @@ -60,7 +60,8 @@ bool CommandController::hasCommand(const std::string &command) return commands.find(command) != commands.end(); } -std::pair CommandController::exec(std::shared_ptr player, const std::string &message) +std::pair CommandController::exec(const std::shared_ptr &player, + const std::string &message, unsigned channel) { char cmdChar = message[0]; if (message.size() < 2 || (cmdChar != '/' && cmdChar != '!')) @@ -72,7 +73,7 @@ std::pair CommandController::exec(st if (cmd != commands.end()) { tokens.pop_front(); - bool result = cmd->second.func(player, sol::as_table(tokens)); + bool result = cmd->second.func(player, sol::as_table(tokens), channel); if (result) return make_pair(ExecResult::SUCCESS, ""); diff --git a/apps/openmw-mp/Script/CommandController.hpp b/apps/openmw-mp/Script/CommandController.hpp index 87c94fc29..7be9b173c 100644 --- a/apps/openmw-mp/Script/CommandController.hpp +++ b/apps/openmw-mp/Script/CommandController.hpp @@ -56,7 +56,7 @@ public: */ bool hasCommand(const std::string &command); - std::pair exec(std::shared_ptr player, const std::string &message); + std::pair exec(const std::shared_ptr &player, const std::string &message, unsigned channel); private: std::deque cmdParser(const std::string &message); Container commands; diff --git a/apps/openmw-mp/Script/EventController.cpp b/apps/openmw-mp/Script/EventController.cpp index e8b630e09..3fab902de 100644 --- a/apps/openmw-mp/Script/EventController.cpp +++ b/apps/openmw-mp/Script/EventController.cpp @@ -64,6 +64,7 @@ EventController::EventController(LuaState *luaCtrl) ADD_CORE_EVENT(ON_PLAYER_REST), ADD_CORE_EVENT(ON_PLAYER_SENDMESSAGE), ADD_CORE_EVENT(ON_PLAYER_ENDCHARGEN), + ADD_CORE_EVENT(ON_CHANNEL_ACTION), ADD_CORE_EVENT(ON_GUI_ACTION), ADD_CORE_EVENT(ON_REQUEST_PLUGIN_LIST), ADD_CORE_EVENT(ON_MP_REFNUM), diff --git a/apps/openmw-mp/Script/EventController.hpp b/apps/openmw-mp/Script/EventController.hpp index b8ce7c26a..d4ca5e1e1 100644 --- a/apps/openmw-mp/Script/EventController.hpp +++ b/apps/openmw-mp/Script/EventController.hpp @@ -41,6 +41,8 @@ namespace CoreEvent ON_PLAYER_SENDMESSAGE, ON_PLAYER_ENDCHARGEN, + ON_CHANNEL_ACTION, + ON_GUI_ACTION, ON_MP_REFNUM, diff --git a/apps/openmw-mp/Script/LuaState.cpp b/apps/openmw-mp/Script/LuaState.cpp index 035f93b82..7fa12ae9f 100644 --- a/apps/openmw-mp/Script/LuaState.cpp +++ b/apps/openmw-mp/Script/LuaState.cpp @@ -279,6 +279,10 @@ LuaState::LuaState() packet->Send(false); }); }); + + lua->set_function("createChannel", [](){ + return mwmp::Networking::get().createChannel(); + }); } sol::environment LuaState::openModule(std::string homePath, std::string moduleName) diff --git a/apps/openmw-mp/processors/player/ProcessorChatMsg.hpp b/apps/openmw-mp/processors/player/ProcessorChatMsg.hpp index ba97b1262..0f57e26a9 100644 --- a/apps/openmw-mp/processors/player/ProcessorChatMsg.hpp +++ b/apps/openmw-mp/processors/player/ProcessorChatMsg.hpp @@ -23,19 +23,19 @@ namespace mwmp auto &lua = Networking::get().getState(); - auto cmdExecResult = lua.getCmdCtrl().exec(player, player->chatMessage); + auto cmdExecResult = lua.getCmdCtrl().exec(player, player->chat.message, player->chat.channel); switch (cmdExecResult.first) { case CommandController::ExecResult::NOT_FOUND: // err cmd not found - player->message("#FF0000Command not found.\n"); // inform player that command not found + player->message(player->chat.channel, "#FF0000Command not found.\n"); // inform player that command not found break; case CommandController::ExecResult::NOT_CMD: // cmd length < 2 or message is not cmd - lua.getEventCtrl().Call(player, player->chatMessage); + lua.getEventCtrl().Call(player, player->chat.message, player->chat.channel); break; case CommandController::ExecResult::SUCCESS: // returned true from function break; case CommandController::ExecResult::FAIL: // returned false from function - player->message("#B8860B"+cmdExecResult.second); // show "help msg" to player + player->message(player->chat.channel, "#B8860B"+cmdExecResult.second); // show "help msg" to player break; } } diff --git a/apps/openmw/mwmp/DedicatedPlayer.hpp b/apps/openmw/mwmp/DedicatedPlayer.hpp index 212279852..d63e6b7b6 100644 --- a/apps/openmw/mwmp/DedicatedPlayer.hpp +++ b/apps/openmw/mwmp/DedicatedPlayer.hpp @@ -23,8 +23,6 @@ namespace MWMechanics namespace mwmp { - struct DedicatedPlayer; - class DedicatedPlayer : public BasePlayer { friend class PlayerList; diff --git a/apps/openmw/mwmp/GUI/GUIChat.cpp b/apps/openmw/mwmp/GUI/GUIChat.cpp index 00d49a975..64f0616bb 100644 --- a/apps/openmw/mwmp/GUI/GUIChat.cpp +++ b/apps/openmw/mwmp/GUI/GUIChat.cpp @@ -10,11 +10,17 @@ #include "apps/openmw/mwinput/inputmanagerimp.hpp" #include #include +#include +#include #include "../Networking.hpp" #include "../Main.hpp" #include "../LocalPlayer.hpp" +#include "../../mwbase/world.hpp" + +#include "../../mwworld/timestamp.hpp" + #include "../GUIController.hpp" @@ -28,6 +34,17 @@ namespace mwmp getWidget(mCommandLine, "edit_Command"); getWidget(mHistory, "list_History"); + getWidget(mChannelPrevBtn, "btn_prev_ch"); + mChannelPrevBtn->eventMouseButtonClick += MyGUI::newDelegate(this, &GUIChat::prevChannels); + getWidget(mChannelNextBtn, "btn_next_ch"); + mChannelNextBtn->eventMouseButtonClick += MyGUI::newDelegate(this, &GUIChat::nextChannels); + getWidget(mBoxChannels, "box_channels"); + + for (int i = 0; i < 3; ++i) + { + getWidget(mChannelBtns[i], "btn_ch" + std::to_string(i + 1)); + mChannelBtns[i]->eventMouseButtonClick += MyGUI::newDelegate(this, &GUIChat::onClickChannel); + } // Set up the command line box mCommandLine->eventEditSelectAccept += @@ -35,8 +52,6 @@ namespace mwmp mCommandLine->eventKeyButtonPressed += newDelegate(this, &GUIChat::keyPress); - setTitle("Chat"); - mHistory->setOverflowToTheLeft(true); mHistory->setEditWordWrap(true); mHistory->setTextShadow(true); @@ -45,8 +60,14 @@ namespace mwmp mHistory->setNeedKeyFocus(false); windowState = 0; - mCommandLine->setVisible(0); + mCommandLine->setVisible(false); + mBoxChannels->setVisible(false); delay = 3; // 3 sec. + page = 0; + currentChannel = 0; + addChannel(0, "Default"); + setChannel(0); + redrawChnnels(); } void GUIChat::onOpen() @@ -73,18 +94,42 @@ namespace mwmp void GUIChat::acceptCommand(MyGUI::EditBox *_sender) { - const std::string &cm = MyGUI::TextIterator::toTagsString(mCommandLine->getCaption()); - + const std::string &cm = MyGUI::TextIterator::toTagsString(mCommandLine->getCaption()); + // If they enter nothing, then it should be canceled. // Otherwise, there's no way of closing without having text. if (cm.empty()) { mCommandLine->setCaption(""); - SetEditState(0); + SetEditState(false); return; } - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Player: %s", cm.c_str()); + if (cm.find("//", 0, 2) == 0) //clientside commands + { + typedef boost::char_separator csep; + csep sep(" "); + boost::tokenizer tokens(cm, sep); + + std::vector cmd; + for (const auto& t : tokens) + cmd.push_back(t); + + if (cmd[0] == "//channel") + { + if (cmd[1] == "close") + { + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Closed \"%d\" channel", currentChannel); + closeChannel(currentChannel); + } + } + + } + else + { + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Player: %s", cm); + send (cm); + } // Add the command to the history, and set the current pointer to // the end of the list @@ -93,12 +138,9 @@ namespace mwmp mCurrent = mCommandHistory.end(); mEditString.clear(); - // Reset the command line before the command execution. - // It prevents the re-triggering of the acceptCommand() event for the same command - // during the actual command execution mCommandLine->setCaption(""); - SetEditState(0); - send (cm); + SetEditState(false); + } void GUIChat::onResChange(int width, int height) @@ -112,33 +154,28 @@ namespace mwmp mCommandLine->setFontName(fntName); } - void GUIChat::print(const std::string &msg, const std::string &color) + void GUIChat::print(unsigned channelId, const std::string &msg, const std::string &color) { if (windowState == 2 && !isVisible()) { setVisible(true); } - if(msg.size() == 0) + if (channelId != currentChannel) { - clean(); - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Chat cleaned"); + try + { + auto it = getChannel(channelId); + if (it != channels.end()) + it->channelText = color + msg; + } + catch(std::out_of_range &e) {} } else - { mHistory->addText(color + msg); - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "%s", msg.c_str()); - } - } + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "%s", msg.c_str()); - void GUIChat::printOK(const std::string &msg) - { - print(msg + "\n", "#FF00FF"); - } - void GUIChat::printError(const std::string &msg) - { - print(msg + "\n", "#FF2222"); } void GUIChat::send(const std::string &str) @@ -147,15 +184,26 @@ namespace mwmp Networking *networking = Main::get().getNetworking(); - localPlayer->chatMessage = str; + localPlayer->chat.action = Chat::Action::print; + localPlayer->chat.channel = currentChannel; + localPlayer->chat.message = str; networking->getPlayerPacket(ID_CHAT_MESSAGE)->setPlayer(localPlayer); networking->getPlayerPacket(ID_CHAT_MESSAGE)->Send(); } - void GUIChat::clean() + void GUIChat::clean(unsigned channelId) { - mHistory->setCaption(""); + if(channelId == currentChannel) + mHistory->setCaption(""); + else + { + auto it = getChannel(channelId); + if(it != channels.end()) + { + it->channelText.clear(); + } + } } void GUIChat::pressedChatMode() @@ -174,7 +222,7 @@ namespace mwmp { case CHAT_DISABLED: this->mMainWidget->setVisible(false); - SetEditState(0); + SetEditState(false); break; case CHAT_ENABLED: this->mMainWidget->setVisible(true); @@ -189,6 +237,7 @@ namespace mwmp { editState = state; mCommandLine->setVisible(editState); + mBoxChannels->setVisible(editState); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(editState ? mCommandLine : nullptr); } @@ -239,7 +288,6 @@ namespace mwmp mCommandLine->setCaption(mEditString); } } - } void GUIChat::Update(float dt) @@ -254,13 +302,18 @@ namespace mwmp } } - if(netStat) + if (netStat) { auto rss = Main::get().getNetworking()->getNetworkStatistics(); mHistory->setCaption(rss); } } + void GUIChat::setCaption(const std::string &str) + { + mHistory->setCaption(str); + } + void GUIChat::setDelay(float delay) { this->delay = delay; @@ -270,4 +323,139 @@ namespace mwmp { netStat = !netStat; } + + void GUIChat::addChannel(unsigned ch, const std::string &name) + { + auto channel = getChannel(ch); + if(channel == channels.end()) + { + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Adding channel id: %d %s", ch, name); + channels.push_back(ChannelData{ch, name.substr(0, 9), ""}); + } + redrawChnnels(); + } + + void GUIChat::renameChannel(unsigned ch, const std::string &newName) + { + auto it = getChannel(ch); + if(it != channels.end()) + { + it->channelName = newName.substr(0, 9); + redrawChnnels(); + } + } + + void GUIChat::setChannel(const std::string &channel, bool saveHistory) + { + auto ch = channel.substr(0, 9); + auto it = std::find_if(channels.begin(), channels.end(), [&ch](const ChannelData &item) { + return item.channelName == ch; + }); + + if (it != channels.end()) + setChannel(it, saveHistory); + } + + void GUIChat::setChannel(unsigned ch, bool saveHistory) + { + auto it = getChannel(ch); + if (it != channels.end()) + setChannel(it, saveHistory); + } + + void GUIChat::setChannel(ChannelIter it, bool saveHistory) + { + if (saveHistory) + channels[currentChannel].channelText = mHistory->getCaption(); + + mHistory->setCaption(it->channelText); + currentChannel = it->channel; + + redrawChnnels(); + } + + void GUIChat::closeChannel(unsigned ch) + { + if (ch == 0) // that's impossible + return; + auto it = getChannel(ch); + + if (it != channels.end()) + { + if (ch == it->channel) + setChannel(0, false); // reset to default channel + channels.erase(it); + } + redrawChnnels(); + } + + void GUIChat::redrawChnnels() + { + mChannelPrevBtn->setVisible(page != 0); + mChannelNextBtn->setVisible(channels.size() > 3 && page != lastPage()); + + if (page >=lastPage()) + page = lastPage(); + unsigned showElems = page * pageM + 3; + if (showElems > channels.size()) + showElems = static_cast(channels.size()); + auto it = channels.begin() + page * pageM; + auto endIt = channels.begin() + showElems; + for (auto &btn : mChannelBtns) + { + static const auto defaultColour = btn->getTextColour(); + if (it == endIt) + btn->setVisible(false); + else + { + btn->setVisible(true); + if (currentChannel == it->channel) + { + setTitle("Chat: " + it->channelName + "(" + std::to_string(it->channel) + ")"); + btn->setEnabled(false); + btn->setStateSelected(true); + btn->setTextColour(MyGUI::Colour::Red); + } + else + { + btn->setEnabled(true); + btn->setTextColour(defaultColour); + } + btn->setCaption(it++->channelName); + } + } + } + + void GUIChat::nextChannels(MyGUI::Widget* _sender) + { + page++; + if (page > lastPage()) + page = lastPage(); + redrawChnnels(); + } + + void GUIChat::prevChannels(MyGUI::Widget* _sender) + { + if (page > 0) + page--; + redrawChnnels(); + } + + void GUIChat::onClickChannel(MyGUI::Widget *_sender) + { + auto sender = dynamic_cast(_sender); + setChannel(sender->getCaption().asUTF8()); + } + + unsigned GUIChat::lastPage() + { + return static_cast(channels.size() / pageM + channels.size() % pageM - 1); + } + + GUIChat::ChannelIter GUIChat::getChannel(unsigned ch) + { + return std::find_if(channels.begin(), channels.end(), [&ch](const ChannelData &data){ + return data.channel == ch; + }); + } } diff --git a/apps/openmw/mwmp/GUI/GUIChat.hpp b/apps/openmw/mwmp/GUI/GUIChat.hpp index 82769b000..ee6d9f35c 100644 --- a/apps/openmw/mwmp/GUI/GUIChat.hpp +++ b/apps/openmw/mwmp/GUI/GUIChat.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "apps/openmw/mwgui/windowbase.hpp" @@ -27,6 +29,10 @@ namespace mwmp MyGUI::EditBox* mCommandLine; MyGUI::EditBox* mHistory; + MyGUI::Button* mChannelBtns[3]; + MyGUI::Button* mChannelNextBtn; + MyGUI::Button* mChannelPrevBtn; + Gui::HBox* mBoxChannels; typedef std::list StringList; @@ -43,33 +49,40 @@ namespace mwmp void Update(float dt); + void setCaption(const std::string &str); + virtual void onOpen(); virtual void onClose(); virtual bool exit(); + /*virtual void open(); + virtual void close();*/ void setFont(const std::string &fntName); void onResChange(int width, int height); // Print a message to the console, in specified color. - void print(const std::string &msg, const std::string& color = "#FFFFFF"); + void print(unsigned channelId, const std::string &msg, const std::string& color = "#FFFFFF"); // Clean chat - void clean(); - - // These are pre-colored versions that you should use. - - /// Output from successful console command - void printOK(const std::string &msg); - - /// Error message - void printError(const std::string &msg); + void clean(unsigned channelId); void send(const std::string &str); void switchNetstat(); + void addChannel(unsigned ch, const std::string &name); + void setChannel(const std::string &newName, bool saveHistory = true); + void renameChannel(unsigned ch, const std::string &channel); + void setChannel(unsigned ch, bool saveHistory = true); + void closeChannel(unsigned ch); + void redrawChnnels(); + unsigned lastPage(); + void nextChannels(MyGUI::Widget* _sender); + void prevChannels(MyGUI::Widget* _sender); + void onClickChannel(MyGUI::Widget* _sender); + protected: private: @@ -87,6 +100,20 @@ namespace mwmp bool netStat; float delay; float curTime; + struct ChannelData + { + unsigned channel; + std::string channelName; + MyGUI::UString channelText; + }; + std::vector channels; + unsigned currentChannel; + unsigned page; + const int pageM = 3; + + typedef std::vector::iterator ChannelIter; + void setChannel(ChannelIter iter, bool saveHistory); + ChannelIter getChannel(unsigned ch); }; } #endif //OPENMW_GUICHAT_HPP diff --git a/apps/openmw/mwmp/GUIController.cpp b/apps/openmw/mwmp/GUIController.cpp index 4a2ea3b46..1e656be3a 100644 --- a/apps/openmw/mwmp/GUIController.cpp +++ b/apps/openmw/mwmp/GUIController.cpp @@ -77,10 +77,38 @@ void mwmp::GUIController::setupChat(const Settings::Manager &mgr) mChat->setDelay(chatDelay); } -void mwmp::GUIController::printChatMessage(std::string &msg) +void mwmp::GUIController::printChatMessage(const mwmp::Chat &chat) { if (mChat != nullptr) - mChat->print(msg); + { + switch (chat.action) + { + case Chat::Action::print: + mChat->print(chat.channel, chat.message); + break; + case Chat::Action::clear: + mChat->clean(chat.channel); + break; + case Chat::Action::addchannel: + mChat->addChannel(chat.channel, chat.message); + break; + case Chat::Action::setchannel: + mChat->setChannel(chat.channel); + break; + case Chat::Action::closechannel: + mChat->closeChannel(chat.channel); + break; + case Chat::Action::renamechannel: + mChat->renameChannel(chat.channel, chat.message); + break; + } + } +} + +void mwmp::GUIController::setChatCaption(const std::string &msg) +{ + if (mChat != nullptr) + mChat->setCaption(msg); } diff --git a/apps/openmw/mwmp/GUIController.hpp b/apps/openmw/mwmp/GUIController.hpp index a3603d199..730e4fd7f 100644 --- a/apps/openmw/mwmp/GUIController.hpp +++ b/apps/openmw/mwmp/GUIController.hpp @@ -39,7 +39,8 @@ namespace mwmp void cleanUp(); void setupChat(const Settings::Manager &manager); - void printChatMessage(std::string &msg); + void printChatMessage(const mwmp::Chat &chat); + void setChatCaption(const std::string &msg); void setChatVisible(bool chatVisible); void showMessageBox(const BasePlayer::GUIMessageBox &guiMessageBox); diff --git a/apps/openmw/mwmp/PlayerList.hpp b/apps/openmw/mwmp/PlayerList.hpp index 84c39077e..b4f10c4c1 100644 --- a/apps/openmw/mwmp/PlayerList.hpp +++ b/apps/openmw/mwmp/PlayerList.hpp @@ -19,7 +19,7 @@ namespace MWMechanics namespace mwmp { - struct DedicatedPlayer; + class DedicatedPlayer; class PlayerList { diff --git a/apps/openmw/mwmp/processors/player/ProcessorChatMessage.hpp b/apps/openmw/mwmp/processors/player/ProcessorChatMessage.hpp index ee512cf76..75bf75acb 100644 --- a/apps/openmw/mwmp/processors/player/ProcessorChatMessage.hpp +++ b/apps/openmw/mwmp/processors/player/ProcessorChatMessage.hpp @@ -23,7 +23,7 @@ namespace mwmp virtual void Do(PlayerPacket &packet, BasePlayer *player) { if (player != 0) - Main::get().getGUIController()->printChatMessage(player->chatMessage); + Main::get().getGUIController()->printChatMessage(player->chat); } }; } diff --git a/components/openmw-mp/Base/BasePlayer.hpp b/components/openmw-mp/Base/BasePlayer.hpp index c690f08cc..76d73b7b7 100644 --- a/components/openmw-mp/Base/BasePlayer.hpp +++ b/components/openmw-mp/Base/BasePlayer.hpp @@ -19,6 +19,21 @@ namespace mwmp { + struct Chat + { + enum class Action : uint8_t { + print = 0, + clear, + addchannel, + setchannel, + closechannel, + renamechannel + }; + unsigned channel; + Action action; + std::string message; + }; + struct CurrentContainer { std::string refId; @@ -254,7 +269,7 @@ namespace mwmp ESM::NpcStats npcStats; ESM::Class charClass; std::string birthsign; - std::string chatMessage; + Chat chat; CharGenState charGenState; std::string passw; diff --git a/components/openmw-mp/Packets/Player/PacketChatMessage.cpp b/components/openmw-mp/Packets/Player/PacketChatMessage.cpp index 83ea3ce52..58fc7668c 100644 --- a/components/openmw-mp/Packets/Player/PacketChatMessage.cpp +++ b/components/openmw-mp/Packets/Player/PacketChatMessage.cpp @@ -15,5 +15,7 @@ void mwmp::PacketChatMessage::Packet(RakNet::BitStream *bs, bool send) { PlayerPacket::Packet(bs, send); - RW(player->chatMessage, send); + RW(player->chat.channel, send); + RW(player->chat.action, send); + RW(player->chat.message, send); } diff --git a/files/mygui/tes3mp_chat.layout b/files/mygui/tes3mp_chat.layout index 3bddf1b75..df2acb249 100644 --- a/files/mygui/tes3mp_chat.layout +++ b/files/mygui/tes3mp_chat.layout @@ -1,24 +1,49 @@  - - - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - diff --git a/files/mygui/tes3mp_chat.skin.xml b/files/mygui/tes3mp_chat.skin.xml index 1bf49c85e..04486e719 100644 --- a/files/mygui/tes3mp_chat.skin.xml +++ b/files/mygui/tes3mp_chat.skin.xml @@ -17,4 +17,8 @@ + + + +