mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-05-02 10:39:20 +02:00
592 lines
14 KiB
C++
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;
|
|
}
|