summaryrefslogtreecommitdiff
path: root/src/account-server
diff options
context:
space:
mode:
authorGuillaume Melquiond <guillaume.melquiond@gmail.com>2006-12-29 13:43:24 +0000
committerGuillaume Melquiond <guillaume.melquiond@gmail.com>2006-12-29 13:43:24 +0000
commit291ad04d5b5c4ab08d85eadde116f968cd579b77 (patch)
treee4dced5715a5d9792cfdc0455a6b3ee6d3116079 /src/account-server
parent3d404e743105bb9168c89e3451cf35d7d59120b1 (diff)
downloadmanaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.tar.gz
manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.tar.bz2
manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.tar.xz
manaserv-291ad04d5b5c4ab08d85eadde116f968cd579b77.zip
Physically split the server into one tmwserv-acount program (account +
chat + database) and multiple tmwserv-game programs (selected with respect to the maps). Cleaned the repository by moving server-specific source files into dedicated directories.
Diffstat (limited to 'src/account-server')
-rw-r--r--src/account-server/account.cpp218
-rw-r--r--src/account-server/account.hpp232
-rw-r--r--src/account-server/accountclient.cpp63
-rw-r--r--src/account-server/accountclient.hpp95
-rw-r--r--src/account-server/accounthandler.cpp707
-rw-r--r--src/account-server/accounthandler.hpp82
-rw-r--r--src/account-server/dalstorage.cpp742
-rw-r--r--src/account-server/dalstorage.hpp177
-rw-r--r--src/account-server/dalstoragesql.hpp334
-rw-r--r--src/account-server/main-account.cpp327
-rw-r--r--src/account-server/serverhandler.cpp115
-rw-r--r--src/account-server/serverhandler.hpp90
-rw-r--r--src/account-server/storage.cpp145
-rw-r--r--src/account-server/storage.hpp290
14 files changed, 3617 insertions, 0 deletions
diff --git a/src/account-server/account.cpp b/src/account-server/account.cpp
new file mode 100644
index 00000000..c827a92d
--- /dev/null
+++ b/src/account-server/account.cpp
@@ -0,0 +1,218 @@
+/*
+ * 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 <cassert>
+
+#include "account-server/account.hpp"
+#include "utils/functors.h"
+
+/**
+ * Constructor with initial account info.
+ */
+Account::Account(const std::string& name,
+ const std::string& password,
+ const std::string& email,
+ int id)
+ : mID(id),
+ mName(name),
+ mPassword(password),
+ mEmail(email),
+ mCharacters(),
+ mLevel(AL_NORMAL)
+{
+ // NOOP
+}
+
+
+/**
+ * Constructor with initial account info.
+ */
+Account::Account(const std::string& name,
+ const std::string& password,
+ const std::string& email,
+ const Players& characters)
+ : mName(name),
+ mPassword(password),
+ mEmail(email),
+ mCharacters(characters),
+ mLevel(AL_NORMAL)
+{
+ // NOOP
+}
+
+
+/**
+ * Destructor.
+ */
+Account::~Account()
+{
+ // mCharacters is a list of smart pointers which will take care about
+ // deallocating the memory so nothing to deallocate here :)
+}
+
+
+/**
+ * Set the user name.
+ */
+void
+Account::setName(const std::string& name)
+{
+ mName = name;
+}
+
+
+/**
+ * Get the user name.
+ */
+const std::string&
+Account::getName(void) const
+{
+ return mName;
+}
+
+
+/**
+ * Set the user password.
+ */
+void
+Account::setPassword(const std::string& password)
+{
+ mPassword = password;
+}
+
+
+/**
+ * Get the user password.
+ */
+const std::string
+Account::getPassword(void) const
+{
+ return mPassword;
+}
+
+
+/**
+ * Set the user email address.
+ */
+void
+Account::setEmail(const std::string& email)
+{
+ // should we check that the email address is valid first?
+ mEmail = email;
+}
+
+
+/**
+ * Get the user email address.
+ */
+const std::string&
+Account::getEmail(void) const
+{
+ return mEmail;
+}
+
+
+/**
+ * Set the account level.
+ */
+void
+Account::setLevel(AccountLevel level)
+{
+ mLevel = level;
+}
+
+
+/**
+ * Get the account level.
+ */
+AccountLevel
+Account::getLevel(void) const
+{
+ return mLevel;
+}
+
+
+/**
+ * Set the characters.
+ */
+void
+Account::setCharacters(const Players& characters)
+{
+ mCharacters = characters;
+}
+
+
+/**
+ * Add a new character.
+ */
+void
+Account::addCharacter(PlayerPtr character)
+{
+ if (character.get() != 0) {
+ mCharacters.push_back(character);
+ }
+}
+
+/**
+ * Remove a character.
+ */
+bool Account::delCharacter(std::string const &name)
+{
+ Players::iterator
+ end = mCharacters.end(),
+ it = std::find_if(mCharacters.begin(), end,
+ std::bind2nd(obj_name_is<PlayerPtr>(), name));
+
+ if (it == end) return false;
+ mCharacters.erase(it);
+ return true;
+}
+
+
+/**
+ * Get all the characters.
+ */
+Players &Account::getCharacters()
+{
+ return mCharacters;
+}
+
+
+/**
+ * Get a character by name.
+ */
+PlayerPtr Account::getCharacter(const std::string& name)
+{
+ Players::iterator
+ end = mCharacters.end(),
+ it = std::find_if(mCharacters.begin(), end,
+ std::bind2nd(obj_name_is<PlayerPtr>(), name));
+
+ if (it != end) return *it;
+ return PlayerPtr();
+}
+
+void Account::setID(int id)
+{
+ assert(mID < 0);
+ mID = id;
+}
diff --git a/src/account-server/account.hpp b/src/account-server/account.hpp
new file mode 100644
index 00000000..e9d9e408
--- /dev/null
+++ b/src/account-server/account.hpp
@@ -0,0 +1,232 @@
+/*
+ * 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$
+ */
+
+#ifndef _TMWSERV_ACCOUNT_H_
+#define _TMWSERV_ACCOUNT_H_
+
+#include <string>
+
+#include "player.h"
+
+/**
+ * Notes:
+ * - change from the previous implementation: this class does not encrypt
+ * passwords anymore and will just store the passwords as they are
+ * passed to setPassword().
+ * - the encryption should and must be performed externally from this
+ * class or else we would end up with the password being encrypted many
+ * times (e.g setPassword(getPassword()) would encrypt the password
+ * twice or setPassword(encrypted_password_from_database) would also
+ * encrypt the password twice).
+ */
+
+
+/**
+ * A player account.
+ */
+class Account
+{
+ public:
+ /**
+ * Constructor with initial account info.
+ *
+ * @param name the user name.
+ * @param password the user password.
+ * @param email the user email.
+ */
+ Account(const std::string& name,
+ const std::string& password,
+ const std::string& email,
+ int id = -1);
+
+
+ /**
+ * Constructor with initial account info.
+ *
+ * @param name the user name.
+ * @param password the user password.
+ * @param email the user email.
+ * @param characters the characters.
+ */
+ Account(const std::string& name,
+ const std::string& password,
+ const std::string& email,
+ const Players& characters);
+
+
+ /**
+ * Destructor.
+ */
+ ~Account();
+
+
+ /**
+ * Set the user name.
+ *
+ * @param name the user name.
+ */
+ void
+ setName(const std::string& name);
+
+
+ /**
+ * Get the user name.
+ *
+ * @return the user name.
+ */
+ const std::string&
+ getName() const;
+
+
+ /**
+ * Set the user password.
+ *
+ * @param password the user password.
+ */
+ void
+ setPassword(const std::string& password);
+
+
+ /**
+ * Get the user password.
+ *
+ * @return the user password.
+ */
+ const std::string
+ getPassword(void) const;
+
+
+ /**
+ * Set the user email address.
+ *
+ * @param email the user email address.
+ */
+ void
+ setEmail(const std::string& email);
+
+
+ /**
+ * Get the user email address.
+ *
+ * @return the user email address.
+ */
+ const std::string&
+ getEmail(void) const;
+
+
+ /**
+ * Set the account level.
+ *
+ * @param level the new level.
+ */
+ void
+ setLevel(AccountLevel level);
+
+
+ /**
+ * Get the account level.
+ *
+ * @return the account level.
+ */
+ AccountLevel
+ getLevel() const;
+
+
+ /**
+ * Set the characters.
+ *
+ * @param characters a list of characters.
+ */
+ void
+ setCharacters(const Players& characters);
+
+
+ /**
+ * Add a new character.
+ *
+ * @param character the new character.
+ */
+ void
+ addCharacter(PlayerPtr character);
+
+ /**
+ * Remove a character.
+ *
+ * @param name The character's name to delete.
+ */
+ bool
+ delCharacter(std::string const &name);
+
+
+ /**
+ * Get all the characters.
+ *
+ * @return all the characters.
+ */
+ Players&
+ getCharacters();
+
+ /**
+ * Get a character by name.
+ *
+ * @return the character if found, NULL otherwise.
+ */
+ PlayerPtr
+ getCharacter(const std::string& name);
+
+ /**
+ * Get account ID.
+ *
+ * @return the unique ID of the account, a negative number if none yet.
+ */
+ int getID() const
+ { return mID; }
+
+ /**
+ * Set account ID.
+ * The account shall not have any ID yet.
+ */
+ void setID(int);
+
+ private:
+ Account();
+ Account(Account const &rhs);
+ Account &operator=(Account const &rhs);
+
+
+ private:
+ int mID; /**< unique id */
+ std::string mName; /**< user name */
+ std::string mPassword; /**< user password (encrypted) */
+ std::string mEmail; /**< user email address */
+ Players mCharacters; /**< player data */
+ AccountLevel mLevel; /**< account level */
+};
+
+
+/**
+ * Type definition for a smart pointer to Account.
+ */
+typedef utils::CountedPtr<Account> AccountPtr;
+
+
+#endif // _TMWSERV_ACCOUNT_H_
diff --git a/src/account-server/accountclient.cpp b/src/account-server/accountclient.cpp
new file mode 100644
index 00000000..79d3cf0c
--- /dev/null
+++ b/src/account-server/accountclient.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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 "account-server/account.hpp"
+#include "account-server/accountclient.hpp"
+#include "account-server/accounthandler.hpp"
+
+AccountClient::AccountClient(ENetPeer *peer):
+ NetComputer(peer),
+ mAccountPtr(NULL),
+ mCharacterPtr(NULL)
+{
+}
+
+AccountClient::~AccountClient()
+{
+ unsetAccount();
+}
+
+
+void AccountClient::setAccount(AccountPtr acc)
+{
+ unsetAccount();
+ mAccountPtr = acc;
+}
+
+void AccountClient::setCharacter(PlayerPtr ch)
+{
+ unsetCharacter();
+ mCharacterPtr = ch;
+}
+
+void AccountClient::unsetAccount()
+{
+ unsetCharacter();
+ mAccountPtr = AccountPtr(NULL);
+}
+
+void AccountClient::unsetCharacter()
+{
+ if (mCharacterPtr.get() == NULL) return;
+ mCharacterPtr = PlayerPtr(NULL);
+}
diff --git a/src/account-server/accountclient.hpp b/src/account-server/accountclient.hpp
new file mode 100644
index 00000000..8d42e0e4
--- /dev/null
+++ b/src/account-server/accountclient.hpp
@@ -0,0 +1,95 @@
+/*
+ * 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$
+ */
+
+#ifndef _TMWSERV_ACCOUNTCLIENT_H_
+#define _TMWSERV_ACCOUNTCLIENT_H_
+
+#include <enet/enet.h>
+
+#include "account-server/account.hpp"
+#include "net/netcomputer.hpp"
+
+class AccountHandler;
+
+/**
+ * A connected computer that can have an account and character associated with
+ * it.
+ */
+class AccountClient : public NetComputer
+{
+ public:
+ /**
+ * Constructor.
+ */
+ AccountClient(ENetPeer *peer);
+
+ /**
+ * Destructor.
+ */
+ ~AccountClient();
+
+ /**
+ * Set the account associated with the connection
+ */
+ void
+ setAccount(AccountPtr acc);
+
+ /**
+ * Unset the account associated with the connection
+ */
+ void
+ unsetAccount();
+
+ /**
+ * Get account associated with the connection.
+ */
+ AccountPtr
+ getAccount() const { return mAccountPtr; }
+
+ /**
+ * Set the selected character associated with connection.
+ */
+ void
+ setCharacter(PlayerPtr ch);
+
+ /**
+ * Deselect the character associated with connection.
+ */
+ void
+ unsetCharacter();
+
+ /**
+ * Get character associated with the connection
+ */
+ PlayerPtr
+ getCharacter() const { return mCharacterPtr; }
+
+ private:
+ /** Account associated with connection */
+ AccountPtr mAccountPtr;
+
+ /** Selected character */
+ PlayerPtr mCharacterPtr;
+};
+
+#endif
diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp
new file mode 100644
index 00000000..72a23818
--- /dev/null
+++ b/src/account-server/accounthandler.cpp
@@ -0,0 +1,707 @@
+/*
+ * 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 "configuration.h"
+#include "debug.h"
+#include "point.h"
+#include "account-server/accounthandler.hpp"
+#include "account-server/account.hpp"
+#include "account-server/accountclient.hpp"
+#include "account-server/serverhandler.hpp"
+#include "account-server/storage.hpp"
+#include "chat-server/chathandler.hpp"
+#include "net/connectionhandler.hpp"
+#include "net/messagein.hpp"
+#include "net/messageout.hpp"
+#include "net/netcomputer.hpp"
+#include "utils/logger.h"
+#include "utils/stringfilter.h"
+
+bool
+AccountHandler::startListen(enet_uint16 port)
+{
+ LOG_INFO("Account handler started:", 0);
+ return ConnectionHandler::startListen(port);
+}
+
+NetComputer*
+AccountHandler::computerConnected(ENetPeer *peer)
+{
+ return new AccountClient(peer);
+}
+
+void
+AccountHandler::computerDisconnected(NetComputer *comp)
+{
+ delete comp;
+}
+
+/**
+ * Generic interface convention for getting a message and sending it to the
+ * correct subroutines. Account handler takes care of determining the
+ * current step in the account process, be it creation, setup, or login.
+ */
+void
+AccountHandler::processMessage(NetComputer *comp, MessageIn &message)
+{
+ AccountClient &computer = *static_cast< AccountClient * >(comp);
+
+ Storage &store = Storage::instance("tmw");
+
+#if defined (SQLITE_SUPPORT)
+ // Reopen the db in this thread for sqlite, to avoid
+ // Library Call out of sequence problem due to thread safe.
+ store.setUser(config.getValue("dbuser", ""));
+ store.setPassword(config.getValue("dbpass", ""));
+ store.close();
+ store.open();
+#endif
+
+ MessageOut result;
+
+ switch (message.getId())
+ {
+ case PAMSG_LOGIN:
+ handleLoginMessage(computer, message);
+ break;
+
+ case PAMSG_LOGOUT:
+ handleLogoutMessage(computer, message);
+ break;
+
+ case PAMSG_REGISTER:
+ handleRegisterMessage(computer, message);
+ break;
+
+ case PAMSG_UNREGISTER:
+ handleUnregisterMessage(computer, message);
+ break;
+
+ case PAMSG_EMAIL_CHANGE:
+ {
+ result.writeShort(APMSG_EMAIL_CHANGE_RESPONSE);
+
+ if (computer.getAccount().get() == NULL) {
+ result.writeByte(ERRMSG_NO_LOGIN);
+ LOG_INFO("Not logged in. Can't change your Account's Email.", 1);
+ break;
+ }
+
+ std::string email = message.readString();
+ if (!stringFilter->isEmailValid(email))
+ {
+ result.writeByte(ERRMSG_INVALID_ARGUMENT);
+ LOG_INFO(email << ": Invalid format, cannot change Email for " <<
+ computer.getAccount()->getName(), 1);
+ }
+ else if (stringFilter->findDoubleQuotes(email))
+ {
+ result.writeByte(ERRMSG_INVALID_ARGUMENT);
+ LOG_INFO(email << ": has got double quotes in it.", 1);
+ }
+ else if (store.doesEmailAddressExist(email))
+ {
+ result.writeByte(EMAILCHG_EXISTS_EMAIL);
+ LOG_INFO(email << ": New Email already exists.", 1);
+ }
+ else
+ {
+ computer.getAccount()->setEmail(email);
+ result.writeByte(ERRMSG_OK);
+ LOG_INFO(computer.getAccount()->getName() << ": Email changed to: " <<
+ email, 1);
+ }
+ }
+ break;
+
+ case PAMSG_EMAIL_GET:
+ {
+ result.writeShort(APMSG_EMAIL_GET_RESPONSE);
+ if (computer.getAccount().get() == NULL) {
+ result.writeByte(ERRMSG_NO_LOGIN);
+ LOG_INFO("Not logged in. Can't get your Account's current Email.", 1);
+ break;
+ }
+ else
+ {
+ result.writeByte(ERRMSG_OK);
+ result.writeString(computer.getAccount()->getEmail());
+ }
+ }
+ break;
+
+ case PAMSG_PASSWORD_CHANGE:
+ handlePasswordChangeMessage(computer, message);
+ break;
+
+ case PAMSG_CHAR_CREATE:
+ handleCharacterCreateMessage(computer, message);
+ break;
+
+ case PAMSG_CHAR_SELECT:
+ {
+ result.writeShort(APMSG_CHAR_SELECT_RESPONSE);
+
+ if (computer.getAccount().get() == NULL)
+ {
+ result.writeByte(ERRMSG_NO_LOGIN);
+ LOG_INFO("Not logged in. Can't select a Character.", 1);
+ break; // not logged in
+ }
+
+ unsigned char charNum = message.readByte();
+
+ Players &chars = computer.getAccount()->getCharacters();
+ // Character ID = 0 to Number of Characters - 1.
+ if (charNum >= chars.size()) {
+ // invalid char selection
+ result.writeByte(ERRMSG_INVALID_ARGUMENT);
+ LOG_INFO("Character Selection: Selection out of ID range.", 1);
+ break;
+ }
+
+ std::string address;
+ short port;
+ if (!serverHandler->getGameServerFromMap(chars[charNum]->getMapId(), address, port))
+ {
+ result.writeByte(ERRMSG_FAILURE);
+ LOG_ERROR("Character Selection: No game server for the map.", 0);
+ break;
+ }
+
+ // set character
+ computer.setCharacter(chars[charNum]);
+ PlayerPtr selectedChar = computer.getCharacter();
+ result.writeByte(ERRMSG_OK);
+
+ selectedChar->setDestination(selectedChar->getPosition());
+ selectedChar->setSpeed(150); // TODO
+
+ LOG_INFO(selectedChar->getName()
+ << " is trying to enter the servers.", 1);
+
+ std::string magic_token(32, ' ');
+ for (int i = 0; i < 32; ++i) {
+ magic_token[i] =
+ 1 + (int) (127 * (rand() / (RAND_MAX + 1.0)));
+ }
+ result.writeString(magic_token, 32);
+ result.writeString(address);
+ result.writeShort(port);
+ // TODO: get correct address and port for the chat server
+ result.writeString(config.getValue("accountServerAddress", "localhost"));
+ result.writeShort(int(config.getValue("accountServerPort", DEFAULT_SERVER_PORT)) + 2);
+
+ serverHandler->registerGameClient(magic_token, selectedChar);
+ registerChatClient(magic_token, selectedChar->getName(),
+ AL_NORMAL);
+ }
+ break;
+
+ case PAMSG_CHAR_DELETE:
+ {
+ result.writeShort(APMSG_CHAR_DELETE_RESPONSE);
+
+ if (computer.getAccount().get() == NULL)
+ {
+ result.writeByte(ERRMSG_NO_LOGIN);
+ LOG_INFO("Not logged in. Can't delete a Character.", 1);
+ break; // not logged in
+ }
+
+ unsigned char charNum = message.readByte();
+
+ Players &chars = computer.getAccount()->getCharacters();
+ // Character ID = 0 to Number of Characters - 1.
+ if (charNum >= chars.size()) {
+ // invalid char selection
+ result.writeByte(ERRMSG_INVALID_ARGUMENT);
+ LOG_INFO("Character Deletion : Selection out of ID range.", 1);
+ break;
+ }
+
+ // Delete the character
+ // if the character to delete is the current character, get off of it in
+ // memory.
+ if ( computer.getCharacter().get() != NULL )
+ {
+ if ( computer.getCharacter()->getName() == chars[charNum].get()->getName() )
+ {
+ computer.unsetCharacter();
+ }
+ }
+
+ std::string deletedCharacter = chars[charNum].get()->getName();
+ computer.getAccount()->delCharacter(deletedCharacter);
+ store.flush(computer.getAccount());
+ LOG_INFO(deletedCharacter << ": Character deleted...", 1);
+ result.writeByte(ERRMSG_OK);
+
+ }
+ break;
+
+ default:
+ LOG_WARN("Invalid message type", 0);
+ result.writeShort(XXMSG_INVALID);
+ break;
+ }
+
+ // return result
+ if (result.getLength() > 0)
+ computer.send(result);
+}
+
+void
+AccountHandler::handleLoginMessage(AccountClient &computer, MessageIn &msg)
+{
+ unsigned long clientVersion = msg.readLong();
+ std::string username = msg.readString();
+ std::string password = msg.readString();
+
+ LOG_INFO(username << " is trying to login.", 1);
+
+ MessageOut reply(APMSG_LOGIN_RESPONSE);
+
+ if (clientVersion < config.getValue("clientVersion", 0))
+ {
+ LOG_INFO("Client has an insufficient version number to login.", 1);
+ reply.writeByte(LOGIN_INVALID_VERSION);
+ }
+ if (stringFilter->findDoubleQuotes(username))
+ {
+ LOG_INFO(username << ": has got double quotes in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ if (computer.getAccount().get() != NULL) {
+ LOG_INFO("Already logged in as " << computer.getAccount()->getName()
+ << ".", 1);
+ LOG_INFO("Please logout first.", 1);
+ reply.writeByte(ERRMSG_FAILURE);
+ }
+ if (getClientNumber() >= MAX_CLIENTS )
+ {
+ LOG_INFO("Client couldn't login. Already has " << MAX_CLIENTS
+ << " logged in.", 1);
+ reply.writeByte(LOGIN_SERVER_FULL);
+ }
+ else
+ {
+ // Check if the account exists
+ Storage &store = Storage::instance("tmw");
+ AccountPtr acc = store.getAccount(username);
+
+ if (!acc.get() || acc->getPassword() != password)
+ {
+ LOG_INFO(username << ": Account does not exist or the password is "
+ "invalid.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else
+ {
+ LOG_INFO("Login OK by " << username, 1);
+
+ // Associate account with connection
+ computer.setAccount(acc);
+
+ reply.writeByte(ERRMSG_OK);
+ computer.send(reply);
+
+ // Return information about available characters
+ Players &chars = computer.getAccount()->getCharacters();
+
+ LOG_INFO(username << "'s account has " << chars.size()
+ << " character(s).", 1);
+
+ // Send characters list
+ for (unsigned int i = 0; i < chars.size(); i++)
+ {
+ MessageOut charInfo(APMSG_CHAR_INFO);
+ charInfo.writeByte(i); // Slot
+ charInfo.writeString(chars[i]->getName());
+ charInfo.writeByte((unsigned char) chars[i]->getGender());
+ charInfo.writeByte(chars[i]->getHairStyle());
+ charInfo.writeByte(chars[i]->getHairColor());
+ charInfo.writeByte(chars[i]->getLevel());
+ charInfo.writeShort(chars[i]->getMoney());
+ for (int j = 0; j < NB_RSTAT; ++j)
+ charInfo.writeShort(chars[i]->getRawStat(j));
+ computer.send(charInfo);
+ }
+ return;
+ }
+ }
+
+ computer.send(reply);
+}
+
+void
+AccountHandler::handleLogoutMessage(AccountClient &computer, MessageIn &msg)
+{
+ MessageOut reply(APMSG_LOGOUT_RESPONSE);
+
+ if (computer.getAccount().get() == NULL)
+ {
+ LOG_INFO("Can't logout. Not even logged in.", 1);
+ reply.writeByte(ERRMSG_NO_LOGIN);
+ }
+ else
+ {
+ LOG_INFO(computer.getAccount()->getName() << " logged out.", 1);
+ computer.unsetAccount();
+ reply.writeByte(ERRMSG_OK);
+ }
+
+ computer.send(reply);
+}
+
+void
+AccountHandler::handleRegisterMessage(AccountClient &computer, MessageIn &msg)
+{
+ unsigned long clientVersion = msg.readLong();
+ std::string username = msg.readString();
+ std::string password = msg.readString();
+ std::string email = msg.readString();
+
+ LOG_INFO(username << " is trying to register.", 1);
+
+ MessageOut reply(APMSG_REGISTER_RESPONSE);
+
+ if (clientVersion < config.getValue("clientVersion", 0))
+ {
+ LOG_INFO("Client has an unsufficient version number to login.", 1);
+ reply.writeByte(REGISTER_INVALID_VERSION);
+ }
+ else if (stringFilter->findDoubleQuotes(username))
+ {
+ LOG_INFO(username << ": has got double quotes in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if (stringFilter->findDoubleQuotes(email))
+ {
+ LOG_INFO(email << ": has got double quotes in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if ((username.length() < MIN_LOGIN_LENGTH) ||
+ (username.length() > MAX_LOGIN_LENGTH))
+ {
+ LOG_INFO(username << ": Username too short or too long.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if ((password.length() < MIN_PASSWORD_LENGTH) ||
+ (password.length() > MAX_PASSWORD_LENGTH))
+ {
+ LOG_INFO(email << ": Password too short or too long.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if (!stringFilter->isEmailValid(email))
+ {
+ LOG_INFO(email << ": Email Invalid, only a@b.c format is accepted.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ // Checking if the Name is slang's free.
+ else if (!stringFilter->filterContent(username))
+ {
+ LOG_INFO(username << ": has got bad words in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else
+ {
+ Storage &store = Storage::instance("tmw");
+ AccountPtr accPtr = store.getAccount(username);
+
+ // Check whether the account already exists.
+ if (accPtr.get())
+ {
+ LOG_INFO(username << ": Username already exists.", 1);
+ reply.writeByte(REGISTER_EXISTS_USERNAME);
+ }
+ // Find out whether the email is already in use.
+ else if (store.doesEmailAddressExist(email))
+ {
+ LOG_INFO(email << ": Email already exists.", 1);
+ reply.writeByte(REGISTER_EXISTS_EMAIL);
+ }
+ else
+ {
+ AccountPtr acc(new Account(username, password, email));
+ store.addAccount(acc);
+ LOG_INFO(username << ": Account registered.", 1);
+
+ reply.writeByte(ERRMSG_OK);
+ }
+ }
+
+ computer.send(reply);
+}
+
+void
+AccountHandler::handleUnregisterMessage(AccountClient &computer,
+ MessageIn &msg)
+{
+ std::string username = msg.readString();
+ std::string password = msg.readString();
+
+ LOG_INFO(username << " wants to be deleted from our accounts.", 1);
+
+ MessageOut reply(APMSG_UNREGISTER_RESPONSE);
+
+ if (stringFilter->findDoubleQuotes(username))
+ {
+ LOG_INFO(username << ": has got double quotes in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else
+ {
+ // See if the account exists
+ Storage &store = Storage::instance("tmw");
+ AccountPtr accPtr = store.getAccount(username);
+
+ if (!accPtr.get() || accPtr->getPassword() != password)
+ {
+ LOG_INFO("Account does not exist or bad password for "
+ << username << ".", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else
+ {
+ // If the account to delete is the current account we're logged in.
+ // Get out of it in memory.
+ if (computer.getAccount().get() != NULL )
+ {
+ if (computer.getAccount()->getName() == username)
+ {
+ computer.unsetAccount();
+ }
+ }
+
+ // Delete account and associated characters
+ LOG_INFO("Farewell " << username << " ...", 1);
+ store.delAccount(accPtr);
+ reply.writeByte(ERRMSG_OK);
+ }
+ }
+
+ computer.send(reply);
+}
+
+void
+AccountHandler::handlePasswordChangeMessage(AccountClient &computer,
+ MessageIn &msg)
+{
+ std::string oldPassword = msg.readString();
+ std::string newPassword = msg.readString();
+
+ MessageOut reply(APMSG_PASSWORD_CHANGE_RESPONSE);
+
+ if (computer.getAccount().get() == NULL)
+ {
+ LOG_INFO("Not logged in. Can't change your Account's Password.", 1);
+ reply.writeByte(ERRMSG_NO_LOGIN);
+ }
+ else if (newPassword.length() < MIN_PASSWORD_LENGTH ||
+ newPassword.length() > MAX_PASSWORD_LENGTH)
+ {
+ LOG_INFO(computer.getAccount()->getName() <<
+ ": New password too long or too short.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if (stringFilter->findDoubleQuotes(newPassword))
+ {
+ LOG_INFO(newPassword << ": has got double quotes in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if (oldPassword != computer.getAccount()->getPassword())
+ {
+ LOG_INFO(computer.getAccount()->getName() <<
+ ": Old password is wrong.", 1);
+ reply.writeByte(ERRMSG_FAILURE);
+ }
+ else
+ {
+ LOG_INFO(computer.getAccount()->getName() <<
+ ": The password was changed.", 1);
+ computer.getAccount()->setPassword(newPassword);
+ reply.writeByte(ERRMSG_OK);
+ }
+
+ computer.send(reply);
+}
+
+void
+AccountHandler::handleCharacterCreateMessage(AccountClient &computer,
+ MessageIn &msg)
+{
+ std::string name = msg.readString();
+ char hairStyle = msg.readByte();
+ char hairColor = msg.readByte();
+ Gender gender = (Gender) msg.readByte();
+
+ MessageOut reply(APMSG_CHAR_CREATE_RESPONSE);
+
+ if (computer.getAccount().get() == NULL) {
+ LOG_INFO("Not logged in. Can't create a Character.", 1);
+ reply.writeByte(ERRMSG_NO_LOGIN);
+ }
+ else if (!stringFilter->filterContent(name))
+ {
+ LOG_INFO(name << ": Character has got bad words in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if (stringFilter->findDoubleQuotes(name))
+ {
+ LOG_INFO(name << ": has got double quotes in it.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else if ((hairStyle < 0) || (hairStyle > (signed) MAX_HAIRSTYLE_VALUE))
+ {
+ LOG_INFO(name << ": Character's hair Style is invalid.", 1);
+ reply.writeByte(CREATE_INVALID_HAIRSTYLE);
+ }
+ else if ((hairColor < 0) || (hairColor > (signed) MAX_HAIRCOLOR_VALUE))
+ {
+ LOG_INFO(name << ": Character's hair Color is invalid.", 1);
+ reply.writeByte(CREATE_INVALID_HAIRCOLOR);
+ }
+ else if ((gender < 0) || (gender > (signed) MAX_GENDER_VALUE))
+ {
+ LOG_INFO(name << ": Character's gender is invalid.", 1);
+ reply.writeByte(CREATE_INVALID_GENDER);
+ }
+ else if ((name.length() < MIN_CHARACTER_LENGTH) ||
+ (name.length() > MAX_CHARACTER_LENGTH))
+ {
+ LOG_INFO(name << ": Character's name too short or too long.", 1);
+ reply.writeByte(ERRMSG_INVALID_ARGUMENT);
+ }
+ else
+ {
+ Storage &store = Storage::instance("tmw");
+ if (store.doesCharacterNameExist(name))
+ {
+ LOG_INFO(name << ": Character's name already exists.", 1);
+ reply.writeByte(CREATE_EXISTS_NAME);
+ computer.send(reply);
+ return;
+ }
+
+ // A player shouldn't have more than MAX_OF_CHARACTERS characters.
+ Players &chars = computer.getAccount()->getCharacters();
+ if (chars.size() >= MAX_OF_CHARACTERS)
+ {
+ LOG_INFO("Already has " << chars.size()
+ << " characters. Can't create another Character.", 1);
+ reply.writeByte(CREATE_TOO_MUCH_CHARACTERS);
+ computer.send(reply);
+ return;
+ }
+
+ // LATER_ON: Add race, face and maybe special attributes.
+
+ // Customization of player's stats...
+ RawStatistics rawStats;
+ for (int i = 0; i < NB_RSTAT; ++i)
+ rawStats.stats[i] = msg.readShort();
+
+ // We see if the difference between the lowest stat and the highest
+ // isn't too big.
+ unsigned short lowestStat = 0;
+ unsigned short highestStat = 0;
+ unsigned int totalStats = 0;
+ bool validNonZeroRawStats = true;
+ for (int i = 0; i < NB_RSTAT; ++i)
+ {
+ unsigned short stat = rawStats.stats[i];
+ // For good total stat check.
+ totalStats = totalStats + stat;
+
+ // For checking if all stats are at least > 0
+ if (stat <= 0) validNonZeroRawStats = false;
+ if (lowestStat == 0 || lowestStat > stat) lowestStat = stat;
+ if (highestStat == 0 || highestStat < stat) highestStat = stat;
+ }
+
+ if (totalStats > POINTS_TO_DISTRIBUTES_AT_LVL1)
+ {
+ LOG_INFO(name << ": Character's stats are too high to be of "
+ "level 1.", 1);
+ reply.writeByte(CREATE_RAW_STATS_TOO_HIGH);
+ }
+ else if (totalStats < POINTS_TO_DISTRIBUTES_AT_LVL1)
+ {
+ LOG_INFO(name << ": Character's stats are too low to be of "
+ "level 1.", 1);
+ reply.writeByte(CREATE_RAW_STATS_TOO_LOW);
+ }
+ else if ((highestStat - lowestStat) > (signed) MAX_DIFF_BETWEEN_STATS)
+ {
+ LOG_INFO(name << ": Character's stats difference is too high to "
+ "be accepted.", 1);
+ reply.writeByte(CREATE_RAW_STATS_INVALID_DIFF);
+ }
+ else if (!validNonZeroRawStats)
+ {
+ LOG_INFO(name << ": One stat is equal to zero.", 1);
+ reply.writeByte(CREATE_RAW_STATS_EQUAL_TO_ZERO);
+ }
+ else
+ {
+ PlayerPtr newCharacter(new Player(name));
+ for (int i = 0; i < NB_RSTAT; ++i)
+ newCharacter->setRawStat(i, rawStats.stats[i]);
+ newCharacter->setMoney(0);
+ newCharacter->setLevel(1);
+ newCharacter->setGender(gender);
+ newCharacter->setHairStyle(hairStyle);
+ newCharacter->setHairColor(hairColor);
+ newCharacter->setMapId((int) config.getValue("defaultMap", 1));
+ Point startingPos = { (int) config.getValue("startX", 0),
+ (int) config.getValue("startY", 0) };
+ newCharacter->setPosition(startingPos);
+ computer.getAccount()->addCharacter(newCharacter);
+
+ LOG_INFO("Character " << name << " was created for "
+ << computer.getAccount()->getName() << "'s account.", 1);
+
+ store.flush(computer.getAccount()); // flush changes
+ reply.writeByte(ERRMSG_OK);
+ computer.send(reply);
+
+ // Send new characters infos back to client
+ MessageOut charInfo(APMSG_CHAR_INFO);
+ int slot = chars.size() - 1;
+ charInfo.writeByte(slot);
+ charInfo.writeString(chars[slot]->getName());
+ charInfo.writeByte((unsigned char) chars[slot]->getGender());
+ charInfo.writeByte(chars[slot]->getHairStyle());
+ charInfo.writeByte(chars[slot]->getHairColor());
+ charInfo.writeByte(chars[slot]->getLevel());
+ charInfo.writeShort(chars[slot]->getMoney());
+ for (int j = 0; j < NB_RSTAT; ++j)
+ charInfo.writeShort(chars[slot]->getRawStat(j));
+ computer.send(charInfo);
+ return;
+ }
+ }
+
+ computer.send(reply);
+}
diff --git a/src/account-server/accounthandler.hpp b/src/account-server/accounthandler.hpp
new file mode 100644
index 00000000..2d75fbf5
--- /dev/null
+++ b/src/account-server/accounthandler.hpp
@@ -0,0 +1,82 @@
+/*
+ * 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$
+ */
+
+#ifndef _TMWSERV_ACCOUNTHANDLER_H_
+#define _TMWSERV_ACCOUNTHANDLER_H_
+
+#include "net/connectionhandler.hpp"
+
+class AccountClient;
+
+/**
+ * Manages the data stored in user accounts and provides a reliable interface
+ * for working with an account. The account handler class can be used as a link
+ * to a working account handle, and can be assigned to a user persistently as
+ * an interface between the computer and account. (Messages from the user can
+ * be traced to this account through the NetComputer structure, then processed
+ * here with the persistent stored data).
+ */
+class AccountHandler : public ConnectionHandler
+{
+ public:
+ /**
+ * Start the handler
+ */
+ bool
+ startListen(enet_uint16 port);
+
+ protected:
+ /**
+ * Process account related messages.
+ */
+ void
+ processMessage(NetComputer *computer, MessageIn &message);
+
+ NetComputer*
+ computerConnected(ENetPeer *peer);
+
+ void
+ computerDisconnected(NetComputer *comp);
+
+ // --- message handling ---
+
+ void
+ handleLoginMessage(AccountClient &computer, MessageIn &msg);
+
+ void
+ handleLogoutMessage(AccountClient &computer, MessageIn &msg);
+
+ void
+ handleRegisterMessage(AccountClient &computer, MessageIn &msg);
+
+ void
+ handleUnregisterMessage(AccountClient &computer, MessageIn &msg);
+
+ void
+ handlePasswordChangeMessage(AccountClient &computer, MessageIn &msg);
+
+ void
+ handleCharacterCreateMessage(AccountClient &computer, MessageIn &msg);
+};
+
+#endif
diff --git a/src/account-server/dalstorage.cpp b/src/account-server/dalstorage.cpp
new file mode 100644
index 00000000..95d6c019
--- /dev/null
+++ b/src/account-server/dalstorage.cpp
@@ -0,0 +1,742 @@
+/*
+ * 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 <cassert>
+
+#include "configuration.h"
+#include "point.h"
+#include "account-server/dalstorage.hpp"
+#include "account-server/dalstoragesql.hpp"
+#include "dal/dalexcept.h"
+#include "dal/dataproviderfactory.h"
+#include "utils/cipher.h"
+#include "utils/functors.h"
+#include "utils/logger.h"
+
+/**
+ * Functor used to search an Account by name in Accounts.
+ */
+class account_by_name
+{
+ public:
+ account_by_name(const std::string& name)
+ : mName(name)
+ {}
+
+ bool operator()(std::pair<unsigned, AccountPtr> const &elem) const
+ { return elem.second->getName() == mName; }
+
+ private:
+ std::string mName; /**< the name to look for */
+};
+
+
+/**
+ * Constructor.
+ */
+DALStorage::DALStorage()
+ : mDb(dal::DataProviderFactory::createDataProvider())
+{
+ // the connection to the database will be made on the first request
+ // to the database.
+}
+
+
+/**
+ * Destructor.
+ */
+DALStorage::~DALStorage()
+ throw()
+{
+ if (mDb->isConnected()) {
+ close();
+ }
+
+ // mAccounts and mCharacters contain smart pointers that will deallocate
+ // the memory so nothing else to do here :)
+}
+
+
+/**
+ * Connect to the database and initialize it if necessary.
+ */
+void
+DALStorage::open(void)
+{
+ // Do nothing if already connected.
+ if (mDb->isConnected()) {
+ return;
+ }
+
+ using namespace dal;
+
+ static bool dbFileShown = false;
+ std::string dbFile(getName());
+ try {
+ // open a connection to the database.
+#if defined (MYSQL_SUPPORT) || defined (POSTGRESQL_SUPPORT)
+ mDb->connect(getName(), getUser(), getPassword());
+ if (!dbFileShown)
+ {
+ LOG_INFO("Using " << dbFile << " as Database Name.", 0);
+ dbFileShown = true;
+ }
+#elif defined (SQLITE_SUPPORT)
+ // create the database file name.
+ dbFile += ".db";
+ mDb->connect(dbFile, "", "");
+ if (!dbFileShown)
+ {
+ LOG_INFO("SQLite uses ./" << dbFile << " as DB.", 0);
+ dbFileShown = true;
+ }
+#endif
+
+ // ensure that the required tables are created.
+ //
+ // strategy1: find a way to obtain the list of tables from the
+ // underlying database and create the tables that are
+ // missing.
+ //
+ // strategy2: try to create the tables and check the exceptions
+ // thrown.
+ //
+ // comments:
+ // - strategy1 is easy to achieve if we are using MysQL as
+ // executing the request "show tables;" returns the list of
+ // tables. However, there is not such a query for SQLite3.
+ // When using SQLite3 from the interactive shell or the
+ // command line, the command ".tables" returns the list of
+ // tables but sqlite3_exec() does not validate this statement
+ // and fails.
+ // The cost of this strategy is:
+ // (num. tables to create + 1) queries at most and
+ // 1 at minimum.
+ //
+ // - strategy2 will work with probably most databases.
+ // The cost of this strategy is:
+ // (num. tables to create) queries.
+
+ // we will stick with strategy2 for the moment as we are focusing
+ // on SQLite.
+
+ // FIXME: The tables should be checked/created at startup in order to avoid
+ // a DbSqlQueryExecFailure assert on sqlite while registering.
+ // Also, this would initialize connection to the database earlier in memory.
+
+ createTable(ACCOUNTS_TBL_NAME, SQL_ACCOUNTS_TABLE);
+ createTable(CHARACTERS_TBL_NAME, SQL_CHARACTERS_TABLE);
+ createTable(ITEMS_TBL_NAME, SQL_ITEMS_TABLE);
+ createTable(WORLD_ITEMS_TBL_NAME, SQL_WORLD_ITEMS_TABLE);
+ createTable(INVENTORIES_TBL_NAME, SQL_INVENTORIES_TABLE);
+ createTable(CHANNELS_TBL_NAME, SQL_CHANNELS_TABLE);
+ }
+ catch (const DbConnectionFailure& e) {
+ LOG_ERROR("unable to connect to the database: " << e.what(), 0);
+ }
+ catch (const DbSqlQueryExecFailure& e) {
+ LOG_ERROR("SQL query failure: " << e.what(), 0);
+ }
+
+ mIsOpen = mDb->isConnected();
+}
+
+
+/**
+ * Disconnect from the database.
+ */
+void
+DALStorage::close(void)
+{
+ mDb->disconnect();
+ mIsOpen = mDb->isConnected();
+}
+
+
+/**
+ * Get an account by user name.
+ */
+AccountPtr
+DALStorage::getAccount(const std::string& userName)
+{
+ // connect to the database (if not connected yet).
+ open();
+
+ // look for the account in the list first.
+ Accounts::iterator it_end = mAccounts.end(),
+ it = std::find_if(mAccounts.begin(), it_end, account_by_name(userName));
+
+ if (it != it_end)
+ return it->second;
+
+ using namespace dal;
+
+ // the account was not in the list, look for it in the database.
+ try {
+ std::string sql("select * from ");
+ sql += ACCOUNTS_TBL_NAME;
+ sql += " where username = \"";
+ sql += userName;
+ sql += "\";";
+ const RecordSet& accountInfo = mDb->execSql(sql);
+
+ // if the account is not even in the database then
+ // we have no choice but to return nothing.
+ if (accountInfo.isEmpty()) {
+ return AccountPtr(NULL);
+ }
+
+ // specialize the string_to functor to convert
+ // a string to an unsigned int.
+ string_to<unsigned short> toUint;
+ unsigned id = toUint(accountInfo(0, 0));
+
+ // create an Account instance
+ // and initialize it with information about the user.
+ AccountPtr account(new Account(accountInfo(0, 1),
+ accountInfo(0, 2),
+ accountInfo(0, 3), id));
+
+ // specialize the string_to functor to convert
+ // a string to an unsigned short.
+ string_to<unsigned short> toUshort;
+
+ mAccounts.insert(std::make_pair(id, account));
+
+ // load the characters associated with the account.
+ sql = "select * from ";
+ sql += CHARACTERS_TBL_NAME;
+ sql += " where user_id = '";
+ sql += accountInfo(0, 0);
+ sql += "';";
+ const RecordSet& charInfo = mDb->execSql(sql);
+
+ if (!charInfo.isEmpty()) {
+ Players players;
+
+ LOG_INFO(userName << "'s account has " << charInfo.rows()
+ << " character(s) in database.", 1);
+
+ // As the recordset functions are set to be able to get one
+ // recordset at a time, we store charInfo in a temp array of
+ // strings. To avoid the problem where values of charInfo were
+ // erased by the values of mapInfo.
+ std::string strCharInfo[charInfo.rows()][charInfo.cols()];
+ for (unsigned int i = 0; i < charInfo.rows(); ++i)
+ {
+ for (unsigned int j = 0; j < charInfo.cols(); ++j)
+ {
+ strCharInfo[i][j] = charInfo(i,j);
+ }
+ }
+ unsigned int charRows = charInfo.rows();
+
+ for (unsigned int k = 0; k < charRows; ++k) {
+ PlayerPtr player(new Player(strCharInfo[k][2], toUint(strCharInfo[k][0])));
+ player->setGender((Gender) toUshort(strCharInfo[k][3]));
+ player->setHairStyle(toUshort(strCharInfo[k][4]));
+ player->setHairColor(toUshort(strCharInfo[k][5]));
+ player->setLevel(toUshort(strCharInfo[k][6]));
+ player->setMoney(toUint(strCharInfo[k][7]));
+ Point pos = { toUshort(strCharInfo[k][8]),
+ toUshort(strCharInfo[k][9]) };
+ player->setPosition(pos);
+ for (int i = 0; i < NB_RSTAT; ++i)
+ player->setRawStat(i, toUshort(strCharInfo[k][11 + i]));
+
+ unsigned int mapId = toUint(strCharInfo[k][10]);
+ if ( mapId > 0 )
+ {
+ player->setMapId(mapId);
+ }
+ else
+ {
+ // Set player to default map and one of the default location
+ // Default map is to be 1, as not found return value will be 0.
+ player->setMapId((int)config.getValue("defaultMap", 1));
+ }
+
+ mCharacters.push_back(player);
+ players.push_back(player);
+ } // End of for each characters
+
+ account->setCharacters(players);
+ } // End if there are characters.
+
+ return account;
+ }
+ catch (const DbSqlQueryExecFailure& e) {
+ return AccountPtr(NULL); // TODO: Throw exception here
+ }
+}
+
+
+/**
+ * Return the list of all Emails addresses.
+ */
+std::list<std::string>
+DALStorage::getEmailList()
+{
+ // If not opened already
+ open();
+
+ std::list <std::string> emailList;
+
+ try {
+ std::string sql("select email from ");
+ sql += ACCOUNTS_TBL_NAME;
+ sql += ";";
+ const dal::RecordSet& accountInfo = mDb->execSql(sql);
+
+ // if the account is not even in the database then
+ // we have no choice but to return nothing.
+ if (accountInfo.isEmpty()) {
+ return emailList;
+ }
+ for (unsigned int i = 0; i < accountInfo.rows(); i++)
+ {
+ // We add all these addresses to the list
+ emailList.push_front(accountInfo(i, 0));
+ }
+ }
+ catch (const dal::DbSqlQueryExecFailure& e) {
+ // TODO: throw an exception.
+ LOG_ERROR("SQL query failure: " << e.what(), 0);
+ }
+
+ return emailList;
+}
+
+/**
+ * Tells if the email address already exists
+ * @return true if the email address exists.
+ */
+bool DALStorage::doesEmailAddressExist(std::string const &email)
+{
+ // If not opened already
+ open();
+
+ try {
+ std::ostringstream sql;
+ sql << "select count(email) from " << ACCOUNTS_TBL_NAME
+ << " where upper(email) = upper(\"" << email << "\");";
+ dal::RecordSet const &accountInfo = mDb->execSql(sql.str());
+
+ std::istringstream ssStream(accountInfo(0, 0));
+ unsigned int iReturn = 1;
+ ssStream >> iReturn;
+ return iReturn != 0;
+ } catch (std::exception const &e) {
+ // TODO: throw an exception.
+ LOG_ERROR("SQL query failure: " << e.what(), 0);
+ }
+
+ return true;
+}
+
+/**
+ * Tells if the character's name already exists
+ * @return true if character's name exists.
+ */
+bool DALStorage::doesCharacterNameExist(const std::string& name)
+{
+ // If not opened already
+ open();
+
+ try {
+ std::ostringstream sql;
+ sql << "select count(name) from " << CHARACTERS_TBL_NAME
+ << " where name = \"" << name << "\";";
+ dal::RecordSet const &accountInfo = mDb->execSql(sql.str());
+
+ std::istringstream ssStream(accountInfo(0, 0));
+ int iReturn = 1;
+ ssStream >> iReturn;
+ return iReturn != 0;
+ } catch (std::exception const &e) {
+ // TODO: throw an exception.
+ LOG_ERROR("SQL query failure: " << e.what(), 0);
+ }
+
+ return true;
+}
+
+std::map<short, ChatChannel>
+DALStorage::getChannelList()
+{
+ // If not opened already
+ open();
+
+ // specialize the string_to functor to convert
+ // a string to a short.
+ string_to<short> toShort;
+
+ // The formatted datas
+ std::map<short, ChatChannel> channels;
+
+ try {
+ std::stringstream sql;
+ sql << "select id, name, announcement, password from ";
+ sql << CHANNELS_TBL_NAME;
+ sql << ";";
+
+ const dal::RecordSet& channelInfo = mDb->execSql(sql.str());
+
+ // If the map return is empty then we have no choice but to return false.
+ if (channelInfo.isEmpty()) {
+ return channels;
+ }
+
+ for ( unsigned int i = 0; i < channelInfo.rows(); ++i)
+ {
+ channels.insert(std::make_pair(toShort(channelInfo(i,0)),
+ ChatChannel(channelInfo(i,1),
+ channelInfo(i,2),
+ channelInfo(i,3))));
+
+ LOG_DEBUG("Channel (" << channelInfo(i,0) << ") loaded: " << channelInfo(i,1)
+ << ": " << channelInfo(i,2), 5);
+ }
+
+ return channels;
+ }
+ catch (const dal::DbSqlQueryExecFailure& e) {
+ // TODO: throw an exception.
+ LOG_ERROR("SQL query failure: " << e.what(), 0);
+ }
+
+ return channels;
+}
+
+void
+DALStorage::updateChannels(std::map<short, ChatChannel>& channelList)
+{
+#if defined (SQLITE_SUPPORT)
+ // Reopen the db in this thread for sqlite, to avoid
+ // Library Call out of sequence problem due to thread safe.
+ close();
+#endif
+ open();
+
+ try {
+ // Empties the table
+ std::stringstream sql;
+ sql << "delete from "
+ << CHANNELS_TBL_NAME
+ << ";";
+
+ mDb->execSql(sql.str());
+
+ for (std::map<short, ChatChannel>::iterator i = channelList.begin();
+ i != channelList.end();)
+ {
+ // insert registered channel if id < MAX_PUBLIC_CHANNELS_RANGE;
+ if ( i->first < (signed)MAX_PUBLIC_CHANNELS_RANGE )
+ {
+ if (i->second.getName() != "")
+ {
+ sql.str("");
+ sql << "insert into "
+ << CHANNELS_TBL_NAME
+ << " (id, name, announcement, password)"
+ << " values ("
+ << i->first << ", \""
+ << i->second.getName() << "\", \""
+ << i->second.getAnnouncement() << "\", \""
+ << i->second.getPassword() << "\");";
+
+ LOG_DEBUG("Channel (" << i->first << ") saved: " << i->second.getName()
+ << ": " << i->second.getAnnouncement(), 5);
+ }
+
+ mDb->execSql(sql.str());
+ }
+ ++i;
+ }
+
+ }
+ catch (const dal::DbSqlQueryExecFailure& e) {
+ // TODO: throw an exception.
+ LOG_ERROR("SQL query failure: " << e.what(), 0);
+ }
+}
+
+
+/**
+ * Create the specified table.
+ */
+void
+DALStorage::createTable(const std::string& tblName,
+ const std::string& sql)
+{
+ try {
+ mDb->execSql(sql);
+ }
+ catch (const dal::DbSqlQueryExecFailure& e) {
+ // error message to check against.
+#if defined (MYSQL_SUPPORT)
+ std::string alreadyExists("Table '");
+ alreadyExists += tblName;
+ alreadyExists += "' already exists";
+#elif defined (POSTGRESQL_SUPPORT)
+ std::string alreadyExists("table ");
+ alreadyExists += tblName;
+ alreadyExists += " already exists";
+#else // SQLITE_SUPPORT
+ std::string alreadyExists("table ");
+ alreadyExists += tblName;
+ alreadyExists += " already exists";
+#endif
+
+ const std::string msg(e.what());
+
+ // oops, another problem occurred.
+ if (msg != alreadyExists) {
+ // rethrow to let other error handlers manage the problem.
+ throw;
+ }
+ }
+}
+
+
+/**
+ * Add an account to the database.
+ */
+void DALStorage::addAccount(AccountPtr const &account)
+{
+ assert(account->getCharacters().size() == 0);
+
+ using namespace dal;
+
+ // TODO: we should start a transaction here so that in case of problem
+ // the lost of data would be minimized.
+
+ // insert the account.
+ std::ostringstream sql1;
+ sql1 << "insert into " << ACCOUNTS_TBL_NAME
+ << " (username, password, email, level, banned)"
+ << " values (\""
+ << account->getName() << "\", \""
+ << account->getPassword() << "\", \""
+ << account->getEmail() << "\", "
+ << account->getLevel() << ", 0);";
+ mDb->execSql(sql1.str());
+
+ // get the account id.
+ std::ostringstream sql2;
+ sql2 << "select id from " << ACCOUNTS_TBL_NAME
+ << " where username = \"" << account->getName() << "\";";
+ const RecordSet& accountInfo = mDb->execSql(sql2.str());
+ string_to<unsigned int> toUint;
+ unsigned id = toUint(accountInfo(0, 0));
+ account->setID(id);
+ mAccounts.insert(std::make_pair(id, account));
+}
+
+/**
+ * Update all the accounts from the database.
+ */
+void DALStorage::flushAll()
+{
+ for (Accounts::iterator i = mAccounts.begin(),
+ i_end = mAccounts.end(); i != i_end; ++i)
+ flush(i->second);
+}
+
+/**
+ * Update an account from the database.
+ */
+void DALStorage::flush(AccountPtr const &account)
+{
+ assert(account->getID() >= 0);
+
+ using namespace dal;
+
+ // TODO: we should start a transaction here so that in case of problem
+ // the loss of data would be minimized.
+
+ // update the account.
+ std::ostringstream sql1;
+ sql1 << "update " << ACCOUNTS_TBL_NAME
+ << " set username = \"" << account->getName() << "\", "
+ << "password = \"" << account->getPassword() << "\", "
+ << "email = \"" << account->getEmail() << "\", "
+ << "level = '" << account->getLevel() << "' "
+ << "where id = '" << account->getID() << "';";
+ mDb->execSql(sql1.str());
+
+ // get the list of characters that belong to this account.
+ Players &characters = account->getCharacters();
+
+ // insert or update the characters.
+ for (Players::const_iterator it = characters.begin(),
+ it_end = characters.end(); it != it_end; ++it) {
+
+ std::ostringstream sql3;
+ if ((*it)->getDatabaseID() < 0) {
+ // insert the character
+ sql3 << "insert into " << CHARACTERS_TBL_NAME
+ << " (user_id, name, gender, hair_style, hair_color, level, money,"
+ " x, y, map_id, str, agi, vit, int, dex, luck) values ("
+ << account->getID() << ", \""
+ << (*it)->getName() << "\", "
+ << (*it)->getGender() << ", "
+ << (int)(*it)->getHairStyle() << ", "
+ << (int)(*it)->getHairColor() << ", "
+ << (int)(*it)->getLevel() << ", "
+ << (*it)->getMoney() << ", "
+ << (*it)->getPosition().x << ", "
+ << (*it)->getPosition().y << ", "
+ << (*it)->getMapId() << ", "
+ << (*it)->getRawStat(STAT_STRENGTH) << ", "
+ << (*it)->getRawStat(STAT_AGILITY) << ", "
+ << (*it)->getRawStat(STAT_VITALITY) << ", "
+ << (*it)->getRawStat(STAT_INTELLIGENCE) << ", "
+ << (*it)->getRawStat(STAT_DEXTERITY) << ", "
+ << (*it)->getRawStat(STAT_LUCK) << ");";
+
+ // get the character id
+ std::ostringstream sql2;
+ sql2 << "select id from " << CHARACTERS_TBL_NAME
+ << " where name = \"" << (*it)->getName() << "\";";
+ RecordSet const &charInfo = mDb->execSql(sql2.str());
+ if (charInfo.isEmpty()) {
+ // FIXME: this does not make any sense to me -- silene
+ (*it)->setDatabaseID(1);
+ }
+ else
+ {
+ string_to<unsigned int> toUint;
+ (*it)->setDatabaseID(toUint(charInfo(0, 0)));
+ }
+ } else {
+ sql3 << "update " << CHARACTERS_TBL_NAME
+ << " set name = \"" << (*it)->getName() << "\", "
+ << " gender = " << (*it)->getGender() << ", "
+ << " hair_style = " << (int)(*it)->getHairStyle() << ", "
+ << " hair_color = " << (int)(*it)->getHairColor() << ", "
+ << " level = " << (int)(*it)->getLevel() << ", "
+ << " money = " << (*it)->getMoney() << ", "
+ << " x = " << (*it)->getPosition().x << ", "
+ << " y = " << (*it)->getPosition().y << ", "
+ << " map_id = " << (*it)->getMapId() << ", "
+ << " str = " << (*it)->getRawStat(STAT_STRENGTH) << ", "
+ << " agi = " << (*it)->getRawStat(STAT_AGILITY) << ", "
+ << " vit = " << (*it)->getRawStat(STAT_VITALITY) << ", "
+#if defined(MYSQL_SUPPORT) || defined(POSTGRESQL_SUPPORT)
+ << " `int` = " << (*it)->getRawStat(STAT_INTELLIGENCE) << ", "
+#else
+ << " int = " << (*it)->getRawStat(STAT_INTELLIGENCE) << ", "
+#endif
+ << " dex = " << (*it)->getRawStat(STAT_DEXTERITY) << ", "
+ << " luck = " << (*it)->getRawStat(STAT_LUCK)
+ << " where id = " << (*it)->getDatabaseID() << ";";
+ }
+ mDb->execSql(sql3.str());
+
+ // TODO: inventories.
+ }
+
+ // Existing characters in memory have been inserted or updated in database.
+ // Now, let's remove those who are no more in memory from database.
+
+ // specialize the string_to functor to convert
+ // a string to an unsigned int.
+ string_to<unsigned short> toUint;
+
+ std::ostringstream sql4;
+ sql4 << "select name, id from " << CHARACTERS_TBL_NAME
+ << " where user_id = '" << account->getID() << "';";
+ const RecordSet& charInMemInfo = mDb->execSql(sql4.str());
+
+ // We compare chars from memory and those existing in db,
+ // And delete those not in mem but existing in db.
+ bool charFound;
+ for ( unsigned int i = 0; i < charInMemInfo.rows(); ++i) // in database
+ {
+ charFound = false;
+ for (Players::const_iterator it = characters.begin(),
+ it_end = characters.end(); it != it_end; ++it) // In memory
+ {
+ if ( charInMemInfo(i, 0) == (*it)->getName() )
+ {
+ charFound = true;
+ break;
+ }
+ }
+ if ( !charFound )
+ {
+ // The char is db but not in memory,
+ // It will be removed from database.
+ // We store the id of the char to delete
+ // Because as deleted, the RecordSet is also emptied
+ // That creates an error.
+ unsigned int charId = toUint(charInMemInfo(i, 1));
+
+ // delete the inventory.
+ std::ostringstream sql5;
+ sql5 << "delete from ";
+ sql5 << INVENTORIES_TBL_NAME;
+ sql5 << " where owner_id = '";
+ sql5 << charId;
+ sql5 << "';";
+ mDb->execSql(sql5.str());
+
+ // now delete the character.
+ std::ostringstream sql6;
+ sql6 << "delete from ";
+ sql6 << CHARACTERS_TBL_NAME;
+ sql6 << " where id = '";
+ sql6 << charId;
+ sql6 << "';";
+ mDb->execSql(sql6.str());
+ }
+ }
+}
+
+
+/**
+ * Delete an account and its associated data from the database.
+ */
+void DALStorage::delAccount(AccountPtr const &account)
+{
+ using namespace dal;
+
+ account->setCharacters(Players());
+ flush(account);
+ mAccounts.erase(account->getID());
+
+ // delete the account.
+ std::ostringstream sql;
+ sql << "delete from " << ACCOUNTS_TBL_NAME
+ << " where id = '" << account->getID() << "';";
+ mDb->execSql(sql.str());
+}
+
+/**
+ * Unload an account from memory.
+ */
+void DALStorage::unloadAccount(AccountPtr const &account)
+{
+ flush(account);
+ mAccounts.erase(account->getID());
+}
diff --git a/src/account-server/dalstorage.hpp b/src/account-server/dalstorage.hpp
new file mode 100644
index 00000000..22c6c6e8
--- /dev/null
+++ b/src/account-server/dalstorage.hpp
@@ -0,0 +1,177 @@
+/*
+ * 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$
+ */
+
+
+#ifndef _TMWSERV_DALSTORAGE_H_
+#define _TMWSERV_DALSTORAGE_H_
+
+#include "account-server/storage.hpp"
+#include "dal/dataprovider.h"
+
+/**
+ * A storage class that relies on DAL.
+ *
+ * Notes:
+ * - this class cannot be instanciated nor duplicated in order to force
+ * a user class to use the Storage singleton.
+ */
+class DALStorage: public Storage
+{
+ // friend so that Storage can call the constructor.
+ friend class Storage;
+
+
+ public:
+ /**
+ * Connect to the database and initialize it if necessary.
+ */
+ void
+ open(void);
+
+
+ /**
+ * Disconnect from the database.
+ */
+ void
+ close(void);
+
+
+ /**
+ * Get an account by user name.
+ *
+ * @param userName the owner of the account.
+ *
+ * @return the account associated to the user name.
+ */
+ AccountPtr
+ getAccount(const std::string& userName);
+
+
+ /**
+ * Add a new account.
+ *
+ * @param account the new account.
+ */
+ void
+ addAccount(const AccountPtr& account);
+
+
+ /**
+ * Delete an account.
+ *
+ * @param account the account to delete.
+ */
+ void delAccount(AccountPtr const &account);
+
+ /**
+ * Flush and unload an account.
+ *
+ * @param account the account to unload.
+ */
+ void unloadAccount(AccountPtr const &account);
+
+ /**
+ * Get the list of Emails in the accounts list.
+ * @return the list of Email's Addresses.
+ */
+ std::list<std::string>
+ getEmailList();
+
+ /**
+ * Tells if the email address already exists.
+ * @return true if the email address exists.
+ */
+ bool doesEmailAddressExist(std::string const &email);
+
+ /**
+ * Tells if the character's name already exists
+ * @return true if character's name exists.
+ */
+ bool doesCharacterNameExist(std::string const &name);
+
+ /**
+ * Gives the list of opened public channels registered in database
+ * @return a map of the public channels
+ */
+ std::map<short, ChatChannel>
+ getChannelList();
+
+ /**
+ * apply channel differences from the list in memory
+ * to the one in db.
+ */
+ void
+ updateChannels(std::map<short, ChatChannel>& channelList);
+
+ /**
+ * Save changes to the database permanently.
+ *
+ * @exception tmwserv::dal::DbSqlQueryExecFailure.
+ */
+ void flushAll();
+ void flush(AccountPtr const &);
+
+ private:
+ /**
+ * Constructor.
+ */
+ DALStorage(void);
+
+
+ /**
+ * Destructor.
+ */
+ ~DALStorage(void)
+ throw();
+
+
+ /**
+ * Copy constructor.
+ */
+ DALStorage(const DALStorage& rhs);
+
+
+ /**
+ * Assignment operator.
+ */
+ DALStorage&
+ operator=(const DALStorage& rhs);
+
+
+ /**
+ * Create the specified table.
+ *
+ * @param tblName the table name.
+ * @param sql the SQL query to execute.
+ *
+ * @exception tmwserv::dal::DbSqlQueryExecFailure.
+ */
+ void
+ createTable(const std::string& tblName,
+ const std::string& sql);
+
+
+ private:
+ std::auto_ptr<dal::DataProvider> mDb; /**< the data provider */
+};
+
+#endif // _TMWSERV_DALSTORAGE_H_
diff --git a/src/account-server/dalstoragesql.hpp b/src/account-server/dalstoragesql.hpp
new file mode 100644
index 00000000..391b5572
--- /dev/null
+++ b/src/account-server/dalstoragesql.hpp
@@ -0,0 +1,334 @@
+/*
+ * 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$
+ */
+
+
+#ifndef _TMWSERV_DALSTORAGE_SQL_H_
+#define _TMWSERV_DALSTORAGE_SQL_H_
+
+
+#if !defined (MYSQL_SUPPORT) && !defined (SQLITE_SUPPORT) && \
+ !defined (POSTGRESQL_SUPPORT)
+
+#error "(dalstorage.h) no database backend defined"
+#endif
+
+
+#include <string>
+
+// TODO: Fix problem with PostgreSQL null primary key's.
+
+/**
+ * MySQL specificities:
+ * - TINYINT is an integer (1 byte) type defined as an extension to
+ * the SQL standard.
+ * - all integer types can have an optional (non-standard) attribute
+ * UNSIGNED (http://dev.mysql.com/doc/mysql/en/numeric-types.html)
+ *
+ * SQLite3 specificities:
+ * - any column (but only one for each table) with the exact type of
+ * 'INTEGER PRIMARY KEY' is taken as auto-increment.
+ * - the supported data types are: NULL, INTEGER, REAL, TEXT and BLOB
+ * (http://www.sqlite.org/datatype3.html)
+ * - the size of TEXT cannot be set, it is just ignored by the engine.
+ * - IMPORTANT: foreign key constraints are not yet supported
+ * (http://www.sqlite.org/omitted.html). Included in case of future
+ * support.
+ *
+ * Notes:
+ * - the SQL queries will take advantage of the most appropriate data
+ * types supported by a particular database engine in order to
+ * optimize the server database size.
+ */
+
+
+namespace {
+
+
+/**
+ * TABLE: tmw_accounts.
+ *
+ * Notes:
+ * - the user levels are:
+ * 0: normal user
+ * 1: moderator (has medium level rights)
+ * 2: administrator (i am god :))
+ * - the 'banned' field contains the UNIX time of unban (default = 0)
+ */
+const std::string ACCOUNTS_TBL_NAME("tmw_accounts");
+const std::string SQL_ACCOUNTS_TABLE(
+ "CREATE TABLE tmw_accounts ("
+#if defined (MYSQL_SUPPORT)
+ "id INTEGER PRIMARY KEY AUTO_INCREMENT,"
+ "username VARCHAR(32) NOT NULL UNIQUE,"
+ "password VARCHAR(32) NOT NULL,"
+ "email VARCHAR(64) NOT NULL,"
+ "level TINYINT UNSIGNED NOT NULL,"
+ "banned TINYINT UNSIGNED NOT NULL,"
+ "INDEX (id)"
+#elif defined (SQLITE_SUPPORT)
+ "id INTEGER PRIMARY KEY,"
+ "username TEXT NOT NULL UNIQUE,"
+ "password TEXT NOT NULL,"
+ "email TEXT NOT NULL,"
+ "level INTEGER NOT NULL,"
+ "banned INTEGER NOT NULL"
+#elif defined (POSTGRESQL_SUPPORT)
+ "id SERIAL PRIMARY KEY,"
+ "username TEXT NOT NULL UNIQUE,"
+ "password TEXT NOT NULL,"
+ "email TEXT NOT NULL,"
+ "level INTEGER NOT NULL,"
+ "banned INTEGER NOT NULL"
+#endif
+ ");"
+);
+
+
+/**
+ * TABLE: tmw_characters.
+ *
+ * Notes:
+ * - the stats will need to be thought over, as we'll be implementing a
+ * much more elaborate skill based system; we should probably have a
+ * separate table for storing the skill levels.
+ * - gender is 0 for male, 1 for female.
+ */
+const std::string CHARACTERS_TBL_NAME("tmw_characters");
+const std::string SQL_CHARACTERS_TABLE(
+ "CREATE TABLE tmw_characters ("
+#if defined (MYSQL_SUPPORT)
+ "id INTEGER PRIMARY KEY AUTO_INCREMENT,"
+ "user_id INTEGER UNSIGNED NOT NULL,"
+ "name VARCHAR(32) NOT NULL UNIQUE,"
+ // general information about the character
+ "gender TINYINT UNSIGNED NOT NULL,"
+ "hair_style TINYINT UNSIGNED NOT NULL,"
+ "hair_color TINYINT UNSIGNED NOT NULL,"
+ "level TINYINT UNSIGNED NOT NULL,"
+ "money INTEGER UNSIGNED NOT NULL,"
+ // location on the map
+ "x SMALLINT UNSIGNED NOT NULL,"
+ "y SMALLINT UNSIGNED NOT NULL,"
+ "map_id TINYINT NOT NULL,"
+ // stats
+ "str SMALLINT UNSIGNED NOT NULL,"
+ "agi SMALLINT UNSIGNED NOT NULL,"
+ "vit SMALLINT UNSIGNED NOT NULL,"
+ // note: int must be backquoted as it's a MySQL keyword
+ "`int` SMALLINT UNSIGNED NOT NULL,"
+ "dex SMALLINT UNSIGNED NOT NULL,"
+ "luck SMALLINT UNSIGNED NOT NULL,"
+ "FOREIGN KEY (user_id) REFERENCES tmw_accounts(id),"
+ "FOREIGN KEY (map_id) REFERENCES tmw_maps(id),"
+ "INDEX (id)"
+#elif defined (SQLITE_SUPPORT)
+ "id INTEGER PRIMARY KEY,"
+ "user_id INTEGER NOT NULL,"
+ "name TEXT NOT NULL UNIQUE,"
+ // general information about the character
+ "gender INTEGER NOT NULL,"
+ "hair_style INTEGER NOT NULL,"
+ "hair_color INTEGER NOT NULL,"
+ "level INTEGER NOT NULL,"
+ "money INTEGER NOT NULL,"
+ // location on the map
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "map_id INTEGER NOT NULL,"
+ // stats
+ "str INTEGER NOT NULL,"
+ "agi INTEGER NOT NULL,"
+ "vit INTEGER NOT NULL,"
+ "int INTEGER NOT NULL,"
+ "dex INTEGER NOT NULL,"
+ "luck INTEGER NOT NULL,"
+ "FOREIGN KEY (user_id) REFERENCES tmw_accounts(id),"
+ "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
+#elif defined (POSTGRESQL_SUPPORT)
+ "id SERIAL PRIMARY KEY,"
+ "user_id INTEGER NOT NULL,"
+ "name TEXT NOT NULL UNIQUE,"
+ // general information about the character
+ "gender INTEGER NOT NULL,"
+ "hair_style INTEGER NOT NULL,"
+ "hair_color INTEGER NOT NULL,"
+ "level INTEGER NOT NULL,"
+ "money INTEGER NOT NULL,"
+ // location on the map
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "map_id INTEGER NOT NULL,"
+ // stats
+ "str INTEGER NOT NULL,"
+ "agi INTEGER NOT NULL,"
+ "vit INTEGER NOT NULL,"
+ "int INTEGER NOT NULL,"
+ "dex INTEGER NOT NULL,"
+ "luck INTEGER NOT NULL,"
+ "FOREIGN KEY (user_id) REFERENCES tmw_accounts(id),"
+ "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
+#endif
+ ");"
+);
+
+
+/**
+ * TABLE: tmw_items.
+ *
+ * Notes:
+ * - amount: indicates how many items of the same kind can stack.
+ * - state: (optional) item state saved by script.
+ */
+const std::string ITEMS_TBL_NAME("tmw_items");
+const std::string SQL_ITEMS_TABLE(
+ "CREATE TABLE tmw_items ("
+#if defined (MYSQL_SUPPORT)
+ "id SMALLINT PRIMARY KEY AUTO_INCREMENT,"
+ "amount TINYINT UNSIGNED NOT NULL,"
+ "type TINYINT UNSIGNED NOT NULL,"
+ "state TEXT,"
+ "INDEX (id)"
+#elif defined (SQLITE_SUPPORT)
+ "id INTEGER PRIMARY KEY,"
+ "amount INTEGER NOT NULL,"
+ "type INTEGER NOT NULL,"
+ "state TEXT"
+#elif defined (POSTGRESQL_SUPPORT)
+ "id SERIAL PRIMARY KEY,"
+ "amount INTEGER NOT NULL,"
+ "type INTEGER NOT NULL,"
+ "state TEXT"
+#endif
+ ");"
+);
+
+
+/**
+ * TABLE: tmw_world_items.
+ *
+ * Notes:
+ * - store items on the ground in the game world.
+ */
+const std::string WORLD_ITEMS_TBL_NAME("tmw_world_items");
+// NOTE: Problem here with primary key (only one type of item is allowed on the same map at one time).
+const std::string SQL_WORLD_ITEMS_TABLE(
+ "CREATE TABLE tmw_world_items ("
+#if defined (MYSQL_SUPPORT)
+ "id SMALLINT UNSIGNED NOT NULL,"
+ // location on the map
+ "x SMALLINT UNSIGNED NOT NULL,"
+ "y SMALLINT UNSIGNED NOT NULL,"
+ "map_id TINYINT NOT NULL,"
+ // time to die (UNIX time)
+ "deathtime INTEGER UNSIGNED NOT NULL,"
+ "PRIMARY KEY (id, map_id),"
+ "FOREIGN KEY (id) REFERENCES tmw_items(id),"
+ "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
+#elif defined (SQLITE_SUPPORT)
+ "id INTEGER NOT NULL,"
+ // location on the map
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "map_id INTEGER NOT NULL,"
+ // time to die (UNIX time)
+ "deathtime INTEGER NOT NULL,"
+ "PRIMARY KEY (id, map_id),"
+ "FOREIGN KEY (id) REFERENCES tmw_items(id),"
+ "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
+#elif defined (POSTGRESQL_SUPPORT)
+ "id INTEGER NOT NULL,"
+ // location on the map
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
+ "map_id INTEGER NOT NULL,"
+ // time to die (UNIX time)
+ "deathtime INTEGER NOT NULL,"
+ "PRIMARY KEY (id, map_id),"
+ "FOREIGN KEY (id) REFERENCES tmw_items(id),"
+ "FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
+#endif
+ ");"
+);
+
+
+/**
+ * TABLE: tmw_inventories.
+ */
+const std::string INVENTORIES_TBL_NAME("tmw_inventories");
+const std::string SQL_INVENTORIES_TABLE(
+ "CREATE TABLE tmw_inventories ("
+#if defined (MYSQL_SUPPORT)
+ "id SMALLINT NOT NULL,"
+ "owner_id INTEGER NOT NULL,"
+ "amount SMALLINT NOT NULL,"
+ "equipped TINYINT NOT NULL,"
+ "FOREIGN KEY (id) REFERENCES tmw_items(id),"
+ "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)"
+#elif defined (SQLITE_SUPPORT)
+ "id INTEGER NOT NULL,"
+ "owner_id INTEGER NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "equipped INTEGER NOT NULL,"
+ "FOREIGN KEY (id) REFERENCES tmw_items(id),"
+ "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)"
+#elif defined (POSTGRESQL_SUPPORT)
+ "id INTEGER NOT NULL,"
+ "owner_id INTEGER NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "equipped INTEGER NOT NULL,"
+ "FOREIGN KEY (id) REFERENCES tmw_items(id),"
+ "FOREIGN KEY (owner_id) REFERENCES tmw_characters(id)"
+#endif
+ ");"
+);
+
+/**
+ * TABLE: tmw_channels.
+ * Keeps opened public Channel list
+ */
+const std::string CHANNELS_TBL_NAME("tmw_channels");
+const std::string SQL_CHANNELS_TABLE(
+ "CREATE TABLE tmw_channels ("
+#if defined (MYSQL_SUPPORT)
+ "id INTEGER PRIMARY KEY,"
+ "name VARCHAR(32) NOT NULL UNIQUE,"
+ "announcement VARCHAR(256) NOT NULL,"
+ "password VARCHAR(32) NOT NULL"
+#elif defined (SQLITE_SUPPORT)
+ "id INTEGER PRIMARY KEY,"
+ "name TEXT NOT NULL UNIQUE,"
+ "announcement TEXT NOT NULL,"
+ "password TEXT NOT NULL"
+#elif defined (POSTGRESQL_SUPPORT)
+ "id SERIAL PRIMARY KEY,"
+ "name TEXT NOT NULL UNIQUE,"
+ "announcement TEXT NOT NULL,"
+ "password TEXT NOT NULL"
+#endif
+ ");"
+);
+
+
+} // anonymous namespace
+
+
+#endif // _TMWSERV_DALSTORAGE_SQL_H_
diff --git a/src/account-server/main-account.cpp b/src/account-server/main-account.cpp
new file mode 100644
index 00000000..f462ff08
--- /dev/null
+++ b/src/account-server/main-account.cpp
@@ -0,0 +1,327 @@
+/*
+ * 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 <cstdlib>
+#include <getopt.h>
+#include <signal.h>
+#include <iostream>
+#include <physfs.h>
+#include <enet/enet.h>
+
+#if (defined __USE_UNIX98 || defined __FreeBSD__)
+#include "../config.h"
+#elif defined WIN32
+#include "../tmwserv_private.h"
+#define PACKAGE_VERSION PRODUCT_VERSION
+#endif
+
+#include "configuration.h"
+#include "resourcemanager.h"
+#include "skill.h"
+#include "account-server/accounthandler.hpp"
+#include "account-server/serverhandler.hpp"
+#include "account-server/storage.hpp"
+#include "chat-server/chatchannelmanager.hpp"
+#include "chat-server/chathandler.hpp"
+#include "net/connectionhandler.hpp"
+#include "net/messageout.hpp"
+#include "utils/logger.h"
+#include "utils/stringfilter.h"
+#include "utils/timer.h"
+
+// Default options that automake should be able to override.
+#define DEFAULT_LOG_FILE "tmwserv.log"
+#define DEFAULT_CONFIG_FILE "tmwserv.xml"
+#define DEFAULT_ITEMSDB_FILE "items.xml"
+
+utils::Timer worldTimer(100, false); /**< Timer for world tics set to 100 ms */
+int worldTime = 0; /**< Current world time in 100ms ticks */
+bool running = true; /**< Determines if server keeps running */
+
+Skill skillTree("base"); /**< Skill tree */
+
+Configuration config; /**< XML config reader */
+
+utils::StringFilter *stringFilter; /**< Slang's Filter */
+
+/** Account message handler */
+AccountHandler *accountHandler;
+
+/** Communications (chat) message handler */
+ChatHandler *chatHandler;
+
+/** Server message handler */
+ServerHandler *serverHandler;
+
+/** Chat Channels Manager */
+ChatChannelManager *chatChannelManager;
+
+/**
+ * Initializes the server.
+ */
+void initialize()
+{
+
+ // Reset to default segmentation fault handling for debugging purposes
+ signal(SIGSEGV, SIG_DFL);
+
+ // Set enet to quit on exit.
+ atexit(enet_deinitialize);
+
+ /*
+ * If the path values aren't defined, we set the default
+ * depending on the platform.
+ */
+ // The config path
+#if defined CONFIG_FILE
+ std::string configPath = CONFIG_FILE;
+#else
+
+#if (defined __USE_UNIX98 || defined __FreeBSD__)
+ std::string configPath = getenv("HOME");
+ configPath += "/.";
+ configPath += DEFAULT_CONFIG_FILE;
+#else // Win32, ...
+ std::string configPath = DEFAULT_CONFIG_FILE;
+#endif
+
+#endif // defined CONFIG_FILE
+
+ // The log path
+#if defined LOG_FILE
+ std::string logPath = LOG_FILE;
+#else
+
+#if (defined __USE_UNIX98 || defined __FreeBSD__)
+ std::string logPath = getenv("HOME");
+ logPath += "/.";
+ logPath += DEFAULT_LOG_FILE;
+#else // Win32, ...
+ std::string logPath = DEFAULT_LOG_FILE;
+#endif
+
+#endif // defined LOG_FILE
+
+ // Initialize PhysicsFS
+ PHYSFS_init("");
+
+ // Initialize the logger.
+ using namespace utils;
+ Logger::instance().setLogFile(logPath);
+
+ // write the messages to both the screen and the log file.
+ Logger::instance().setTeeMode(true);
+
+ config.init(configPath);
+ LOG_INFO("Using Config File: " << configPath, 0);
+ LOG_INFO("Using Log File: " << logPath, 0);
+
+ // --- Initialize the managers
+ // Initialize the slang's and double quotes filter.
+ stringFilter = new StringFilter(&config);
+ // Initialize the Chat channels manager
+ chatChannelManager = new ChatChannelManager();
+
+ // --- Initialize the global handlers
+ // FIXME: Make the global handlers global vars or part of a bigger
+ // singleton or a local variable in the event-loop
+ accountHandler = new AccountHandler();
+ chatHandler = new ChatHandler();
+ serverHandler = new ServerHandler();
+
+ // --- Initialize enet.
+ if (enet_initialize() != 0) {
+ LOG_FATAL("An error occurred while initializing ENet", 0);
+ exit(2);
+ }
+
+
+#if defined (MYSQL_SUPPORT)
+ LOG_INFO("Using MySQL DB Backend.", 0);
+#elif defined (POSTGRESQL_SUPPORT)
+ LOG_INFO("Using PostGreSQL DB Backend.", 0);
+#elif defined (SQLITE_SUPPORT)
+ LOG_INFO("Using SQLite DB Backend.", 0);
+#else
+ LOG_WARN("No Database Backend Support.", 0);
+#endif
+
+ // Initialize configuration defaults
+ config.setValue("dbuser", "");
+ config.setValue("dbpass", "");
+ config.setValue("dbhost", "");
+}
+
+
+/**
+ * Deinitializes the server.
+ */
+void deinitialize()
+{
+ delete stringFilter;
+ // Write configuration file
+ config.write();
+
+ // Stop world timer
+ worldTimer.stop();
+
+ // Quit ENet
+ enet_deinitialize();
+
+ // Destroy message handlers
+ delete serverHandler;
+ delete chatHandler;
+ delete accountHandler;
+
+ // Destroy Managers
+ delete chatChannelManager;
+
+ // Get rid of persistent data storage
+ Storage::destroy();
+
+ PHYSFS_deinit();
+}
+
+
+/**
+ * Show command line arguments
+ */
+void printHelp()
+{
+ std::cout << "tmwserv" << std::endl << std::endl
+ << "Options: " << std::endl
+ << " -h --help : Display this help" << std::endl
+ << " --verbosity <n> : Set the verbosity level" << std::endl
+ << " --port <n> : Set the default port to listen on" << std::endl;
+ exit(0);
+}
+
+/**
+ * Parse the command line arguments
+ */
+void parseOptions(int argc, char *argv[])
+{
+ const char *optstring = "h";
+
+ const struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "verbosity", required_argument, 0, 'v' },
+ { "port", required_argument, 0, 'p' },
+ { 0 }
+ };
+
+ while (optind < argc) {
+ int result = getopt_long(argc, argv, optstring, long_options, NULL);
+
+ if (result == -1) {
+ break;
+ }
+
+ switch (result) {
+ default: // Unknown option
+ case 'h':
+ // Print help
+ printHelp();
+ break;
+ case 'v':
+ // Set Verbosity to level
+ unsigned short verbosityLevel;
+ verbosityLevel = atoi(optarg);
+ utils::Logger::instance().setVerbosity(verbosityLevel);
+ LOG_INFO("Setting Log Verbosity Level to " << verbosityLevel, 0);
+ break;
+ case 'p':
+ // Change the port to listen on.
+ unsigned short portToListenOn;
+ portToListenOn = atoi(optarg);
+ config.setValue("ListenOnPort", portToListenOn);
+ LOG_INFO("Setting Default Port to " << portToListenOn, 0);
+ break;
+ }
+ }
+}
+
+
+/**
+ * Main function, initializes and runs server.
+ */
+int main(int argc, char *argv[])
+{
+ int elapsedWorldTicks;
+
+ LOG_INFO("The Mana World Server v" << PACKAGE_VERSION, 0);
+
+ // Parse Command Line Options
+ parseOptions(argc, argv);
+
+ // General Initialization
+ initialize();
+
+ int port = int(config.getValue("accountServerPort", DEFAULT_SERVER_PORT));
+ if (!accountHandler->startListen(port) ||
+ !serverHandler->startListen(port + 1) ||
+ !chatHandler->startListen(port + 2)) {
+ LOG_ERROR("Unable to create an ENet server host.", 0);
+ return 3;
+ }
+
+ // Create storage wrapper
+ Storage& store = Storage::instance("tmw");
+ store.setUser(config.getValue("dbuser", ""));
+ store.setPassword(config.getValue("dbpass", ""));
+ store.close();
+ store.open();
+
+ // Initialize world timer
+ worldTimer.start();
+
+ while (running) {
+ elapsedWorldTicks = worldTimer.poll();
+ if (elapsedWorldTicks > 0) {
+ worldTime += elapsedWorldTicks;
+
+ if (elapsedWorldTicks > 1)
+ {
+ LOG_WARN(elapsedWorldTicks -1 << " World Tick(s) skipped "
+ "because of insufficient time. please buy a faster "
+ "machine ;-)", 0);
+ };
+
+ // Print world time at 10 second intervals to show we're alive
+ if (worldTime % 100 == 0) {
+ LOG_INFO("World time: " << worldTime, 0);
+ }
+
+ // Handle all messages that are in the message queues
+ accountHandler->process();
+ chatHandler->process();
+ serverHandler->process();
+ }
+ worldTimer.sleep();
+ }
+
+ LOG_INFO("Received: Quit signal, closing down...", 0);
+ serverHandler->stopListen();
+ chatHandler->stopListen();
+ accountHandler->stopListen();
+ deinitialize();
+}
diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp
new file mode 100644
index 00000000..5300a066
--- /dev/null
+++ b/src/account-server/serverhandler.cpp
@@ -0,0 +1,115 @@
+/*
+ * 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 <cassert>
+#include <sstream>
+
+#include "account-server/serverhandler.hpp"
+#include "net/messagein.hpp"
+#include "net/messageout.hpp"
+#include "net/netcomputer.hpp"
+#include "utils/logger.h"
+
+bool ServerHandler::startListen(enet_uint16 port)
+{
+ LOG_INFO("Server handler started:", 0);
+ return ConnectionHandler::startListen(port);
+}
+
+NetComputer *ServerHandler::computerConnected(ENetPeer *peer)
+{
+ return new NetComputer(peer);
+}
+
+void ServerHandler::computerDisconnected(NetComputer *comp)
+{
+ delete comp;
+}
+
+bool ServerHandler::getGameServerFromMap(unsigned mapId, std::string &address, short &port)
+{
+ Servers::const_iterator i = servers.find(mapId);
+ if (i == servers.end()) return false;
+ address = i->second.address;
+ port = i->second.port;
+ return true;
+}
+
+void ServerHandler::registerGameClient(std::string const &token, PlayerPtr ptr)
+{
+ unsigned mapId = ptr->getMapId();
+ MessageOut msg(AGMSG_PLAYER_ENTER);
+ msg.writeLong(ptr->getDatabaseID());
+ msg.writeString(ptr->getName());
+ msg.writeByte(ptr->getGender());
+ msg.writeByte(ptr->getHairStyle());
+ msg.writeByte(ptr->getHairColor());
+ msg.writeByte(ptr->getLevel());
+ msg.writeShort(ptr->getMoney());
+ for (int j = 0; j < NB_RSTAT; ++j)
+ msg.writeShort(ptr->getRawStat(j));
+ Point pos = ptr->getPosition();
+ msg.writeShort(pos.x);
+ msg.writeShort(pos.y);
+ msg.writeShort(mapId);
+ msg.writeString(token, 32);
+ Servers::const_iterator i = servers.find(mapId);
+ assert(i != servers.end());
+ i->second.server->send(msg);
+}
+
+void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg)
+{
+ MessageOut result;
+
+ switch (msg.getId())
+ {
+ case GAMSG_REGISTER:
+ {
+ // TODO: check the credentials of the game server
+ std::string address = msg.readString();
+ int port = msg.readShort();
+ Server s = { address, port, comp };
+ LOG_INFO("Game server " << address << ':' << port
+ << " wants to register " << (msg.getUnreadLength() / 2)
+ << " maps.", 0);
+ while (msg.getUnreadLength())
+ {
+ unsigned id = msg.readShort();
+ if (!servers.insert(std::make_pair(id, s)).second)
+ {
+ LOG_ERROR("Server Handler: map is already registered.", 0);
+ }
+ }
+ } break;
+
+ default:
+ LOG_WARN("Invalid message type.", 0);
+ result.writeShort(XXMSG_INVALID);
+ break;
+ }
+
+ // return result
+ if (result.getLength() > 0)
+ comp->send(result);
+}
diff --git a/src/account-server/serverhandler.hpp b/src/account-server/serverhandler.hpp
new file mode 100644
index 00000000..797e0a48
--- /dev/null
+++ b/src/account-server/serverhandler.hpp
@@ -0,0 +1,90 @@
+/*
+ * The Mana World Server
+ * Copyright 2006 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$
+ */
+
+#ifndef _TMWSERV_SERVERHANDLER_H_
+#define _TMWSERV_SERVERHANDLER_H_
+
+#include <map>
+
+#include "player.h"
+#include "net/connectionhandler.hpp"
+
+/**
+ * Manages communications with all the game servers. This class also keeps
+ * track of the maps each game server supports.
+ */
+class ServerHandler: public ConnectionHandler
+{
+ public:
+ /**
+ * Starts the handler on the given port.
+ */
+ bool startListen(enet_uint16 port);
+
+ /**
+ * Returns the information a client needs to connect to the game server
+ * corresponding to the given map ID.
+ */
+ bool getGameServerFromMap(unsigned, std::string &address, short &port);
+
+ /**
+ * Sends a magic token and player data to the relevant game server.
+ */
+ void registerGameClient(std::string const &, PlayerPtr);
+
+ protected:
+ /**
+ * Processes server messages.
+ */
+ void processMessage(NetComputer *computer, MessageIn &message);
+
+ /**
+ * Called when a game server connects. Initializes a simple NetComputer
+ * as these connections are stateless.
+ */
+ NetComputer *computerConnected(ENetPeer *peer);
+
+ /**
+ * Called when a game server disconnects.
+ */
+ void computerDisconnected(NetComputer *comp);
+
+ private:
+ struct Server
+ {
+ std::string address;
+ short port;
+ NetComputer *server;
+ };
+
+ typedef std::map< unsigned, Server > Servers;
+
+ /**
+ * Maps map IDs to game server data.
+ */
+ Servers servers;
+};
+
+extern ServerHandler *serverHandler;
+
+#endif
diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp
new file mode 100644
index 00000000..a02ec010
--- /dev/null
+++ b/src/account-server/storage.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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 "account-server/dalstorage.hpp"
+#include "account-server/storage.hpp"
+
+// initialize the static attributes.
+Storage* Storage::mInstance = 0;
+std::string Storage::mName("");
+std::string Storage::mUser("");
+std::string Storage::mPassword("");
+
+
+/**
+ * Constructor.
+ */
+Storage::Storage(void)
+ throw()
+{
+ // NOOP
+}
+
+
+/**
+ * Destructor.
+ */
+Storage::~Storage(void)
+ throw()
+{
+ // NOOP
+}
+
+
+/**
+ * Create an instance of Storage.
+ */
+Storage&
+Storage::instance(const std::string& name)
+{
+ if (mInstance == 0) {
+ mInstance = new DALStorage();
+
+ // set the name of the storage.
+ mName = name;
+ }
+
+ return (*mInstance);
+}
+
+
+/**
+ * Delete the instance.
+ */
+void
+Storage::destroy(void)
+{
+ if (mInstance != 0) {
+ delete mInstance;
+ mInstance = 0;
+ }
+
+ // reset the attributes.
+ mName = "";
+ mUser = "";
+ mPassword = "";
+}
+
+
+/**
+ * Check if the storage is open.
+ */
+bool
+Storage::isOpen(void) const
+{
+ return mIsOpen;
+}
+
+
+/**
+ * Get the storage name.
+ */
+const std::string&
+Storage::getName(void) const
+{
+ return mName;
+}
+
+
+/**
+ * Set a user name for the storage.
+ */
+void
+Storage::setUser(const std::string& userName)
+{
+ mUser = userName;
+}
+
+
+/**
+ * Get the user name.
+ */
+const std::string&
+Storage::getUser(void) const
+{
+ return mUser;
+}
+
+
+/**
+ * Set a user password for the storage.
+ */
+void
+Storage::setPassword(const std::string& password)
+{
+ mPassword = password;
+}
+
+
+/**
+ * Get the user password.
+ */
+const std::string&
+Storage::getPassword(void) const
+{
+ return mPassword;
+}
diff --git a/src/account-server/storage.hpp b/src/account-server/storage.hpp
new file mode 100644
index 00000000..fec6dcf3
--- /dev/null
+++ b/src/account-server/storage.hpp
@@ -0,0 +1,290 @@
+/*
+ * 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$
+ */
+
+
+#ifndef _TMWSERV_STORAGE_H_
+#define _TMWSERV_STORAGE_H_
+
+#include <list>
+#include <map>
+
+#include "account-server/account.hpp"
+#include "chat-server/chatchannel.hpp"
+
+/**
+ * Data type for the list of accounts.
+ */
+typedef std::map<unsigned, AccountPtr> Accounts;
+
+
+/**
+ * A storage to load and persist dynamic data.
+ *
+ * Notes:
+ * - this class implements the singleton design pattern.
+ * - destroy() must be called at least once before the application
+ * exits or else there will be a memory leak.
+ */
+class Storage
+{
+ public:
+ /**
+ * Create a named instance of Storage.
+ *
+ * @param name the name of the storage.
+ *
+ * @return the unique instance of Storage.
+ *
+ * @exception std::bad_alloc if the instance cannot be created.
+ *
+ * Notes:
+ * - it is up to the underlying implementation of Storage to
+ * decide about what to do with the name, it could serve as the
+ * name of the database or the name of the file into which the
+ * storage will be dumped to.
+ * - the name of the storage is saved only when it's the first
+ * invocation of instance() or only when instance() is invoked
+ * after destroy().
+ */
+ static Storage&
+ instance(const std::string& name);
+
+
+ /**
+ * Delete the storage.
+ */
+ static void
+ destroy(void);
+
+
+ /**
+ * Open the storage for read/write access.
+ *
+ * Depending on the underlying implementation of Storage, opening
+ * a storage would mean either opening a file or connecting to a
+ * database.
+ */
+ virtual void
+ open(void) = 0;
+
+
+ /**
+ * Close the storage.
+ *
+ * Depending on the underlying implementation of Storage, closing
+ * a storage would mean either closing a file or disconnecting from
+ * a database.
+ */
+ virtual void
+ close(void) = 0;
+
+
+ /**
+ * Check if the storage is open.
+ *
+ * @return true if the storage is open.
+ */
+ bool
+ isOpen(void) const;
+
+
+ /**
+ * Get the storage name.
+ *
+ * @return the storage name.
+ */
+ const std::string&
+ getName(void) const;
+
+
+ /**
+ * Set a user name for the storage.
+ *
+ * Depending on the underlying implementation of Storage, setting
+ * the user name may have no effect (e.g. DALStorage running on
+ * SQLite).
+ *
+ * @param userName the user name.
+ */
+ void
+ setUser(const std::string& userName);
+
+
+ /**
+ * Get the user name.
+ *
+ * @return the user name (it may be an empty string if not set
+ * previously).
+ */
+ const std::string&
+ getUser(void) const;
+
+
+ /**
+ * Set a user password for the storage.
+ *
+ * Depending on the underlying implementation of Storage, setting
+ * the user password may have no effect (e.g. DALStorage running on
+ * SQLite).
+ *
+ * @param password the user password.
+ */
+ void
+ setPassword(const std::string& password);
+
+
+ /**
+ * Get the user password.
+ *
+ * @return the user password (it may be an empty string if not set
+ * previously).
+ */
+ const std::string&
+ getPassword(void) const;
+
+
+ /**
+ * Get an account by user name.
+ *
+ * @param userName the owner of the account.
+ *
+ * @return the account associated to the user name.
+ */
+ virtual AccountPtr
+ getAccount(const std::string& userName) = 0;
+
+ /**
+ * Add a new account.
+ *
+ * @param account the new account.
+ */
+ virtual void
+ addAccount(const AccountPtr& account) = 0;
+
+ /**
+ * Delete an account.
+ *
+ * @param account the account to delete.
+ */
+ virtual void
+ delAccount(AccountPtr const &account) = 0;
+
+ /**
+ * Flush and unload an account.
+ *
+ * @param account the account to unload.
+ */
+ virtual void
+ unloadAccount(AccountPtr const &account) = 0;
+
+ /**
+ * Get the list of Emails in the accounts list.
+ * @return the list of Email's Addresses.
+ *
+ * @deprecated The only purpose of using this list inside the server is
+ * for checking for existing email addresses, which is
+ * covered by Storage::getSameEmailNumber().
+ * It could later be used for mailing list announcement.
+ */
+ virtual
+ std::list<std::string> getEmailList() = 0;
+
+ /**
+ * Tells if the email address already exists.
+ * @return true if the email address exists.
+ */
+ virtual bool doesEmailAddressExist(std::string const &email) = 0;
+
+ /**
+ * Tells if the character's name already exists
+ * @return true if character's name exists.
+ */
+ virtual bool doesCharacterNameExist(std::string const &name) = 0;
+
+ /**
+ * Gives the list of opened public channels registered in database
+ * @return a map of the public channels
+ */
+ virtual std::map<short, ChatChannel>
+ getChannelList() = 0;
+
+ /**
+ * apply channel differences from the list in memory
+ * to the one in db.
+ */
+ virtual void
+ updateChannels(std::map<short, ChatChannel>& channelList) = 0;
+
+ /**
+ * Saves the changes to all the accounts permanently.
+ */
+ virtual void flushAll() = 0;
+
+ /**
+ * Saves the changes to one account permanently.
+ */
+ virtual void flush(AccountPtr const &account) = 0;
+
+
+ protected:
+ /**
+ * Default constructor.
+ */
+ Storage(void)
+ throw();
+
+
+ /**
+ * Destructor.
+ */
+ virtual
+ ~Storage(void)
+ throw();
+
+
+ /**
+ * Copy constructor.
+ */
+ Storage(const Storage& rhs);
+
+
+ /**
+ * Assignment operator.
+ */
+ Storage&
+ operator=(const Storage& rhs);
+
+
+ protected:
+ Accounts mAccounts; /**< list of accounts in memory */
+ Players mCharacters; /**< the loaded characters */
+ bool mIsOpen; /**< flag is true if the storage is open */
+
+
+ private:
+ static Storage* mInstance; /**< the unique instance of Storage */
+ static std::string mName; /**< the name of the storage */
+ static std::string mUser; /**< the user name */
+ static std::string mPassword; /**< the user password */
+};
+
+#endif // _TMWSERV_STORAGE_H_