summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYohann Ferreira <yohann_dot_ferreira_at_orange_dot_efer>2011-08-11 02:54:10 +0200
committerYohann Ferreira <yohann_dot_ferreira_at_orange_dot_efer>2011-08-11 02:54:10 +0200
commit9527d079307454178fea3b2958e301875cecc15a (patch)
tree333ed71036813c9cdec4c8e1de1e058b86275bca
parent0820d6632b0ce5887c49d16929ac7903aa185fe4 (diff)
parent8766149dc12d197205b1632ec6e9fc663de05990 (diff)
downloadmanaserv-9527d079307454178fea3b2958e301875cecc15a.tar.gz
manaserv-9527d079307454178fea3b2958e301875cecc15a.tar.xz
manaserv-9527d079307454178fea3b2958e301875cecc15a.zip
Merge branch 'master' of git://gitorious.org/~bertram/mana/manaserv-equipment-fix into equipment-fix
Conflicts: src/common/manaserv_protocol.h
-rw-r--r--example/clientdata/equip.xml25
-rw-r--r--src/account-server/storage.cpp42
-rw-r--r--src/common/inventorydata.h54
-rw-r--r--src/common/manaserv_protocol.h894
-rw-r--r--src/game-server/buysell.cpp6
-rw-r--r--src/game-server/character.cpp2
-rw-r--r--src/game-server/gamehandler.cpp16
-rw-r--r--src/game-server/inventory.cpp924
-rw-r--r--src/game-server/inventory.h140
-rw-r--r--src/game-server/item.h4
-rw-r--r--src/game-server/itemmanager.cpp123
-rw-r--r--src/game-server/itemmanager.h44
-rw-r--r--src/game-server/state.cpp10
-rw-r--r--src/game-server/trade.cpp4
-rw-r--r--src/scripting/lua.cpp9
-rw-r--r--src/serialize/characterdata.h45
-rw-r--r--src/sql/mysql/createTables.sql6
-rw-r--r--src/sql/sqlite/createTables.sql3
18 files changed, 1205 insertions, 1146 deletions
diff --git a/example/clientdata/equip.xml b/example/clientdata/equip.xml
index 0d1c1d3..097229c 100644
--- a/example/clientdata/equip.xml
+++ b/example/clientdata/equip.xml
@@ -1,12 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Notes:
+ The id and name parameters must be unique.
+ The count parameter indicates the 'size' of the slot.
+ The visible parameter indicates whether the item is visible
+ on the map character. Hence, whether the server should send appearance
+ updates when making changes on it.
+-->
<equip-slots>
- <slot name="hand" count="2" visible="true" />
- <slot name="torso" count="1" visible="true" />
- <slot name="arms" count="1" />
- <slot name="head" count="1" visible="true" />
- <slot name="legs" count="1" visible="true" />
- <slot name="ring" count="1" />
- <slot name="necklace" count="1" />
- <slot name="feet" count="1" />
- <slot name="ammo" count="1" />
+ <slot id="1" name="hand" capacity="2" visible="true" />
+ <slot id="2" name="torso" capacity="1" visible="true" />
+ <slot id="3" name="arms" capacity="1" />
+ <slot id="4" name="head" capacity="1" visible="true" />
+ <slot id="5" name="legs" capacity="1" visible="true" />
+ <slot id="6" name="ring" capacity="1" />
+ <slot id="7" name="necklace" capacity="1" />
+ <slot id="8" name="feet" capacity="1" />
+ <slot id="9" name="ammo" capacity="1" />
</equip-slots>
diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp
index 9660120..05f8ec6 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<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 +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 4c637cc..e1c5bfa 100644
--- a/src/common/inventorydata.h
+++ b/src/common/inventorydata.h
@@ -26,29 +26,71 @@
/**
* 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
{
+ 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 }
+// 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 dc7e47b..710d2f4 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 };
+
+/**
+ * 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 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 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
+ 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/buysell.cpp b/src/game-server/buysell.cpp
index a965854..78c2bfe 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 bbe26bd..ef00163 100644
--- a/src/game-server/character.cpp
+++ b/src/game-server/character.cpp
@@ -81,7 +81,7 @@ Character::Character(MessageIn &msg):
setName(msg.readString());
deserializeCharacterData(*this, msg);
mOld = getPosition();
- Inventory(this).initialise();
+ Inventory(this).initialize();
modifiedAllAttribute();
setSize(16);
diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp
index 58d289f..30a620c 100644
--- a/src/game-server/gamehandler.cpp
+++ b/src/game-server/gamehandler.cpp
@@ -489,7 +489,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)))
@@ -509,8 +509,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)))
@@ -547,22 +547,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 9939065..1eaf8b9 100644
--- a/src/game-server/inventory.cpp
+++ b/src/game-server/inventory.cpp
@@ -25,196 +25,13 @@
#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"
-// TODO:
-// - Inventory::initialise() Usable but could use a few more things
-// - Inventory::equip() Usable but last part would be nice
-
-typedef std::set<unsigned int> ItemIdSet;
-
-Inventory::Inventory(Character *p, bool d):
- mPoss(&p->getPossessions()), mInvMsg(GPMSG_INVENTORY),
- mEqmMsg(GPMSG_EQUIP), mClient(p), mDelayed(d)
-{
-}
-
-Inventory::~Inventory()
-{
- commit(false);
-}
-
-void Inventory::restart()
+Inventory::Inventory(Character *p):
+ mPoss(&p->getPossessions()), mCharacter(p)
{
- mInvMsg.clear();
- mInvMsg.writeInt16(GPMSG_INVENTORY);
-}
-
-void Inventory::cancel()
-{
- assert(mDelayed);
- Possessions &poss = mClient->getPossessions();
- if (mPoss != &poss)
- {
- delete mPoss;
- mPoss = &poss;
- }
- restart();
-}
-
-void Inventory::commit(bool doRestart)
-{
- Possessions &poss = mClient->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(mClient, 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(mClient, 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))
- mClient->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 = &mClient->getPossessions();
- if (mPoss == poss)
- mPoss = new Possessions(*poss);
}
void Inventory::sendFull() const
@@ -238,43 +55,42 @@ 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(mClient, m);
+ gameHandler->sendTo(mCharacter, m);
}
-void Inventory::initialise()
+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.
*/
-
- 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(mClient, ITT_IN_INVY);
+ item->useTrigger(mCharacter, ITT_IN_INVY);
++it1;
}
else
{
LOG_WARN("Inventory: deleting unknown item type "
<< it1->second.itemId << " from the inventory of '"
- << mClient->getName()
+ << mCharacter->getName()
<< "'!");
mPoss->inventory.erase(it1++);
}
@@ -282,46 +98,57 @@ void Inventory::initialise()
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(mClient, 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;
+ }
+
+ /*
+ * 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);
}
- }
- equipment.clear();
+ ++it2;
+ }
- checkSize();
+ checkInventorySize();
}
-void Inventory::checkSize()
+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
- || mClient->getModifiedAttribute(ATTR_INV_CAPACITY) < 0)
+ || mCharacter->getModifiedAttribute(ATTR_INV_CAPACITY) < 0)
{
LOG_WARN("Inventory: oversize inventory! Deleting '"
<< mPoss->inventory.rbegin()->second.amount
@@ -330,11 +157,26 @@ void Inventory::checkSize()
<< "' from slot '"
<< mPoss->inventory.rbegin()->first
<< "' of character '"
- << mClient->getName()
+ << 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() << "'!");
+ }
}
}
@@ -346,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
@@ -360,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)
{
@@ -392,15 +244,21 @@ 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;
}
- checkSize();
+ // Send that first, before checking potential removals
+ if (invMsg.getLength() > 2)
+ gameHandler->sendTo(mCharacter, invMsg);
+
+ checkInventorySize();
return amount;
}
@@ -416,73 +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)
- itemManager->getItem(itemId)->useTrigger(mClient, ITT_LEAVE_INVY);
- // Rather inefficient, but still usable for now assuming small invy size.
- // FIXME
- return inv && !force ? remove(itemId, amount, true) : amount;
+ }
+
+ if (triggerLeaveInventory)
+ itemManager->getItem(itemId)->useTrigger(mCharacter, ITT_LEAVE_INVY);
+
+ if (invMsg.getLength() > 2)
+ gameHandler->sendTo(mCharacter, invMsg);
+
+ return amount;
}
-unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned int 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();
@@ -490,14 +348,7 @@ unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned in
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)
@@ -511,63 +362,113 @@ unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned in
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();
@@ -580,212 +481,319 @@ 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(mClient, 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.
if (!oldI && !newI)
return;
if (oldI && newI)
- oldI->useTrigger(mClient, ITT_EQUIPCHG);
+ oldI->useTrigger(mCharacter, ITT_EQUIPCHG);
else if (oldI)
- oldI->useTrigger(mClient, ITT_UNEQUIP);
+ oldI->useTrigger(mCharacter, ITT_UNEQUIP);
else if (newI)
- newI->useTrigger(mClient, ITT_EQUIP);
+ newI->useTrigger(mCharacter, ITT_EQUIP);
}
-bool Inventory::equip(int slot, bool override)
+unsigned int Inventory::getNewEquipItemInstance()
{
- if (mPoss->equipSlots.count(slot))
+ 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)
+{
+ 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;
- // 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<unsigned int> 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->getMaxSlotsFromId(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;
- break;
+ // 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;
}
}
- switch (fail)
+ }
+
+ // Potential Pre-unequipment process
+ for (std::list<unsigned int>::const_iterator it3 =
+ equipSlotsToUnequipFirst.begin();
+ it3 != equipSlotsToUnequipFirst.end(); ++it3)
+ {
+ if (!unequip(*it3))
{
- case 0:
- /*
- * Clean fit. Equip and apply immediately.
- */
- if (!mDelayed) {
- mEqmMsg.writeInt16(slot); // Inventory slot
- mEqmMsg.writeInt8(it2->size()); // Equip slot type count
- }
- for (it3 = it2->begin(),
- it3_end = it2->end();
- it3 != it3_end;
- ++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)
{
- if (!mDelayed) {
- mEqmMsg.writeInt8(it3->first); // Equip slot
- mEqmMsg.writeInt8(it3->second); // How many are used
+ // 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;
}
- /*
- * 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));
}
- 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)
- {
- /*
- * 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.
- */
- // TODO - this would increase ease of use substatially, add as soon as
- // there is time to do so.
+ // 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)
+ {
+ unsigned int maxCapacity =
+ itemManager->getEquipSlotCapacity(it2->first);
+
+ // 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>
+ (it2->first, equipItem));
+ --capacityLeft;
+ }
+ }
+
+ // Equip slot
+ equipMsg.writeInt16(it2->first);
+ // Capacity used
+ equipMsg.writeInt16(it2->second);
}
- /*
- * 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);
+ // New item trigger
+ updateEquipmentTrigger(0, it->second.itemId);
+
+ // Remove item from inventory
+ removeFromSlot(inventorySlot, 1);
+
+ if (equipMsg.getLength() > 2)
+ gameHandler->sendTo(mCharacter, equipMsg);
+
+ return true;
}
-bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp)
+bool Inventory::unequip(unsigned int equipmentSlot)
{
- prepare();
- EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(),
- it_end = mPoss->equipSlots.end();
+ // map of { itemInstance, itemId }
+ std::map<unsigned int, unsigned int> itemIdListToInventory;
bool changed = 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.
+ 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->second == 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<unsigned int, unsigned int>::const_iterator it2 =
+ itemIdListToInventory.find(it->second.itemInstance);
+ if (it2 == itemIdListToInventory.end())
+ {
+ itemIdListToInventory.insert(
+ std::make_pair<unsigned int, unsigned int>
+ (it->second.itemInstance, it->second.itemId));
+ }
+
changed = true;
- mPoss->equipSlots.erase(it++);
- }
- else
- {
- ++it;
+ it->second.itemId = 0;
+ it->second.itemInstance = 0;
}
}
- if (changed && !mDelayed)
+ // Apply unequip trigger(s), and move the item(s) back to inventory.
+ for (std::map<unsigned int, unsigned int>::const_iterator it2 =
+ itemIdListToInventory.begin(), it2_end = itemIdListToInventory.end();
+ it2 != it2_end; ++it2)
+ {
+ updateEquipmentTrigger(it2->second, 0);
+ insert(it2->second, 1);
+ }
+
+ if (changed)
{
- changeEquipment(mPoss->inventory.at(slot).itemId, 0);
- mEqmMsg.writeInt16(slot);
- mEqmMsg.writeInt8(0);
+ equipMsg.writeInt16(equipmentSlot);
+ equipMsg.writeInt16(0); // Capacity used, set to 0 to unequip.
}
+ if (equipMsg.getLength() > 2)
+ gameHandler->sendTo(mCharacter, equipMsg);
+
return changed;
}
diff --git a/src/game-server/inventory.h b/src/game-server/inventory.h
index bd9da5c..06f55e0 100644
--- a/src/game-server/inventory.h
+++ b/src/game-server/inventory.h
@@ -24,37 +24,6 @@
#include "game-server/character.h"
#include "net/messageout.h"
-/*enum
-{
-// Equipment rules:
-// 1 torso equipment
- EQUIP_TORSO_SLOT = 0,
-// 1 arms equipment
- EQUIP_ARMS_SLOT = 1,
-// 1 head equipment
- EQUIP_HEAD_SLOT = 2,
-// 1 legs equipment
- EQUIP_LEGS_SLOT = 3,
-// 1 feet equipment
- EQUIP_FEET_SLOT = 4,
-// 2 rings
- EQUIP_RING1_SLOT = 5,
- EQUIP_RING2_SLOT = 6,
-// 1 necklace
- EQUIP_NECKLACE_SLOT = 7,
-// Fight:
-// 2 one-handed weapons
-// or 1 two-handed weapon
-// or 1 one-handed weapon + 1 shield.
- EQUIP_FIGHT1_SLOT = 8,
- EQUIP_FIGHT2_SLOT = 9,
-// Projectile:
-// this item does not amount to one, it only indicates the chosen projectile.
- EQUIP_PROJECTILE_SLOT = 10,
-
- EQUIP_CLIENT_INVENTORY = 32
-};*/
-
class ItemClass;
/**
@@ -66,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.
@@ -100,29 +54,21 @@ class Inventory
* Ensures the inventory is sane and apply equipment modifiers.
* Should be run only once and the very first time.
*/
- void initialise();
+ void initialize();
/**
* 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.
@@ -132,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.
@@ -160,51 +106,61 @@ class Inventory
unsigned int getItem(unsigned int slot) const;
private:
-
/**
- * Make sure that changes are being done on a copy, not directly.
- * No effect when not in delayed mode.
+ * 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.
*/
- void prepare();
+ bool checkEquipmentCapacity(unsigned int equipmentSlot,
+ unsigned int capacityRequested);
/**
- * Starts a new notification message.
+ * Test whether the inventory has enough space to welcome
+ * the willing-to-be equipment slot.
+ * @todo
*/
- void restart();
+ 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; }
/**
- * Check the inventory is within the slot limit and capacity.
- * Forcibly delete items from the end if it is not.
- * @todo Drop items instead?
+ * Test the items equipment for scripted requirements.
+ * @todo
*/
- void checkSize();
+ bool testEquipScriptRequirements(unsigned int itemId)
+ { return true; }
/**
- * Helper function for equip() when computing changes to equipment
- * When newCount is 0, the item is being unequipped.
+ * 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.
*/
- // inventory slot -> {equip slots}
- typedef std::multimap<unsigned int, unsigned short> IdSlotMap;
- void equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it);
+ unsigned int getNewEquipItemInstance();
/**
- * Changes equipment and adjusts character attributes.
+ * Check the inventory is within the slot limit and capacity.
+ * Forcibly delete items from the end if it is not.
+ * @todo Drop items instead?
*/
- void changeEquipment(unsigned int oldId, unsigned int itemId);
- void changeEquipment(ItemClass *oldI, ItemClass *newI);
+ void checkInventorySize();
- 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)
+ * Apply equipment triggers.
*/
- MessageOut mInvMsg;
- MessageOut mEqmMsg; /**< Update message containing equipment changes */
- Character *mClient; /**< Character to notify. */
- bool mDelayed; /**< Delayed changes. */
-};
+ void updateEquipmentTrigger(unsigned int oldId, unsigned int itemId);
+ void updateEquipmentTrigger(ItemClass *oldI, ItemClass *newI);
+ Possessions *mPoss; /**< Pointer to the modified possessions. */
+
+ Character *mCharacter; /**< Character to notify. */
+};
#endif
diff --git a/src/game-server/item.h b/src/game-server/item.h
index 1c7639c..caef386 100644
--- a/src/game-server/item.h
+++ b/src/game-server/item.h
@@ -28,7 +28,9 @@
class Being;
class Script;
-typedef std::list< std::pair< unsigned int, unsigned int> > ItemEquipInfo;
+// A pair indicating: Equipment slot id -> how much slots required.
+typedef std::pair< unsigned int, unsigned int> ItemEquipInfo;
+// The list of required slots to equip.
typedef std::list< ItemEquipInfo > ItemEquipsInfo;
/**
diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp
index d21c791..074b834 100644
--- a/src/game-server/itemmanager.cpp
+++ b/src/game-server/itemmanager.cpp
@@ -51,6 +51,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();
}
@@ -71,52 +79,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
-{
- 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;
-}
-
-unsigned int ItemManager::getMaxSlotsFromId(unsigned int id) const
+unsigned int ItemManager::getEquipSlotIdFromName(const std::string &name) const
{
- return mEquipSlots.at(id).second;
+ EquipSlotInfo *slotInfo = mNamedEquipSlotsInfo.find(name);
+ return slotInfo ? slotInfo->slotId : 0;
}
-unsigned int ItemManager::getVisibleSlotCount() const
+unsigned int ItemManager::getEquipSlotCapacity(unsigned int id) 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()
@@ -133,42 +111,61 @@ 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
+
+ bool visible = XML::getBoolProperty(node, "visible", false);
+ if (visible)
{
- bool visible = XML::getProperty(node, "visible", "false") != "false";
- if (visible)
+ if (++mVisibleEquipSlotCount > 7)
{
- 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!");
+ 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;
}
+
+ EquipSlotsInfo::iterator i = mEquipSlotsInfo.find(slotId);
+
+ if (i != mEquipSlotsInfo.end())
+ {
+ 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()
@@ -263,7 +260,6 @@ 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"))
@@ -274,17 +270,26 @@ void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item)
LOG_WARN("Item Manager: empty equip slot definition!");
continue;
}
- req.push_back(std::make_pair(getEquipIdFromName(slot),
+ if (utils::isNumeric(slot))
+ {
+ // When the slot id is given
+ item->mEquip.push_back(std::make_pair(utils::stringToInt(slot),
+ XML::getProperty(subNode, "required", 1)));
+ }
+ else
+ {
+ // When its name is given
+ item->mEquip.push_back(std::make_pair(getEquipSlotIdFromName(slot),
XML::getProperty(subNode, "required", 1)));
+ }
}
}
- if (req.empty())
+ if (item->mEquip.empty())
{
LOG_WARN("Item Manager: empty equip requirement "
"definition for item " << item->getDatabaseID() << "!");
return;
}
- item->mEquip.push_back(req);
}
void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item)
diff --git a/src/game-server/itemmanager.h b/src/game-server/itemmanager.h
index c310df4..9eee326 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:
@@ -73,13 +91,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;
@@ -94,19 +111,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/state.cpp b/src/game-server/state.cpp
index 03d4b71..d8f9b18 100644
--- a/src/game-server/state.cpp
+++ b/src/game-server/state.cpp
@@ -108,8 +108,8 @@ static void updateMap(MapComposite *map)
*/
static void serializeLooks(Character *ch, MessageOut &msg, bool full)
{
- const Possessions &poss = ch->getPossessions();
- unsigned int nb_slots = itemManager->getVisibleSlotCount();
+ const EquipData &equipData = ch->getPossessions().getEquipment();
+ unsigned int nb_slots = itemManager->getVisibleEquipSlotCount();
// Bitmask describing the changed entries.
int changed = (1 << nb_slots) - 1;
@@ -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<unsigned int, unsigned int>::const_iterator it =
- poss.equipSlots.begin();
+ std::map<unsigned int, EquipmentItem>::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/game-server/trade.cpp b/src/game-server/trade.cpp
index 5150930..e1779d2 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 47bf335..2ea2187 100644
--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -366,7 +366,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)) ||
@@ -404,12 +404,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
diff --git a/src/serialize/characterdata.h b/src/serialize/characterdata.h
index 4466c98..c453e68 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 9e1e31a..d017cee 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 b366606..1027b58 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)
);