[General] Introduce chat channels

sol2-server-rewrite
Koncord 7 years ago
parent c55f0f73b8
commit aa183e6844

@ -117,10 +117,8 @@ void Cell::readActorList(unsigned char packetID, const mwmp::BaseActorList *newA
bool Cell::containsActor(int refNumIndex, int mpNum) 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) if (actor->refNumIndex == refNumIndex && actor->mpNum == mpNum)
return true; return true;
} }
@ -129,14 +127,12 @@ bool Cell::containsActor(int refNumIndex, int mpNum)
mwmp::BaseActor *Cell::getActor(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) if (actor->refNumIndex == refNumIndex && actor->mpNum == mpNum)
return actor.get(); return actor.get();
} }
return 0; return nullptr;
} }
void Cell::removeActors(const mwmp::BaseActorList *newActorList) void Cell::removeActors(const mwmp::BaseActorList *newActorList)

@ -57,6 +57,7 @@ Networking::Networking(RakNet::RakPeerInterface *peer) : mclient(nullptr)
serverPassword = TES3MP_DEFAULT_PASSW; serverPassword = TES3MP_DEFAULT_PASSW;
ProcessorInitializer(); ProcessorInitializer();
createChannel(); // create Default channel
} }
Networking::~Networking() Networking::~Networking()
@ -545,3 +546,41 @@ void Networking::postInit()
} }
} }
} }
std::shared_ptr<ChatChannel> 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<ChatChannel>();
return id;
}
bool Networking::closeChannel(unsigned id)
{
auto it = chatChannels.find(id);
if (it != chatChannels.end())
{
it->second = nullptr;
return true;
}
return false;
}

@ -14,6 +14,14 @@
class MasterClient; class MasterClient;
namespace mwmp namespace mwmp
{ {
struct ChatChannel
{
ChatChannel()
{
}
std::vector<std::weak_ptr<Player>> members;
};
class Networking class Networking
{ {
public: public:
@ -63,6 +71,11 @@ namespace mwmp
static Networking *getPtr(); static Networking *getPtr();
void postInit(); void postInit();
std::shared_ptr<ChatChannel> getChannel(unsigned id);
unsigned createChannel();
bool closeChannel(unsigned id);
private: private:
LuaState luaState; LuaState luaState;
PacketPreInit::PluginContainer getPluginListSample(); PacketPreInit::PluginContainer getPluginListSample();
@ -83,6 +96,8 @@ namespace mwmp
bool running; bool running;
int exitCode; int exitCode;
PacketPreInit::PluginContainer samples; PacketPreInit::PluginContainer samples;
std::unordered_map<unsigned, std::shared_ptr<ChatChannel>> chatChannels;
}; };
} }

@ -9,6 +9,8 @@
#include "Player.hpp" #include "Player.hpp"
#include "Inventory.hpp" #include "Inventory.hpp"
#include "Settings.hpp" #include "Settings.hpp"
#include "Players.hpp"
#include "Script/EventController.hpp"
using namespace std; using namespace std;
@ -39,7 +41,13 @@ void Player::Init(LuaState &lua)
"getAvgPing", &Player::getAvgPing, "getAvgPing", &Player::getAvgPing,
"message", &Player::message, "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), "pid", sol::readonly_property(&Player::id),
"guid", sol::readonly_property(&Player::getGUID), "guid", sol::readonly_property(&Player::getGUID),
@ -85,6 +93,12 @@ void Player::Init(LuaState &lua)
"setAuthority", &Player::setAuthority, "setAuthority", &Player::setAuthority,
"customData", &Player::customData "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), 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)); 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(); auto packet = mwmp::Networking::get().getPlayerPacketController();
packet->GetPacket(ID_CHAT_MESSAGE)->setPlayer(this); packet->GetPacket(ID_CHAT_MESSAGE)->setPlayer(this);
@ -352,16 +368,142 @@ void Player::setCharGenStages(int currentStage, int endStage)
packet->Send(false); 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))
{ {
chatMessage = message; 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); auto packet = mwmp::Networking::get().getPlayerPacketController()->GetPacket(ID_CHAT_MESSAGE);
packet->setPlayer(this); packet->setPlayer(this);
packet->Send(false); packet->Send(false);
if (toAll) if (toAll)
packet->Send(true); {
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)
{
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);
mwmp::Networking::get().getState().getEventCtrl().Call<CoreEvent::ON_CHANNEL_ACTION>(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<CoreEvent::ON_CHANNEL_ACTION>(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 int Player::getLevel() const

@ -75,14 +75,21 @@ public:
public: public:
void kick() const; void kick() const;
void ban() 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(); int getAvgPing();
std::string getName(); std::string getName();
void setName(const std::string &name); void setName(const std::string &name);
void setCharGenStages(int currentStage, int endStage); void setCharGenStages(int currentStage, int endStage);
void message(const std::string &message, bool toAll = false);
int getGender() const; int getGender() const;
void setGender(int gender); void setGender(int gender);
std::string getRace() const; std::string getRace() const;

@ -38,17 +38,26 @@ std::shared_ptr<Player> Players::getPlayerByGUID(RakNet::RakNetGUID guid)
const auto &ls = store.get<ByGUID>(); const auto &ls = store.get<ByGUID>();
auto it = ls.find(guid.g); auto it = ls.find(guid.g);
if (it != ls.end()) if (it != ls.end())
{
LOG_MESSAGE_SIMPLE(Log::LOG_TRACE, "%d references: %d", guid.g, it->use_count());
return *it; return *it;
}
return nullptr; return nullptr;
} }
void deleter(Player *pl)
{
LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Player %lu deleted", pl->guid.g);
delete pl;
}
std::shared_ptr<Player> Players::addPlayer(RakNet::RakNetGUID guid) std::shared_ptr<Player> Players::addPlayer(RakNet::RakNetGUID guid)
{ {
const int maxConnections = 65535; 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<Player>(guid); auto player = shared_ptr<Player>(new Player(guid), deleter);
unsigned short findPid = 0; unsigned short findPid = 0;
const auto &ls = store.get<ByID>(); const auto &ls = store.get<ByID>();
@ -71,25 +80,27 @@ std::shared_ptr<Player> Players::addPlayer(RakNet::RakNetGUID guid)
void Players::deletePlayerByPID(int pid) 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<ByID>(); auto &ls = store.get<ByID>();
auto it = ls.find(pid); auto it = ls.find(pid);
if (it != ls.end()) if (it != ls.end())
{ {
(*it)->markedForDeletion = true;
ls.erase(it); ls.erase(it);
LOG_APPEND(Log::LOG_TRACE, "- references: %d", it->use_count());
} }
} }
void Players::deletePlayerByGUID(RakNet::RakNetGUID guid) 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<ByGUID>(); auto &ls = store.get<ByGUID>();
auto it = ls.find(guid.g); auto it = ls.find(guid.g);
if (it != ls.end()) if (it != ls.end())
{ {
LOG_APPEND(Log::LOG_INFO, "- references: %d", it->use_count()); (*it)->markedForDeletion = true;
ls.erase(it); ls.erase(it);
LOG_APPEND(Log::LOG_INFO, "- references: %d", it->use_count()); LOG_APPEND(Log::LOG_TRACE, "- references: %d", it->use_count());
} }
} }

@ -60,7 +60,8 @@ bool CommandController::hasCommand(const std::string &command)
return commands.find(command) != commands.end(); return commands.find(command) != commands.end();
} }
std::pair<CommandController::ExecResult, std::string> CommandController::exec(std::shared_ptr<Player> player, const std::string &message) std::pair<CommandController::ExecResult, std::string> CommandController::exec(const std::shared_ptr<Player> &player,
const std::string &message, unsigned channel)
{ {
char cmdChar = message[0]; char cmdChar = message[0];
if (message.size() < 2 || (cmdChar != '/' && cmdChar != '!')) if (message.size() < 2 || (cmdChar != '/' && cmdChar != '!'))
@ -72,7 +73,7 @@ std::pair<CommandController::ExecResult, std::string> CommandController::exec(st
if (cmd != commands.end()) if (cmd != commands.end())
{ {
tokens.pop_front(); 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) if (result)
return make_pair(ExecResult::SUCCESS, ""); return make_pair(ExecResult::SUCCESS, "");

@ -56,7 +56,7 @@ public:
*/ */
bool hasCommand(const std::string &command); bool hasCommand(const std::string &command);
std::pair<ExecResult, std::string> exec(std::shared_ptr<Player> player, const std::string &message); std::pair<ExecResult, std::string> exec(const std::shared_ptr<Player> &player, const std::string &message, unsigned channel);
private: private:
std::deque<std::string> cmdParser(const std::string &message); std::deque<std::string> cmdParser(const std::string &message);
Container commands; Container commands;

@ -64,6 +64,7 @@ EventController::EventController(LuaState *luaCtrl)
ADD_CORE_EVENT(ON_PLAYER_REST), ADD_CORE_EVENT(ON_PLAYER_REST),
ADD_CORE_EVENT(ON_PLAYER_SENDMESSAGE), ADD_CORE_EVENT(ON_PLAYER_SENDMESSAGE),
ADD_CORE_EVENT(ON_PLAYER_ENDCHARGEN), ADD_CORE_EVENT(ON_PLAYER_ENDCHARGEN),
ADD_CORE_EVENT(ON_CHANNEL_ACTION),
ADD_CORE_EVENT(ON_GUI_ACTION), ADD_CORE_EVENT(ON_GUI_ACTION),
ADD_CORE_EVENT(ON_REQUEST_PLUGIN_LIST), ADD_CORE_EVENT(ON_REQUEST_PLUGIN_LIST),
ADD_CORE_EVENT(ON_MP_REFNUM), ADD_CORE_EVENT(ON_MP_REFNUM),

@ -41,6 +41,8 @@ namespace CoreEvent
ON_PLAYER_SENDMESSAGE, ON_PLAYER_SENDMESSAGE,
ON_PLAYER_ENDCHARGEN, ON_PLAYER_ENDCHARGEN,
ON_CHANNEL_ACTION,
ON_GUI_ACTION, ON_GUI_ACTION,
ON_MP_REFNUM, ON_MP_REFNUM,

@ -279,6 +279,10 @@ LuaState::LuaState()
packet->Send(false); packet->Send(false);
}); });
}); });
lua->set_function("createChannel", [](){
return mwmp::Networking::get().createChannel();
});
} }
sol::environment LuaState::openModule(std::string homePath, std::string moduleName) sol::environment LuaState::openModule(std::string homePath, std::string moduleName)

@ -23,19 +23,19 @@ namespace mwmp
auto &lua = Networking::get().getState(); 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) switch (cmdExecResult.first)
{ {
case CommandController::ExecResult::NOT_FOUND: // err cmd not found 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; break;
case CommandController::ExecResult::NOT_CMD: // cmd length < 2 or message is not cmd case CommandController::ExecResult::NOT_CMD: // cmd length < 2 or message is not cmd
lua.getEventCtrl().Call<CoreEvent::ON_PLAYER_SENDMESSAGE>(player, player->chatMessage); lua.getEventCtrl().Call<CoreEvent::ON_PLAYER_SENDMESSAGE>(player, player->chat.message, player->chat.channel);
break; break;
case CommandController::ExecResult::SUCCESS: // returned true from function case CommandController::ExecResult::SUCCESS: // returned true from function
break; break;
case CommandController::ExecResult::FAIL: // returned false from function 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; break;
} }
} }

@ -23,8 +23,6 @@ namespace MWMechanics
namespace mwmp namespace mwmp
{ {
struct DedicatedPlayer;
class DedicatedPlayer : public BasePlayer class DedicatedPlayer : public BasePlayer
{ {
friend class PlayerList; friend class PlayerList;

@ -10,11 +10,17 @@
#include "apps/openmw/mwinput/inputmanagerimp.hpp" #include "apps/openmw/mwinput/inputmanagerimp.hpp"
#include <MyGUI_InputManager.h> #include <MyGUI_InputManager.h>
#include <components/openmw-mp/Log.hpp> #include <components/openmw-mp/Log.hpp>
#include <boost/tokenizer.hpp>
#include <string>
#include "../Networking.hpp" #include "../Networking.hpp"
#include "../Main.hpp" #include "../Main.hpp"
#include "../LocalPlayer.hpp" #include "../LocalPlayer.hpp"
#include "../../mwbase/world.hpp"
#include "../../mwworld/timestamp.hpp"
#include "../GUIController.hpp" #include "../GUIController.hpp"
@ -28,6 +34,17 @@ namespace mwmp
getWidget(mCommandLine, "edit_Command"); getWidget(mCommandLine, "edit_Command");
getWidget(mHistory, "list_History"); 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 // Set up the command line box
mCommandLine->eventEditSelectAccept += mCommandLine->eventEditSelectAccept +=
@ -35,8 +52,6 @@ namespace mwmp
mCommandLine->eventKeyButtonPressed += mCommandLine->eventKeyButtonPressed +=
newDelegate(this, &GUIChat::keyPress); newDelegate(this, &GUIChat::keyPress);
setTitle("Chat");
mHistory->setOverflowToTheLeft(true); mHistory->setOverflowToTheLeft(true);
mHistory->setEditWordWrap(true); mHistory->setEditWordWrap(true);
mHistory->setTextShadow(true); mHistory->setTextShadow(true);
@ -45,8 +60,14 @@ namespace mwmp
mHistory->setNeedKeyFocus(false); mHistory->setNeedKeyFocus(false);
windowState = 0; windowState = 0;
mCommandLine->setVisible(0); mCommandLine->setVisible(false);
mBoxChannels->setVisible(false);
delay = 3; // 3 sec. delay = 3; // 3 sec.
page = 0;
currentChannel = 0;
addChannel(0, "Default");
setChannel(0);
redrawChnnels();
} }
void GUIChat::onOpen() void GUIChat::onOpen()
@ -80,11 +101,35 @@ namespace mwmp
if (cm.empty()) if (cm.empty())
{ {
mCommandLine->setCaption(""); mCommandLine->setCaption("");
SetEditState(0); SetEditState(false);
return; return;
} }
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Player: %s", cm.c_str()); if (cm.find("//", 0, 2) == 0) //clientside commands
{
typedef boost::char_separator<char> csep;
csep sep(" ");
boost::tokenizer<csep> tokens(cm, sep);
std::vector<std::string> 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 // Add the command to the history, and set the current pointer to
// the end of the list // the end of the list
@ -93,12 +138,9 @@ namespace mwmp
mCurrent = mCommandHistory.end(); mCurrent = mCommandHistory.end();
mEditString.clear(); 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(""); mCommandLine->setCaption("");
SetEditState(0); SetEditState(false);
send (cm);
} }
void GUIChat::onResChange(int width, int height) void GUIChat::onResChange(int width, int height)
@ -112,33 +154,28 @@ namespace mwmp
mCommandLine->setFontName(fntName); 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()) if (windowState == 2 && !isVisible())
{ {
setVisible(true); setVisible(true);
} }
if(msg.size() == 0) if (channelId != currentChannel)
{
try
{ {
clean(); auto it = getChannel(channelId);
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Chat cleaned"); if (it != channels.end())
it->channelText = color + msg;
}
catch(std::out_of_range &e) {}
} }
else else
{
mHistory->addText(color + msg); 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) void GUIChat::send(const std::string &str)
@ -147,15 +184,26 @@ namespace mwmp
Networking *networking = Main::get().getNetworking(); 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)->setPlayer(localPlayer);
networking->getPlayerPacket(ID_CHAT_MESSAGE)->Send(); networking->getPlayerPacket(ID_CHAT_MESSAGE)->Send();
} }
void GUIChat::clean() void GUIChat::clean(unsigned channelId)
{ {
if(channelId == currentChannel)
mHistory->setCaption(""); mHistory->setCaption("");
else
{
auto it = getChannel(channelId);
if(it != channels.end())
{
it->channelText.clear();
}
}
} }
void GUIChat::pressedChatMode() void GUIChat::pressedChatMode()
@ -174,7 +222,7 @@ namespace mwmp
{ {
case CHAT_DISABLED: case CHAT_DISABLED:
this->mMainWidget->setVisible(false); this->mMainWidget->setVisible(false);
SetEditState(0); SetEditState(false);
break; break;
case CHAT_ENABLED: case CHAT_ENABLED:
this->mMainWidget->setVisible(true); this->mMainWidget->setVisible(true);
@ -189,6 +237,7 @@ namespace mwmp
{ {
editState = state; editState = state;
mCommandLine->setVisible(editState); mCommandLine->setVisible(editState);
mBoxChannels->setVisible(editState);
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(editState ? mCommandLine : nullptr); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(editState ? mCommandLine : nullptr);
} }
@ -239,7 +288,6 @@ namespace mwmp
mCommandLine->setCaption(mEditString); mCommandLine->setCaption(mEditString);
} }
} }
} }
void GUIChat::Update(float dt) void GUIChat::Update(float dt)
@ -261,6 +309,11 @@ namespace mwmp
} }
} }
void GUIChat::setCaption(const std::string &str)
{
mHistory->setCaption(str);
}
void GUIChat::setDelay(float delay) void GUIChat::setDelay(float delay)
{ {
this->delay = delay; this->delay = delay;
@ -270,4 +323,139 @@ namespace mwmp
{ {
netStat = !netStat; 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<unsigned>(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<MyGUI::Button*>(_sender);
setChannel(sender->getCaption().asUTF8());
}
unsigned GUIChat::lastPage()
{
return static_cast<unsigned>(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;
});
}
} }

@ -8,6 +8,8 @@
#include <list> #include <list>
#include <string> #include <string>
#include <vector> #include <vector>
#include <unordered_map>
#include <components/widgets/box.hpp>
#include "apps/openmw/mwgui/windowbase.hpp" #include "apps/openmw/mwgui/windowbase.hpp"
@ -27,6 +29,10 @@ namespace mwmp
MyGUI::EditBox* mCommandLine; MyGUI::EditBox* mCommandLine;
MyGUI::EditBox* mHistory; MyGUI::EditBox* mHistory;
MyGUI::Button* mChannelBtns[3];
MyGUI::Button* mChannelNextBtn;
MyGUI::Button* mChannelPrevBtn;
Gui::HBox* mBoxChannels;
typedef std::list<std::string> StringList; typedef std::list<std::string> StringList;
@ -43,33 +49,40 @@ namespace mwmp
void Update(float dt); void Update(float dt);
void setCaption(const std::string &str);
virtual void onOpen(); virtual void onOpen();
virtual void onClose(); virtual void onClose();
virtual bool exit(); virtual bool exit();
/*virtual void open();
virtual void close();*/
void setFont(const std::string &fntName); void setFont(const std::string &fntName);
void onResChange(int width, int height); void onResChange(int width, int height);
// Print a message to the console, in specified color. // 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 // Clean chat
void clean(); void clean(unsigned channelId);
// 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 send(const std::string &str); void send(const std::string &str);
void switchNetstat(); 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: protected:
private: private:
@ -87,6 +100,20 @@ namespace mwmp
bool netStat; bool netStat;
float delay; float delay;
float curTime; float curTime;
struct ChannelData
{
unsigned channel;
std::string channelName;
MyGUI::UString channelText;
};
std::vector<ChannelData> channels;
unsigned currentChannel;
unsigned page;
const int pageM = 3;
typedef std::vector<ChannelData>::iterator ChannelIter;
void setChannel(ChannelIter iter, bool saveHistory);
ChannelIter getChannel(unsigned ch);
}; };
} }
#endif //OPENMW_GUICHAT_HPP #endif //OPENMW_GUICHAT_HPP

@ -77,10 +77,38 @@ void mwmp::GUIController::setupChat(const Settings::Manager &mgr)
mChat->setDelay(chatDelay); mChat->setDelay(chatDelay);
} }
void mwmp::GUIController::printChatMessage(std::string &msg) void mwmp::GUIController::printChatMessage(const mwmp::Chat &chat)
{ {
if (mChat != nullptr) 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);
} }

@ -39,7 +39,8 @@ namespace mwmp
void cleanUp(); void cleanUp();
void setupChat(const Settings::Manager &manager); 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 setChatVisible(bool chatVisible);
void showMessageBox(const BasePlayer::GUIMessageBox &guiMessageBox); void showMessageBox(const BasePlayer::GUIMessageBox &guiMessageBox);

@ -19,7 +19,7 @@ namespace MWMechanics
namespace mwmp namespace mwmp
{ {
struct DedicatedPlayer; class DedicatedPlayer;
class PlayerList class PlayerList
{ {

@ -23,7 +23,7 @@ namespace mwmp
virtual void Do(PlayerPacket &packet, BasePlayer *player) virtual void Do(PlayerPacket &packet, BasePlayer *player)
{ {
if (player != 0) if (player != 0)
Main::get().getGUIController()->printChatMessage(player->chatMessage); Main::get().getGUIController()->printChatMessage(player->chat);
} }
}; };
} }

@ -19,6 +19,21 @@
namespace mwmp 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 struct CurrentContainer
{ {
std::string refId; std::string refId;
@ -254,7 +269,7 @@ namespace mwmp
ESM::NpcStats npcStats; ESM::NpcStats npcStats;
ESM::Class charClass; ESM::Class charClass;
std::string birthsign; std::string birthsign;
std::string chatMessage; Chat chat;
CharGenState charGenState; CharGenState charGenState;
std::string passw; std::string passw;

@ -15,5 +15,7 @@ void mwmp::PacketChatMessage::Packet(RakNet::BitStream *bs, bool send)
{ {
PlayerPacket::Packet(bs, send); PlayerPacket::Packet(bs, send);
RW(player->chatMessage, send); RW(player->chat.channel, send);
RW(player->chat.action, send);
RW(player->chat.message, send);
} }

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout"> <MyGUI type="Layout">
<Widget type="Window" skin="MW_Window" position="0 0 400 400" layer="Console" name="_Main"> <!--<Widget type="Window" skin="TES3MP_ChatWindowBL" position="0 0 400 400" layer="Console" name="_Main">-->
<Widget type="Window" skin="MW_Window" position="0 0 400 428" layer="Windows" name="_Main">
<Property key="Caption" value="#{sConsoleTitle}"/> <Property key="Caption" value="#{sConsoleTitle}"/>
<Property key="MinSize" value="40 40"/> <Property key="MinSize" value="40 40"/>
<Property key="Visible" value="false"/> <Property key="Visible" value="false"/>
@ -15,10 +16,34 @@
<Property key="InvertSelected" value="false"/> <Property key="InvertSelected" value="false"/>
</Widget> </Widget>
<!-- Command line --> <!-- Command line -->
<Widget type="EditBox" skin="TES3MP_ChatMsg" position="0 338 384 28" align="HStretch Bottom" name="edit_Command"> <Widget type="VBox" align="Bottom HStretch" position="0 338 384 56">
<Property key="InvertSelected" value="false"/> <UserString key="HStretch" value="true"/>
<Widget type="HBox" align="Bottom HStretch" name="box_channels">
<Widget type="AutoSizedButton" skin="MW_Button" name="btn_prev_ch">
<Property key="Caption" value="<"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="btn_ch1">
<Property key="Caption" value="ZZZZZZZZZ"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="btn_ch2">
<Property key="Caption" value="ZZZZZZZZZ"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="btn_ch3">
<Property key="Caption" value="ZZZZZZZZZ"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="btn_next_ch">
<Property key="Caption" value=">"/>
</Widget> </Widget>
</Widget>
<Widget type="Spacer"/>
<Widget type="EditBox" skin="TES3MP_ChatMsg" position="0 338 384 28" align="HStretch Bottom"
name="edit_Command">
<Property key="InvertSelected" value="false"/>
</Widget>
</Widget> </Widget>
</Widget>
</MyGUI> </MyGUI>

@ -17,4 +17,8 @@
<Child type="TextBox" skin="TES3MP_EditClient" offset="4 2 19 22" align="Bottom Stretch" name="Client"/> <Child type="TextBox" skin="TES3MP_EditClient" offset="4 2 19 22" align="Bottom Stretch" name="Client"/>
</Resource> </Resource>
<Resource type="ResourceSkin" name="TES3MP_ChatWindowBL" size="256 54">
<Child type="Widget" skin="BlackBG" offset="0 0 256 256" align="Stretch" name="Client"/>
</Resource>
</MyGUI> </MyGUI>

Loading…
Cancel
Save