diff options
Diffstat (limited to 'src')
34 files changed, 1813 insertions, 1218 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe5b0788..03b2b5ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -149,6 +149,7 @@ SET(SRCS_MANASERVACCOUNT account-server/accounthandler.cpp account-server/character.h account-server/character.cpp + account-server/flooritem.h account-server/serverhandler.h account-server/serverhandler.cpp account-server/storage.h diff --git a/src/account-server/flooritem.h b/src/account-server/flooritem.h new file mode 100644 index 00000000..436dedbc --- /dev/null +++ b/src/account-server/flooritem.h @@ -0,0 +1,66 @@ +/* + * The Mana Server + * Copyright (C) 2011 The Mana Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server 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 Server 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 Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef FLOOR_ITEM_H +#define FLOOR_ITEM_H + +class FloorItem +{ +public: + FloorItem(): + mItemId(0), mItemAmount(0), mPosX(0), mPosY(0) + {} + + FloorItem(int itemId, int itemAmount, int posX, int posY): + mItemId(itemId), mItemAmount(itemAmount), mPosX(posX), mPosY(posY) + {} + + /** + * Returns the item id + */ + int getItemId() const + { return mItemId; } + + /** + * Returns the amount of items + */ + int getItemAmount() const + { return mItemAmount; } + + /** + * Returns the position x of the item(s) + */ + int getPosX() const + { return mPosX; } + + /** + * Returns the position x of the item(s) + */ + int getPosY() const + { return mPosY; } + +private: + int mItemId; + int mItemAmount; + int mPosX; + int mPosY; +}; + +#endif // FLOOR_ITEM_H diff --git a/src/account-server/main-account.cpp b/src/account-server/main-account.cpp index 01ec0cea..0adf1285 100644 --- a/src/account-server/main-account.cpp +++ b/src/account-server/main-account.cpp @@ -347,8 +347,9 @@ int main(int argc, char *argv[]) LOG_INFO("The Mana Account+Chat Server (unknown version)"); #endif LOG_INFO("Manaserv Protocol version " << ManaServ::PROTOCOL_VERSION - << ", " << "Enet version " << ENET_VERSION_MAJOR << "." - << ENET_VERSION_MINOR << "." << ENET_VERSION_PATCH); + << ", Enet version " << ENET_VERSION_MAJOR << "." + << ENET_VERSION_MINOR << "." << ENET_VERSION_PATCH + << ", Database version " << ManaServ::SUPPORTED_DB_VERSION); if (!options.verbosityChanged) options.verbosity = static_cast<Logger::Level>( diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp index e5bfdc40..6a41d715 100644 --- a/src/account-server/serverhandler.cpp +++ b/src/account-server/serverhandler.cpp @@ -27,6 +27,7 @@ #include "account-server/accountclient.h" #include "account-server/accounthandler.h" #include "account-server/character.h" +#include "account-server/flooritem.h" #include "account-server/storage.h" #include "chat-server/chathandler.h" #include "chat-server/post.h" @@ -241,9 +242,15 @@ void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg) else { MessageOut outMsg(AGMSG_ACTIVE_MAP); + + // Map variables outMsg.writeInt16(id); std::map<std::string, std::string> variables; variables = storage->getAllWorldStateVars(id); + + // Map vars number + outMsg.writeInt16(variables.size()); + for (std::map<std::string, std::string>::iterator i = variables.begin(); i != variables.end(); i++) @@ -251,6 +258,23 @@ void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg) outMsg.writeString(i->first); outMsg.writeString(i->second); } + + // Persistent Floor Items + std::list<FloorItem> items; + items = storage->getFloorItemsFromMap(id); + + outMsg.writeInt16(items.size()); //number of floor items + + // Send each map item: item_id, amount, pos_x, pos_y + for (std::list<FloorItem>::iterator i = items.begin(); + i != items.end(); ++i) + { + outMsg.writeInt32(i->getItemId()); + outMsg.writeInt16(i->getItemAmount()); + outMsg.writeInt16(i->getPosX()); + outMsg.writeInt16(i->getPosY()); + } + comp->send(outMsg); MapStatistics &m = server->maps[id]; m.nbThings = 0; @@ -549,6 +573,34 @@ void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg) chatHandler->handlePartyInvite(msg); break; + case GAMSG_CREATE_ITEM_ON_MAP: + { + int mapId = msg.readInt32(); + int itemId = msg.readInt32(); + int amount = msg.readInt16(); + int posX = msg.readInt16(); + int posY = msg.readInt16(); + + LOG_DEBUG("Gameserver create item " << itemId + << " on map " << mapId); + + storage->addFloorItem(mapId, itemId, amount, posX, posY); + } break; + + case GAMSG_REMOVE_ITEM_ON_MAP: + { + int mapId = msg.readInt32(); + int itemId = msg.readInt32(); + int amount = msg.readInt16(); + int posX = msg.readInt16(); + int posY = msg.readInt16(); + + LOG_DEBUG("Gameserver removed item " << itemId + << " from map " << mapId); + + storage->removeFloorItem(mapId, itemId, amount, posX, posY); + } break; + default: LOG_WARN("ServerHandler::processMessage, Invalid message type: " << msg.getId()); diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp index 3d6d4f51..a1d1694b 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -24,14 +24,17 @@ #include "account-server/storage.h" #include "account-server/account.h" +#include "account-server/flooritem.h" #include "chat-server/chatchannel.h" #include "chat-server/guild.h" #include "chat-server/post.h" #include "common/configuration.h" +#include "common/manaserv_protocol.h" #include "dal/dalexcept.h" #include "dal/dataproviderfactory.h" #include "utils/functors.h" #include "utils/point.h" +#include "utils/string.h" #include "utils/throwerror.h" #include "utils/xml.h" @@ -41,7 +44,6 @@ static const char *DEFAULT_ITEM_FILE = "items.xml"; // Defines the supported db version static const char *DB_VERSION_PARAMETER = "database_version"; -static const char *SUPPORTED_DB_VERSION = "16"; /* * MySQL specificities: @@ -89,6 +91,7 @@ static const char *AUCTION_TBL_NAME = "mana_auctions"; static const char *AUCTION_BIDS_TBL_NAME = "mana_auction_bids"; static const char *ONLINE_USERS_TBL_NAME = "mana_online_list"; static const char *TRANSACTION_TBL_NAME = "mana_transactions"; +static const char *FLOOR_ITEMS_TBL_NAME = "mana_floor_items"; Storage::Storage() : mDb(dal::DataProviderFactory::createDataProvider()), @@ -118,12 +121,14 @@ void Storage::open() mDb->connect(); // Check database version here - std::string dbversion = getWorldStateVar(DB_VERSION_PARAMETER); - if (dbversion != SUPPORTED_DB_VERSION) + int dbversion = utils::stringToInt( + getWorldStateVar(DB_VERSION_PARAMETER)); + int supportedDbVersion = ManaServ::SUPPORTED_DB_VERSION; + if (dbversion != supportedDbVersion) { std::ostringstream errmsg; errmsg << "Database version is not supported. " - << "Needed version: '" << SUPPORTED_DB_VERSION + << "Needed version: '" << supportedDbVersion << "', current version: '" << dbversion << "'"; utils::throwError(errmsg.str()); } @@ -135,6 +140,15 @@ void Storage::open() std::ostringstream sql; sql << "DELETE FROM " << ONLINE_USERS_TBL_NAME; mDb->execSql(sql.str()); + + // In case where the server shouldn't keep floor item in database, + // we remove remnants at startup + if (Configuration::getValue("game_floorItemDecayTime", 0) > 0) + { + sql.clear(); + sql << "DELETE FROM " << FLOOR_ITEMS_TBL_NAME; + mDb->execSql(sql.str()); + } } catch (const DbConnectionFailure& e) { @@ -490,17 +504,26 @@ Character *Storage::getCharacterBySQL(Account *owner) try { std::ostringstream sql; - sql << " select slot_type, inventory_slot from " + sql << " select slot_type, item_id, item_instance from " << CHAR_EQUIPS_TBL_NAME << " where owner_id = '" << character->getDatabaseID() << "' order by slot_type desc;"; + EquipData equipData; const dal::RecordSet &equipInfo = mDb->execSql(sql.str()); if (!equipInfo.isEmpty()) + { + EquipmentItem equipItem; for (int k = 0, size = equipInfo.rows(); k < size; ++k) - poss.equipSlots.insert(std::pair<unsigned int, unsigned int>( - toUint(equipInfo(k, 0)), - toUint(equipInfo(k, 1)))); + { + equipItem.itemId = toUint(equipInfo(k, 1)); + equipItem.itemInstance = toUint(equipInfo(k, 2)); + equipData.insert(std::pair<unsigned int, EquipmentItem>( + toUint(equipInfo(k, 0)), + equipItem)); + } + } + poss.setEquipment(equipData); } catch (const dal::DbSqlQueryExecFailure &e) { @@ -515,6 +538,7 @@ Character *Storage::getCharacterBySQL(Account *owner) << " where owner_id = '" << character->getDatabaseID() << "' order by slot asc;"; + InventoryData inventoryData; const dal::RecordSet &itemInfo = mDb->execSql(sql.str()); if (!itemInfo.isEmpty()) { @@ -524,9 +548,10 @@ Character *Storage::getCharacterBySQL(Account *owner) unsigned short slot = toUint(itemInfo(k, 2)); item.itemId = toUint(itemInfo(k, 3)); item.amount = toUint(itemInfo(k, 4)); - poss.inventory[slot] = item; + inventoryData[slot] = item; } } + poss.setInventory(inventoryData); } catch (const dal::DbSqlQueryExecFailure &e) { @@ -798,18 +823,18 @@ bool Storage::updateCharacter(Character *character) std::ostringstream sql; sql << "insert into " << CHAR_EQUIPS_TBL_NAME - << " (owner_id, slot_type, inventory_slot) values (" + << " (owner_id, slot_type, item_id, item_instance) values (" << character->getDatabaseID() << ", "; std::string base = sql.str(); const Possessions &poss = character->getPossessions(); - for (EquipData::const_iterator it = poss.equipSlots.begin(), - it_end = poss.equipSlots.end(); - it != it_end; - ++it) + const EquipData &equipData = poss.getEquipment(); + for (EquipData::const_iterator it = equipData.begin(), + it_end = equipData.end(); it != it_end; ++it) { sql.str(""); - sql << base << it->first << ", " << it->second << ");"; + sql << base << it->first << ", " << it->second.itemId + << ", " << it->second.itemInstance << ");"; mDb->execSql(sql.str()); } @@ -820,13 +845,14 @@ bool Storage::updateCharacter(Character *character) << character->getDatabaseID() << ", "; base = sql.str(); - for (InventoryData::const_iterator j = poss.inventory.begin(), - j_end = poss.inventory.end(); j != j_end; ++j) + const InventoryData &inventoryData = poss.getInventory(); + for (InventoryData::const_iterator j = inventoryData.begin(), + j_end = inventoryData.end(); j != j_end; ++j) { sql.str(""); unsigned short slot = j->first; - unsigned int itemId = j->second.itemId; - unsigned int amount = j->second.amount; + unsigned int itemId = j->second.itemId; + unsigned int amount = j->second.amount; assert(itemId); sql << base << slot << ", " << itemId << ", " << amount << ");"; mDb->execSql(sql.str()); @@ -1361,6 +1387,82 @@ void Storage::removeGuildMember(int guildId, int memberId) } } +void Storage::addFloorItem(int mapId, int itemId, int amount, + int posX, int posY) +{ + try + { + std::ostringstream sql; + sql << "INSERT INTO " << FLOOR_ITEMS_TBL_NAME + << " (map_id, item_id, amount, pos_x, pos_y)" + << " VALUES (" + << mapId << ", " + << itemId << ", " + << amount << ", " + << posX << ", " + << posY << ");"; + mDb->execSql(sql.str()); + } + catch (const dal::DbSqlQueryExecFailure& e) + { + utils::throwError("(DALStorage::addFloorItem) SQL query failure: ", e); + } +} + +void Storage::removeFloorItem(int mapId, int itemId, int amount, + int posX, int posY) +{ + try + { + std::ostringstream sql; + sql << "DELETE FROM " << FLOOR_ITEMS_TBL_NAME + << " WHERE map_id = " + << mapId << " AND item_id = " + << itemId << " AND amount = " + << amount << " AND pos_x = " + << posX << " AND pos_y = " + << posY << ";"; + mDb->execSql(sql.str()); + } + catch (const dal::DbSqlQueryExecFailure& e) + { + utils::throwError("(DALStorage::removeFloorItem) SQL query failure: ", + e); + } +} + +std::list<FloorItem> Storage::getFloorItemsFromMap(int mapId) +{ + std::list<FloorItem> floorItems; + + try + { + std::ostringstream sql; + sql << "SELECT * FROM " << FLOOR_ITEMS_TBL_NAME + << " WHERE map_id = " << mapId; + + string_to< unsigned > toUint; + const dal::RecordSet &itemInfo = mDb->execSql(sql.str()); + if (!itemInfo.isEmpty()) + { + for (int k = 0, size = itemInfo.rows(); k < size; ++k) + { + floorItems.push_back(FloorItem(toUint(itemInfo(k, 2)), + toUint(itemInfo(k, 3)), + toUint(itemInfo(k, 4)), + toUint(itemInfo(k, 5)))); + } + } + } + catch (const dal::DbSqlQueryExecFailure &e) + { + utils::throwError("DALStorage::getFloorItemsFromMap " + "SQL query failure: ", e); + } + + return floorItems; +} + void Storage::setMemberRights(int guildId, int memberId, int rights) { try diff --git a/src/account-server/storage.h b/src/account-server/storage.h index a44156a4..3c629920 100644 --- a/src/account-server/storage.h +++ b/src/account-server/storage.h @@ -32,6 +32,7 @@ class Account; class Character; class ChatChannel; +class FloorItem; class Guild; class Letter; class Post; @@ -288,6 +289,38 @@ class Storage std::list<Guild*> getGuildList(); /** + * Add a floor item to map. + * + * Used to keep the floor item persistently between two server restart. + * + * @param mapId The map id + * @param itemId The item id + * @param posX Position X of the item in pixels + * @param posY Position Y of the item in pixels + */ + void addFloorItem(int mapId, int itemId, int amount, + int posX, int posY); + + /** + * Remove item from map persistence + * + * @param mapId The map id + * @param itemId The item id + * @param posX Position X of the item in pixels + * @param posY Position Y of the item in pixels + */ + void removeFloorItem(int mapId, int itemId, int amount, + int posX, int posY); + + + /** + * Get all persistent items from the given map id + * + * @param mapId The map id + */ + std::list<FloorItem> getFloorItemsFromMap(int mapId); + + /** * Update an account to the database. * * @param Account object to update. diff --git a/src/common/defines.h b/src/common/defines.h index c14aca50..140a6929 100644 --- a/src/common/defines.h +++ b/src/common/defines.h @@ -126,6 +126,17 @@ enum Element }; /** + * Damage type, used to know how to compute them. + */ +enum DamageType +{ + DAMAGE_PHYSICAL = 0, + DAMAGE_MAGICAL, + DAMAGE_DIRECT, + DAMAGE_OTHER = -1 +}; + +/** * A series of hardcoded attributes that must be defined. * FIXME: Much of these serve only to indicate derivatives, and so would not be * needed once this is no longer a hardcoded system. diff --git a/src/common/inventorydata.h b/src/common/inventorydata.h index 9127c816..490abff2 100644 --- a/src/common/inventorydata.h +++ b/src/common/inventorydata.h @@ -35,20 +35,62 @@ */ struct InventoryItem { + InventoryItem(): + itemId(0), amount(0) + {} + unsigned int itemId; unsigned int amount; }; -// slot id -> { item } -typedef std::map< unsigned short, InventoryItem > InventoryData; -// equip slot type -> { slot ids } -// Equipment taking up multiple slots will be referenced multiple times -typedef std::multimap< unsigned int, unsigned int > EquipData; + +struct EquipmentItem +{ + EquipmentItem(): + itemId(0), itemInstance(0) + {} + + EquipmentItem(unsigned int itemId, unsigned int itemInstance) + { + this->itemId = itemId; + this->itemInstance = itemInstance; + } + + // The item id taken from the item db. + unsigned int itemId; + // A unique instance number used to separate items when equipping the same + // item id multiple times on possible multiple slots. + unsigned int itemInstance; +}; + +// inventory slot id -> { item } +typedef std::map< unsigned int, InventoryItem > InventoryData; + +// equip slot id -> { item id, item instance } +// Equipment taking up multiple equip slot ids will be referenced multiple times +typedef std::multimap< unsigned int, EquipmentItem > EquipData; /** * Structure storing the equipment and inventory of a Player. */ struct Possessions { + friend class Inventory; +public: + const EquipData &getEquipment() const + { return equipSlots; } + + const InventoryData &getInventory() const + { return inventory; } + + /** + * Should be done only at character serialization and storage load time. + */ + void setEquipment(EquipData &equipData) + { equipSlots.swap(equipData); } + void setInventory(InventoryData &inventoryData) + { inventory.swap(inventoryData); } + +private: InventoryData inventory; EquipData equipSlots; }; diff --git a/src/common/manaserv_protocol.h b/src/common/manaserv_protocol.h index dc7e47b4..5bcfeb66 100644 --- a/src/common/manaserv_protocol.h +++ b/src/common/manaserv_protocol.h @@ -1,448 +1,448 @@ -/* - * The Mana Server - * Copyright (C) 2004-2010 The Mana World Development Team - * - * - * This file is part of The Mana Server. - * - * The Mana Server 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 Server 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 Server. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef MANASERV_PROTOCOL_H -#define MANASERV_PROTOCOL_H - -namespace ManaServ { - -enum { PROTOCOL_VERSION = 1 }; - -/** - * Enumerated type for communicated messages: - * - * - PAMSG_*: from client to account server - * - APMSG_*: from account server to client - * - PCMSG_*: from client to chat server - * - CPMSG_*: from chat server to client - * - PGMSG_*: from client to game server - * - GPMSG_*: from game server to client - * - GAMSG_*: from game server to account server - * - * Components: B byte, W word, D double word, S variable-size string - * C tile-based coordinates (B*3) - * - * Hosts: P (player's client), A (account server), C (char server), - * G (game server) - * - * TODO - Document specific error codes for each packet - */ -enum { - // Login/Register - PAMSG_REGISTER = 0x0000, // D version, S username, S password, S email, S captcha response - APMSG_REGISTER_RESPONSE = 0x0002, // B error, S updatehost, S Client data URL, B Character slots - PAMSG_UNREGISTER = 0x0003, // S username, S password - APMSG_UNREGISTER_RESPONSE = 0x0004, // B error - PAMSG_REQUEST_REGISTER_INFO = 0x0005, // - APMSG_REGISTER_INFO_RESPONSE = 0x0006, // B byte registration Allowed, byte minNameLength, byte maxNameLength, string captchaURL, string captchaInstructions - PAMSG_LOGIN = 0x0010, // D version, S username, S password - APMSG_LOGIN_RESPONSE = 0x0012, // B error, S updatehost, S Client data URL, B Character slots - PAMSG_LOGOUT = 0x0013, // - - APMSG_LOGOUT_RESPONSE = 0x0014, // B error - PAMSG_LOGIN_RNDTRGR = 0x0015, // S username - APMSG_LOGIN_RNDTRGR_RESPONSE = 0x0016, // S random seed - PAMSG_CHAR_CREATE = 0x0020, // S name, B hair style, B hair color, B gender, B slot, {W stats}* - APMSG_CHAR_CREATE_RESPONSE = 0x0021, // B error - PAMSG_CHAR_DELETE = 0x0022, // B slot - APMSG_CHAR_DELETE_RESPONSE = 0x0023, // B error - // B slot, S name, B gender, B hair style, B hair color, W level, - // W character points, W correction points, - // {D attr id, D base value (in 1/256ths) D mod value (in 256ths) }* - APMSG_CHAR_INFO = 0x0024, // ^ - PAMSG_CHAR_SELECT = 0x0026, // B slot - APMSG_CHAR_SELECT_RESPONSE = 0x0027, // B error, B*32 token, S game address, W game port, S chat address, W chat port - PAMSG_EMAIL_CHANGE = 0x0030, // S email - APMSG_EMAIL_CHANGE_RESPONSE = 0x0031, // B error - PAMSG_PASSWORD_CHANGE = 0x0034, // S old password, S new password - APMSG_PASSWORD_CHANGE_RESPONSE = 0x0035, // B error - - PGMSG_CONNECT = 0x0050, // B*32 token - GPMSG_CONNECT_RESPONSE = 0x0051, // B error - PCMSG_CONNECT = 0x0053, // B*32 token - CPMSG_CONNECT_RESPONSE = 0x0054, // B error - - PGMSG_DISCONNECT = 0x0060, // B reconnect account - GPMSG_DISCONNECT_RESPONSE = 0x0061, // B error, B*32 token - PCMSG_DISCONNECT = 0x0063, // - - CPMSG_DISCONNECT_RESPONSE = 0x0064, // B error - - PAMSG_RECONNECT = 0x0065, // B*32 token - APMSG_RECONNECT_RESPONSE = 0x0066, // B error - - // Game - GPMSG_PLAYER_MAP_CHANGE = 0x0100, // S filename, W x, W y - GPMSG_PLAYER_SERVER_CHANGE = 0x0101, // B*32 token, S game address, W game port - PGMSG_PICKUP = 0x0110, // W*2 position - PGMSG_DROP = 0x0111, // B slot, B amount - PGMSG_EQUIP = 0x0112, // B slot - PGMSG_UNEQUIP = 0x0113, // B slot - PGMSG_MOVE_ITEM = 0x0114, // B slot1, B slot2, B amount - GPMSG_INVENTORY = 0x0120, // { W slot, W item id [, W amount] (if item id is nonzero) }* - GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount }, { B equip slot, W invy slot}* - GPMSG_EQUIP = 0x0122, // { W Invy slot, B equip slot type count { B equip slot, B number used} }* - GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, D base value (in 1/256ths), D modified value (in 1/256ths)}* - GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { W skill, D exp got, D exp needed }* - GPMSG_LEVELUP = 0x0150, // W new level, W character points, W correction points - GPMSG_LEVEL_PROGRESS = 0x0151, // B percent completed to next levelup - PGMSG_RAISE_ATTRIBUTE = 0x0160, // W attribute - GPMSG_RAISE_ATTRIBUTE_RESPONSE = 0x0161, // B error, W attribute - PGMSG_LOWER_ATTRIBUTE = 0x0170, // W attribute - GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, W attribute - PGMSG_RESPAWN = 0x0180, // - - GPMSG_BEING_ENTER = 0x0200, // B type, W being id, B action, W*2 position, B direction - // character: S name, B hair style, B hair color, B gender, B item bitmask, { W item id }* - // monster: W type id - // npc: W type id - GPMSG_BEING_LEAVE = 0x0201, // W being id - GPMSG_ITEM_APPEAR = 0x0202, // W item id, W*2 position - GPMSG_BEING_LOOKS_CHANGE = 0x0210, // W weapon, W hat, W top clothes, W bottom clothes - PGMSG_WALK = 0x0260, // W*2 destination - PGMSG_ACTION_CHANGE = 0x0270, // B Action - GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action - PGMSG_DIRECTION_CHANGE = 0x0272, // B Direction - GPMSG_BEING_DIR_CHANGE = 0x0273, // W being id, B direction - GPMSG_BEING_HEALTH_CHANGE = 0x0274, // W being id, W hp, W max hp - GPMSG_BEINGS_MOVE = 0x0280, // { W being id, B flags [, [W*2 position,] W*2 destination, B speed] }* - GPMSG_ITEMS = 0x0281, // { W item id, W*2 position }* - PGMSG_ATTACK = 0x0290, // W being id - GPMSG_BEING_ATTACK = 0x0291, // W being id, B direction, B attack Id - PGMSG_USE_SPECIAL = 0x0292, // B specialID - GPMSG_SPECIAL_STATUS = 0x0293, // { B specialID, D current, D max, D recharge } - PGMSG_SAY = 0x02A0, // S text - GPMSG_SAY = 0x02A1, // W being id, S text - GPMSG_NPC_CHOICE = 0x02B0, // W being id, { S text }* - GPMSG_NPC_MESSAGE = 0x02B1, // W being id, B* text - PGMSG_NPC_TALK = 0x02B2, // W being id - PGMSG_NPC_TALK_NEXT = 0x02B3, // W being id - PGMSG_NPC_SELECT = 0x02B4, // W being id, B choice - GPMSG_NPC_BUY = 0x02B5, // W being id, { W item id, W amount, W cost }* - GPMSG_NPC_SELL = 0x02B6, // W being id, { W item id, W amount, W cost }* - PGMSG_NPC_BUYSELL = 0x02B7, // W item id, W amount - GPMSG_NPC_ERROR = 0x02B8, // B error - GPMSG_NPC_CLOSE = 0x02B9, // W being id - GPMSG_NPC_POST = 0x02D0, // W being id - PGMSG_NPC_POST_SEND = 0x02D1, // W being id, { S name, S text, W item id } - GPMSG_NPC_POST_GET = 0x02D2, // W being id, { S name, S text, W item id } - PGMSG_NPC_NUMBER = 0x02D3, // W being id, D number - PGMSG_NPC_STRING = 0x02D4, // W being id, S string - GPMSG_NPC_NUMBER = 0x02D5, // W being id, D max, D min, D default - GPMSG_NPC_STRING = 0x02D6, // W being id - PGMSG_TRADE_REQUEST = 0x02C0, // W being id - GPMSG_TRADE_REQUEST = 0x02C1, // W being id - GPMSG_TRADE_START = 0x02C2, // - - GPMSG_TRADE_COMPLETE = 0x02C3, // - - PGMSG_TRADE_CANCEL = 0x02C4, // - - GPMSG_TRADE_CANCEL = 0x02C5, // - - PGMSG_TRADE_AGREED = 0x02C6, // - - GPMSG_TRADE_AGREED = 0x02C7, // - - PGMSG_TRADE_CONFIRM = 0x02C8, // - - GPMSG_TRADE_CONFIRM = 0x02C9, // - - PGMSG_TRADE_ADD_ITEM = 0x02CA, // B slot, B amount - GPMSG_TRADE_ADD_ITEM = 0x02CB, // W item id, B amount - PGMSG_TRADE_SET_MONEY = 0x02CC, // D amount - GPMSG_TRADE_SET_MONEY = 0x02CD, // D amount - GPMSG_TRADE_BOTH_CONFIRM = 0x02CE, // - - PGMSG_USE_ITEM = 0x0300, // B slot - GPMSG_USE_RESPONSE = 0x0301, // B error - GPMSG_BEINGS_DAMAGE = 0x0310, // { W being id, W amount }* - GPMSG_CREATE_EFFECT_POS = 0x0320, // W effect id, W*2 position - GPMSG_CREATE_EFFECT_BEING = 0x0321, // W effect id, W BeingID +/*
+ * The Mana Server
+ * Copyright (C) 2004-2010 The Mana World Development Team
+ *
+ *
+ * This file is part of The Mana Server.
+ *
+ * The Mana Server 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 Server 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 Server. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MANASERV_PROTOCOL_H
+#define MANASERV_PROTOCOL_H
+
+namespace ManaServ {
+
+enum {
+ PROTOCOL_VERSION = 1,
+ SUPPORTED_DB_VERSION = 17
+};
+
+/**
+ * Enumerated type for communicated messages:
+ *
+ * - PAMSG_*: from client to account server
+ * - APMSG_*: from account server to client
+ * - PCMSG_*: from client to chat server
+ * - CPMSG_*: from chat server to client
+ * - PGMSG_*: from client to game server
+ * - GPMSG_*: from game server to client
+ * - GAMSG_*: from game server to account server
+ *
+ * Components: B byte, W word, D double word, S variable-size string
+ * C tile-based coordinates (B*3)
+ *
+ * Hosts: P (player's client), A (account server), C (char server),
+ * G (game server)
+ *
+ * TODO - Document specific error codes for each packet
+ */
+enum {
+ // Login/Register
+ PAMSG_REGISTER = 0x0000, // D version, S username, S password, S email, S captcha response
+ APMSG_REGISTER_RESPONSE = 0x0002, // B error, S updatehost, S Client data URL, B Character slots
+ PAMSG_UNREGISTER = 0x0003, // S username, S password
+ APMSG_UNREGISTER_RESPONSE = 0x0004, // B error
+ PAMSG_REQUEST_REGISTER_INFO = 0x0005, //
+ APMSG_REGISTER_INFO_RESPONSE = 0x0006, // B byte registration Allowed, byte minNameLength, byte maxNameLength, string captchaURL, string captchaInstructions
+ PAMSG_LOGIN = 0x0010, // D version, S username, S password
+ APMSG_LOGIN_RESPONSE = 0x0012, // B error, S updatehost, S Client data URL, B Character slots
+ PAMSG_LOGOUT = 0x0013, // -
+ APMSG_LOGOUT_RESPONSE = 0x0014, // B error
+ PAMSG_LOGIN_RNDTRGR = 0x0015, // S username
+ APMSG_LOGIN_RNDTRGR_RESPONSE = 0x0016, // S random seed
+ PAMSG_CHAR_CREATE = 0x0020, // S name, B hair style, B hair color, B gender, B slot, {W stats}*
+ APMSG_CHAR_CREATE_RESPONSE = 0x0021, // B error
+ PAMSG_CHAR_DELETE = 0x0022, // B slot
+ APMSG_CHAR_DELETE_RESPONSE = 0x0023, // B error
+ // B slot, S name, B gender, B hair style, B hair color, W level,
+ // W character points, W correction points,
+ // {D attr id, D base value (in 1/256ths) D mod value (in 256ths) }*
+ APMSG_CHAR_INFO = 0x0024, // ^
+ PAMSG_CHAR_SELECT = 0x0026, // B slot
+ APMSG_CHAR_SELECT_RESPONSE = 0x0027, // B error, B*32 token, S game address, W game port, S chat address, W chat port
+ PAMSG_EMAIL_CHANGE = 0x0030, // S email
+ APMSG_EMAIL_CHANGE_RESPONSE = 0x0031, // B error
+ PAMSG_PASSWORD_CHANGE = 0x0034, // S old password, S new password
+ APMSG_PASSWORD_CHANGE_RESPONSE = 0x0035, // B error
+
+ PGMSG_CONNECT = 0x0050, // B*32 token
+ GPMSG_CONNECT_RESPONSE = 0x0051, // B error
+ PCMSG_CONNECT = 0x0053, // B*32 token
+ CPMSG_CONNECT_RESPONSE = 0x0054, // B error
+
+ PGMSG_DISCONNECT = 0x0060, // B reconnect account
+ GPMSG_DISCONNECT_RESPONSE = 0x0061, // B error, B*32 token
+ PCMSG_DISCONNECT = 0x0063, // -
+ CPMSG_DISCONNECT_RESPONSE = 0x0064, // B error
+
+ PAMSG_RECONNECT = 0x0065, // B*32 token
+ APMSG_RECONNECT_RESPONSE = 0x0066, // B error
+
+ // Game
+ GPMSG_PLAYER_MAP_CHANGE = 0x0100, // S filename, W x, W y
+ GPMSG_PLAYER_SERVER_CHANGE = 0x0101, // B*32 token, S game address, W game port
+ PGMSG_PICKUP = 0x0110, // W*2 position
+ PGMSG_DROP = 0x0111, // B slot, B amount
+ PGMSG_EQUIP = 0x0112, // W inventory slot
+ PGMSG_UNEQUIP = 0x0113, // W item Instance id
+ PGMSG_MOVE_ITEM = 0x0114, // W slot1, W slot2, W amount
+ GPMSG_INVENTORY = 0x0120, // { W slot, W item id [, W amount] (if item id is nonzero) }*
+ GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount }, { W equip slot, W item Id, W item Instance}*
+ GPMSG_EQUIP = 0x0122, // W item Id, W equip slot type count //{ W equip slot, W capacity used}*//<- When equipping, //{ W item instance, W 0}*//<- When unequipping
+ GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, D base value (in 1/256ths), D modified value (in 1/256ths)}*
+ GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { W skill, D exp got, D exp needed }*
+ GPMSG_LEVELUP = 0x0150, // W new level, W character points, W correction points
+ GPMSG_LEVEL_PROGRESS = 0x0151, // B percent completed to next levelup
+ PGMSG_RAISE_ATTRIBUTE = 0x0160, // W attribute
+ GPMSG_RAISE_ATTRIBUTE_RESPONSE = 0x0161, // B error, W attribute
+ PGMSG_LOWER_ATTRIBUTE = 0x0170, // W attribute
+ GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, W attribute
+ PGMSG_RESPAWN = 0x0180, // -
+ GPMSG_BEING_ENTER = 0x0200, // B type, W being id, B action, W*2 position, B direction
+ // character: S name, B hair style, B hair color, B gender, B sprite layers changed, { B slot type, W item id }*
+ // monster: W type id
+ // npc: W type id
+ GPMSG_BEING_LEAVE = 0x0201, // W being id
+ GPMSG_ITEM_APPEAR = 0x0202, // W item id, W*2 position
+ GPMSG_BEING_LOOKS_CHANGE = 0x0210, // B sprite layers changed, { B slot type, W item id }*
+ PGMSG_WALK = 0x0260, // W*2 destination
+ PGMSG_ACTION_CHANGE = 0x0270, // B Action
+ GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action
+ PGMSG_DIRECTION_CHANGE = 0x0272, // B Direction
+ GPMSG_BEING_DIR_CHANGE = 0x0273, // W being id, B direction
+ GPMSG_BEING_HEALTH_CHANGE = 0x0274, // W being id, W hp, W max hp
+ GPMSG_BEINGS_MOVE = 0x0280, // { W being id, B flags [, [W*2 position,] W*2 destination, B speed] }*
+ GPMSG_ITEMS = 0x0281, // { W item id, W*2 position }*
+ PGMSG_ATTACK = 0x0290, // W being id
+ GPMSG_BEING_ATTACK = 0x0291, // W being id, B direction, B attack Id
+ PGMSG_USE_SPECIAL = 0x0292, // B specialID
+ GPMSG_SPECIAL_STATUS = 0x0293, // { B specialID, D current, D max, D recharge }
+ PGMSG_SAY = 0x02A0, // S text
+ GPMSG_SAY = 0x02A1, // W being id, S text
+ GPMSG_NPC_CHOICE = 0x02B0, // W being id, { S text }*
+ GPMSG_NPC_MESSAGE = 0x02B1, // W being id, B* text
+ PGMSG_NPC_TALK = 0x02B2, // W being id
+ PGMSG_NPC_TALK_NEXT = 0x02B3, // W being id
+ PGMSG_NPC_SELECT = 0x02B4, // W being id, B choice
+ GPMSG_NPC_BUY = 0x02B5, // W being id, { W item id, W amount, W cost }*
+ GPMSG_NPC_SELL = 0x02B6, // W being id, { W item id, W amount, W cost }*
+ PGMSG_NPC_BUYSELL = 0x02B7, // W item id, W amount
+ GPMSG_NPC_ERROR = 0x02B8, // B error
+ GPMSG_NPC_CLOSE = 0x02B9, // W being id
+ GPMSG_NPC_POST = 0x02D0, // W being id
+ PGMSG_NPC_POST_SEND = 0x02D1, // W being id, { S name, S text, W item id }
+ GPMSG_NPC_POST_GET = 0x02D2, // W being id, { S name, S text, W item id }
+ PGMSG_NPC_NUMBER = 0x02D3, // W being id, D number
+ PGMSG_NPC_STRING = 0x02D4, // W being id, S string
+ GPMSG_NPC_NUMBER = 0x02D5, // W being id, D max, D min, D default
+ GPMSG_NPC_STRING = 0x02D6, // W being id
+ PGMSG_TRADE_REQUEST = 0x02C0, // W being id
+ GPMSG_TRADE_REQUEST = 0x02C1, // W being id
+ GPMSG_TRADE_START = 0x02C2, // -
+ GPMSG_TRADE_COMPLETE = 0x02C3, // -
+ PGMSG_TRADE_CANCEL = 0x02C4, // -
+ GPMSG_TRADE_CANCEL = 0x02C5, // -
+ PGMSG_TRADE_AGREED = 0x02C6, // -
+ GPMSG_TRADE_AGREED = 0x02C7, // -
+ PGMSG_TRADE_CONFIRM = 0x02C8, // -
+ GPMSG_TRADE_CONFIRM = 0x02C9, // -
+ PGMSG_TRADE_ADD_ITEM = 0x02CA, // B slot, B amount
+ GPMSG_TRADE_ADD_ITEM = 0x02CB, // W item id, B amount
+ PGMSG_TRADE_SET_MONEY = 0x02CC, // D amount
+ GPMSG_TRADE_SET_MONEY = 0x02CD, // D amount
+ GPMSG_TRADE_BOTH_CONFIRM = 0x02CE, // -
+ PGMSG_USE_ITEM = 0x0300, // B slot
+ GPMSG_USE_RESPONSE = 0x0301, // B error
+ GPMSG_BEINGS_DAMAGE = 0x0310, // { W being id, W amount }*
+ GPMSG_CREATE_EFFECT_POS = 0x0320, // W effect id, W*2 position
+ GPMSG_CREATE_EFFECT_BEING = 0x0321, // W effect id, W BeingID
GPMSG_SHAKE = 0x0330, // W intensityX, W intensityY, [W decay_times_10000, [W duration]]
- - // Guild - PCMSG_GUILD_CREATE = 0x0350, // S name - CPMSG_GUILD_CREATE_RESPONSE = 0x0351, // B error, W guild, B rights, W channel - PCMSG_GUILD_INVITE = 0x0352, // W id, S name - CPMSG_GUILD_INVITE_RESPONSE = 0x0353, // B error - PCMSG_GUILD_ACCEPT = 0x0354, // W id - CPMSG_GUILD_ACCEPT_RESPONSE = 0x0355, // B error, W guild, B rights, W channel - PCMSG_GUILD_GET_MEMBERS = 0x0356, // W id - CPMSG_GUILD_GET_MEMBERS_RESPONSE = 0x0357, // S names, B online - CPMSG_GUILD_UPDATE_LIST = 0x0358, // W id, S name, B event - PCMSG_GUILD_QUIT = 0x0360, // W id - CPMSG_GUILD_QUIT_RESPONSE = 0x0361, // B error - PCMSG_GUILD_PROMOTE_MEMBER = 0x0365, // W guild, S name, B rights - CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE = 0x0366, // B error - PCMSG_GUILD_KICK_MEMBER = 0x0370, // W guild, S name - CPMSG_GUILD_KICK_MEMBER_RESPONSE = 0x0371, // B error - - CPMSG_GUILD_INVITED = 0x0388, // S char name, S guild name, W id - CPMSG_GUILD_REJOIN = 0x0389, // S name, W guild, W rights, W channel, S announce - - // Party - PGMSG_PARTY_INVITE = 0x03A0, // S name - GPMSG_PARTY_INVITE_ERROR = 0x03A1, // S name - GCMSG_PARTY_INVITE = 0x03A2, // S inviter, S invitee - CPMSG_PARTY_INVITED = 0x03A4, // S name - PCMSG_PARTY_INVITE_ANSWER = 0x03A5, // S name, B accept - CPMSG_PARTY_INVITE_ANSWER_RESPONSE = 0x03A6, // B error, { S name } - CPMSG_PARTY_REJECTED = 0x03A8, // S name, B error - PCMSG_PARTY_QUIT = 0x03AA, // - - CPMSG_PARTY_QUIT_RESPONSE = 0x03AB, // B error - CPMSG_PARTY_NEW_MEMBER = 0x03B0, // S name, S inviter - CPMSG_PARTY_MEMBER_LEFT = 0x03B1, // D character id - - // Chat - CPMSG_ERROR = 0x0401, // B error - CPMSG_ANNOUNCEMENT = 0x0402, // S text - CPMSG_PRIVMSG = 0x0403, // S user, S text - CPMSG_PUBMSG = 0x0404, // W channel, S user, S text - PCMSG_CHAT = 0x0410, // S text, W channel - PCMSG_ANNOUNCE = 0x0411, // S text - PCMSG_PRIVMSG = 0x0412, // S user, S text - PCMSG_WHO = 0x0415, // - - CPMSG_WHO_RESPONSE = 0x0416, // { S user } - - // -- Channeling - CPMSG_CHANNEL_EVENT = 0x0430, // W channel, B event, S info - PCMSG_ENTER_CHANNEL = 0x0440, // S channel, S password - CPMSG_ENTER_CHANNEL_RESPONSE = 0x0441, // B error, W id, S name, S topic, S userlist - PCMSG_QUIT_CHANNEL = 0x0443, // W channel id - CPMSG_QUIT_CHANNEL_RESPONSE = 0x0444, // B error, W channel id - PCMSG_LIST_CHANNELS = 0x0445, // - - CPMSG_LIST_CHANNELS_RESPONSE = 0x0446, // S names, W number of users - PCMSG_LIST_CHANNELUSERS = 0x0460, // S channel - CPMSG_LIST_CHANNELUSERS_RESPONSE = 0x0461, // S channel, { S user, B mode } - PCMSG_TOPIC_CHANGE = 0x0462, // W channel id, S topic - // -- User modes - PCMSG_USER_MODE = 0x0465, // W channel id, S name, B mode - PCMSG_KICK_USER = 0x0466, // W channel id, S name - - // Inter-server - GAMSG_REGISTER = 0x0500, // S address, W port, S password, D items db revision, { W map id }* - AGMSG_REGISTER_RESPONSE = 0x0501, // C item version, C password response, { S globalvar_key, S globalvar_value } - AGMSG_ACTIVE_MAP = 0x0502, // W map id, { S mapvar_key, S mapvar_value } - AGMSG_PLAYER_ENTER = 0x0510, // B*32 token, D id, S name, serialised character data - GAMSG_PLAYER_DATA = 0x0520, // D id, serialised character data - GAMSG_REDIRECT = 0x0530, // D id - AGMSG_REDIRECT_RESPONSE = 0x0531, // D id, B*32 token, S game address, W game port - GAMSG_PLAYER_RECONNECT = 0x0532, // D id, B*32 token - GAMSG_PLAYER_SYNC = 0x0533, // serialised sync data - GAMSG_SET_VAR_CHR = 0x0540, // D id, S name, S value - GAMSG_GET_VAR_CHR = 0x0541, // D id, S name - AGMSG_GET_VAR_CHR_RESPONSE = 0x0542, // D id, S name, S value - //reserved GAMSG_SET_VAR_ACC = 0x0543, // D charid, S name, S value - //reserved GAMSG_GET_VAR_ACC = 0x0544, // D charid, S name - //reserved AGMSG_GET_VAR_ACC_RESPONSE = 0x0545, // D charid, S name, S value - GAMSG_SET_VAR_MAP = 0x0546, // D mapid, S name, S value - GAMSG_SET_VAR_WORLD = 0x0547, // S name, S value - AGMSG_SET_VAR_WORLD = 0x0548, // S name, S value - GAMSG_BAN_PLAYER = 0x0550, // D id, W duration - GAMSG_CHANGE_PLAYER_LEVEL = 0x0555, // D id, W level - GAMSG_CHANGE_ACCOUNT_LEVEL = 0x0556, // D id, W level - GAMSG_STATISTICS = 0x0560, // { W map id, W thing nb, W monster nb, W player nb, { D character id }* }* - CGMSG_CHANGED_PARTY = 0x0590, // D character id, D party id - GCMSG_REQUEST_POST = 0x05A0, // D character id - CGMSG_POST_RESPONSE = 0x05A1, // D receiver id, { S sender name, S letter, W num attachments { W attachment item id, W quantity } } - GCMSG_STORE_POST = 0x05A5, // D sender id, S receiver name, S letter, { W attachment item id, W quantity } - CGMSG_STORE_POST_RESPONSE = 0x05A6, // D id, B error - GAMSG_TRANSACTION = 0x0600, // D character id, D action, S message - - XXMSG_INVALID = 0x7FFF -}; - -// Generic return values - -enum { - ERRMSG_OK = 0, // everything is fine - ERRMSG_FAILURE, // the action failed - ERRMSG_NO_LOGIN, // the user is not yet logged - ERRMSG_NO_CHARACTER_SELECTED, // the user needs a character - ERRMSG_INSUFFICIENT_RIGHTS, // the user is not privileged - ERRMSG_INVALID_ARGUMENT, // part of the received message was invalid - ERRMSG_EMAIL_ALREADY_EXISTS, // The Email Address already exists - ERRMSG_ALREADY_TAKEN, // name used was already taken - ERRMSG_SERVER_FULL, // the server is overloaded - ERRMSG_TIME_OUT, // data failed to arrive in due time - ERRMSG_LIMIT_REACHED, // limit reached - ERRMSG_ADMINISTRATIVE_LOGOFF // kicked by server administrator -}; - -// used in AGMSG_REGISTER_RESPONSE to show state of item db -enum { - DATA_VERSION_OK = 0x00, - DATA_VERSION_OUTDATED = 0x01 -}; - -// used in AGMSG_REGISTER_RESPNSE to show if password was accepted -enum { - PASSWORD_OK = 0x00, - PASSWORD_BAD = 0x01 -}; - -// used to identify part of sync message -enum { - SYNC_CHARACTER_POINTS = 0x01, // D charId, D charPoints, D corrPoints - SYNC_CHARACTER_ATTRIBUTE = 0x02, // D charId, D attrId, DF base, DF mod - SYNC_CHARACTER_SKILL = 0x03, // D charId, B skillId, D skill value - SYNC_ONLINE_STATUS = 0x04 // D charId, B 0 = offline, 1 = online -}; - -// Login specific return values -enum { - LOGIN_INVALID_VERSION = 0x40, // the user is using an incompatible protocol - LOGIN_INVALID_TIME = 0x50, // the user tried logging in too fast - LOGIN_BANNED // the user is currently banned -}; - -// Account register specific return values -enum { - REGISTER_INVALID_VERSION = 0x40, // the user is using an incompatible protocol - REGISTER_EXISTS_USERNAME, // there already is an account with this username - REGISTER_EXISTS_EMAIL, // there already is an account with this email address - REGISTER_CAPTCHA_WRONG // user didn't solve the captcha correctly -}; - -// Character creation specific return values -enum { - CREATE_INVALID_HAIRSTYLE = 0x40, - CREATE_INVALID_HAIRCOLOR, - CREATE_INVALID_GENDER, - CREATE_ATTRIBUTES_TOO_HIGH, - CREATE_ATTRIBUTES_TOO_LOW, - CREATE_ATTRIBUTES_OUT_OF_RANGE, - CREATE_EXISTS_NAME, - CREATE_TOO_MUCH_CHARACTERS, - CREATE_INVALID_SLOT -}; - -// Character attribute modification specific return value -enum AttribmodResponseCode { - ATTRIBMOD_OK = ERRMSG_OK, - ATTRIBMOD_INVALID_ATTRIBUTE = 0x40, - ATTRIBMOD_NO_POINTS_LEFT, - ATTRIBMOD_DENIED -}; - -// Object type enumeration -enum ThingType -{ - // A simple item. - OBJECT_ITEM = 0, - // An item that toggle map/quest actions (doors, switchs, ...) - // and can speak (map panels). - OBJECT_ACTOR, - // Non-Playable-Character is an actor capable of movement and maybe actions. - OBJECT_NPC, - // A monster (moving actor with AI. Should be able to toggle map/quest - // actions, too). - OBJECT_MONSTER, - // A normal being. - OBJECT_CHARACTER, - // A effect to be shown. - OBJECT_EFFECT, - // Server-only object. - OBJECT_OTHER -}; - -// Moving object flags -enum { - // Payload contains the current position. - MOVING_POSITION = 1, - // Payload contains the destination. - MOVING_DESTINATION = 2 -}; - -// Chat errors return values -enum { - CHAT_USING_BAD_WORDS = 0x40, - CHAT_UNHANDLED_COMMAND -}; - -// Chat channels event values -enum { - CHAT_EVENT_NEW_PLAYER = 0, - CHAT_EVENT_LEAVING_PLAYER, - CHAT_EVENT_TOPIC_CHANGE, - CHAT_EVENT_MODE_CHANGE, - CHAT_EVENT_KICKED_PLAYER -}; - -// Guild member event values -enum { - GUILD_EVENT_NEW_PLAYER = 0, - GUILD_EVENT_LEAVING_PLAYER, - GUILD_EVENT_ONLINE_PLAYER, - GUILD_EVENT_OFFLINE_PLAYER -}; - -/** - * Moves enum for beings and actors for others players vision. - * WARNING: Has to be in sync with the same enum in the Being class - * of the client! - */ -enum BeingAction -{ - STAND, - WALK, - ATTACK, - SIT, - DEAD, - HURT -}; - -/** - * Moves enum for beings and actors for others players attack types. - * WARNING: Has to be in sync with the same enum in the Being class - * of the client! - */ -enum AttackType -{ - HIT = 0x00, - CRITICAL = 0x0a, - MULTI = 0x08, - REFLECT = 0x04, - FLEE = 0x0b -}; - -/** - * Beings and actors directions - * WARNING: Has to be in sync with the same enum in the Being class - * of the client! - */ -enum BeingDirection -{ - DOWN = 1, - LEFT = 2, - UP = 4, - RIGHT = 8 -}; - -/** - * enum for sprites layers. - * WARNING: Has to be in sync with the same enum in the Sprite class - * of the client! - */ -enum SpriteLayer -{ - SPRITE_BASE = 0, - SPRITE_SHOE, - SPRITE_BOTTOMCLOTHES, - SPRITE_TOPCLOTHES, - SPRITE_HAIR, - SPRITE_HAT, - SPRITE_WEAPON, - SPRITE_VECTOREND -}; - -} // namespace ManaServ - -#endif // MANASERV_PROTOCOL_H +
+ // Guild
+ PCMSG_GUILD_CREATE = 0x0350, // S name
+ CPMSG_GUILD_CREATE_RESPONSE = 0x0351, // B error, W guild, B rights, W channel
+ PCMSG_GUILD_INVITE = 0x0352, // W id, S name
+ CPMSG_GUILD_INVITE_RESPONSE = 0x0353, // B error
+ PCMSG_GUILD_ACCEPT = 0x0354, // W id
+ CPMSG_GUILD_ACCEPT_RESPONSE = 0x0355, // B error, W guild, B rights, W channel
+ PCMSG_GUILD_GET_MEMBERS = 0x0356, // W id
+ CPMSG_GUILD_GET_MEMBERS_RESPONSE = 0x0357, // S names, B online
+ CPMSG_GUILD_UPDATE_LIST = 0x0358, // W id, S name, B event
+ PCMSG_GUILD_QUIT = 0x0360, // W id
+ CPMSG_GUILD_QUIT_RESPONSE = 0x0361, // B error
+ PCMSG_GUILD_PROMOTE_MEMBER = 0x0365, // W guild, S name, B rights
+ CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE = 0x0366, // B error
+ PCMSG_GUILD_KICK_MEMBER = 0x0370, // W guild, S name
+ CPMSG_GUILD_KICK_MEMBER_RESPONSE = 0x0371, // B error
+
+ CPMSG_GUILD_INVITED = 0x0388, // S char name, S guild name, W id
+ CPMSG_GUILD_REJOIN = 0x0389, // S name, W guild, W rights, W channel, S announce
+
+ // Party
+ PGMSG_PARTY_INVITE = 0x03A0, // S name
+ GPMSG_PARTY_INVITE_ERROR = 0x03A1, // S name
+ GCMSG_PARTY_INVITE = 0x03A2, // S inviter, S invitee
+ CPMSG_PARTY_INVITED = 0x03A4, // S name
+ PCMSG_PARTY_INVITE_ANSWER = 0x03A5, // S name, B accept
+ CPMSG_PARTY_INVITE_ANSWER_RESPONSE = 0x03A6, // B error, { S name }
+ CPMSG_PARTY_REJECTED = 0x03A8, // S name, B error
+ PCMSG_PARTY_QUIT = 0x03AA, // -
+ CPMSG_PARTY_QUIT_RESPONSE = 0x03AB, // B error
+ CPMSG_PARTY_NEW_MEMBER = 0x03B0, // S name, S inviter
+ CPMSG_PARTY_MEMBER_LEFT = 0x03B1, // D character id
+
+ // Chat
+ CPMSG_ERROR = 0x0401, // B error
+ CPMSG_ANNOUNCEMENT = 0x0402, // S text
+ CPMSG_PRIVMSG = 0x0403, // S user, S text
+ CPMSG_PUBMSG = 0x0404, // W channel, S user, S text
+ PCMSG_CHAT = 0x0410, // S text, W channel
+ PCMSG_ANNOUNCE = 0x0411, // S text
+ PCMSG_PRIVMSG = 0x0412, // S user, S text
+ PCMSG_WHO = 0x0415, // -
+ CPMSG_WHO_RESPONSE = 0x0416, // { S user }
+
+ // -- Channeling
+ CPMSG_CHANNEL_EVENT = 0x0430, // W channel, B event, S info
+ PCMSG_ENTER_CHANNEL = 0x0440, // S channel, S password
+ CPMSG_ENTER_CHANNEL_RESPONSE = 0x0441, // B error, W id, S name, S topic, S userlist
+ PCMSG_QUIT_CHANNEL = 0x0443, // W channel id
+ CPMSG_QUIT_CHANNEL_RESPONSE = 0x0444, // B error, W channel id
+ PCMSG_LIST_CHANNELS = 0x0445, // -
+ CPMSG_LIST_CHANNELS_RESPONSE = 0x0446, // S names, W number of users
+ PCMSG_LIST_CHANNELUSERS = 0x0460, // S channel
+ CPMSG_LIST_CHANNELUSERS_RESPONSE = 0x0461, // S channel, { S user, B mode }
+ PCMSG_TOPIC_CHANGE = 0x0462, // W channel id, S topic
+ // -- User modes
+ PCMSG_USER_MODE = 0x0465, // W channel id, S name, B mode
+ PCMSG_KICK_USER = 0x0466, // W channel id, S name
+
+ // Inter-server
+ GAMSG_REGISTER = 0x0500, // S address, W port, S password, D items db revision, { W map id }*
+ AGMSG_REGISTER_RESPONSE = 0x0501, // C item version, C password response, { S globalvar_key, S globalvar_value }
+ AGMSG_ACTIVE_MAP = 0x0502, // W map id, W Number of mapvar_key mapvar_value sent, { S mapvar_key, S mapvar_value }, W Number of map items, { D item Id, W amount, W posX, W posY }
+ AGMSG_PLAYER_ENTER = 0x0510, // B*32 token, D id, S name, serialised character data
+ GAMSG_PLAYER_DATA = 0x0520, // D id, serialised character data
+ GAMSG_REDIRECT = 0x0530, // D id
+ AGMSG_REDIRECT_RESPONSE = 0x0531, // D id, B*32 token, S game address, W game port
+ GAMSG_PLAYER_RECONNECT = 0x0532, // D id, B*32 token
+ GAMSG_PLAYER_SYNC = 0x0533, // serialised sync data
+ GAMSG_SET_VAR_CHR = 0x0540, // D id, S name, S value
+ GAMSG_GET_VAR_CHR = 0x0541, // D id, S name
+ AGMSG_GET_VAR_CHR_RESPONSE = 0x0542, // D id, S name, S value
+ //reserved GAMSG_SET_VAR_ACC = 0x0543, // D charid, S name, S value
+ //reserved GAMSG_GET_VAR_ACC = 0x0544, // D charid, S name
+ //reserved AGMSG_GET_VAR_ACC_RESPONSE = 0x0545, // D charid, S name, S value
+ GAMSG_SET_VAR_MAP = 0x0546, // D mapid, S name, S value
+ GAMSG_SET_VAR_WORLD = 0x0547, // S name, S value
+ AGMSG_SET_VAR_WORLD = 0x0548, // S name, S value
+ GAMSG_BAN_PLAYER = 0x0550, // D id, W duration
+ GAMSG_CHANGE_PLAYER_LEVEL = 0x0555, // D id, W level
+ GAMSG_CHANGE_ACCOUNT_LEVEL = 0x0556, // D id, W level
+ GAMSG_STATISTICS = 0x0560, // { W map id, W thing nb, W monster nb, W player nb, { D character id }* }*
+ CGMSG_CHANGED_PARTY = 0x0590, // D character id, D party id
+ GCMSG_REQUEST_POST = 0x05A0, // D character id
+ CGMSG_POST_RESPONSE = 0x05A1, // D receiver id, { S sender name, S letter, W num attachments { W attachment item id, W quantity } }
+ GCMSG_STORE_POST = 0x05A5, // D sender id, S receiver name, S letter, { W attachment item id, W quantity }
+ CGMSG_STORE_POST_RESPONSE = 0x05A6, // D id, B error
+ GAMSG_TRANSACTION = 0x0600, // D character id, D action, S message
+ GAMSG_CREATE_ITEM_ON_MAP = 0x0601, // D map id, D item id, W amount, W pos x, W pos y
+ GAMSG_REMOVE_ITEM_ON_MAP = 0x0602, // D map id, D item id, W amount, W pos x, W pos y
+
+ XXMSG_INVALID = 0x7FFF
+};
+
+// Generic return values
+
+enum {
+ ERRMSG_OK = 0, // everything is fine
+ ERRMSG_FAILURE, // the action failed
+ ERRMSG_NO_LOGIN, // the user is not yet logged
+ ERRMSG_NO_CHARACTER_SELECTED, // the user needs a character
+ ERRMSG_INSUFFICIENT_RIGHTS, // the user is not privileged
+ ERRMSG_INVALID_ARGUMENT, // part of the received message was invalid
+ ERRMSG_EMAIL_ALREADY_EXISTS, // The Email Address already exists
+ ERRMSG_ALREADY_TAKEN, // name used was already taken
+ ERRMSG_SERVER_FULL, // the server is overloaded
+ ERRMSG_TIME_OUT, // data failed to arrive in due time
+ ERRMSG_LIMIT_REACHED, // limit reached
+ ERRMSG_ADMINISTRATIVE_LOGOFF // kicked by server administrator
+};
+
+// used in AGMSG_REGISTER_RESPONSE to show state of item db
+enum {
+ DATA_VERSION_OK = 0x00,
+ DATA_VERSION_OUTDATED = 0x01
+};
+
+// used in AGMSG_REGISTER_RESPNSE to show if password was accepted
+enum {
+ PASSWORD_OK = 0x00,
+ PASSWORD_BAD = 0x01
+};
+
+// used to identify part of sync message
+enum {
+ SYNC_CHARACTER_POINTS = 0x01, // D charId, D charPoints, D corrPoints
+ SYNC_CHARACTER_ATTRIBUTE = 0x02, // D charId, D attrId, DF base, DF mod
+ SYNC_CHARACTER_SKILL = 0x03, // D charId, B skillId, D skill value
+ SYNC_ONLINE_STATUS = 0x04 // D charId, B 0 = offline, 1 = online
+};
+
+// Login specific return values
+enum {
+ LOGIN_INVALID_VERSION = 0x40, // the user is using an incompatible protocol
+ LOGIN_INVALID_TIME = 0x50, // the user tried logging in too fast
+ LOGIN_BANNED // the user is currently banned
+};
+
+// Account register specific return values
+enum {
+ REGISTER_INVALID_VERSION = 0x40, // the user is using an incompatible protocol
+ REGISTER_EXISTS_USERNAME, // there already is an account with this username
+ REGISTER_EXISTS_EMAIL, // there already is an account with this email address
+ REGISTER_CAPTCHA_WRONG // user didn't solve the captcha correctly
+};
+
+// Character creation specific return values
+enum {
+ CREATE_INVALID_HAIRSTYLE = 0x40,
+ CREATE_INVALID_HAIRCOLOR,
+ CREATE_INVALID_GENDER,
+ CREATE_ATTRIBUTES_TOO_HIGH,
+ CREATE_ATTRIBUTES_TOO_LOW,
+ CREATE_ATTRIBUTES_OUT_OF_RANGE,
+ CREATE_EXISTS_NAME,
+ CREATE_TOO_MUCH_CHARACTERS,
+ CREATE_INVALID_SLOT
+};
+
+// Character attribute modification specific return value
+enum AttribmodResponseCode {
+ ATTRIBMOD_OK = ERRMSG_OK,
+ ATTRIBMOD_INVALID_ATTRIBUTE = 0x40,
+ ATTRIBMOD_NO_POINTS_LEFT,
+ ATTRIBMOD_DENIED
+};
+
+// Object type enumeration
+enum ThingType
+{
+ // A simple item.
+ OBJECT_ITEM = 0,
+ // An item that toggle map/quest actions (doors, switchs, ...)
+ // and can speak (map panels).
+ OBJECT_ACTOR,
+ // Non-Playable-Character is an actor capable of movement and maybe actions.
+ OBJECT_NPC,
+ // A monster (moving actor with AI. Should be able to toggle map/quest
+ // actions, too).
+ OBJECT_MONSTER,
+ // A normal being.
+ OBJECT_CHARACTER,
+ // A effect to be shown.
+ OBJECT_EFFECT,
+ // Server-only object.
+ OBJECT_OTHER
+};
+
+// Moving object flags
+enum {
+ // Payload contains the current position.
+ MOVING_POSITION = 1,
+ // Payload contains the destination.
+ MOVING_DESTINATION = 2
+};
+
+// Chat errors return values
+enum {
+ CHAT_USING_BAD_WORDS = 0x40,
+ CHAT_UNHANDLED_COMMAND
+};
+
+// Chat channels event values
+enum {
+ CHAT_EVENT_NEW_PLAYER = 0,
+ CHAT_EVENT_LEAVING_PLAYER,
+ CHAT_EVENT_TOPIC_CHANGE,
+ CHAT_EVENT_MODE_CHANGE,
+ CHAT_EVENT_KICKED_PLAYER
+};
+
+// Guild member event values
+enum {
+ GUILD_EVENT_NEW_PLAYER = 0,
+ GUILD_EVENT_LEAVING_PLAYER,
+ GUILD_EVENT_ONLINE_PLAYER,
+ GUILD_EVENT_OFFLINE_PLAYER
+};
+
+/**
+ * Moves enum for beings and actors for others players vision.
+ * WARNING: Has to be in sync with the same enum in the Being class
+ * of the client!
+ */
+enum BeingAction
+{
+ STAND,
+ WALK,
+ ATTACK,
+ SIT,
+ DEAD,
+ HURT
+};
+
+/**
+ * Moves enum for beings and actors for others players attack types.
+ * WARNING: Has to be in sync with the same enum in the Being class
+ * of the client!
+ */
+enum AttackType
+{
+ HIT = 0x00,
+ CRITICAL = 0x0a,
+ MULTI = 0x08,
+ REFLECT = 0x04,
+ FLEE = 0x0b
+};
+
+/**
+ * Beings and actors directions
+ * WARNING: Has to be in sync with the same enum in the Being class
+ * of the client!
+ */
+enum BeingDirection
+{
+ DOWN = 1,
+ LEFT = 2,
+ UP = 4,
+ RIGHT = 8
+};
+
+/**
+ * Beings Genders
+ * WARNING: Has to be in sync with the same enum in the Being class
+ * of the client!
+ */
+enum BeingGender
+{
+ GENDER_MALE = 0,
+ GENDER_FEMALE,
+ GENDER_UNSPECIFIED
+};
+
+} // namespace ManaServ
+
+#endif // MANASERV_PROTOCOL_H
diff --git a/src/game-server/accountconnection.cpp b/src/game-server/accountconnection.cpp index a6dc552f..36f30f33 100644 --- a/src/game-server/accountconnection.cpp +++ b/src/game-server/accountconnection.cpp @@ -26,6 +26,7 @@ #include "game-server/map.h" #include "game-server/mapcomposite.h" #include "game-server/mapmanager.h" +#include "game-server/item.h" #include "game-server/itemmanager.h" #include "game-server/postman.h" #include "game-server/quest.h" @@ -158,18 +159,45 @@ void AccountConnection::processMessage(MessageIn &msg) case AGMSG_ACTIVE_MAP: { - int id = msg.readInt16(); - if (MapManager::activateMap(id)) + int mapId = msg.readInt16(); + if (MapManager::activateMap(mapId)) { - // set map variables - MapComposite *m = MapManager::getMap(id); - while (msg.getUnreadLength()) + // Set map variables + MapComposite *m = MapManager::getMap(mapId); + int mapVarsNumber = msg.readInt16(); + for(int i = 0; i < mapVarsNumber; ++i) { std::string key = msg.readString(); std::string value = msg.readString(); if (!key.empty() && !value.empty()) - { m->setVariableFromDbserver(key, value); + } + + // Recreate potential persistent floor items + LOG_DEBUG("Recreate persistant items on map " << mapId); + int floorItemsNumber = msg.readInt16(); + + for(int i = 0; i < floorItemsNumber; i += 4) + { + int itemId = msg.readInt32(); + int amount = msg.readInt16(); + int posX = msg.readInt16(); + int posY = msg.readInt16(); + + if (ItemClass *ic = itemManager->getItem(itemId)) + { + Item *item = new Item(ic, amount); + item->setMap(m); + Point dst(posX, posY); + item->setPosition(dst); + + if (!GameState::insertOrDelete(item)) + { + // The map is full. + LOG_WARN("Couldn't add floor item(s) " << itemId + << " into map " << mapId); + return; + } } } } @@ -469,3 +497,27 @@ void AccountConnection::sendTransaction(int id, int action, const std::string &m msg.writeString(message); send(msg); } + +void AccountConnection::createFloorItems(int mapId, int itemId, int amount, + int posX, int posY) +{ + MessageOut msg(GAMSG_CREATE_ITEM_ON_MAP); + msg.writeInt32(mapId); + msg.writeInt32(itemId); + msg.writeInt16(amount); + msg.writeInt16(posX); + msg.writeInt16(posY); + send(msg); +} + +void AccountConnection::removeFloorItems(int mapId, int itemId, int amount, + int posX, int posY) +{ + MessageOut msg(GAMSG_REMOVE_ITEM_ON_MAP); + msg.writeInt32(mapId); + msg.writeInt32(itemId); + msg.writeInt16(amount); + msg.writeInt16(posX); + msg.writeInt16(posY); + send(msg); +} diff --git a/src/game-server/accountconnection.h b/src/game-server/accountconnection.h index 4e763158..a144a1d1 100644 --- a/src/game-server/accountconnection.h +++ b/src/game-server/accountconnection.h @@ -160,6 +160,21 @@ class AccountConnection : public Connection void updateOnlineStatus(int charId, bool online); /** + * Adds floor items info on database. + * + * This is used to make them potentially persistent between two server + * restart. + */ + void createFloorItems(int mapId, int itemId, int amount, + int posX, int posY); + + /** + * Remove floor items from the database + */ + void removeFloorItems(int mapId, int itemId, int amount, + int posX, int posY); + + /** * Send transaction to account server */ void sendTransaction(int id, int action, const std::string &message); diff --git a/src/game-server/attributemanager.cpp b/src/game-server/attributemanager.cpp index 31566bed..bb307846 100644 --- a/src/game-server/attributemanager.cpp +++ b/src/game-server/attributemanager.cpp @@ -97,7 +97,10 @@ bool AttributeManager::isAttributeDirectlyModifiable(int id) const ModifierLocation AttributeManager::getLocation(const std::string &tag) const { - return mTagMap.at(tag); + if (mTagMap.find(tag) != mTagMap.end()) + return mTagMap.at(tag); + else + return ModifierLocation(0, 0); } const std::string *AttributeManager::getTag(const ModifierLocation &location) const diff --git a/src/game-server/autoattack.h b/src/game-server/autoattack.h index 2e891fa9..5995d248 100644 --- a/src/game-server/autoattack.h +++ b/src/game-server/autoattack.h @@ -23,18 +23,8 @@ #include <cstddef> #include <list> -#include <limits> -/** - * Methods of damage calculation - */ -enum DamageType -{ - DAMAGE_PHYSICAL = 0, - DAMAGE_MAGICAL, - DAMAGE_DIRECT, - DAMAGE_OTHER = -1 -}; +#include "common/defines.h" /** * Structure that describes the severity and nature of an attack a being can @@ -42,28 +32,30 @@ enum DamageType */ struct Damage { + unsigned int skill; /**< Skill used by source (needed for exp calculation) */ unsigned short base; /**< Base amount of damage. */ unsigned short delta; /**< Additional damage when lucky. */ unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */ unsigned char element; /**< Elemental damage. */ DamageType type; /**< Damage type: Physical or magical? */ - unsigned trueStrike : 1; /**< Override dodge calculation */ - std::list<size_t> usedSkills; /**< Skills used by source (needed for exp calculation) */ - unsigned short range; /**< Maximum distance that this attack can be used from */ + bool trueStrike; /**< Override dodge calculation */ + unsigned short range; /**< Maximum distance that this attack can be used from, in pixels */ - Damage(unsigned short base, + Damage(unsigned int skill, + unsigned short base, unsigned short delta, unsigned short cth, unsigned char element, DamageType type = DAMAGE_OTHER, - unsigned short range = std::numeric_limits<unsigned short>::max()) - : base(base) - , delta(delta) - , cth(cth) - , element(element) - , type(type) - , trueStrike(false) - , range(range) + unsigned short range = DEFAULT_TILE_LENGTH): + skill(skill), + base(base), + delta(delta), + cth(cth), + element(element), + type(type), + trueStrike(false), + range(range) {} }; @@ -74,11 +66,11 @@ struct Damage class AutoAttack { public: - AutoAttack(Damage &damage, unsigned int delay, unsigned int warmup) - : mDamage(damage) - , mTimer(0) - , mAspd(delay) - , mWarmup(warmup && warmup < delay ? warmup : delay >> 2) + AutoAttack(Damage &damage, unsigned int warmup, unsigned int cooldown): + mDamage(damage), + mTimer(0), + mAspd(cooldown), + mWarmup(warmup && warmup < cooldown ? warmup : cooldown >> 2) {} unsigned short getTimer() const { return mTimer; } diff --git a/src/game-server/buysell.cpp b/src/game-server/buysell.cpp index a9658546..78c2bfe0 100644 --- a/src/game-server/buysell.cpp +++ b/src/game-server/buysell.cpp @@ -72,9 +72,9 @@ int BuySell::registerPlayerItems() // We parse the player inventory and add all item // in a sell list. - const Possessions &charPoss = mChar->getPossessions(); - for (InventoryData::const_iterator it = charPoss.inventory.begin(), - it_end = charPoss.inventory.end(); it != it_end; ++it) + const InventoryData &inventoryData = mChar->getPossessions().getInventory(); + for (InventoryData::const_iterator it = inventoryData.begin(), + it_end = inventoryData.end(); it != it_end; ++it) { unsigned int nb = it->second.amount; if (!nb) diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index ef001638..0cdd57b1 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -36,6 +36,7 @@ #include "game-server/gamehandler.h" #include "game-server/mapcomposite.h" #include "game-server/mapmanager.h" +#include "game-server/skillmanager.h" #include "game-server/state.h" #include "game-server/trade.h" #include "scripting/script.h" @@ -58,7 +59,7 @@ Character::Character(MessageIn &msg): mRechargePerSpecial(0), mSpecialUpdateNeeded(false), mDatabaseID(-1), - mGender(0), + mGender(GENDER_UNSPECIFIED), mHairStyle(0), mHairColor(0), mLevel(1), @@ -161,7 +162,8 @@ void Character::perform() { int damageBase = getModifiedAttribute(ATTR_STR); int damageDelta = damageBase / 2; - Damage knuckleDamage(damageBase, damageDelta, 2, ELEMENT_NEUTRAL, + Damage knuckleDamage(skillManager->getDefaultSkillId(), + damageBase, damageDelta, 2, ELEMENT_NEUTRAL, DAMAGE_PHYSICAL, (getSize() < DEFAULT_TILE_LENGTH) ? DEFAULT_TILE_LENGTH : getSize()); @@ -295,6 +297,19 @@ void Character::cancelTransaction() } } +void Character::setGender(int gender) +{ + switch (gender) + { + case GENDER_MALE: + case GENDER_FEMALE: + mGender = (BeingGender)gender; + break; + default: + mGender = GENDER_UNSPECIFIED; + } +} + Trade *Character::getTrading() const { return mTransaction == TRANS_TRADE diff --git a/src/game-server/character.h b/src/game-server/character.h index 83445916..30e39633 100644 --- a/src/game-server/character.h +++ b/src/game-server/character.h @@ -174,12 +174,11 @@ class Character : public Being void setDatabaseID(int id) { mDatabaseID = id; } /** Gets the gender of the character (male or female). */ - int getGender() const + BeingGender getGender() const { return mGender; } /** Sets the gender of the character (male or female). */ - void setGender(int gender) - { mGender = gender; } + void setGender(int gender); int getHairStyle() const { return mHairStyle; } void setHairStyle(int style) { mHairStyle = style; } @@ -443,7 +442,7 @@ class Character : public Being bool mSpecialUpdateNeeded; int mDatabaseID; /**< Character's database ID. */ - unsigned char mGender; /**< Gender of the character. */ + BeingGender mGender; /**< Gender of the character. */ unsigned char mHairStyle; /**< Hair Style of the character. */ unsigned char mHairColor; /**< Hair Color of the character. */ int mLevel; /**< Level of the character. */ diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp index 4c69da20..e6c4737f 100644 --- a/src/game-server/gamehandler.cpp +++ b/src/game-server/gamehandler.cpp @@ -472,11 +472,22 @@ void GameHandler::handlePickup(GameClient &client, MessageIn &message) { Item *item = static_cast< Item * >(o); ItemClass *ic = item->getItemClass(); + int amount = item->getAmount(); if (!Inventory(client.character).insert(ic->getDatabaseID(), - item->getAmount())) + amount)) { - GameState::remove(item); + + // We only do this when items are to be kept in memory + // between two server restart. + if (!Configuration::getValue("game_floorItemDecayTime", 0)) + { + // Remove the floor item from map + accountHandler->removeFloorItems(map->getID(), + ic->getDatabaseID(), + amount, x, y); + } + // log transaction std::stringstream str; str << "User picked up item " << ic->getDatabaseID() @@ -494,7 +505,7 @@ void GameHandler::handlePickup(GameClient &client, MessageIn &message) void GameHandler::handleUseItem(GameClient &client, MessageIn &message) { - const int slot = message.readInt8(); + const int slot = message.readInt16(); Inventory inv(client.character); if (ItemClass *ic = itemManager->getItem(inv.getItem(slot))) @@ -514,8 +525,8 @@ void GameHandler::handleUseItem(GameClient &client, MessageIn &message) void GameHandler::handleDrop(GameClient &client, MessageIn &message) { - const int slot = message.readInt8(); - const int amount = message.readInt8(); + const int slot = message.readInt16(); + const int amount = message.readInt16(); Inventory inv(client.character); if (ItemClass *ic = itemManager->getItem(inv.getItem(slot))) @@ -531,8 +542,20 @@ void GameHandler::handleDrop(GameClient &client, MessageIn &message) delete item; return; } - // log transaction + Point pt = client.character->getPosition(); + + // We store the item in database only when the floor items are meant + // to be persistent between two server restarts. + if (!Configuration::getValue("game_floorItemDecayTime", 0)) + { + // Create the floor item on map + accountHandler->createFloorItems(client.character->getMap()->getID(), + ic->getDatabaseID(), + amount, pt.x, pt.y); + } + + // log transaction std::stringstream str; str << "User dropped item " << ic->getDatabaseID() << " at " << pt.x << "x" << pt.y; @@ -552,22 +575,22 @@ void GameHandler::handleWalk(GameClient &client, MessageIn &message) void GameHandler::handleEquip(GameClient &client, MessageIn &message) { - const int slot = message.readInt8(); + const int slot = message.readInt16(); Inventory(client.character).equip(slot); } void GameHandler::handleUnequip(GameClient &client, MessageIn &message) { - const int slot = message.readInt8(); + const int slot = message.readInt16(); if (slot >= 0 && slot < INVENTORY_SLOTS) Inventory(client.character).unequip(slot); } void GameHandler::handleMoveItem(GameClient &client, MessageIn &message) { - const int slot1 = message.readInt8(); - const int slot2 = message.readInt8(); - const int amount = message.readInt8(); + const int slot1 = message.readInt16(); + const int slot2 = message.readInt16(); + const int amount = message.readInt16(); Inventory(client.character).move(slot1, slot2, amount); // log transaction diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index d7baedf2..e486f7c0 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -25,193 +25,15 @@ #include "game-server/inventory.h" #include "game-server/item.h" #include "game-server/itemmanager.h" +#include "game-server/state.h" #include "net/messageout.h" #include "utils/logger.h" -Inventory::Inventory(Character *p, bool d): - mPoss(&p->getPossessions()), mInvMsg(GPMSG_INVENTORY), - mEqmMsg(GPMSG_EQUIP), mCharacter(p), mDelayed(d) +Inventory::Inventory(Character *p): + mPoss(&p->getPossessions()), mCharacter(p) { } -Inventory::~Inventory() -{ - commit(false); -} - -void Inventory::restart() -{ - mInvMsg.clear(); - mInvMsg.writeInt16(GPMSG_INVENTORY); -} - -void Inventory::cancel() -{ - assert(mDelayed); - Possessions &poss = mCharacter->getPossessions(); - if (mPoss != &poss) - { - delete mPoss; - mPoss = &poss; - } - restart(); -} - -void Inventory::commit(bool doRestart) -{ - Possessions &poss = mCharacter->getPossessions(); - /* Sends changes, whether delayed or not. */ - if (mInvMsg.getLength() > 2) - { - /* Send the message to the client directly. Perhaps this should be - done through an update flag, too? */ - gameHandler->sendTo(mCharacter, mInvMsg); - } - if (mPoss != &poss) - { - if (mDelayed) - { - /* - * Search for any and all changes to equipment. - * Search through equipment for changes between - * old and new equipment. - * Send changes directly when there is a change. - * Even when equipment references to invy slots are the same, - * it still needs to be searched for changes - * to the internal equiment slot usage. - * This is probably the worst part of doing this in delayed mode. - */ - IdSlotMap oldEquip, newEquip; - { - EquipData::const_iterator it1, it2, it1_end, it2_end; - for (it1 = mPoss->equipSlots.begin(), - it1_end = mPoss->equipSlots.end(); - it1 != it1_end; - ++it1) - { -#ifdef INV_CONST_BOUND_DEBUG - IdSlotMap::const_iterator temp2, temp = -#endif - newEquip.insert( - newEquip.upper_bound(it1->second), - std::make_pair(it1->second, it1->first)); -#ifdef INV_CONST_BOUND_DEBUG - if (temp != - --(temp2 = newEquip.upper_bound(it1->second))) - throw; -#endif - } - for (it2 = poss.equipSlots.begin(), - it2_end = poss.equipSlots.end(); - it2 != it2_end; - ++it2) - oldEquip.insert( - oldEquip.upper_bound(it2->second), - std::make_pair(it2->second, it2->first)); - } - { - IdSlotMap::const_iterator it1 = newEquip.begin(), - it2 = oldEquip.begin(), - it1_end = newEquip.end(), - it2_end = oldEquip.end(), - temp1, temp2; - while (it1 != it1_end || it2 != it2_end) - { - if (it1 == it1_end) - { - if (it2 == it2_end) - break; - equip_sub(0, it1); - } - else if (it2 == it2_end) - equip_sub(newEquip.count(it2->first), it2); - else if (it1->first == it2->first) - { - double invSlot = it1->first; - while ((it1 != it1_end && it1->first == invSlot) || - (it2 != it2_end && it2->first == invSlot)) - { - /* - * Item is still equipped, but need to check - * that the slots didn't change. - */ - if (it1->second == it2->second) - { - // No change. - ++it1; - ++it2; - continue; - } - unsigned int itemId = - mPoss->inventory.at(it1->first).itemId; - changeEquipment(itemId, itemId); - break; - } - } - else if (it1->first > it2->first) - equip_sub(newEquip.count(it2->first), it2); - else // it1->first < it2->first - equip_sub(0, it1); - } - } - } - poss = *mPoss; - delete mPoss; - mPoss = &poss; - } - - /* Update server sided states if in delayed mode. If we are not in - delayed mode, the server sided states already reflect the changes - that have just been sent to the client. */ - - if (mEqmMsg.getLength() > 2) - gameHandler->sendTo(mCharacter, mEqmMsg); - - if (doRestart) - restart(); -} - -void Inventory::equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it) -{ - const unsigned int invSlot = it->first; - unsigned int count = 0, eqSlot = it->second; - mEqmMsg.writeInt16(invSlot); - mEqmMsg.writeInt8(newCount); - do { - if (newCount) - { - if (it->second != eqSlot) - { - mEqmMsg.writeInt8(eqSlot); - mEqmMsg.writeInt8(count); - count = 1; - eqSlot = it->second; - } - ++count; - } - if (itemManager->isEquipSlotVisible(it->second)) - mCharacter->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); - } while ((++it)->first == invSlot); - if (count) - { - mEqmMsg.writeInt8(eqSlot); - mEqmMsg.writeInt8(count); - } - mEqmMsg.writeInt16(invSlot); - changeEquipment(newCount ? 0 : mPoss->inventory.at(invSlot).itemId, - newCount ? mPoss->inventory.at(invSlot).itemId : 0); -} - -void Inventory::prepare() -{ - if (!mDelayed) - return; - - Possessions *poss = &mCharacter->getPossessions(); - if (mPoss == poss) - mPoss = new Possessions(*poss); -} - void Inventory::sendFull() const { /* Sends all the information needed to construct inventory @@ -233,8 +55,9 @@ void Inventory::sendFull() const k != k_end; ++k) { - m.writeInt8(k->first); // equip slot - m.writeInt16(k->second); // inventory slot + m.writeInt16(k->first); // Equip slot id + m.writeInt16(k->second.itemId); // Item id + m.writeInt16(k->second.itemInstance); // Item instance } gameHandler->sendTo(mCharacter, m); @@ -242,26 +65,23 @@ void Inventory::sendFull() const void Inventory::initialize() { - assert(!mDelayed); - - InventoryData::iterator it1; - EquipData::const_iterator it2, it2_end = mPoss->equipSlots.end(); /* - * Apply all exists triggers. - * Remove unknown inventory items. + * Construct a set of item Ids to keep track of duplicate item Ids. */ - - typedef std::set<unsigned int> ItemIdSet; - ItemIdSet itemIds; + std::set<unsigned int> itemIds; /* * Construct a set of itemIds to keep track of duplicate itemIds. */ + InventoryData::iterator it1; for (it1 = mPoss->inventory.begin(); it1 != mPoss->inventory.end();) { ItemClass *item = itemManager->getItem(it1->second.itemId); if (item) { + // If the insertion succeeded, it's the first time we're + // adding the item in the inventory. Hence, we can trigger + // item presence in inventory effect. if (itemIds.insert(it1->second.itemId).second) item->useTrigger(mCharacter, ITT_IN_INVY); ++it1; @@ -278,32 +98,42 @@ void Inventory::initialize() itemIds.clear(); - typedef std::set<unsigned int> SlotSet; - SlotSet equipment; - /* - * Construct a set of slot references from equipment to keep track of - * duplicate slot usage. + * Equipment effects can be cumulative if more than one item instance + * is equipped, but we check to trigger the item presence in equipment + * effect only based on the first item instance insertion. */ - for (it2 = mPoss->equipSlots.begin(); it2 != it2_end; ++it2) + EquipData::iterator it2; + for (it2 = mPoss->equipSlots.begin(); it2 != mPoss->equipSlots.end();) { - if (equipment.insert(it2->second).second) + ItemClass *item = itemManager->getItem(it2->second.itemId); + if (item) { - /* - * Perform checks for equipped items - * Check that all needed slots are available. - */ - // TODO - Not needed for testing everything else right now, but - // will be needed for production - /* - * Apply all equip triggers. - */ - itemManager->getItem(mPoss->inventory.at(it2->second).itemId) - ->useTrigger(mCharacter, ITT_EQUIP); + // TODO: Check equip conditions. + // If not all needed slots are there, put the item back + // in the inventory. + } + else + { + LOG_WARN("Equipment: deleting unknown item id " + << it2->second.itemId << " from the equipment of '" + << mCharacter->getName() + << "'!"); + mPoss->equipSlots.erase(it2++); + continue; } - } - equipment.clear(); + /* + * Apply all equip triggers at first item instance insertion + */ + if (itemIds.insert(it2->second.itemInstance).second) + { + itemManager->getItem(it2->second.itemId) + ->useTrigger(mCharacter, ITT_EQUIP); + } + + ++it2; + } checkInventorySize(); } @@ -313,9 +143,9 @@ void Inventory::checkInventorySize() /* * Check that the inventory size is greater than or equal to the size * needed. - * If not, forcibly delete (drop?) items from the end until it is. + * If not, forcibly drop items from the end until it is. * Check that inventory capacity is greater than or equal to zero. - * If not, forcibly delete (drop?) items from the end until it is. + * If not, forcibly drop items from the end until it is. */ while (mPoss->inventory.size() > INVENTORY_SLOTS || mCharacter->getModifiedAttribute(ATTR_INV_CAPACITY) < 0) @@ -329,9 +159,24 @@ void Inventory::checkInventorySize() << "' of character '" << mCharacter->getName() << "'!"); - // FIXME Should probably be dropped rather than deleted. + + // Remove the items from inventory removeFromSlot(mPoss->inventory.rbegin()->first, mPoss->inventory.rbegin()->second.amount); + + // Drop them on the floor + ItemClass *ic = itemManager->getItem(mPoss->inventory.rbegin()->first); + int nb = mPoss->inventory.rbegin()->second.amount; + Item *item = new Item(ic, nb); + item->setMap(mCharacter->getMap()); + item->setPosition(mCharacter->getPosition()); + if (!GameState::insert(item)) + { + // Warn about drop failure + LOG_WARN("Impossible to drop " << nb << " item(s) id: " + << ic->getDatabaseID() << " for character: '" + << mCharacter->getName() << "'!"); + } } } @@ -343,13 +188,19 @@ unsigned int Inventory::getItem(unsigned int slot) const unsigned int Inventory::insert(unsigned int itemId, unsigned int amount) { - unsigned int maxPerSlot = itemManager->getItem(itemId)->getMaxPerSlot(); if (!itemId || !amount) return 0; - prepare(); + + MessageOut invMsg(GPMSG_INVENTORY); + unsigned int maxPerSlot = itemManager->getItem(itemId)->getMaxPerSlot(); + + LOG_DEBUG("Inventory: Inserting " << amount << " item(s) Id: " << itemId + << " for character '" << mCharacter->getName() << "'."); + InventoryData::iterator it, it_end = mPoss->inventory.end(); // Add to slots with existing items of this type first. for (it = mPoss->inventory.begin(); it != it_end; ++it) + { if (it->second.itemId == itemId) { // If the slot is full, try the next slot @@ -357,31 +208,35 @@ unsigned int Inventory::insert(unsigned int itemId, unsigned int amount) continue; // Add everything that'll fit to the stack - unsigned short spaceleft = maxPerSlot - it->second.amount; - if (spaceleft >= amount) + unsigned short spaceLeft = maxPerSlot - it->second.amount; + if (spaceLeft >= amount) { it->second.amount += amount; amount = 0; + LOG_DEBUG("Everything inserted at slot id: " << it->first); } else { - it->second.amount += spaceleft; - amount -= spaceleft; + it->second.amount += spaceLeft; + amount -= spaceLeft; + LOG_DEBUG(spaceLeft << " item(s) inserted at slot id: " + << it->first); } - mInvMsg.writeInt16(it->first); - mInvMsg.writeInt16(itemId); - mInvMsg.writeInt16(it->second.amount); + invMsg.writeInt16(it->first); + invMsg.writeInt16(itemId); + invMsg.writeInt16(it->second.amount); if (!amount) - return 0; + break; } + } int slot = 0; // We still have some left, so add to blank slots. for (it = mPoss->inventory.begin();; ++it) { if (!amount) - return 0; + break; int lim = (it == it_end) ? INVENTORY_SLOTS : it->first; while (amount && slot < lim) { @@ -389,14 +244,20 @@ unsigned int Inventory::insert(unsigned int itemId, unsigned int amount) mPoss->inventory[slot].itemId = itemId; mPoss->inventory[slot].amount = additions; amount -= additions; - mInvMsg.writeInt16(slot++); // Last read, so also increment - mInvMsg.writeInt16(itemId); - mInvMsg.writeInt16(additions); + LOG_DEBUG(additions << " item(s) inserted at slot id: " << slot); + invMsg.writeInt16(slot++); // Last read, so also increment + invMsg.writeInt16(itemId); + invMsg.writeInt16(additions); } ++slot; // Skip the slot that the iterator points to - if (it == it_end) break; + if (it == it_end) + break; } + // Send that first, before checking potential removals + if (invMsg.getLength() > 2) + gameHandler->sendTo(mCharacter, invMsg); + checkInventorySize(); return amount; @@ -413,75 +274,73 @@ unsigned int Inventory::count(unsigned int itemId) const return nb; } -unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool force) +unsigned int Inventory::remove(unsigned int itemId, unsigned int amount) { - prepare(); - bool inv = false, - eq = !itemManager->getItem(itemId)->getItemEquipData().empty(); + if (!itemId || !amount) + return amount; + + LOG_DEBUG("Inventory: Request remove of " << amount << " item(s) id: " + << itemId << " for character: '" << mCharacter->getName() + << "'."); + + MessageOut invMsg(GPMSG_INVENTORY); + bool triggerLeaveInventory = true; for (InventoryData::iterator it = mPoss->inventory.begin(), - it_end = mPoss->inventory.end(); - it != it_end; ++it) + it_end = mPoss->inventory.end(); it != it_end; ++it) + { if (it->second.itemId == itemId) { if (amount) { - if (eq) - { - // If the item is equippable, - // we have additional checks to make. - bool ch = false; - for (EquipData::iterator it2 = mPoss->equipSlots.begin(), - it2_end = mPoss->equipSlots.end(); - it2 != it2_end; - ++it2) - if (it2->second == it->first) - { - if (force) - unequip(it2); - else - ch = inv = true; - break; - } - if (ch && !force) - continue; - } unsigned int sub = std::min(amount, it->second.amount); amount -= sub; it->second.amount -= sub; - mInvMsg.writeInt16(it->first); + invMsg.writeInt16(it->first); if (it->second.amount) { - mInvMsg.writeInt16(it->second.itemId); - mInvMsg.writeInt16(it->second.amount); + invMsg.writeInt16(it->second.itemId); + invMsg.writeInt16(it->second.amount); // Some still exist, and we have none left to remove, so // no need to run leave invy triggers. if (!amount) - return 0; + triggerLeaveInventory = false; + LOG_DEBUG("Slot id: " << it->first << " has now " + << it->second.amount << "item(s)."); } else { - mInvMsg.writeInt16(0); + invMsg.writeInt16(0); mPoss->inventory.erase(it); + LOG_DEBUG("Slot id: " << it->first << " is now empty."); } } else + { // We found an instance of them existing and have none left to // remove, so no need to run leave invy triggers. - return 0; + triggerLeaveInventory = false; + } } - if (force) + } + + if (triggerLeaveInventory) itemManager->getItem(itemId)->useTrigger(mCharacter, ITT_LEAVE_INVY); - // Rather inefficient, but still usable for now assuming small invy size. - // FIXME - return inv && !force ? remove(itemId, amount, true) : amount; + + if (invMsg.getLength() > 2) + gameHandler->sendTo(mCharacter, invMsg); + + return amount; } unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned int amount) { + LOG_DEBUG(amount << " item(s) requested to move from: " << slot1 << " to " + << slot2 << " for character: '" << mCharacter->getName() << "'."); + if (!amount || slot1 == slot2 || slot2 >= INVENTORY_SLOTS) return amount; - prepare(); + InventoryData::iterator it1 = mPoss->inventory.find(slot1), it2 = mPoss->inventory.find(slot2), inv_end = mPoss->inventory.end(); @@ -489,14 +348,7 @@ unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, if (it1 == inv_end) return amount; - EquipData::iterator it, it_end = mPoss->equipSlots.end(); - for (it = mPoss->equipSlots.begin(); - it != it_end; - ++it) - if (it->second == slot1) - // Bad things will happen when you can stack multiple equippable - // items in the same slot anyway. - it->second = slot2; + MessageOut invMsg(GPMSG_INVENTORY); unsigned int nb = std::min(amount, it1->second.amount); if (it2 == inv_end) @@ -510,63 +362,113 @@ unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, it1->second.amount -= nb; amount -= nb; - mInvMsg.writeInt16(slot1); // Slot + //Save the itemId in case of deletion of the iterator + unsigned int itemId = it1->second.itemId; + invMsg.writeInt16(slot1); // Slot if (it1->second.amount) { - mInvMsg.writeInt16(it1->second.itemId); // Item Id - mInvMsg.writeInt16(it1->second.amount); // Amount + invMsg.writeInt16(it1->second.itemId); // Item Id + invMsg.writeInt16(it1->second.amount); // Amount + LOG_DEBUG("Left " << amount << " item(s) id:" + << it1->second.itemId << " into slot: " << slot1); } else { - mInvMsg.writeInt16(0); + invMsg.writeInt16(0); mPoss->inventory.erase(it1); + LOG_DEBUG("Slot: " << slot1 << " is now empty."); } - mInvMsg.writeInt16(slot2); // Slot - mInvMsg.writeInt16(it1->second.itemId); // Item Id (same as slot 1) - mInvMsg.writeInt16(nb); // Amount + invMsg.writeInt16(slot2); // Slot + invMsg.writeInt16(itemId); // Item Id (same as slot 1) + invMsg.writeInt16(nb); // Amount + LOG_DEBUG("Slot: " << slot2 << " has now " << nb << " of item id: " + << itemId); } else { // Slot2 exists. if (it2->second.itemId != it1->second.itemId) - return amount; // Cannot stack items of a different type. - nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot() - - it2->second.amount, - nb); - - it1->second.amount -= nb; - it2->second.amount += nb; - amount -= nb; - - mInvMsg.writeInt16(slot1); // Slot - if (it1->second.amount) - { - mInvMsg.writeInt16(it1->second.itemId); // Item Id - mInvMsg.writeInt16(it1->second.amount); // Amount + { + // Swap items when they are of a different type + // and when all the amount of slot 1 is moving onto slot 2. + if (amount >= it1->second.amount) + { + unsigned int itemId = it1->second.itemId; + unsigned int amount = it1->second.amount; + it1->second.itemId = it2->second.itemId; + it1->second.amount = it2->second.amount; + it2->second.itemId = itemId; + it2->second.amount = amount; + + // Sending swapped slots. + invMsg.writeInt16(slot1); + invMsg.writeInt16(it1->second.itemId); + invMsg.writeInt16(it1->second.amount); + invMsg.writeInt16(slot2); + invMsg.writeInt16(it2->second.itemId); + invMsg.writeInt16(it2->second.amount); + LOG_DEBUG("Swapping items in slots " << slot1 + << " and " << slot2); + } + else + { + // Cannot partially stack items of a different type. + LOG_DEBUG("Cannot move " << amount << " item(s) from slot " + << slot1 << " to " << slot2); + return amount; + } } - else + else // Same item type on slot 2. { - mInvMsg.writeInt16(0); - mPoss->inventory.erase(it1); + // Number of items moving + nb = std::min(itemManager->getItem( + it1->second.itemId)->getMaxPerSlot() + - it2->second.amount, nb); + + // If nothing can move, we can abort + if (!nb) + return amount; + + it1->second.amount -= nb; + it2->second.amount += nb; + amount -= nb; + + invMsg.writeInt16(slot1); // Slot + if (it1->second.amount) + { + invMsg.writeInt16(it1->second.itemId); // Item Id + invMsg.writeInt16(it1->second.amount); // Amount + } + else + { + invMsg.writeInt16(0); + mPoss->inventory.erase(it1); + } + invMsg.writeInt16(slot2); // Slot + invMsg.writeInt16(it2->second.itemId); // Item Id + invMsg.writeInt16(it2->second.amount); // Amount } - mInvMsg.writeInt16(slot2); // Slot - mInvMsg.writeInt16(it2->second.itemId); // Item Id - mInvMsg.writeInt16(it2->second.amount); // Amount } + + if (invMsg.getLength() > 2) + gameHandler->sendTo(mCharacter, invMsg); + return amount; } unsigned int Inventory::removeFromSlot(unsigned int slot, unsigned int amount) { - prepare(); - InventoryData::iterator it = mPoss->inventory.find(slot); // When the given slot doesn't exist, we can't remove anything if (it == mPoss->inventory.end()) return amount; - // Check if an item of the same class exists elsewhere in the inventory + LOG_DEBUG("Inventory: Request Removal of " << amount << " item(s) in slot: " + << slot << " for character: '" << mCharacter->getName() << "'."); + + MessageOut invMsg(GPMSG_INVENTORY); + // Check if an item of the same id exists elsewhere in the inventory bool exists = false; for (InventoryData::const_iterator it2 = mPoss->inventory.begin(), it2_end = mPoss->inventory.end(); @@ -579,38 +481,50 @@ unsigned int Inventory::removeFromSlot(unsigned int slot, unsigned int amount) break; } } - if (!exists && it->second.itemId) { - if (ItemClass *ic = itemManager->getItem(it->second.itemId)) - ic->useTrigger(mCharacter, ITT_LEAVE_INVY); - } + + // We check whether it's the last slot where we can find that item id. + bool lastSlotOfItemRemaining = false; + if (!exists && it->second.itemId) + lastSlotOfItemRemaining = true; unsigned int sub = std::min(amount, it->second.amount); amount -= sub; it->second.amount -= sub; - mInvMsg.writeInt16(it->first); + invMsg.writeInt16(it->first); if (it->second.amount) { - mInvMsg.writeInt16(it->second.itemId); - mInvMsg.writeInt16(it->second.amount); + invMsg.writeInt16(it->second.itemId); + invMsg.writeInt16(it->second.amount); } else { - mInvMsg.writeInt16(0); + invMsg.writeInt16(0); + + // The item(s) was(were) the last one(s) in the inventory. + if (lastSlotOfItemRemaining) + { + if (ItemClass *ic = itemManager->getItem(it->second.itemId)) + ic->useTrigger(mCharacter, ITT_LEAVE_INVY); + } mPoss->inventory.erase(it); } + + if (invMsg.getLength() > 2) + gameHandler->sendTo(mCharacter, invMsg); + return amount; } -void Inventory::changeEquipment(unsigned int oldId, unsigned int newId) +void Inventory::updateEquipmentTrigger(unsigned int oldId, unsigned int newId) { if (!oldId && !newId) return; - changeEquipment(oldId ? itemManager->getItem(oldId) : 0, + updateEquipmentTrigger(oldId ? itemManager->getItem(oldId) : 0, newId ? itemManager->getItem(newId) : 0); } -void Inventory::changeEquipment(ItemClass *oldI, ItemClass *newI) +void Inventory::updateEquipmentTrigger(ItemClass *oldI, ItemClass *newI) { // This should only be called when applying changes, either directly // in non-delayed mode or when the changes are committed in delayed mode. @@ -624,169 +538,272 @@ void Inventory::changeEquipment(ItemClass *oldI, ItemClass *newI) newI->useTrigger(mCharacter, ITT_EQUIP); } -bool Inventory::equip(int slot, bool override) +unsigned int Inventory::getNewEquipItemInstance() +{ + unsigned int itemInstance = 1; + + for (EquipData::const_iterator it = mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); it != it_end; ++it) + { + if (it->second.itemInstance == itemInstance) + { + ++itemInstance; + it = mPoss->equipSlots.begin(); + } + } + + return itemInstance; +} + +bool Inventory::checkEquipmentCapacity(unsigned int equipmentSlot, + unsigned int capacityRequested) { - if (mPoss->equipSlots.count(slot)) + int capacity = itemManager->getEquipSlotCapacity(equipmentSlot); + + // If the equipement slot doesn't exist, we can't equip on it. + if (capacity <= 0) return false; + + // Test whether the slot capacity requested is reached. + for (EquipData::const_iterator it = mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); it != it_end; ++it) + { + if (it->first == equipmentSlot) + { + if (it->second.itemInstance != 0) + { + capacity--; + } + } + } + + assert(capacity >= 0); // A should never happen case. + + if (capacity < (int)capacityRequested) + return false; + + return true; +} + +bool Inventory::equip(int inventorySlot) +{ + // Test inventory slot existence InventoryData::iterator it; - if ((it = mPoss->inventory.find(slot)) == mPoss->inventory.end()) + if ((it = mPoss->inventory.find(inventorySlot)) == mPoss->inventory.end()) + { + return false; + LOG_DEBUG("No existing item in inventory at slot: " << inventorySlot); + } + + // Test the equipment scripted requirements + if (!testEquipScriptRequirements(it->second.itemId)) + return false; + + // Test the equip requirements. If none, it's not an equipable item. + const ItemEquipRequirement &equipReq = + itemManager->getItem(it->second.itemId)->getItemEquipRequirement(); + if (!equipReq.equipSlotId) + { + LOG_DEBUG("No equip requirements for item id: " << it->second.itemId + << " at slot: " << inventorySlot); return false; - const ItemEquipsInfo &eq = itemManager->getItem(it->second.itemId) - ->getItemEquipData(); - if (eq.empty()) + } + + // List of potential unique itemInstances to unequip first. + std::set<unsigned int> equipInstancesToUnequipFirst; + + // We first check the equipment slots for: + // - 1. whether enough total equip slot space is available. + // - 2. whether some other equipment is to be unequipped first. + + // If not enough total space in the equipment slot is available, + // we cannot equip. + if (itemManager->getEquipSlotCapacity(equipReq.equipSlotId) + < equipReq.capacityRequired) + { + LOG_DEBUG("Not enough equip capacity at slot: " << equipReq.equipSlotId + << ", total available: " + << itemManager->getEquipSlotCapacity(equipReq.equipSlotId) + << ", required: " << equipReq.capacityRequired); return false; - ItemEquipInfo const *ovd = 0; - // Iterate through all possible combinations of slots - for (ItemEquipsInfo::const_iterator it2 = eq.begin(), - it2_end = eq.end(); it2 != it2_end; ++it2) + } + + // Test whether some item(s) is(are) to be unequipped first. + if (!checkEquipmentCapacity(equipReq.equipSlotId, + equipReq.capacityRequired)) { - // Iterate through this combination of slots. - /* - * 0 = all ok, slots free - * 1 = possible if other items are unequipped first - * 2 = impossible, requires too many slots - * even with other equipment being removed - */ - int fail = 0; - ItemEquipInfo::const_iterator it3, it3_end; - for (it3 = it2->begin(), - it3_end = it2->end(); - it3 != it3_end; - ++it3) + // And test whether the unequip action would succeed first. + if (testUnequipScriptRequirements(equipReq.equipSlotId) + && hasInventoryEnoughSpace(equipReq.equipSlotId)) { - // it3 -> { slot id, number required } - unsigned int max = itemManager->getMaxSlotsFromId(it3->first), - used = mPoss->equipSlots.count(it3->first); - if (max - used >= it3->second) - continue; - else if (max >= it3->second) + // Then, we unequip each iteminstance of the equip slot + for (EquipData::iterator iter = + mPoss->equipSlots.begin(); + iter != mPoss->equipSlots.end(); ++iter) { - fail |= 1; - if (override) - continue; - else - break; - } - else - { - fail |= 2; - break; + if (iter->first == equipReq.equipSlotId + && iter->second.itemInstance) + equipInstancesToUnequipFirst.insert( + iter->second.itemInstance); } } - switch (fail) + else + { + // Some non-unequippable equipment is to be unequipped first. + // Can be the case of cursed items, + // or when the inventory is full, for instance. + return false; + } + } + + // Potential Pre-unequipment process + for (std::set<unsigned int>::const_iterator it3 = + equipInstancesToUnequipFirst.begin(); + it3 != equipInstancesToUnequipFirst.end(); ++it3) + { + if (!unequip(*it3)) + { + // Something went wrong even when we tested the unequipment process. + LOG_WARN("Unable to unequip even when unequip was tested. " + "Character : " << mCharacter->getName() + << ", unequip slot: " << *it3); + return false; + } + } + + // Actually equip the item now that the requirements has met. + //W equip slot type count, W item id, { W equip slot, W capacity used}* + MessageOut equipMsg(GPMSG_EQUIP); + equipMsg.writeInt16(it->second.itemId); // Item Id + equipMsg.writeInt16(1); // Number of equip slot changed. + + // Compute an unique equip item Instance id (unicity is per character only.) + int itemInstance = getNewEquipItemInstance(); + + unsigned int capacityLeft = equipReq.capacityRequired; + unsigned int capacityUsed = 0; + // Apply equipment changes + for (EquipData::iterator it4 = mPoss->equipSlots.begin(), + it4_end = mPoss->equipSlots.end(); it4 != it4_end; ++it4) + { + if (!capacityLeft) + break; + + // We've found an existing equip slot + if (it4->first == equipReq.equipSlotId) { - case 0: - /* - * Clean fit. Equip and apply immediately. - */ - if (!mDelayed) { - mEqmMsg.writeInt16(slot); // Inventory slot - mEqmMsg.writeInt8(it2->size()); // Equip slot type count + // We've found an empty slot + if (it4->second.itemInstance == 0) + { + it4->second.itemId = it->second.itemId; + it4->second.itemInstance = itemInstance; + --capacityLeft; } - for (it3 = it2->begin(), - it3_end = it2->end(); - it3 != it3_end; - ++it3) + else // The slot is already in use. { - if (!mDelayed) { - mEqmMsg.writeInt8(it3->first); // Equip slot - mEqmMsg.writeInt8(it3->second); // How many are used - } - /* - * This bit can be somewhat inefficient, but is far better for - * average case assuming most equip use one slot max for each - * type and infrequently (<1/3) two of each type max. - * If the reader cares, you're more than welcome to add - * compile time options optimising for other usage. - * For now, this is adequate assuming `normal' usage. - */ - for (unsigned int i = 0; i < it3->second; ++i) - mPoss->equipSlots.insert( - std::make_pair(it3->first, slot)); + ++capacityUsed; } - if (!mDelayed) - changeEquipment(0, it->second.itemId); - return true; - case 1: - /* - * Definitions earlier in the item file have precedence (even if it - * means requiring unequipping more), so no need to store more - * than the first. - */ - if (override && !ovd) - ovd = &*it2; // Iterator -> object -> pointer. - break; - case 2: - default: - /* - * Since slots are currently static (and I don't see any reason to - * change this right now), something probably went wrong. - * The logic to catch this is here rather than in the item manager - * just in case non-static equip slots do want to be - * implemented later. This would not be a trivial task, - * however. - */ - LOG_WARN("Inventory - item '" << it->second.itemId << - "' cannot be equipped, even by unequipping other items!"); - break; } } - // We didn't find a clean equip. - if (ovd) + + // When there is still something to apply even when out of that loop, + // It means that the equip multimapis missing empty slots. + // Hence, we add them back + if(capacityLeft) { - /* - * We did find an equip that works if we unequip other items, - * and we can override. - * Process unequip triggers for all items we have to unequip. - * Process equip triggers for new item. - * Attempt to reequip any equipment we had to remove, - * but disallowing override. - */ + unsigned int maxCapacity = + itemManager->getEquipSlotCapacity(equipReq.equipSlotId); - // TODO - this would increase ease of use substatially, add as soon as - // there is time to do so. + // A should never happen case + assert(maxCapacity >= capacityUsed + capacityLeft); - return false; // Return true when this section is complete + while (capacityLeft) + { + EquipmentItem equipItem(it->second.itemId, itemInstance); + mPoss->equipSlots.insert( + std::make_pair<unsigned int, EquipmentItem> + (equipReq.equipSlotId, equipItem)); + --capacityLeft; + } } - /* - * We cannot equip, either because we could not find any valid equip process - * or because we found a dirty equip and weren't allowed to override. - */ - return false; -} -bool Inventory::unequip(EquipData::iterator it) -{ - return unequip(it->second, &it); + // Equip slot + equipMsg.writeInt16(equipReq.equipSlotId); + // Capacity used + equipMsg.writeInt16(equipReq.capacityRequired); + // Item instance + equipMsg.writeInt16(itemInstance); + + // New item trigger + updateEquipmentTrigger(0, it->second.itemId); + + // Remove item from inventory + removeFromSlot(inventorySlot, 1); + + gameHandler->sendTo(mCharacter, equipMsg); + + // Update look when necessary + checkLookchanges(equipReq.equipSlotId); + + return true; } -bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) +bool Inventory::unequip(unsigned int itemInstance) { - prepare(); - EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(), - it_end = mPoss->equipSlots.end(); - bool changed = false; + if (!itemInstance) + return false; - // Erase all equip entries that point to the given inventory slot - while (it != it_end) + MessageOut equipMsg(GPMSG_EQUIP); + equipMsg.writeInt16(0); // Item Id, useless in case of unequip. + + // The itemId to unequip + unsigned int itemId = 0; + unsigned int slotTypeId = 0; + + // Empties all equip entries that point to the given equipment slot + // The equipment slots should NEVER be erased after initialization! + for (EquipData::iterator it = mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); it != it_end; ++it) { - if (it->second == slot) - { - changed = true; - mPoss->equipSlots.erase(it++); - } - else + if (it->second.itemInstance == itemInstance && it->second.itemId) { - ++it; + // Add the item to the inventory list if not already present there + itemId = it->second.itemId; + it->second.itemId = 0; + it->second.itemInstance = 0; + + // We keep track of the slot type to be able to raise a potential + // change in the character sprite + slotTypeId = it->first; } } - if (changed && !mDelayed) - { - changeEquipment(mPoss->inventory.at(slot).itemId, 0); - mEqmMsg.writeInt16(slot); - mEqmMsg.writeInt8(0); - } + // When there were no corresponding item id, it means no item was to + // be unequipped. + if (!itemId) + return false; + + // Number of slot types touched, + equipMsg.writeInt16(1); + + // Move the item back to inventory. + insert(itemId, 1); - return changed; + equipMsg.writeInt16(itemInstance); + equipMsg.writeInt16(0); // Capacity used, set to 0 to unequip. + + gameHandler->sendTo(mCharacter, equipMsg); + + // Apply unequip trigger + updateEquipmentTrigger(itemId, 0); + + checkLookchanges(slotTypeId); + + return true; +} + +void Inventory::checkLookchanges(unsigned int slotTypeId) +{ + if (itemManager->isEquipSlotVisible(slotTypeId)) + mCharacter->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); } diff --git a/src/game-server/inventory.h b/src/game-server/inventory.h index 4a77ad93..547abdf0 100644 --- a/src/game-server/inventory.h +++ b/src/game-server/inventory.h @@ -35,30 +35,15 @@ class Inventory /** * Creates a view on the possessions of a character. - * @param delayed If the changes need to be cancelable. */ - Inventory(Character *, bool delayed = false); + Inventory(Character *); /** * Commits delayed changes if applicable. * Sends the update message to the client. */ - ~Inventory(); - - /** - * Commits changes. - * Exclusive to delayed mode. - * @param doRestart Whether to prepare the inventory for more changes - after this. If you are unsure, it is safe (though not - terribly efficient) to leave this as true. - */ - void commit(bool doRestart = true); - - /** - * Cancels changes. - * Exclusive to delayed mode. - */ - void cancel(); + ~Inventory() + {} /** * Sends complete inventory status to the client. @@ -73,25 +58,17 @@ class Inventory /** * Equips item from given inventory slot. - * @param slot The slot in which the target item is in. - * @param override Whether this item can unequip other items to equip - * itself. If true, items that are unequipped will be - * attempted to be reequipped, but with override disabled. + * @param inventorySlot The slot in which the target item is in. * @returns whether the item could be equipped. */ - bool equip(int slot, bool override = true); + bool equip(int inventorySlot); /** * Unequips item from given equipment slot. - * @param it Starting iterator. When the only parameter, also extracts - * slot number from it. - * Used so that when we already have an iterator to the first - * occurence from a previous operation we can start from - * there. + * @param itemInstance The item instance id used to know what to unequip * @returns Whether it was unequipped. */ - bool unequip(EquipData::iterator it); - bool unequip(unsigned int slot, EquipData::iterator *itp = 0); + bool unequip(unsigned int itemInstance); /** * Inserts some items into the inventory. @@ -101,16 +78,16 @@ class Inventory /** * Removes some items from inventory. - * @param force If set to true, also remove any equipment encountered * @return number of items not removed. */ - unsigned int remove(unsigned int itemId, unsigned int amount, bool force = false); + unsigned int remove(unsigned int itemId, unsigned int amount); /** * Moves some items from the first slot to the second one. * @returns number of items not moved. */ - unsigned int move(unsigned int slot1, unsigned int slot2, unsigned int amount); + unsigned int move(unsigned int slot1, unsigned int slot2, + unsigned int amount); /** * Removes some items from inventory. @@ -129,18 +106,44 @@ class Inventory unsigned int getItem(unsigned int slot) const; private: + /** + * Tell whether the equipment slot has enough room in an equipment slot. + * @param equipmentSlot the slot in equipement to check. + * @param capacityRequested the capacity needed. + */ + bool checkEquipmentCapacity(unsigned int equipmentSlot, + unsigned int capacityRequested); /** - * Make sure that changes are being done on a copy, not directly. - * No effect when not in delayed mode. + * Test whether the inventory has enough space to welcome + * the willing-to-be equipment slot. + * @todo */ - void prepare(); + bool hasInventoryEnoughSpace(unsigned int equipmentSlot) + { return true; } /** - * Starts a new notification message. + * Test the items unequipment requirements. + * This is especially useful for scripted equipment. + * @todo */ - void restart(); + bool testUnequipScriptRequirements(unsigned int equipementSlot) + { return true; } + /** + * Test the items equipment for scripted requirements. + * @todo + */ + bool testEquipScriptRequirements(unsigned int itemId) + { return true; } + + /** + * Return an equip item instance id unique to the item used, + * per character. + * This is used to differenciate some items that can be equipped + * multiple times, like one-handed weapons for instance. + */ + unsigned int getNewEquipItemInstance(); /** * Check the inventory is within the slot limit and capacity. @@ -150,29 +153,19 @@ class Inventory void checkInventorySize(); /** - * Helper function for equip() when computing changes to equipment - * When newCount is 0, the item is being unequipped. + * Check potential visible character sprite changes. */ - // inventory slot -> {equip slots} - typedef std::multimap<unsigned int, unsigned short> IdSlotMap; - void equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it); + void checkLookchanges(unsigned int slotTypeId); /** - * Changes equipment and adjusts character attributes. + * Apply equipment triggers. */ - void changeEquipment(unsigned int oldId, unsigned int itemId); - void changeEquipment(ItemClass *oldI, ItemClass *newI); + void updateEquipmentTrigger(unsigned int oldId, unsigned int itemId); + void updateEquipmentTrigger(ItemClass *oldI, ItemClass *newI); Possessions *mPoss; /**< Pointer to the modified possessions. */ - /** - * Update message containing inventory changes. - * Note that in sendFull(), this is reused to send all full changes - * (for both inventory and equipment) - */ - MessageOut mInvMsg; - MessageOut mEqmMsg; /**< Update message containing equipment changes */ + Character *mCharacter; /**< Character to notify. */ - bool mDelayed; /**< Delayed changes. */ }; #endif diff --git a/src/game-server/item.h b/src/game-server/item.h index dbcc278c..f7c380f1 100644 --- a/src/game-server/item.h +++ b/src/game-server/item.h @@ -28,10 +28,15 @@ class Being; class Script; -// A pair indicating: Equipment slot id -> how much slots required. -typedef std::list< std::pair< unsigned int, unsigned int> > ItemEquipInfo; -// The list of required slots to equip. -typedef std::list< ItemEquipInfo > ItemEquipsInfo; +// Indicates the equip slot "cost" to equip an item. +struct ItemEquipRequirement { + ItemEquipRequirement(): + equipSlotId(0), + capacityRequired(0) + {} + + unsigned int equipSlotId, capacityRequired; +}; /** * State effects to beings, and actors. @@ -226,9 +231,10 @@ class ItemClass { return mSpriteID; } /** - * Returns equip requirements. + * Returns equip requirement. */ - const ItemEquipsInfo &getItemEquipData() const { return mEquip; } + const ItemEquipRequirement &getItemEquipRequirement() const + { return mEquipReq; } private: /** @@ -274,12 +280,9 @@ class ItemClass std::multimap< ItemTriggerType, ItemEffectInfo * > mDispells; /** - * List of list of requirements for equipping. Only one inner list - * need be satisfied to sucessfully equip. Checks occur in order - * from outer front to back. - * All conditions in an inner list must be met for success. + * Requirement for equipping. */ - ItemEquipsInfo mEquip; + ItemEquipRequirement mEquipReq; friend class ItemManager; }; diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index 9d94c389..5e24927b 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -52,6 +52,14 @@ void ItemManager::deinitialize() { delete i->second; } + + for (std::map< unsigned int, EquipSlotInfo* >::iterator it = + mEquipSlotsInfo.begin(), it_end = mEquipSlotsInfo.end(); it != it_end; + ++it) + { + delete it->second; + } + mItemClasses.clear(); mItemClassesByName.clear(); } @@ -72,52 +80,22 @@ unsigned int ItemManager::getDatabaseVersion() const return mItemDatabaseVersion; } -const std::string &ItemManager::getEquipNameFromId(unsigned int id) const -{ - return mEquipSlots.at(id).first; -} - -unsigned int ItemManager::getEquipIdFromName(const std::string &name) const +unsigned int ItemManager::getEquipSlotIdFromName(const std::string &name) const { - for (unsigned int i = 0; i < mEquipSlots.size(); ++i) - if (name == mEquipSlots.at(i).first) - return i; - LOG_WARN("Item Manager: attempt to find equip id from name \"" << - name << "\" not found, defaulting to 0!"); - return 0; + EquipSlotInfo *slotInfo = mNamedEquipSlotsInfo.find(name); + return slotInfo ? slotInfo->slotId : 0; } -unsigned int ItemManager::getMaxSlotsFromId(unsigned int id) const +unsigned int ItemManager::getEquipSlotCapacity(unsigned int id) const { - return mEquipSlots.at(id).second; -} - -unsigned int ItemManager::getVisibleSlotCount() const -{ - if (!mVisibleEquipSlotCount) - { - for (VisibleEquipSlots::const_iterator it = mVisibleEquipSlots.begin(), - it_end = mVisibleEquipSlots.end(); - it != it_end; - ++it) - { - mVisibleEquipSlotCount += mEquipSlots.at(*it).second; - } - } - return mVisibleEquipSlotCount; + EquipSlotsInfo::const_iterator i = mEquipSlotsInfo.find(id); + return i != mEquipSlotsInfo.end() ? i->second->slotCapacity : 0; } bool ItemManager::isEquipSlotVisible(unsigned int id) const { - for (VisibleEquipSlots::const_iterator it = mVisibleEquipSlots.begin(), - it_end = mVisibleEquipSlots.end(); - it != it_end; - ++it) - { - if (*it == id) - return true; - } - return false; + EquipSlotsInfo::const_iterator i = mEquipSlotsInfo.find(id); + return i != mEquipSlotsInfo.end() ? i->second->visibleSlot : false; } void ItemManager::readEquipSlotsFile() @@ -134,42 +112,63 @@ void ItemManager::readEquipSlotsFile() LOG_INFO("Loading equip slots: " << mEquipSlotsFile); - unsigned totalCount = 0; + unsigned totalCapacity = 0; unsigned slotCount = 0; - unsigned visibleSlotCount = 0; + mVisibleEquipSlotCount = 0; for_each_xml_child_node(node, rootNode) { if (xmlStrEqual(node->name, BAD_CAST "slot")) { + const int slotId = XML::getProperty(node, "id", 0); const std::string name = XML::getProperty(node, "name", std::string()); - const int count = XML::getProperty(node, "count", 0); + const int capacity = XML::getProperty(node, "capacity", 0); - if (name.empty() || count <= 0) + if (slotId <= 0 || name.empty() || capacity <= 0) { - LOG_WARN("Item Manager: equip slot has no name or zero count"); + LOG_WARN("Item Manager: equip slot " << slotId + << ": (" << name << ") has no name or zero count. " + "The slot has been ignored."); + continue; } - else + + if (slotId > 255) + { + LOG_WARN("Item Manager: equip slot " << slotId + << ": (" << name << ") is superior to 255 " + "and has been ignored."); + continue; + } + + bool visible = XML::getBoolProperty(node, "visible", false); + if (visible) + ++mVisibleEquipSlotCount; + + EquipSlotsInfo::iterator i = mEquipSlotsInfo.find(slotId); + + if (i != mEquipSlotsInfo.end()) { - bool visible = XML::getProperty(node, "visible", "false") != "false"; - if (visible) - { - mVisibleEquipSlots.push_back(mEquipSlots.size()); - if (++visibleSlotCount > 7) - LOG_WARN("Item Manager: More than 7 visible equip slot!" - "This will not work with current netcode!"); - } - mEquipSlots.push_back(std::pair<std::string, unsigned int> - (name, count)); - totalCount += count; - ++slotCount; + LOG_WARN("Item Manager: Ignoring duplicate definition " + "of equip slot '" << slotId << "'!"); + continue; } + + LOG_DEBUG("Adding equip slot, id: " << slotId << ", name: " << name + << ", capacity: " << capacity << ", visible? " << visible); + EquipSlotInfo *equipSlotInfo = + new EquipSlotInfo(slotId, name, capacity, visible); + mEquipSlotsInfo.insert( + std::make_pair<unsigned int, EquipSlotInfo*>(slotId, equipSlotInfo)); + mNamedEquipSlotsInfo.insert(name, equipSlotInfo); + + totalCapacity += capacity; + ++slotCount; } } LOG_INFO("Loaded '" << slotCount << "' slot types with '" - << totalCount << "' slots."); + << totalCapacity << "' slots."); } void ItemManager::readItemsFile() @@ -264,28 +263,45 @@ void ItemManager::readItemNode(xmlNodePtr itemNode) void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item) { - ItemEquipInfo req; for_each_xml_child_node(subNode, equipNode) { if (xmlStrEqual(subNode->name, BAD_CAST "slot")) { + if (item->mEquipReq.equipSlotId) + { + LOG_WARN("Item Manager: duplicate equip slot definitions!" + " Only the first will apply."); + break; + } + std::string slot = XML::getProperty(subNode, "type", std::string()); if (slot.empty()) { LOG_WARN("Item Manager: empty equip slot definition!"); continue; } - req.push_back(std::make_pair(getEquipIdFromName(slot), - XML::getProperty(subNode, "required", 1))); + if (utils::isNumeric(slot)) + { + // When the slot id is given + item->mEquipReq.equipSlotId = utils::stringToInt(slot); + } + else + { + // When its name is given + item->mEquipReq.equipSlotId = getEquipSlotIdFromName(slot); + } + item->mEquipReq.capacityRequired = + XML::getProperty(subNode, "required", 1); } } - if (req.empty()) + + if (!item->mEquipReq.equipSlotId) { LOG_WARN("Item Manager: empty equip requirement " - "definition for item " << item->getDatabaseID() << "!"); + "definition for item " << item->getDatabaseID() << "!" + " The item will be unequippable."); return; } - item->mEquip.push_back(req); } void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) @@ -310,8 +326,8 @@ void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) * trigger, and the second defines the default * trigger to use for dispelling. */ - triggerTable["existence"].first = ITT_IN_INVY; - triggerTable["existence"].second = ITT_LEAVE_INVY; + triggerTable["in-inventory"].first = ITT_IN_INVY; + triggerTable["in-inventory"].second = ITT_LEAVE_INVY; triggerTable["activation"].first = ITT_ACTIVATE; triggerTable["activation"].second = ITT_NULL; triggerTable["equip"].first = ITT_EQUIP; diff --git a/src/game-server/itemmanager.h b/src/game-server/itemmanager.h index efd31710..cc4fb0ae 100644 --- a/src/game-server/itemmanager.h +++ b/src/game-server/itemmanager.h @@ -30,6 +30,24 @@ class ItemClass; +struct EquipSlotInfo +{ + EquipSlotInfo(): + slotId(0), slotCapacity(0), visibleSlot(false) + {} + + EquipSlotInfo(unsigned int id, const std::string &name, + unsigned int capacity, bool visible): + slotId(id), slotName(name), slotCapacity(capacity), visibleSlot(visible) + {} + + unsigned int slotId; + std::string slotName; + unsigned int slotCapacity; + bool visibleSlot; +}; + + class ItemManager { public: @@ -76,13 +94,12 @@ class ItemManager */ unsigned int getDatabaseVersion() const; - const std::string &getEquipNameFromId(unsigned int id) const; - - unsigned int getEquipIdFromName(const std::string &name) const; + unsigned int getEquipSlotIdFromName(const std::string &name) const; - unsigned int getMaxSlotsFromId(unsigned int id) const; + unsigned int getEquipSlotCapacity(unsigned int id) const; - unsigned int getVisibleSlotCount() const; + unsigned int getVisibleEquipSlotCount() const + { return mVisibleEquipSlotCount; } bool isEquipSlotVisible(unsigned int id) const; @@ -97,19 +114,22 @@ class ItemManager void readEffectNode(xmlNodePtr effectNode, ItemClass *item); typedef std::map< int, ItemClass * > ItemClasses; - // Map a string (name of slot) with (str-id, max-per-equip-slot) - typedef std::vector< std::pair< std::string, unsigned int > > EquipSlots; + ItemClasses mItemClasses; /**< Item reference */ + utils::NameMap<ItemClass*> mItemClassesByName; + + // Map an equip slot id with the equip slot info. + typedef std::map< unsigned int, EquipSlotInfo* > EquipSlotsInfo; // Reference to the vector position of equipSlots typedef std::vector< unsigned int > VisibleEquipSlots; - ItemClasses mItemClasses; /**< Item reference */ - utils::NameMap<ItemClass*> mItemClassesByName; - EquipSlots mEquipSlots; - VisibleEquipSlots mVisibleEquipSlots; + EquipSlotsInfo mEquipSlotsInfo; + // Map a string (name of slot) with (str-id, max-per-equip-slot) + // We only keep a pointer to it: The id map will take care of deletion. + utils::NameMap<EquipSlotInfo* > mNamedEquipSlotsInfo; std::string mItemsFile; std::string mEquipSlotsFile; - mutable unsigned int mVisibleEquipSlotCount; // Cache + unsigned int mVisibleEquipSlotCount; // Cache /** Version of the loaded items database file.*/ unsigned int mItemDatabaseVersion; diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index d0c55073..0f387917 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -131,7 +131,8 @@ void Monster::perform() { setTimerHard(T_M_ATTACK_TIME, mCurrentAttack->aftDelay + mCurrentAttack->preDelay); - Damage dmg(getModifiedAttribute(MOB_ATTR_PHY_ATK_MIN) * + Damage dmg(0, + getModifiedAttribute(MOB_ATTR_PHY_ATK_MIN) * mCurrentAttack->damageFactor, getModifiedAttribute(MOB_ATTR_PHY_ATK_DELTA) * mCurrentAttack->damageFactor, @@ -421,22 +422,14 @@ int Monster::damage(Actor *source, const Damage &damage) { Character *s = static_cast< Character * >(source); - std::list<size_t>::const_iterator iSkill; - for (iSkill = damage.usedSkills.begin(); - iSkill != damage.usedSkills.end(); ++iSkill) + mExpReceivers[s].insert(damage.skill); + if (!isTimerRunning(T_M_KILLSTEAL_PROTECTED) || mOwner == s + || mOwner->getParty() == s->getParty()) { - if (*iSkill) - { - mExpReceivers[s].insert(*iSkill); - if (!isTimerRunning(T_M_KILLSTEAL_PROTECTED) || mOwner == s - || mOwner->getParty() == s->getParty()) - { - mOwner = s; - mLegalExpReceivers.insert(s); - setTimerHard(T_M_KILLSTEAL_PROTECTED, - KILLSTEAL_PROTECTION_TIME); - } - } + mOwner = s; + mLegalExpReceivers.insert(s); + setTimerHard(T_M_KILLSTEAL_PROTECTED, + KILLSTEAL_PROTECTION_TIME); } } return HPLoss; diff --git a/src/game-server/skillmanager.h b/src/game-server/skillmanager.h index 1912e2fc..e789c89d 100644 --- a/src/game-server/skillmanager.h +++ b/src/game-server/skillmanager.h @@ -53,6 +53,8 @@ class SkillManager const std::string getSkillName(unsigned int id) const; const std::string getSetName(unsigned int id) const; + unsigned int getDefaultSkillId() const + { return mDefaultSkillId; } private: struct SkillInfo { SkillInfo(): @@ -86,4 +88,6 @@ class SkillManager unsigned int mDefaultSkillId; }; +extern SkillManager *skillManager; + #endif // SKILLMANAGER_H diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index 03d4b71c..30b57cae 100644 --- a/src/game-server/state.cpp +++ b/src/game-server/state.cpp @@ -106,59 +106,50 @@ static void updateMap(MapComposite *map) /** * Sets message fields describing character look. */ -static void serializeLooks(Character *ch, MessageOut &msg, bool full) +static void serializeLooks(Character *ch, MessageOut &msg) { - const Possessions &poss = ch->getPossessions(); - unsigned int nb_slots = itemManager->getVisibleSlotCount(); + const EquipData &equipData = ch->getPossessions().getEquipment(); - // Bitmask describing the changed entries. - int changed = (1 << nb_slots) - 1; - if (!full) - { - // TODO: do not assume the whole equipment changed, - // when an update is asked for. - changed = (1 << nb_slots) - 1; - } + // We'll use a set to check whether we already sent the update for the given + // item instance. + std::set<unsigned int> itemInstances; + + // The map storing the info about the look changes to send + //{ slot type id, item id } + std::map <unsigned int, unsigned int> lookChanges; - std::vector<unsigned int> items; - items.resize(nb_slots, 0); - // Partially build both kinds of packet, to get their sizes. - unsigned int mask_full = 0, mask_diff = 0; - unsigned int nb_full = 0, nb_diff = 0; - std::map<unsigned int, unsigned int>::const_iterator it = - poss.equipSlots.begin(); - for (unsigned int i = 0; i < nb_slots; ++i) + // Note that we can send several updates on the same slot type as different + // items may have been equipped. + for (EquipData::const_iterator it = equipData.begin(), + it_end = equipData.end(); it != it_end; ++it) { - if (changed & (1 << i)) - { - // Skip slots that have not changed, when sending an update. - ++nb_diff; - mask_diff |= 1 << i; - } - if (it == poss.equipSlots.end() || it->first > i) continue; - ItemClass *eq; - items[i] = it->first && (eq = itemManager->getItem(it->first)) ? - eq->getSpriteID() : 0; - if (items[i]) + if (!itemManager->isEquipSlotVisible(it->first)) + continue; + + if (!it->second.itemInstance + || itemInstances.insert(it->second.itemInstance).second) { - /* If we are sending the whole equipment, only filled slots have to - be accounted for, as the other ones will be automatically cleared. */ - ++nb_full; - mask_full |= 1 << i; + // When the insertion succeeds, its the first time + // we encounter the item, so we can send the look change. + // We also send empty slots for unequipment handling. + lookChanges.insert( + std::make_pair<unsigned int, unsigned int>(it->first, + it->second.itemId)); } } - // Choose the smaller payload. - if (nb_full <= nb_diff) full = true; - - /* Bitmask enumerating the sent slots. - Setting the upper bit tells the client to clear the slots beforehand. */ - int mask = full ? mask_full | (1 << 7) : mask_diff; - - msg.writeInt8(mask); - for (unsigned int i = 0; i < nb_slots; ++i) + if (lookChanges.size() > 0) { - if (mask & (1 << i)) msg.writeInt16(items[i]); + // Number of look changes to send + msg.writeInt8(lookChanges.size()); + + for (std::map<unsigned int, unsigned int>::const_iterator it2 = + lookChanges.begin(), it2_end = lookChanges.end(); + it2 != it2_end; ++it2) + { + msg.writeInt8(it2->first); + msg.writeInt16(it2->second); + } } } @@ -222,7 +213,7 @@ static void informPlayer(MapComposite *map, Character *p, int worldTime) MessageOut LooksMsg(GPMSG_BEING_LOOKS_CHANGE); LooksMsg.writeInt16(oid); Character * c = static_cast<Character * >(o); - serializeLooks(c, LooksMsg, false); + serializeLooks(c, LooksMsg); LooksMsg.writeInt16(c->getHairStyle()); LooksMsg.writeInt16(c->getHairColor()); LooksMsg.writeInt16(c->getGender()); @@ -286,7 +277,7 @@ static void informPlayer(MapComposite *map, Character *p, int worldTime) enterMsg.writeInt8(q->getHairStyle()); enterMsg.writeInt8(q->getHairColor()); enterMsg.writeInt8(q->getGender()); - serializeLooks(q, enterMsg, true); + serializeLooks(q, enterMsg); } break; case OBJECT_MONSTER: diff --git a/src/game-server/trade.cpp b/src/game-server/trade.cpp index 51509307..e1779d2f 100644 --- a/src/game-server/trade.cpp +++ b/src/game-server/trade.cpp @@ -130,7 +130,7 @@ void Trade::agree(Character *c) // Check if both player has the objects in their inventories // and enouth money, then swap them. - Inventory v1(mChar1, true), v2(mChar2, true); + Inventory v1(mChar1), v2(mChar2); if (mChar1->getAttribute(mCurrencyId) >= mMoney1 - mMoney2 && mChar2->getAttribute(mCurrencyId) >= mMoney2 - mMoney1 && perform(mItems1, v1, v2) && @@ -143,8 +143,6 @@ void Trade::agree(Character *c) } else { - v1.cancel(); - v2.cancel(); cancel(); return; } diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp index a322a3f9..03d08771 100644 --- a/src/scripting/lua.cpp +++ b/src/scripting/lua.cpp @@ -367,7 +367,7 @@ static int chr_inv_change(lua_State *s) return 0; } int nb_items = (lua_gettop(s) - 1) / 2; - Inventory inv(q, true); + Inventory inv(q); for (int i = 0; i < nb_items; ++i) { if (!(lua_isnumber(s, i * 2 + 2) || lua_isstring(s, i * 2 + 2)) || @@ -405,12 +405,13 @@ static int chr_inv_change(lua_State *s) id = ic->getDatabaseID(); if (nb < 0) { + // Removing too much item is a success as for the scripter's + // point of view. We log it anyway. nb = inv.remove(id, -nb); if (nb) { - inv.cancel(); - lua_pushboolean(s, 0); - return 1; + LOG_WARN("mana.chr_inv_change() removed more items than owned: " + << "character: " << q->getName() << " item id: " << id); } } else @@ -476,6 +477,23 @@ static int chr_inv_count(lua_State *s) } /** + * mana.chr_get_level(): int level + * Tells the character current level. + */ +static int chr_get_level(lua_State *s) +{ + Character *ch = getCharacter(s, 1); + if (!ch) + { + raiseScriptError(s, "chr_get_level " + "called for nonexistent character."); + } + + lua_pushinteger(s, ch->getLevel()); + return 1; +} + +/** * mana.npc_trade(NPC*, Character*, bool sell, table items): int * Callback for trading between a player and an NPC. * Let the player buy or sell only a subset of predeterminded items. @@ -1155,14 +1173,14 @@ static int monster_change_anger(lua_State *s) */ static int monster_remove(lua_State *s) { - bool monsterEnqueued = false; + bool monsterRemoved = false; Monster *m = getMonster(s, 1); if (m) { - GameState::enqueueRemove(m); - monsterEnqueued = true; + GameState::remove(m); + monsterRemoved = true; } - lua_pushboolean(s, monsterEnqueued); + lua_pushboolean(s, monsterRemoved); return 1; } @@ -1806,6 +1824,25 @@ static int chr_get_gender(lua_State *s) } /** + * mana.chr_set_gender(Character*, int gender): void + * Set the gender of the character. + */ +static int chr_set_gender(lua_State *s) +{ + Character *c = getCharacter(s, 1); + if (!c) + { + raiseScriptError(s, "chr_set_gender called for nonexistent character."); + return 0; + } + + const int gender = luaL_checkinteger(s, 2); + c->setGender(gender); + + return 0; +} + +/** * mana.chr_give_special(Character*, int special): void * Enables a special for a character. */ @@ -2283,6 +2320,7 @@ LuaScript::LuaScript(): { "chr_warp", &chr_warp }, { "chr_inv_change", &chr_inv_change }, { "chr_inv_count", &chr_inv_count }, + { "chr_get_level", &chr_get_level }, { "chr_get_quest", &chr_get_quest }, { "chr_set_quest", &chr_set_quest }, { "getvar_map", &getvar_map }, @@ -2299,6 +2337,7 @@ LuaScript::LuaScript(): { "chr_get_hair_color", &chr_get_hair_color }, { "chr_get_kill_count", &chr_get_kill_count }, { "chr_get_gender", &chr_get_gender }, + { "chr_set_gender", &chr_set_gender }, { "chr_give_special", &chr_give_special }, { "chr_has_special", &chr_has_special }, { "chr_take_special", &chr_take_special }, diff --git a/src/serialize/characterdata.h b/src/serialize/characterdata.h index 4466c98e..c453e685 100644 --- a/src/serialize/characterdata.h +++ b/src/serialize/characterdata.h @@ -97,20 +97,22 @@ void serializeCharacterData(const T &data, MessageOut &msg) // inventory - must be last because size isn't transmitted const Possessions &poss = data.getPossessions(); - msg.writeInt16(poss.equipSlots.size()); // number of equipment - for (EquipData::const_iterator k = poss.equipSlots.begin(), - k_end = poss.equipSlots.end(); - k != k_end; - ++k) + const EquipData &equipData = poss.getEquipment(); + msg.writeInt16(equipData.size()); // number of equipment + for (EquipData::const_iterator k = equipData.begin(), + k_end = equipData.end(); k != k_end; ++k) { - msg.writeInt8(k->first); // Equip slot type - msg.writeInt16(k->second); // Inventory slot + msg.writeInt16(k->first); // Equip slot id + msg.writeInt16(k->second.itemId); // ItemId + msg.writeInt16(k->second.itemInstance); // Item Instance id } - for (InventoryData::const_iterator j = poss.inventory.begin(), - j_end = poss.inventory.end(); j != j_end; ++j) + + const InventoryData &inventoryData = poss.getInventory(); + for (InventoryData::const_iterator j = inventoryData.begin(), + j_end = inventoryData.end(); j != j_end; ++j) { msg.writeInt16(j->first); // slot id - msg.writeInt16(j->second.itemId); // item type + msg.writeInt16(j->second.itemId); // item id msg.writeInt16(j->second.amount); // amount } } @@ -185,26 +187,31 @@ void deserializeCharacterData(T &data, MessageIn &msg) Possessions &poss = data.getPossessions(); - poss.equipSlots.clear(); + EquipData equipData; int equipSlotsSize = msg.readInt16(); - unsigned int eqSlot, invSlot; + unsigned int eqSlot; + EquipmentItem equipItem; for (int j = 0; j < equipSlotsSize; ++j) { - eqSlot = msg.readInt8(); - invSlot = msg.readInt16(); - poss.equipSlots.insert(poss.equipSlots.end(), - std::make_pair(eqSlot, invSlot)); + eqSlot = msg.readInt16(); + equipItem.itemId = msg.readInt16(); + equipItem.itemInstance = msg.readInt16(); + equipData.insert(equipData.end(), + std::make_pair(eqSlot, equipItem)); } - poss.inventory.clear(); - // inventory - must be last because size isn't transmitted + poss.setEquipment(equipData); + + // Loads inventory - must be last because size isn't transmitted + InventoryData inventoryData; while (msg.getUnreadLength()) { InventoryItem i; int slotId = msg.readInt16(); i.itemId = msg.readInt16(); i.amount = msg.readInt16(); - poss.inventory.insert(poss.inventory.end(), std::make_pair(slotId, i)); + inventoryData.insert(inventoryData.end(), std::make_pair(slotId, i)); } + poss.setInventory(inventoryData); } #endif // SERIALIZE_CHARACTERDATA_H diff --git a/src/sql/mysql/createTables.sql b/src/sql/mysql/createTables.sql index 3a5f5187..b75fc0ed 100644 --- a/src/sql/mysql/createTables.sql +++ b/src/sql/mysql/createTables.sql @@ -179,16 +179,32 @@ DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; -- +-- table: `mana_floor_items` +-- +CREATE TABLE IF NOT EXISTS `mana_floor_items` ( + `id` int(10) unsigned NOT NULL auto_increment, + `map_id` int(10) unsigned NOT NULL, + `item_id` int(10) unsigned NOT NULL, + `amount` smallint(5) unsigned NOT NULL, + `pos_x` smallint(5) unsigned NOT NULL, + `pos_y` smallint(5) unsigned NOT NULL, + -- + PRIMARY KEY (`id`) +) ENGINE=InnoDB +DEFAULT CHARSET=utf8 +AUTO_INCREMENT=1 ; + +-- -- table: `mana_char_equips` -- CREATE TABLE IF NOT EXISTS `mana_char_equips` ( `id` int(10) unsigned NOT NULL auto_increment, `owner_id` int(10) unsigned NOT NULL, - `slot_type` tinyint(3) unsigned NOT NULL, - `inventory_slot` tinyint(3) unsigned NOT NULL, + `slot_type` int(10) unsigned NOT NULL, + `item_id` int(10) unsigned NOT NULL, + `item_instance` int(10) unsigned NOT NULL, -- PRIMARY KEY (`id`), - UNIQUE KEY `owner_id` (`owner_id`, `inventory_slot`), FOREIGN KEY (`owner_id`) REFERENCES `mana_characters` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -421,7 +437,7 @@ AUTO_INCREMENT=0 ; INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, NOW()); INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, NOW()); -INSERT INTO mana_world_states VALUES('database_version', NULL,'14', NOW()); +INSERT INTO mana_world_states VALUES('database_version', NULL,'17', NOW()); -- all known transaction codes diff --git a/src/sql/mysql/updates/update_14_to_15.sql b/src/sql/mysql/updates/update_14_to_15.sql new file mode 100644 index 00000000..1036817b --- /dev/null +++ b/src/sql/mysql/updates/update_14_to_15.sql @@ -0,0 +1,22 @@ + +-- Dropping the table will only unequip characters, so it's not an issue. +DROP TABLE mana_char_equips; + +-- Recreate the table using the latest definition. +CREATE TABLE IF NOT EXISTS `mana_char_equips` ( + `id` int(10) unsigned NOT NULL auto_increment, + `owner_id` int(10) unsigned NOT NULL, + `slot_type` int(10) unsigned NOT NULL, + `item_id` int(10) unsigned NOT NULL, + `item_instance` int(10) unsigned NOT NULL, + -- + PRIMARY KEY (`id`), + FOREIGN KEY (`owner_id`) REFERENCES `mana_characters` (`id`) +) ENGINE=InnoDB +DEFAULT CHARSET=utf8; + +-- Update database version. +UPDATE mana_world_states +SET value = '15', +moddate = UNIX_TIMESTAMP() +WHERE state_name = 'database_version'; diff --git a/src/sql/mysql/updates/update_16_to_17.sql b/src/sql/mysql/updates/update_16_to_17.sql new file mode 100644 index 00000000..008983aa --- /dev/null +++ b/src/sql/mysql/updates/update_16_to_17.sql @@ -0,0 +1,19 @@ +-- Create the new floor item table +CREATE TABLE IF NOT EXISTS `mana_floor_items` ( + `id` int(10) unsigned NOT NULL auto_increment, + `map_id` int(10) unsigned NOT NULL, + `item_id` int(10) unsigned NOT NULL, + `amount` smallint(5) unsigned NOT NULL, + `pos_x` smallint(5) unsigned NOT NULL, + `pos_y` smallint(5) unsigned NOT NULL, + -- + PRIMARY KEY (`id`) +) ENGINE=InnoDB +DEFAULT CHARSET=utf8 +AUTO_INCREMENT=1 ; + +-- Update database version. +UPDATE mana_world_states +SET value = '17', +moddate = UNIX_TIMESTAMP() +WHERE state_name = 'database_version'; diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql index b366606b..2d7360da 100644 --- a/src/sql/sqlite/createTables.sql +++ b/src/sql/sqlite/createTables.sql @@ -173,12 +173,25 @@ CREATE INDEX mana_item_attributes_item ON mana_item_attributes ( item_id ); ----------------------------------------------------------------------------- +CREATE TABLE mana_floor_items +( + id INTEGER PRIMARY KEY, + map_id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + amount INTEGER NOT NULL, + pos_x INTEGER NOT NULL, + pos_y INTEGER NOT NULL +); + +----------------------------------------------------------------------------- + CREATE TABLE mana_char_equips ( id INTEGER PRIMARY KEY, owner_id INTEGER NOT NULL, slot_type INTEGER NOT NULL, - inventory_slot INTEGER NOT NULL, + item_id INTEGER NOT NULL, + item_instance INTEGER NOT NULL, -- FOREIGN KEY (owner_id) REFERENCES mana_characters(id) ); @@ -406,7 +419,7 @@ AS INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, strftime('%s','now')); INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, strftime('%s','now')); -INSERT INTO mana_world_states VALUES('database_version', NULL,'14', strftime('%s','now')); +INSERT INTO mana_world_states VALUES('database_version', NULL,'17', strftime('%s','now')); -- all known transaction codes diff --git a/src/sql/sqlite/updates/update_14_to_15.sql b/src/sql/sqlite/updates/update_14_to_15.sql new file mode 100644 index 00000000..777764d8 --- /dev/null +++ b/src/sql/sqlite/updates/update_14_to_15.sql @@ -0,0 +1,21 @@ + +-- Dropping the table will only unequip characters, so it's not an issue. +DROP TABLE mana_char_equips; + +-- Recreate the table using the latest definition. +CREATE TABLE mana_char_equips +( + id INTEGER PRIMARY KEY, + owner_id INTEGER NOT NULL, + slot_type INTEGER NOT NULL, + item_id INTEGER NOT NULL, + item_instance INTEGER NOT NULL, + -- + FOREIGN KEY (owner_id) REFERENCES mana_characters(id) +); + +-- Update the database version, and set date of update +UPDATE mana_world_states + SET value = '15', + moddate = strftime('%s','now') + WHERE state_name = 'database_version'; diff --git a/src/sql/sqlite/updates/update_16_to_17.sql b/src/sql/sqlite/updates/update_16_to_17.sql new file mode 100644 index 00000000..769c26da --- /dev/null +++ b/src/sql/sqlite/updates/update_16_to_17.sql @@ -0,0 +1,16 @@ +-- Create the new floor item table +CREATE TABLE mana_floor_items +( + id INTEGER PRIMARY KEY, + map_id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + amount INTEGER NOT NULL, + pos_x INTEGER NOT NULL, + pos_y INTEGER NOT NULL +); + +-- Update the database version, and set date of update +UPDATE mana_world_states + SET value = '17', + moddate = strftime('%s','now') + WHERE state_name = 'database_version'; |