2019-09-16 20:38:16 +03:00

592 lines
14 KiB
C++

/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
*
* 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<SpeakClasses>(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<uint16_t> 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<uint16_t>(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;
}