From 01b9c1c451d0c082cdc55475b8546e5638da8369 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Wed, 27 Jul 2011 12:36:04 +0200 Subject: Small random code format cleanups, and documentation fixes. This doesn't change anything yet. --- src/common/inventorydata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/common') diff --git a/src/common/inventorydata.h b/src/common/inventorydata.h index 4c637cc3..9127c816 100644 --- a/src/common/inventorydata.h +++ b/src/common/inventorydata.h @@ -26,12 +26,12 @@ /** * Numbers of inventory slots + * TODO: Make this configurable and sent to the client. */ #define INVENTORY_SLOTS 50 /** * Structure storing an item in the inventory. - * When the itemId is zero, this item represents "amount" consecutive empty slots. */ struct InventoryItem { -- cgit v1.2.3-70-g09d2 From 8aaa341a8ee51853737eabf52fe369f75be07e93 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Wed, 27 Jul 2011 14:46:41 +0200 Subject: Begun Applying the new equipment slot handling design. now, the equipment slots are independant from the inventory slots according to the inventory and equipment data. This will permit to avoid checking the equipment each time one touches the inventory and vice versa, and make the former delayed mode useless. Also, note that equipped items will be removed from inventory and readded once unequipped. The design will permit the following, even if not implemented yet: - To make equipment items stackable again, if wanted. - Have more than one item with the same id equipped on different slots using the itemInstance field. Note: I didn't add the database structure updates yet, to see whether other changes may later go along with those. --- src/account-server/storage.cpp | 42 +++++++++++++++++++++------------- src/common/inventorydata.h | 46 ++++++++++++++++++++++++++++++++----- src/game-server/buysell.cpp | 6 ++--- src/game-server/inventory.cpp | 50 +++++++++-------------------------------- src/game-server/state.cpp | 8 +++---- src/serialize/characterdata.h | 45 +++++++++++++++++++++---------------- src/sql/mysql/createTables.sql | 6 ++--- src/sql/sqlite/createTables.sql | 3 ++- 8 files changed, 117 insertions(+), 89 deletions(-) (limited to 'src/common') diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp index 96601207..05f8ec69 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -490,17 +490,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( - toUint(equipInfo(k, 0)), - toUint(equipInfo(k, 1)))); + { + equipItem.itemId = toUint(equipInfo(k, 1)); + equipItem.itemInstance = toUint(equipInfo(k, 2)); + equipData.insert(std::pair( + toUint(equipInfo(k, 0)), + equipItem)); + } + } + poss.setEquipment(equipData); } catch (const dal::DbSqlQueryExecFailure &e) { @@ -515,6 +524,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 +534,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 +809,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 +831,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()); diff --git a/src/common/inventorydata.h b/src/common/inventorydata.h index 9127c816..e7c81170 100644 --- a/src/common/inventorydata.h +++ b/src/common/inventorydata.h @@ -35,20 +35,56 @@ */ 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) + {} + + // 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 } +// 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/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/inventory.cpp b/src/game-server/inventory.cpp index e218a916..8689f299 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -54,8 +54,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); @@ -106,7 +107,7 @@ void Inventory::initialize() */ for (it2 = mPoss->equipSlots.begin(); it2 != it2_end; ++it2) { - if (equipment.insert(it2->second).second) + if (equipment.insert(it2->second.itemInstance).second) { /* * Perform checks for equipped items @@ -117,7 +118,7 @@ void Inventory::initialize() /* * Apply all equip triggers. */ - itemManager->getItem(mPoss->inventory.at(it2->second).itemId) + itemManager->getItem(it2->second.itemId) ->useTrigger(mCharacter, ITT_EQUIP); } } @@ -239,8 +240,7 @@ unsigned int Inventory::count(unsigned int itemId) const unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool force) { - bool inv = false, - eq = !itemManager->getItem(itemId)->getItemEquipData().empty(); + bool inv = false; MessageOut invMsg(GPMSG_INVENTORY); bool triggerLeaveInventory = true; @@ -251,26 +251,6 @@ unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool fo { 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; @@ -320,15 +300,6 @@ 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); @@ -538,9 +509,10 @@ bool Inventory::equip(int slot, bool override) * compile time options optimising for other usage. * For now, this is adequate assuming `normal' usage. */ - for (unsigned int i = 0; i < it3->second; ++i) + /** Part disabled until reimplemented*/ + /*for (unsigned int i = 0; i < it3->second; ++i) mPoss->equipSlots.insert( - std::make_pair(it3->first, slot)); + std::make_pair(it3->first, slot));*/ } changeEquipment(0, it->second.itemId); @@ -598,7 +570,7 @@ bool Inventory::equip(int slot, bool override) bool Inventory::unequip(EquipData::iterator it) { - return unequip(it->second, &it); + return unequip(it->first, &it); } bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) @@ -611,7 +583,7 @@ bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) // Erase all equip entries that point to the given inventory slot while (it != it_end) { - if (it->second == slot) + if (it->first == slot) { changed = true; mPoss->equipSlots.erase(it++); diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index 03d4b71c..1a9c0204 100644 --- a/src/game-server/state.cpp +++ b/src/game-server/state.cpp @@ -108,7 +108,7 @@ static void updateMap(MapComposite *map) */ static void serializeLooks(Character *ch, MessageOut &msg, bool full) { - const Possessions &poss = ch->getPossessions(); + const EquipData &equipData = ch->getPossessions().getEquipment(); unsigned int nb_slots = itemManager->getVisibleSlotCount(); // Bitmask describing the changed entries. @@ -125,8 +125,8 @@ static void serializeLooks(Character *ch, MessageOut &msg, bool full) // 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::const_iterator it = - poss.equipSlots.begin(); + std::map::const_iterator it = + equipData.begin(); for (unsigned int i = 0; i < nb_slots; ++i) { if (changed & (1 << i)) @@ -135,7 +135,7 @@ static void serializeLooks(Character *ch, MessageOut &msg, bool full) ++nb_diff; mask_diff |= 1 << i; } - if (it == poss.equipSlots.end() || it->first > i) continue; + if (it == equipData.end() || it->first > i) continue; ItemClass *eq; items[i] = it->first && (eq = itemManager->getItem(it->first)) ? eq->getSpriteID() : 0; 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 9e1e31a6..d017ceee 100644 --- a/src/sql/mysql/createTables.sql +++ b/src/sql/mysql/createTables.sql @@ -184,11 +184,11 @@ AUTO_INCREMENT=1 ; 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; diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql index b366606b..1027b580 100644 --- a/src/sql/sqlite/createTables.sql +++ b/src/sql/sqlite/createTables.sql @@ -178,7 +178,8 @@ 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) ); -- cgit v1.2.3-70-g09d2 From d49f23c7668418ff4f114ba1ae7164038fae94ae Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Wed, 27 Jul 2011 15:02:05 +0200 Subject: Changed the protocol a bit to use int16 instead of 8. This is in preparation of fixes for issues: #373, and 379. Equip and inventory related packets are the only ones changed. --- src/common/manaserv_protocol.h | 890 ++++++++++++++++++++--------------------- src/game-server/inventory.cpp | 8 +- 2 files changed, 449 insertions(+), 449 deletions(-) (limited to 'src/common') diff --git a/src/common/manaserv_protocol.h b/src/common/manaserv_protocol.h index df7efd18..786330c1 100644 --- a/src/common/manaserv_protocol.h +++ b/src/common/manaserv_protocol.h @@ -1,446 +1,446 @@ -/* - * 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 . - */ - -#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_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 . + */ + +#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_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, // W slot, W amount + PGMSG_EQUIP = 0x0112, // W inventory slot + PGMSG_UNEQUIP = 0x0113, // W equipment slot + 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 invy slot}* + GPMSG_EQUIP = 0x0122, // { W Invy slot, W equip slot type count { W equip slot, W 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 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, { 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 diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index 8689f299..4e5bd534 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -493,14 +493,14 @@ bool Inventory::equip(int slot, bool override) * Clean fit. Equip and apply immediately. */ equipMsg.writeInt16(slot); // Inventory slot - equipMsg.writeInt8(it2->size()); // Equip slot type count + equipMsg.writeInt16(it2->size()); // Equip slot type count for (it3 = it2->begin(), it3_end = it2->end(); it3 != it3_end; ++it3) { - equipMsg.writeInt8(it3->first); // Equip slot - equipMsg.writeInt8(it3->second); // How many are used + equipMsg.writeInt16(it3->first); // Equip slot + equipMsg.writeInt16(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 @@ -598,7 +598,7 @@ bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) { changeEquipment(mPoss->inventory.at(slot).itemId, 0); equipMsg.writeInt16(slot); - equipMsg.writeInt8(0); + equipMsg.writeInt16(0); } if (equipMsg.getLength() > 2) -- cgit v1.2.3-70-g09d2 From 8766149dc12d197205b1632ec6e9fc663de05990 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Thu, 11 Aug 2011 02:20:39 +0200 Subject: Basically redid equip and unequip functions(). I made the system handle the fact that equipment item are completely unlinked to the inventory items. Equip items now have a unique itemInstance number permitting to equip the same item type multiple time when the slot capacity is wide enough to do so. I also prepared the functions to welcome in the near tests against scripted equipment. The equip process is known to be working server-side but the unequip process has yet to be reviewed, even if implemented. --- src/common/inventorydata.h | 6 + src/common/manaserv_protocol.h | 4 +- src/game-server/inventory.cpp | 351 +++++++++++++++++++++++++---------------- src/game-server/inventory.h | 65 +++++--- 4 files changed, 270 insertions(+), 156 deletions(-) (limited to 'src/common') diff --git a/src/common/inventorydata.h b/src/common/inventorydata.h index e7c81170..e1c5bfae 100644 --- a/src/common/inventorydata.h +++ b/src/common/inventorydata.h @@ -49,6 +49,12 @@ struct 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 diff --git a/src/common/manaserv_protocol.h b/src/common/manaserv_protocol.h index 786330c1..6c51aef9 100644 --- a/src/common/manaserv_protocol.h +++ b/src/common/manaserv_protocol.h @@ -94,8 +94,8 @@ enum { PGMSG_UNEQUIP = 0x0113, // W equipment slot 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 invy slot}* - GPMSG_EQUIP = 0x0122, // { W Invy slot, W equip slot type count { W equip slot, W number used} }* + 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}* 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 diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index 75294a1c..1eaf8b91 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -55,9 +55,9 @@ void Inventory::sendFull() const k != k_end; ++k) { - m.writeInt16(k->first); // Equip slot id - m.writeInt16(k->second.itemId); // Item id - m.writeInt16(k->second.itemInstance); // Item instance + 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); @@ -106,9 +106,6 @@ void Inventory::initialize() EquipData::iterator it2; for (it2 = mPoss->equipSlots.begin(); it2 != mPoss->equipSlots.end();) { - /* - * TODO: Check that all needed slots are available here. - */ ItemClass *item = itemManager->getItem(it2->second.itemId); if (item) { @@ -541,170 +538,258 @@ void Inventory::updateEquipmentTrigger(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; - const ItemEquipsInfo &eq = itemManager->getItem(it->second.itemId) - ->getItemEquipData(); - if (eq.empty()) + LOG_DEBUG("No existing item in inventory at slot: " << inventorySlot); + } + + // Test the equipment scripted requirements + if (!testEquipScriptRequirements(it->second.itemId)) return false; - ItemEquipInfo const *ovd = 0; - MessageOut equipMsg(GPMSG_EQUIP); - // Iterate through all possible combinations of slots -/* for (ItemEquipsInfo::const_iterator it2 = eq.begin(), - it2_end = eq.end(); it2 != it2_end; ++it2) + // Test the equip requirements. If none, it's not an equipable item. + const ItemEquipsInfo &equipInfoList = + itemManager->getItem(it->second.itemId)->getItemEquipData(); + if (equipInfoList.empty()) { - // 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) + LOG_DEBUG("No equip requirements for item id: " << it->second.itemId + << " at slot: " << inventorySlot); + return false; + } + + // Iterate through all slots requirements. + std::list equipSlotsToUnequipFirst; + for (ItemEquipsInfo::const_iterator it2 = equipInfoList.begin(), + it2_end = equipInfoList.end(); it2 != it2_end; ++it2) + { + // 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(it2->first) < it2->second) { - // it3 -> { slot id, number required } - unsigned int max = itemManager->getEquipSlotCapacity(it3->first), - used = mPoss->equipSlots.count(it3->first); - if (max - used >= it3->second) - continue; - else if (max >= it3->second) + LOG_DEBUG("Not enough equip capacity at slot: " << it2->first + << ", total available: " + << itemManager->getEquipSlotCapacity(it2->first) + << ", required: " << it2->second); + return false; + } + + // Test whether some item(s) is(are) to be unequipped first. + if (!checkEquipmentCapacity(it2->first, it2->second)) + { + // And test whether the unequip action would succeed first. + if (testUnequipScriptRequirements(it2->first) + && hasInventoryEnoughSpace(it2->first)) { - fail |= 1; - if (override) - continue; - else - break; + equipSlotsToUnequipFirst.push_back(it2->first); } else { - fail |= 2; + // 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::list::const_iterator it3 = + equipSlotsToUnequipFirst.begin(); + it3 != equipSlotsToUnequipFirst.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(equipInfoList.size()); // Number of equip slot changed. + + // Compute an unique equip item Instance id (unicity is per character only.) + int itemInstance = getNewEquipItemInstance(); + + for (ItemEquipsInfo::const_iterator it2 = equipInfoList.begin(), + it2_end = equipInfoList.end(); it2 != it2_end; ++it2) + { + unsigned int capacityLeft = it2->second; + 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 == it2->first) + { + // We've found an empty slot + if (it4->second.itemInstance == 0) + { + it4->second.itemId = it->second.itemId; + it4->second.itemInstance = itemInstance; + --capacityLeft; + } + else // The slot is already in use. + { + ++capacityUsed; + } } } - switch (fail) + + // 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) { - case 0: - /* - * Clean fit. Equip and apply immediately. - */ /* - equipMsg.writeInt16(slot); // Inventory slot - equipMsg.writeInt16(it2->size()); // Equip slot type count - for (it3 = it2->begin(), - it3_end = it2->end(); - it3 != it3_end; - ++it3) + unsigned int maxCapacity = + itemManager->getEquipSlotCapacity(it2->first); + + // A should never happen case + assert(maxCapacity >= capacityUsed + capacityLeft); + + while (capacityLeft) { - equipMsg.writeInt16(it3->first); // Equip slot - equipMsg.writeInt16(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. - */ - /** Part disabled until reimplemented*/ - /*for (unsigned int i = 0; i < it3->second; ++i) - mPoss->equipSlots.insert( - std::make_pair(it3->first, slot));*/ - /* } - - updateEquipmentTrigger(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; + EquipmentItem equipItem(it->second.itemId, itemInstance); + mPoss->equipSlots.insert( + std::make_pair + (it2->first, equipItem)); + --capacityLeft; + } } + + // Equip slot + equipMsg.writeInt16(it2->first); + // Capacity used + equipMsg.writeInt16(it2->second); } - if (equipMsg.getLength() > 2) - gameHandler->sendTo(mCharacter, equipMsg); - // We didn't find a clean equip. - if (ovd) - { - /* - * 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. - */ + // New item trigger + updateEquipmentTrigger(0, it->second.itemId); - // TODO - this would increase ease of use substatially, add as soon as - // there is time to do so. + // Remove item from inventory + removeFromSlot(inventorySlot, 1); - return false; // Return true when this section is complete -/* } - /* - * 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; -} + if (equipMsg.getLength() > 2) + gameHandler->sendTo(mCharacter, equipMsg); -bool Inventory::unequip(EquipData::iterator it) -{ - return unequip(it->first, &it); + return true; } -bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) +bool Inventory::unequip(unsigned int equipmentSlot) { - EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(), - it_end = mPoss->equipSlots.end(); + // map of { itemInstance, itemId } + std::map itemIdListToInventory; bool changed = false; MessageOut equipMsg(GPMSG_EQUIP); - // Erase all equip entries that point to the given inventory slot - while (it != it_end) + equipMsg.writeInt16(0); // Item Id, useless in case of unequip. + equipMsg.writeInt16(1); // Number of slot types touched, + // 1 in case of unequip. + + // 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->first == slot) + if (it->first == equipmentSlot && it->second.itemId != 0) { + if (!it->second.itemInstance) + continue; + + // Add the item to the inventory list if not already present there + std::map::const_iterator it2 = + itemIdListToInventory.find(it->second.itemInstance); + if (it2 == itemIdListToInventory.end()) + { + itemIdListToInventory.insert( + std::make_pair + (it->second.itemInstance, it->second.itemId)); + } + changed = true; - mPoss->equipSlots.erase(it++); - } - else - { - ++it; + it->second.itemId = 0; + it->second.itemInstance = 0; } } + // Apply unequip trigger(s), and move the item(s) back to inventory. + for (std::map::const_iterator it2 = + itemIdListToInventory.begin(), it2_end = itemIdListToInventory.end(); + it2 != it2_end; ++it2) + { + updateEquipmentTrigger(it2->second, 0); + insert(it2->second, 1); + } + if (changed) { - updateEquipmentTrigger(mPoss->inventory.at(slot).itemId, 0); - equipMsg.writeInt16(slot); - equipMsg.writeInt16(0); + equipMsg.writeInt16(equipmentSlot); + equipMsg.writeInt16(0); // Capacity used, set to 0 to unequip. } if (equipMsg.getLength() > 2) diff --git a/src/game-server/inventory.h b/src/game-server/inventory.h index 24d46942..06f55e06 100644 --- a/src/game-server/inventory.h +++ b/src/game-server/inventory.h @@ -58,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 equipmentSlot Where to remove item(s). * @returns Whether it was unequipped. */ - bool unequip(EquipData::iterator it); - bool unequip(unsigned int slot, EquipData::iterator *itp = 0); + bool unequip(unsigned int equipmentSlot); /** * Inserts some items into the inventory. @@ -114,6 +106,45 @@ 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); + + /** + * Test whether the inventory has enough space to welcome + * the willing-to-be equipment slot. + * @todo + */ + bool hasInventoryEnoughSpace(unsigned int equipmentSlot) + { return false; } + + /** + * Test the items unequipment requirements. + * This is especially useful for scripted equipment. + * @todo + */ + 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. * Forcibly delete items from the end if it is not. @@ -122,15 +153,7 @@ class Inventory void checkInventorySize(); /** - * Helper function for equip() when computing changes to equipment - * When newCount is 0, the item is being unequipped. - */ - // inventory slot -> {equip slots} - typedef std::multimap IdSlotMap; - void equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it); - - /** - * Changes equipment and adjusts character attributes. + * Apply equipment triggers. */ void updateEquipmentTrigger(unsigned int oldId, unsigned int itemId); void updateEquipmentTrigger(ItemClass *oldI, ItemClass *newI); -- cgit v1.2.3-70-g09d2