/*
* The Mana World Server
* Copyright 2004 The Mana World Development Team
*
* This file is part of The Mana World.
*
* The Mana World 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
* any later version.
*
* The Mana World 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 The Mana World; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id$
*/
#include "chathandler.h"
#include "chatchannelmanager.h"
#include "connectionhandler.h"
#include "messagein.h"
#include "messageout.h"
#include "netcomputer.h"
#include "packet.h"
#include "utils/logger.h"
#include "utils/stringfilter.h"
class ChatClient: public NetComputer
{
public:
/**
* Constructor.
*/
ChatClient(ChatHandler *, ENetPeer *);
std::string characterName;
AccountLevels accountLevel;
};
ChatClient::ChatClient(ChatHandler *handler, ENetPeer *peer):
NetComputer(handler, peer),
accountLevel(AL_NORMAL)
{
}
struct ChatPendingLogin
{
std::string character;
AccountLevels level;
int timeout;
};
typedef std::map< std::string, ChatPendingLogin > ChatPendingLogins;
static ChatPendingLogins pendingLogins;
typedef std::map< std::string, ChatClient * > ChatPendingClients;
static ChatPendingClients pendingClients;
void registerChatClient(std::string const &token, std::string const &name, int level)
{
ChatPendingClients::iterator i = pendingClients.find(token);
if (i != pendingClients.end())
{
ChatClient *computer = i->second;
computer->characterName = name;
computer->accountLevel = (AccountLevels)level;
pendingClients.erase(i);
MessageOut result;
result.writeShort(CPMSG_CONNECT_RESPONSE);
result.writeByte(ERRMSG_OK);
computer->send(result.getPacket());
}
else
{
ChatPendingLogin p;
p.character = name;
p.level = (AccountLevels)level;
p.timeout = 300; // world ticks
pendingLogins.insert(std::make_pair(token, p));
}
}
void ChatHandler::removeOutdatedPending()
{
ChatPendingLogins::iterator i = pendingLogins.begin(), next;
while (i != pendingLogins.end())
{
next = i; ++next;
if (--i->second.timeout <= 0) pendingLogins.erase(i);
i = next;
}
}
NetComputer *ChatHandler::computerConnected(ENetPeer *peer)
{
return new ChatClient(this, peer);
}
void ChatHandler::computerDisconnected(NetComputer *computer)
{
for (ChatPendingClients::iterator i = pendingClients.begin(), i_end = pendingClients.end();
i != i_end; ++i)
{
if (i->second == computer)
{
pendingClients.erase(i);
break;
}
}
delete computer;
}
void ChatHandler::process()
{
ConnectionHandler::process();
removeOutdatedPending();
}
void ChatHandler::processMessage(NetComputer *comp, MessageIn &message)
{
ChatClient &computer = *static_cast< ChatClient * >(comp);
MessageOut result;
if (computer.characterName.empty()) {
if (message.getId() != PCMSG_CONNECT) return;
std::string magic_token = message.readString(32);
ChatPendingLogins::iterator i = pendingLogins.find(magic_token);
if (i == pendingLogins.end())
{
for (ChatPendingClients::iterator i = pendingClients.begin(), i_end = pendingClients.end();
i != i_end; ++i) {
if (i->second == &computer) return;
}
pendingClients.insert(std::make_pair(magic_token, &computer));
return;
}
computer.characterName = i->second.character;
computer.accountLevel = i->second.level;
pendingLogins.erase(i);
result.writeShort(CPMSG_CONNECT_RESPONSE);
result.writeByte(ERRMSG_OK);
computer.send(result.getPacket());
return;
}
switch (message.getId())
{
case PCMSG_CHAT:
{
// chat to people around area
std::string text = message.readString();
// If it's slang clean,
if (stringFilter->filterContent(text))
{
short channel = message.readShort();
LOG_INFO("Say: (Channel " << channel << "): " << text, 2);
if ( channel == 0 ) // Let's say that is the default channel for now.
{
if ( text.substr(0, 1) == "@" || text.substr(0, 1) == "#" || text.substr(0, 1) == "/" )
{
// The message is a command. Deal with it.
handleCommand(computer, text);
}
}
else
{
// We send the message to the players registered in the channel.
sayInChannel(computer, channel, text);
}
}
else
{
warnPlayerAboutBadWords(computer);
}
}
break;
case PCMSG_ANNOUNCE:
{
std::string text = message.readString();
// If it's slang's free.
if (stringFilter->filterContent(text))
{
// We send the message to every players in the default channel
// as it is an annouce.
announce(computer, text);
}
else
{
warnPlayerAboutBadWords(computer);
}
}
break;
case PCMSG_PRIVMSG:
{
std::string user = message.readString();
std::string text = message.readString();
if (stringFilter->filterContent(text))
{
// We seek the player to whom the message is told
// and send it to her/him.
sayToPlayer(computer, user, text);
}
else
{
warnPlayerAboutBadWords(computer);
}
} break;
// Channels handling
// =================
case PCMSG_REGISTER_CHANNEL:
{
result.writeShort(CPMSG_REGISTER_CHANNEL_RESPONSE);
// 0 public, 1 private
char channelType = message.readByte();
if (!channelType)
{
if (computer.accountLevel != AL_ADMIN &&
computer.accountLevel != AL_GM)
{
result.writeByte(ERRMSG_INSUFFICIENT_RIGHTS);
break;
}
}
std::string channelName = message.readString();
std::string channelAnnouncement = message.readString();
std::string channelPassword = message.readString();
// Checking datas
// Seeking double-quotes in strings
if (channelName.empty() || channelName.length() > MAX_CHANNEL_NAME ||
stringFilter->findDoubleQuotes(channelName))
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
break;
}
if (channelAnnouncement.length() > MAX_CHANNEL_ANNOUNCEMENT ||
stringFilter->findDoubleQuotes(channelAnnouncement))
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
break;
}
if (channelPassword.length() > MAX_CHANNEL_PASSWORD ||
stringFilter->findDoubleQuotes(channelPassword))
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
break;
}
// If it's slang's free.
if (stringFilter->filterContent(channelName) &&
stringFilter->filterContent(channelAnnouncement))
{
// We attempt to create a new channel
short channelId;
if (channelType)
channelId = chatChannelManager->registerPrivateChannel(channelName,
channelAnnouncement,
channelPassword);
else
channelId = chatChannelManager->registerPublicChannel(channelName,
channelAnnouncement,
channelPassword);
if (channelId != 0)
{
// We add the player as admin of this channel as he created it.
// The user registering a private channel is the only one to be able
// to update the password and the announcement in it and also to remove it.
chatChannelManager->addUserInChannel(computer.characterName, channelId);
result.writeByte(ERRMSG_OK);
result.writeShort(channelId);
break;
}
else
{
result.writeByte(ERRMSG_FAILURE);
break;
}
}
else
{
warnPlayerAboutBadWords(computer);
}
}
break;
case PCMSG_UNREGISTER_CHANNEL:
{
result.writeShort(CPMSG_UNREGISTER_CHANNEL_RESPONSE);
short channelId = message.readShort();
if (!chatChannelManager->isChannelRegistered(channelId))
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
}
else if (channelId < (signed)MAX_PUBLIC_CHANNELS_RANGE)
{ // Public channel
if (computer.accountLevel == AL_ADMIN || computer.accountLevel == AL_GM)
{
warnUsersAboutPlayerEventInChat(channelId, "", CHAT_EVENT_LEAVING_PLAYER);
if (chatChannelManager->removeChannel(channelId))
result.writeByte(ERRMSG_OK);
else
result.writeByte(ERRMSG_FAILURE);
}
else
{
result.writeByte(ERRMSG_INSUFFICIENT_RIGHTS);
}
}
else
{ // Private channel
// We first see if the user is the admin (first user) of the channel
std::vector< std::string > const &userList =
chatChannelManager->getUserListInChannel(channelId);
std::vector< std::string >::const_iterator i = userList.begin();
// if it's actually the private channel's admin
if (*i == computer.characterName)
{
// Make every user quit the channel
warnUsersAboutPlayerEventInChat(channelId, "", CHAT_EVENT_LEAVING_PLAYER);
if (chatChannelManager->removeChannel(channelId))
result.writeByte(ERRMSG_OK);
else
result.writeByte(ERRMSG_FAILURE);
}
else
{
result.writeByte(ERRMSG_INSUFFICIENT_RIGHTS);
}
}
} break;
case PCMSG_ENTER_CHANNEL:
{
result.writeShort(CPMSG_ENTER_CHANNEL_RESPONSE);
short channelId = message.readShort();
std::string givenPassword = message.readString();
if (channelId != 0 && chatChannelManager->isChannelRegistered(channelId))
{
std::string channelPassword = chatChannelManager->getChannelPassword(channelId);
if (!channelPassword.empty())
{
if (channelPassword != givenPassword)
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
break;
}
}
if (chatChannelManager->addUserInChannel(computer.characterName, channelId))
{
result.writeByte(ERRMSG_OK);
// The user entered the channel, now give him the announcement string
// and the user list.
result.writeString(chatChannelManager->getChannelAnnouncement(channelId));
std::vector< std::string > const &userList =
chatChannelManager->getUserListInChannel(channelId);
result.writeShort(userList.size());
for (std::vector< std::string >::const_iterator i = userList.begin(), i_end = userList.end();
i != i_end; ++i) {
result.writeString(*i);
}
// Send an CPMSG_UPDATE_CHANNEL to warn other clients a user went
// in the channel.
warnUsersAboutPlayerEventInChat(channelId, computer.characterName,
CHAT_EVENT_NEW_PLAYER);
}
else
{
result.writeByte(ERRMSG_FAILURE);
}
}
else
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
}
}
break;
case PCMSG_QUIT_CHANNEL:
{
result.writeShort(CPMSG_QUIT_CHANNEL_RESPONSE);
short channelId = message.readShort();
if (channelId != 0 && chatChannelManager->isChannelRegistered(channelId))
{
if (chatChannelManager->removeUserFromChannel(computer.characterName, channelId))
{
result.writeByte(ERRMSG_OK);
// Send an CPMSG_UPDATE_CHANNEL to warn other clients a user left
// the channel.
warnUsersAboutPlayerEventInChat(channelId, computer.characterName,
CHAT_EVENT_LEAVING_PLAYER);
}
else
{
result.writeByte(ERRMSG_FAILURE);
}
}
else
{
result.writeByte(ERRMSG_INVALID_ARGUMENT);
}
}
break;
default:
LOG_WARN("Invalid message type", 0);
result.writeShort(XXMSG_INVALID);
break;
}
if (result.getPacket()->length > 0)
computer.send(result.getPacket());
}
void ChatHandler::handleCommand(ChatClient &computer, std::string const &command)
{
LOG_INFO("Chat: Received unhandled command: " << command, 2);
MessageOut result;
result.writeShort(CPMSG_ERROR);
result.writeByte(CHAT_UNHANDLED_COMMAND);
computer.send(result.getPacket());
}
void ChatHandler::warnPlayerAboutBadWords(ChatClient &computer)
{
// We could later count if the player is really often unpolite.
MessageOut result;
result.writeShort(CPMSG_ERROR);
result.writeByte(CHAT_USING_BAD_WORDS); // The Channel
computer.send(result.getPacket());
LOG_INFO(computer.characterName << " says bad words.", 2);
}
void ChatHandler::announce(ChatClient &computer, std::string const &text)
{
MessageOut result;
if (computer.accountLevel == AL_ADMIN ||
computer.accountLevel == AL_GM )
{
LOG_INFO("ANNOUNCE: " << text, 0);
// Send it to every beings.
result.writeShort(CPMSG_ANNOUNCEMENT);
result.writeString(text);
sendToEveryone(result);
}
else
{
result.writeShort(CPMSG_ERROR);
result.writeByte(ERRMSG_INSUFFICIENT_RIGHTS);
computer.send(result.getPacket());
LOG_INFO(computer.characterName <<
" couldn't make an announcement due to insufficient rights.", 2);
}
}
void ChatHandler::sayToPlayer(ChatClient &computer, std::string const &playerName, std::string const &text)
{
MessageOut result;
LOG_INFO(computer.characterName << " says to " << playerName << ": " << text, 2);
// Send it to the being if the being exists
result.writeShort(CPMSG_PRIVMSG);
result.writeString(computer.characterName);
result.writeString(text);
for (NetComputers::iterator i = clients.begin(), i_end = clients.end();
i != i_end; ++i) {
if (static_cast< ChatClient * >(*i)->characterName == playerName)
{
(*i)->send(result.getPacket());
break;
}
}
}
void ChatHandler::sayInChannel(ChatClient &computer, short channel, std::string const &text)
{
MessageOut result;
LOG_INFO(computer.characterName << " says in channel " << channel << ": " << text, 2);
// Send it to every beings in channel
result.writeShort(CPMSG_PUBMSG);
result.writeShort(channel);
result.writeString(computer.characterName);
result.writeString(text);
sendInChannel(channel, result);
}
void ChatHandler::warnUsersAboutPlayerEventInChat(short channelId,
std::string const &userName,
char eventId)
{
MessageOut result;
result.writeShort(CPMSG_CHANNEL_EVENT);
result.writeShort(channelId);
result.writeByte(eventId);
result.writeString(userName);
sendInChannel(channelId, result);
}
void ChatHandler::sendInChannel(short channelId, MessageOut &msg)
{
std::vector< std::string > const &users =
chatChannelManager->getUserListInChannel(channelId);
for (NetComputers::iterator i = clients.begin(), i_end = clients.end();
i != i_end; ++i) {
// If the being is in the channel, send it
std::vector< std::string >::const_iterator j_end = users.end(),
j = std::find(users.begin(), j_end, static_cast< ChatClient * >(*i)->characterName);
if (j != j_end)
{
(*i)->send(msg.getPacket());
}
}
}