/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "chat.h" #include "game.h" #include "pugicast.h" #include "scheduler.h" extern Chat* g_chat; extern Game g_game; bool PrivateChatChannel::isInvited(uint32_t guid) const { if (guid == getOwner()) { return true; } return invites.find(guid) != invites.end(); } bool PrivateChatChannel::removeInvite(uint32_t guid) { return invites.erase(guid) != 0; } void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer) { auto result = invites.emplace(invitePlayer.getGUID(), &invitePlayer); if (!result.second) { return; } std::ostringstream ss; ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel."; invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); ss.str(std::string()); ss << invitePlayer.getName() << " has been invited."; player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); } void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer) { if (!removeInvite(excludePlayer.getGUID())) { return; } removeUser(excludePlayer); std::ostringstream ss; ss << excludePlayer.getName() << " has been excluded."; player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); excludePlayer.sendClosePrivate(id); } void PrivateChatChannel::closeChannel() const { for (const auto& it : users) { it.second->sendClosePrivate(id); } } bool ChatChannel::addUser(Player& player) { if (users.find(player.getID()) != users.end()) { return false; } if (!executeOnJoinEvent(player)) { return false; } users[player.getID()] = &player; return true; } bool ChatChannel::removeUser(const Player& player) { auto iter = users.find(player.getID()); if (iter == users.end()) { return false; } users.erase(iter); executeOnLeaveEvent(player); return true; } bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text) { if (users.find(fromPlayer.getID()) == users.end()) { return false; } for (const auto& it : users) { it.second->sendToChannel(&fromPlayer, type, text, id); } return true; } bool ChatChannel::executeCanJoinEvent(const Player& player) { if (canJoinEvent == -1) { return true; } //canJoin(player) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(canJoinEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(canJoinEvent); LuaScriptInterface::pushUserdata(L, &player); LuaScriptInterface::setMetatable(L, -1, "Player"); return scriptInterface->callFunction(1); } bool ChatChannel::executeOnJoinEvent(const Player& player) { if (onJoinEvent == -1) { return true; } //onJoin(player) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(onJoinEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(onJoinEvent); LuaScriptInterface::pushUserdata(L, &player); LuaScriptInterface::setMetatable(L, -1, "Player"); return scriptInterface->callFunction(1); } bool ChatChannel::executeOnLeaveEvent(const Player& player) { if (onLeaveEvent == -1) { return true; } //onLeave(player) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(onLeaveEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(onLeaveEvent); LuaScriptInterface::pushUserdata(L, &player); LuaScriptInterface::setMetatable(L, -1, "Player"); return scriptInterface->callFunction(1); } bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message) { if (onSpeakEvent == -1) { return true; } //onSpeak(player, type, message) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(onSpeakEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(onSpeakEvent); LuaScriptInterface::pushUserdata(L, &player); LuaScriptInterface::setMetatable(L, -1, "Player"); lua_pushnumber(L, type); LuaScriptInterface::pushString(L, message); bool result = false; int size0 = lua_gettop(L); int ret = scriptInterface->protectedCall(L, 3, 1); if (ret != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); } else if (lua_gettop(L) > 0) { if (lua_isboolean(L, -1)) { result = LuaScriptInterface::getBoolean(L, -1); } else if (lua_isnumber(L, -1)) { result = true; type = LuaScriptInterface::getNumber(L, -1); } lua_pop(L, 1); } if ((lua_gettop(L) + 4) != size0) { LuaScriptInterface::reportError(nullptr, "Stack size changed!"); } scriptInterface->resetScriptEnv(); return result; } Chat::Chat(): scriptInterface("Chat Interface"), dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") { scriptInterface.initState(); } bool Chat::load() { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file("data/chatchannels/chatchannels.xml"); if (!result) { printXMLError("Error - Chat::load", "data/chatchannels/chatchannels.xml", result); return false; } std::forward_list removedChannels; for (auto& channelEntry : normalChannels) { ChatChannel& channel = channelEntry.second; channel.onSpeakEvent = -1; channel.canJoinEvent = -1; channel.onJoinEvent = -1; channel.onLeaveEvent = -1; removedChannels.push_front(channelEntry.first); } for (auto channelNode : doc.child("channels").children()) { ChatChannel channel(pugi::cast(channelNode.attribute("id").value()), channelNode.attribute("name").as_string()); channel.publicChannel = channelNode.attribute("public").as_bool(); pugi::xml_attribute scriptAttribute = channelNode.attribute("script"); if (scriptAttribute) { if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); channel.canJoinEvent = scriptInterface.getEvent("canJoin"); channel.onJoinEvent = scriptInterface.getEvent("onJoin"); channel.onLeaveEvent = scriptInterface.getEvent("onLeave"); } else { std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl; } } removedChannels.remove(channel.id); normalChannels[channel.id] = channel; } for (uint16_t channelId : removedChannels) { normalChannels.erase(channelId); } return true; } ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId) { if (getChannel(player, channelId)) { return nullptr; } switch (channelId) { case CHANNEL_GUILD: { Guild* guild = player.getGuild(); if (guild) { auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName()))); return &ret.first->second; } break; } case CHANNEL_PARTY: { Party* party = player.getParty(); if (party) { auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party"))); return &ret.first->second; } break; } case CHANNEL_PRIVATE: { //only 1 private channel for each premium player if (!player.isPremium() || getPrivateChannel(player)) { return nullptr; } //find a free private channel slot for (uint16_t i = 100; i < 10000; ++i) { auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel"))); if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map auto& newChannel = (*ret.first).second; newChannel.setOwner(player.getGUID()); return &newChannel; } } break; } default: break; } return nullptr; } bool Chat::deleteChannel(const Player& player, uint16_t channelId) { switch (channelId) { case CHANNEL_GUILD: { Guild* guild = player.getGuild(); if (!guild) { return false; } auto it = guildChannels.find(guild->getId()); if (it == guildChannels.end()) { return false; } guildChannels.erase(it); break; } case CHANNEL_PARTY: { Party* party = player.getParty(); if (!party) { return false; } auto it = partyChannels.find(party); if (it == partyChannels.end()) { return false; } partyChannels.erase(it); break; } default: { auto it = privateChannels.find(channelId); if (it == privateChannels.end()) { return false; } it->second.closeChannel(); privateChannels.erase(it); break; } } return true; } ChatChannel* Chat::addUserToChannel(Player& player, uint16_t channelId) { ChatChannel* channel = getChannel(player, channelId); if (channel && channel->addUser(player)) { return channel; } return nullptr; } bool Chat::removeUserFromChannel(const Player& player, uint16_t channelId) { ChatChannel* channel = getChannel(player, channelId); if (!channel || !channel->removeUser(player)) { return false; } if (channel->getOwner() == player.getGUID()) { deleteChannel(player, channelId); } return true; } void Chat::removeUserFromAllChannels(const Player& player) { for (auto& it : normalChannels) { it.second.removeUser(player); } for (auto& it : partyChannels) { it.second.removeUser(player); } for (auto& it : guildChannels) { it.second.removeUser(player); } auto it = privateChannels.begin(); while (it != privateChannels.end()) { PrivateChatChannel* channel = &it->second; channel->removeInvite(player.getGUID()); channel->removeUser(player); if (channel->getOwner() == player.getGUID()) { channel->closeChannel(); it = privateChannels.erase(it); } else { ++it; } } } bool Chat::talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId) { ChatChannel* channel = getChannel(player, channelId); if (!channel) { return false; } if (channelId == CHANNEL_GUILD) { const GuildRank* rank = player.getGuildRank(); if (rank && rank->level > 1) { type = TALKTYPE_CHANNEL_O; } else if (type != TALKTYPE_CHANNEL_Y) { type = TALKTYPE_CHANNEL_Y; } } else if (type != TALKTYPE_CHANNEL_Y && (channelId == CHANNEL_PRIVATE || channelId == CHANNEL_PARTY)) { type = TALKTYPE_CHANNEL_Y; } if (!channel->executeOnSpeakEvent(player, type, text)) { return false; } return channel->talk(player, type, text); } ChannelList Chat::getChannelList(const Player& player) { ChannelList list; if (player.getGuild()) { ChatChannel* channel = getChannel(player, CHANNEL_GUILD); if (channel) { list.push_back(channel); } else { channel = createChannel(player, CHANNEL_GUILD); if (channel) { list.push_back(channel); } } } if (player.getParty()) { ChatChannel* channel = getChannel(player, CHANNEL_PARTY); if (channel) { list.push_back(channel); } else { channel = createChannel(player, CHANNEL_PARTY); if (channel) { list.push_back(channel); } } } for (const auto& it : normalChannels) { ChatChannel* channel = getChannel(player, it.first); if (channel) { list.push_back(channel); } } bool hasPrivate = false; for (auto& it : privateChannels) { if (PrivateChatChannel* channel = &it.second) { uint32_t guid = player.getGUID(); if (channel->isInvited(guid)) { list.push_back(channel); } if (channel->getOwner() == guid) { hasPrivate = true; } } } if (!hasPrivate && player.isPremium()) { list.push_front(&dummyPrivate); } return list; } ChatChannel* Chat::getChannel(const Player& player, uint16_t channelId) { switch (channelId) { case CHANNEL_GUILD: { Guild* guild = player.getGuild(); if (guild) { auto it = guildChannels.find(guild->getId()); if (it != guildChannels.end()) { return &it->second; } } break; } case CHANNEL_PARTY: { Party* party = player.getParty(); if (party) { auto it = partyChannels.find(party); if (it != partyChannels.end()) { return &it->second; } } break; } default: { auto it = normalChannels.find(channelId); if (it != normalChannels.end()) { ChatChannel& channel = it->second; if (!channel.executeCanJoinEvent(player)) { return nullptr; } return &channel; } else { auto it2 = privateChannels.find(channelId); if (it2 != privateChannels.end() && it2->second.isInvited(player.getGUID())) { return &it2->second; } } break; } } return nullptr; } ChatChannel* Chat::getGuildChannelById(uint32_t guildId) { auto it = guildChannels.find(guildId); if (it == guildChannels.end()) { return nullptr; } return &it->second; } ChatChannel* Chat::getChannelById(uint16_t channelId) { auto it = normalChannels.find(channelId); if (it == normalChannels.end()) { return nullptr; } return &it->second; } PrivateChatChannel* Chat::getPrivateChannel(const Player& player) { for (auto& it : privateChannels) { if (it.second.getOwner() == player.getGUID()) { return &it.second; } } return nullptr; }