diff options
Diffstat (limited to 'src/game-server')
-rw-r--r-- | src/game-server/actor.h | 3 | ||||
-rw-r--r-- | src/game-server/buysell.cpp | 6 | ||||
-rw-r--r-- | src/game-server/character.cpp | 2 | ||||
-rw-r--r-- | src/game-server/commandhandler.cpp | 43 | ||||
-rw-r--r-- | src/game-server/gamehandler.cpp | 16 | ||||
-rw-r--r-- | src/game-server/inventory.cpp | 946 | ||||
-rw-r--r-- | src/game-server/inventory.h | 139 | ||||
-rw-r--r-- | src/game-server/item.h | 23 | ||||
-rw-r--r-- | src/game-server/itemmanager.cpp | 146 | ||||
-rw-r--r-- | src/game-server/itemmanager.h | 47 | ||||
-rw-r--r-- | src/game-server/main-game.cpp | 8 | ||||
-rw-r--r-- | src/game-server/monstermanager.h | 7 | ||||
-rw-r--r-- | src/game-server/skillmanager.cpp | 241 | ||||
-rw-r--r-- | src/game-server/skillmanager.h | 55 | ||||
-rw-r--r-- | src/game-server/state.cpp | 83 | ||||
-rw-r--r-- | src/game-server/trade.cpp | 4 | ||||
-rw-r--r-- | src/game-server/trigger.cpp | 11 |
17 files changed, 942 insertions, 838 deletions
diff --git a/src/game-server/actor.h b/src/game-server/actor.h index 529d48c4..abc1e1d0 100644 --- a/src/game-server/actor.h +++ b/src/game-server/actor.h @@ -111,6 +111,9 @@ class Actor : public Thing void setPublicID(int id) { mPublicID = id; } + bool isPublicIdValid() const + { return (mPublicID > 0 && mPublicID != 65535); } + /** * Gets the way the actor blocks pathfinding for other actors. */ diff --git a/src/game-server/buysell.cpp b/src/game-server/buysell.cpp index a9658546..78c2bfe0 100644 --- a/src/game-server/buysell.cpp +++ b/src/game-server/buysell.cpp @@ -72,9 +72,9 @@ int BuySell::registerPlayerItems() // We parse the player inventory and add all item // in a sell list. - const Possessions &charPoss = mChar->getPossessions(); - for (InventoryData::const_iterator it = charPoss.inventory.begin(), - it_end = charPoss.inventory.end(); it != it_end; ++it) + const InventoryData &inventoryData = mChar->getPossessions().getInventory(); + for (InventoryData::const_iterator it = inventoryData.begin(), + it_end = inventoryData.end(); it != it_end; ++it) { unsigned int nb = it->second.amount; if (!nb) diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index bbe26bd6..ef001638 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/commandhandler.cpp b/src/game-server/commandhandler.cpp index f0cbcf3b..0cde9891 100644 --- a/src/game-server/commandhandler.cpp +++ b/src/game-server/commandhandler.cpp @@ -76,6 +76,7 @@ static void handleLog(Character*, std::string&); static void handleLogsay(Character*, std::string&); static void handleKillMonsters(Character*, std::string&); static void handleCraft(Character*, std::string&); +static void handleGetPos(Character*, std::string&); static CmdRef const cmdRef[] = { @@ -133,6 +134,8 @@ static CmdRef const cmdRef[] = "Kills all monsters on the map.", &handleKillMonsters}, {"craft", "{ <item> <amount> }", "Crafts something.", &handleCraft}, + {"getpos", "<character>", + "Gets the position of a character.", &handleGetPos}, {NULL, NULL, NULL, NULL} }; @@ -186,7 +189,15 @@ static std::string getArgument(std::string &args) // Jumps to the next parameter, // after the ending double-quote and space, // and remove the two double-quotes before returning. - args = args.substr(pos + 2); + if (pos + 2 < args.size()) + { + args = args.substr(pos + 2); + } + else + { + // This was the last argument + args.clear(); + } argument = argument.substr(1, pos - 1); } else @@ -1369,6 +1380,36 @@ static void handleCraft(Character *player, std::string &args) } } +static void handleGetPos(Character *player, std::string &args) +{ + std::string character = getArgument(args); + if (character.empty()) + { + say("Invalid amount of arguments given.", player); + say("Usage: @getpos <character>", player); + return; + } + Character *other; + other = getPlayer(character); + if (!other) + { + say("Invalid character, or they are offline.", player); + return; + } + const Point &pos = other->getPosition(); + std::stringstream str; + str << "The current location of " + << character + << " is map " + << other->getMapId() + << " [" + << pos.x + << ":" + << pos.y + << "]"; + say(str.str(), player); +} + void CommandHandler::handleCommand(Character *player, const std::string &command) { diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp index 4c69da20..92971903 100644 --- a/src/game-server/gamehandler.cpp +++ b/src/game-server/gamehandler.cpp @@ -494,7 +494,7 @@ void GameHandler::handlePickup(GameClient &client, MessageIn &message) void GameHandler::handleUseItem(GameClient &client, MessageIn &message) { - const int slot = message.readInt8(); + const int slot = message.readInt16(); Inventory inv(client.character); if (ItemClass *ic = itemManager->getItem(inv.getItem(slot))) @@ -514,8 +514,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))) @@ -552,22 +552,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 99390651..e486f7c0 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,329 @@ 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); +} + +unsigned int Inventory::getNewEquipItemInstance() +{ + unsigned int itemInstance = 1; + + for (EquipData::const_iterator it = mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); it != it_end; ++it) + { + if (it->second.itemInstance == itemInstance) + { + ++itemInstance; + it = mPoss->equipSlots.begin(); + } + } + + return itemInstance; } -bool Inventory::equip(int slot, bool override) +bool Inventory::checkEquipmentCapacity(unsigned int equipmentSlot, + unsigned int capacityRequested) { - if (mPoss->equipSlots.count(slot)) + int capacity = itemManager->getEquipSlotCapacity(equipmentSlot); + + // If the equipement slot doesn't exist, we can't equip on it. + if (capacity <= 0) return false; + + // Test whether the slot capacity requested is reached. + for (EquipData::const_iterator it = mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); it != it_end; ++it) + { + if (it->first == equipmentSlot) + { + if (it->second.itemInstance != 0) + { + capacity--; + } + } + } + + assert(capacity >= 0); // A should never happen case. + + if (capacity < (int)capacityRequested) + return false; + + return true; +} + +bool Inventory::equip(int inventorySlot) +{ + // Test inventory slot existence InventoryData::iterator it; - if ((it = mPoss->inventory.find(slot)) == mPoss->inventory.end()) + if ((it = mPoss->inventory.find(inventorySlot)) == mPoss->inventory.end()) + { + return false; + LOG_DEBUG("No existing item in inventory at slot: " << inventorySlot); + } + + // Test the equipment scripted requirements + if (!testEquipScriptRequirements(it->second.itemId)) + return false; + + // Test the equip requirements. If none, it's not an equipable item. + const ItemEquipRequirement &equipReq = + itemManager->getItem(it->second.itemId)->getItemEquipRequirement(); + if (!equipReq.equipSlotId) + { + LOG_DEBUG("No equip requirements for item id: " << it->second.itemId + << " at slot: " << inventorySlot); return false; - const ItemEquipsInfo &eq = itemManager->getItem(it->second.itemId)->getItemEquipData(); - if (eq.empty()) + } + + // List of potential unique itemInstances to unequip first. + std::set<unsigned int> equipInstancesToUnequipFirst; + + // We first check the equipment slots for: + // - 1. whether enough total equip slot space is available. + // - 2. whether some other equipment is to be unequipped first. + + // If not enough total space in the equipment slot is available, + // we cannot equip. + if (itemManager->getEquipSlotCapacity(equipReq.equipSlotId) + < equipReq.capacityRequired) + { + LOG_DEBUG("Not enough equip capacity at slot: " << equipReq.equipSlotId + << ", total available: " + << itemManager->getEquipSlotCapacity(equipReq.equipSlotId) + << ", required: " << equipReq.capacityRequired); return false; - ItemEquipInfo const *ovd = 0; - // Iterate through all possible combinations of slots - for (ItemEquipsInfo::const_iterator it2 = eq.begin(), - it2_end = eq.end(); - it2 != it2_end; - ++it2) + } + + // Test whether some item(s) is(are) to be unequipped first. + if (!checkEquipmentCapacity(equipReq.equipSlotId, + equipReq.capacityRequired)) { - // Iterate through this combination of slots. - /* - * 0 = all ok, slots free - * 1 = possible if other items are unequipped first - * 2 = impossible, requires too many slots even with other equipment being removed - */ - int fail = 0; - ItemEquipInfo::const_iterator it3, it3_end; - for (it3 = it2->begin(), - it3_end = it2->end(); - it3 != it3_end; - ++it3) + // And test whether the unequip action would succeed first. + if (testUnequipScriptRequirements(equipReq.equipSlotId) + && hasInventoryEnoughSpace(equipReq.equipSlotId)) { - // it3 -> { slot id, number required } - unsigned int max = itemManager->getMaxSlotsFromId(it3->first), - used = mPoss->equipSlots.count(it3->first); - if (max - used >= it3->second) - continue; - else if (max >= it3->second) + // Then, we unequip each iteminstance of the equip slot + for (EquipData::iterator iter = + mPoss->equipSlots.begin(); + iter != mPoss->equipSlots.end(); ++iter) { - fail |= 1; - if (override) - continue; - else - break; - } - else - { - fail |= 2; - break; + if (iter->first == equipReq.equipSlotId + && iter->second.itemInstance) + equipInstancesToUnequipFirst.insert( + iter->second.itemInstance); } } - switch (fail) + else + { + // Some non-unequippable equipment is to be unequipped first. + // Can be the case of cursed items, + // or when the inventory is full, for instance. + return false; + } + } + + // Potential Pre-unequipment process + for (std::set<unsigned int>::const_iterator it3 = + equipInstancesToUnequipFirst.begin(); + it3 != equipInstancesToUnequipFirst.end(); ++it3) + { + if (!unequip(*it3)) + { + // Something went wrong even when we tested the unequipment process. + LOG_WARN("Unable to unequip even when unequip was tested. " + "Character : " << mCharacter->getName() + << ", unequip slot: " << *it3); + return false; + } + } + + // Actually equip the item now that the requirements has met. + //W equip slot type count, W item id, { W equip slot, W capacity used}* + MessageOut equipMsg(GPMSG_EQUIP); + equipMsg.writeInt16(it->second.itemId); // Item Id + equipMsg.writeInt16(1); // Number of equip slot changed. + + // Compute an unique equip item Instance id (unicity is per character only.) + int itemInstance = getNewEquipItemInstance(); + + unsigned int capacityLeft = equipReq.capacityRequired; + unsigned int capacityUsed = 0; + // Apply equipment changes + for (EquipData::iterator it4 = mPoss->equipSlots.begin(), + it4_end = mPoss->equipSlots.end(); it4 != it4_end; ++it4) + { + if (!capacityLeft) + break; + + // We've found an existing equip slot + if (it4->first == equipReq.equipSlotId) { - case 0: - /* - * Clean fit. Equip and apply immediately. - */ - if (!mDelayed) { - mEqmMsg.writeInt16(slot); // Inventory slot - mEqmMsg.writeInt8(it2->size()); // Equip slot type count + // We've found an empty slot + if (it4->second.itemInstance == 0) + { + it4->second.itemId = it->second.itemId; + it4->second.itemInstance = itemInstance; + --capacityLeft; } - for (it3 = it2->begin(), - it3_end = it2->end(); - it3 != it3_end; - ++it3) + else // The slot is already in use. { - if (!mDelayed) { - mEqmMsg.writeInt8(it3->first); // Equip slot - mEqmMsg.writeInt8(it3->second); // How many are used - } - /* - * This bit can be somewhat inefficient, but is far better for - * average case assuming most equip use one slot max for each - * type and infrequently (<1/3) two of each type max. - * If the reader cares, you're more than welcome to add - * compile time options optimising for other usage. - * For now, this is adequate assuming `normal' usage. - */ - for (unsigned int i = 0; i < it3->second; ++i) - mPoss->equipSlots.insert( - std::make_pair(it3->first, slot)); + ++capacityUsed; } - if (!mDelayed) - changeEquipment(0, it->second.itemId); - return true; - case 1: - /* - * Definitions earlier in the item file have precedence (even if it - * means requiring unequipping more), so no need to store more - * than the first. - */ - if (override && !ovd) - ovd = &*it2; // Iterator -> object -> pointer. - break; - case 2: - default: - /* - * Since slots are currently static (and I don't see any reason to - * change this right now), something probably went wrong. - * The logic to catch this is here rather than in the item manager - * just in case non-static equip slots do want to be - * implemented later. This would not be a trivial task, - * however. - */ - LOG_WARN("Inventory - item '" << it->second.itemId << - "' cannot be equipped, even by unequipping other items!"); - break; } } - // We didn't find a clean equip. - if (ovd) + + // When there is still something to apply even when out of that loop, + // It means that the equip multimapis missing empty slots. + // Hence, we add them back + if(capacityLeft) { - /* - * We did find an equip that works if we unequip other items, and we can override. - * Process unequip triggers for all items we have to unequip. - * Process equip triggers for new item. - * Attempt to reequip any equipment we had to remove, but disallowing override. - */ + unsigned int maxCapacity = + itemManager->getEquipSlotCapacity(equipReq.equipSlotId); - // TODO - this would increase ease of use substatially, add as soon as - // there is time to do so. + // A should never happen case + assert(maxCapacity >= capacityUsed + capacityLeft); - return false; // Return true when this section is complete + while (capacityLeft) + { + EquipmentItem equipItem(it->second.itemId, itemInstance); + mPoss->equipSlots.insert( + std::make_pair<unsigned int, EquipmentItem> + (equipReq.equipSlotId, equipItem)); + --capacityLeft; + } } - /* - * We cannot equip, either because we could not find any valid equip process - * or because we found a dirty equip and weren't allowed to override. - */ - return false; -} -bool Inventory::unequip(EquipData::iterator it) -{ - return unequip(it->second, &it); + // Equip slot + equipMsg.writeInt16(equipReq.equipSlotId); + // Capacity used + equipMsg.writeInt16(equipReq.capacityRequired); + // Item instance + equipMsg.writeInt16(itemInstance); + + // New item trigger + updateEquipmentTrigger(0, it->second.itemId); + + // Remove item from inventory + removeFromSlot(inventorySlot, 1); + + gameHandler->sendTo(mCharacter, equipMsg); + + // Update look when necessary + checkLookchanges(equipReq.equipSlotId); + + return true; } -bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) +bool Inventory::unequip(unsigned int itemInstance) { - prepare(); - EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(), - it_end = mPoss->equipSlots.end(); - bool changed = false; + if (!itemInstance) + return false; - // Erase all equip entries that point to the given inventory slot - while (it != it_end) + MessageOut equipMsg(GPMSG_EQUIP); + equipMsg.writeInt16(0); // Item Id, useless in case of unequip. + + // The itemId to unequip + unsigned int itemId = 0; + unsigned int slotTypeId = 0; + + // Empties all equip entries that point to the given equipment slot + // The equipment slots should NEVER be erased after initialization! + for (EquipData::iterator it = mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); it != it_end; ++it) { - if (it->second == slot) - { - changed = true; - mPoss->equipSlots.erase(it++); - } - else + if (it->second.itemInstance == itemInstance && it->second.itemId) { - ++it; + // Add the item to the inventory list if not already present there + itemId = it->second.itemId; + it->second.itemId = 0; + it->second.itemInstance = 0; + + // We keep track of the slot type to be able to raise a potential + // change in the character sprite + slotTypeId = it->first; } } - if (changed && !mDelayed) - { - changeEquipment(mPoss->inventory.at(slot).itemId, 0); - mEqmMsg.writeInt16(slot); - mEqmMsg.writeInt8(0); - } + // When there were no corresponding item id, it means no item was to + // be unequipped. + if (!itemId) + return false; + + // Number of slot types touched, + equipMsg.writeInt16(1); + + // Move the item back to inventory. + insert(itemId, 1); - return changed; + equipMsg.writeInt16(itemInstance); + equipMsg.writeInt16(0); // Capacity used, set to 0 to unequip. + + gameHandler->sendTo(mCharacter, equipMsg); + + // Apply unequip trigger + updateEquipmentTrigger(itemId, 0); + + checkLookchanges(slotTypeId); + + return true; +} + +void Inventory::checkLookchanges(unsigned int slotTypeId) +{ + if (itemManager->isEquipSlotVisible(slotTypeId)) + mCharacter->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); } diff --git a/src/game-server/inventory.h b/src/game-server/inventory.h index bd9da5c3..547abdf0 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 itemInstance The item instance id used to know what to unequip * @returns Whether it was unequipped. */ - bool unequip(EquipData::iterator it); - bool unequip(unsigned int slot, EquipData::iterator *itp = 0); + bool unequip(unsigned int itemInstance); /** * Inserts some items into the inventory. @@ -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,66 @@ class Inventory unsigned int getItem(unsigned int slot) const; private: + /** + * Tell whether the equipment slot has enough room in an equipment slot. + * @param equipmentSlot the slot in equipement to check. + * @param capacityRequested the capacity needed. + */ + bool checkEquipmentCapacity(unsigned int equipmentSlot, + unsigned int capacityRequested); /** - * Make sure that changes are being done on a copy, not directly. - * No effect when not in delayed mode. + * Test whether the inventory has enough space to welcome + * the willing-to-be equipment slot. + * @todo */ - void prepare(); + bool hasInventoryEnoughSpace(unsigned int equipmentSlot) + { return true; } /** - * Starts a new notification message. + * Test the items unequipment requirements. + * This is especially useful for scripted equipment. + * @todo */ - void restart(); + bool testUnequipScriptRequirements(unsigned int equipementSlot) + { return true; } + /** + * Test the items equipment for scripted requirements. + * @todo + */ + bool testEquipScriptRequirements(unsigned int itemId) + { return true; } + + /** + * Return an equip item instance id unique to the item used, + * per character. + * This is used to differenciate some items that can be equipped + * multiple times, like one-handed weapons for instance. + */ + unsigned int getNewEquipItemInstance(); /** * Check the inventory is within the slot limit and capacity. * Forcibly delete items from the end if it is not. * @todo Drop items instead? */ - void checkSize(); + void checkInventorySize(); /** - * Helper function for equip() when computing changes to equipment - * When newCount is 0, the item is being unequipped. + * Check potential visible character sprite changes. */ - // inventory slot -> {equip slots} - typedef std::multimap<unsigned int, unsigned short> IdSlotMap; - void equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it); + void checkLookchanges(unsigned int slotTypeId); /** - * Changes equipment and adjusts character attributes. + * Apply equipment triggers. */ - void changeEquipment(unsigned int oldId, unsigned int itemId); - void changeEquipment(ItemClass *oldI, ItemClass *newI); + void updateEquipmentTrigger(unsigned int oldId, unsigned int itemId); + void updateEquipmentTrigger(ItemClass *oldI, ItemClass *newI); Possessions *mPoss; /**< Pointer to the modified possessions. */ - /** - * Update message containing inventory changes. - * Note that in sendFull(), this is reused to send all full changes - * (for both inventory and equipment) - */ - MessageOut mInvMsg; - MessageOut mEqmMsg; /**< Update message containing equipment changes */ - Character *mClient; /**< Character to notify. */ - bool mDelayed; /**< Delayed changes. */ -}; + Character *mCharacter; /**< Character to notify. */ +}; #endif diff --git a/src/game-server/item.h b/src/game-server/item.h index 1c7639c5..f7c380f1 100644 --- a/src/game-server/item.h +++ b/src/game-server/item.h @@ -28,8 +28,15 @@ class Being; class Script; -typedef std::list< std::pair< unsigned int, unsigned int> > ItemEquipInfo; -typedef std::list< ItemEquipInfo > ItemEquipsInfo; +// Indicates the equip slot "cost" to equip an item. +struct ItemEquipRequirement { + ItemEquipRequirement(): + equipSlotId(0), + capacityRequired(0) + {} + + unsigned int equipSlotId, capacityRequired; +}; /** * State effects to beings, and actors. @@ -224,9 +231,10 @@ class ItemClass { return mSpriteID; } /** - * Returns equip requirements. + * Returns equip requirement. */ - const ItemEquipsInfo &getItemEquipData() const { return mEquip; } + const ItemEquipRequirement &getItemEquipRequirement() const + { return mEquipReq; } private: /** @@ -272,12 +280,9 @@ class ItemClass std::multimap< ItemTriggerType, ItemEffectInfo * > mDispells; /** - * List of list of requirements for equipping. Only one inner list - * need be satisfied to sucessfully equip. Checks occur in order - * from outer front to back. - * All conditions in an inner list must be met for success. + * Requirement for equipping. */ - ItemEquipsInfo mEquip; + ItemEquipRequirement mEquipReq; friend class ItemManager; }; diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index d21c791c..ac7f13c8 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 +unsigned int ItemManager::getEquipSlotIdFromName(const std::string &name) const { - for (unsigned int i = 0; i < mEquipSlots.size(); ++i) - if (name == mEquipSlots.at(i).first) - return i; - LOG_WARN("Item Manager: attempt to find equip id from name \"" << - name << "\" not found, defaulting to 0!"); - return 0; + EquipSlotInfo *slotInfo = mNamedEquipSlotsInfo.find(name); + return slotInfo ? slotInfo->slotId : 0; } -unsigned int ItemManager::getMaxSlotsFromId(unsigned int id) const +unsigned int ItemManager::getEquipSlotCapacity(unsigned int id) const { - return mEquipSlots.at(id).second; -} - -unsigned int ItemManager::getVisibleSlotCount() const -{ - if (!mVisibleEquipSlotCount) - { - for (VisibleEquipSlots::const_iterator it = mVisibleEquipSlots.begin(), - it_end = mVisibleEquipSlots.end(); - it != it_end; - ++it) - { - mVisibleEquipSlotCount += mEquipSlots.at(*it).second; - } - } - return mVisibleEquipSlotCount; + EquipSlotsInfo::const_iterator i = mEquipSlotsInfo.find(id); + return i != mEquipSlotsInfo.end() ? i->second->slotCapacity : 0; } bool ItemManager::isEquipSlotVisible(unsigned int id) const { - for (VisibleEquipSlots::const_iterator it = mVisibleEquipSlots.begin(), - it_end = mVisibleEquipSlots.end(); - it != it_end; - ++it) - { - if (*it == id) - return true; - } - return false; + EquipSlotsInfo::const_iterator i = mEquipSlotsInfo.find(id); + return i != mEquipSlotsInfo.end() ? i->second->visibleSlot : false; } void ItemManager::readEquipSlotsFile() @@ -133,42 +111,63 @@ void ItemManager::readEquipSlotsFile() LOG_INFO("Loading equip slots: " << mEquipSlotsFile); - unsigned totalCount = 0; + unsigned totalCapacity = 0; unsigned slotCount = 0; - unsigned visibleSlotCount = 0; + mVisibleEquipSlotCount = 0; for_each_xml_child_node(node, rootNode) { if (xmlStrEqual(node->name, BAD_CAST "slot")) { + const int slotId = XML::getProperty(node, "id", 0); const std::string name = XML::getProperty(node, "name", std::string()); - const int count = XML::getProperty(node, "count", 0); + const int capacity = XML::getProperty(node, "capacity", 0); - if (name.empty() || count <= 0) + if (slotId <= 0 || name.empty() || capacity <= 0) { - LOG_WARN("Item Manager: equip slot has no name or zero count"); + LOG_WARN("Item Manager: equip slot " << slotId + << ": (" << name << ") has no name or zero count. " + "The slot has been ignored."); + continue; } - else + + if (slotId > 255) + { + LOG_WARN("Item Manager: equip slot " << slotId + << ": (" << name << ") is superior to 255 " + "and has been ignored."); + continue; + } + + bool visible = XML::getBoolProperty(node, "visible", false); + if (visible) + ++mVisibleEquipSlotCount; + + EquipSlotsInfo::iterator i = mEquipSlotsInfo.find(slotId); + + if (i != mEquipSlotsInfo.end()) { - bool visible = XML::getProperty(node, "visible", "false") != "false"; - if (visible) - { - mVisibleEquipSlots.push_back(mEquipSlots.size()); - if (++visibleSlotCount > 7) - LOG_WARN("Item Manager: More than 7 visible equip slot!" - "This will not work with current netcode!"); - } - mEquipSlots.push_back(std::pair<std::string, unsigned int> - (name, count)); - totalCount += count; - ++slotCount; + LOG_WARN("Item Manager: Ignoring duplicate definition " + "of equip slot '" << slotId << "'!"); + continue; } + + LOG_DEBUG("Adding equip slot, id: " << slotId << ", name: " << name + << ", capacity: " << capacity << ", visible? " << visible); + EquipSlotInfo *equipSlotInfo = + new EquipSlotInfo(slotId, name, capacity, visible); + mEquipSlotsInfo.insert( + std::make_pair<unsigned int, EquipSlotInfo*>(slotId, equipSlotInfo)); + mNamedEquipSlotsInfo.insert(name, equipSlotInfo); + + totalCapacity += capacity; + ++slotCount; } } LOG_INFO("Loaded '" << slotCount << "' slot types with '" - << totalCount << "' slots."); + << totalCapacity << "' slots."); } void ItemManager::readItemsFile() @@ -263,28 +262,45 @@ void ItemManager::readItemNode(xmlNodePtr itemNode) void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item) { - ItemEquipInfo req; for_each_xml_child_node(subNode, equipNode) { if (xmlStrEqual(subNode->name, BAD_CAST "slot")) { + if (item->mEquipReq.equipSlotId) + { + LOG_WARN("Item Manager: duplicate equip slot definitions!" + " Only the first will apply."); + break; + } + std::string slot = XML::getProperty(subNode, "type", std::string()); if (slot.empty()) { LOG_WARN("Item Manager: empty equip slot definition!"); continue; } - req.push_back(std::make_pair(getEquipIdFromName(slot), - XML::getProperty(subNode, "required", 1))); + if (utils::isNumeric(slot)) + { + // When the slot id is given + item->mEquipReq.equipSlotId = utils::stringToInt(slot); + } + else + { + // When its name is given + item->mEquipReq.equipSlotId = getEquipSlotIdFromName(slot); + } + item->mEquipReq.capacityRequired = + XML::getProperty(subNode, "required", 1); } } - if (req.empty()) + + if (!item->mEquipReq.equipSlotId) { LOG_WARN("Item Manager: empty equip requirement " - "definition for item " << item->getDatabaseID() << "!"); + "definition for item " << item->getDatabaseID() << "!" + " The item will be unequippable."); return; } - item->mEquip.push_back(req); } void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) @@ -309,8 +325,8 @@ void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) * trigger, and the second defines the default * trigger to use for dispelling. */ - triggerTable["existence"].first = ITT_IN_INVY; - triggerTable["existence"].second = ITT_LEAVE_INVY; + triggerTable["in-inventory"].first = ITT_IN_INVY; + triggerTable["in-inventory"].second = ITT_LEAVE_INVY; triggerTable["activation"].first = ITT_ACTIVATE; triggerTable["activation"].second = ITT_NULL; triggerTable["equip"].first = ITT_EQUIP; diff --git a/src/game-server/itemmanager.h b/src/game-server/itemmanager.h index c310df44..cc4fb0ae 100644 --- a/src/game-server/itemmanager.h +++ b/src/game-server/itemmanager.h @@ -30,6 +30,24 @@ class ItemClass; +struct EquipSlotInfo +{ + EquipSlotInfo(): + slotId(0), slotCapacity(0), visibleSlot(false) + {} + + EquipSlotInfo(unsigned int id, const std::string &name, + unsigned int capacity, bool visible): + slotId(id), slotName(name), slotCapacity(capacity), visibleSlot(visible) + {} + + unsigned int slotId; + std::string slotName; + unsigned int slotCapacity; + bool visibleSlot; +}; + + class ItemManager { public: @@ -40,6 +58,9 @@ class ItemManager mItemDatabaseVersion(0) {} + ~ItemManager() + { deinitialize(); } + /** * Loads item reference file. */ @@ -73,13 +94,12 @@ class ItemManager */ unsigned int getDatabaseVersion() const; - const std::string &getEquipNameFromId(unsigned int id) const; - - unsigned int getEquipIdFromName(const std::string &name) const; + unsigned int getEquipSlotIdFromName(const std::string &name) const; - unsigned int getMaxSlotsFromId(unsigned int id) const; + unsigned int getEquipSlotCapacity(unsigned int id) const; - unsigned int getVisibleSlotCount() const; + unsigned int getVisibleEquipSlotCount() const + { return mVisibleEquipSlotCount; } bool isEquipSlotVisible(unsigned int id) const; @@ -94,19 +114,22 @@ class ItemManager void readEffectNode(xmlNodePtr effectNode, ItemClass *item); typedef std::map< int, ItemClass * > ItemClasses; - // Map a string (name of slot) with (str-id, max-per-equip-slot) - typedef std::vector< std::pair< std::string, unsigned int > > EquipSlots; + ItemClasses mItemClasses; /**< Item reference */ + utils::NameMap<ItemClass*> mItemClassesByName; + + // Map an equip slot id with the equip slot info. + typedef std::map< unsigned int, EquipSlotInfo* > EquipSlotsInfo; // Reference to the vector position of equipSlots typedef std::vector< unsigned int > VisibleEquipSlots; - ItemClasses mItemClasses; /**< Item reference */ - utils::NameMap<ItemClass*> mItemClassesByName; - EquipSlots mEquipSlots; - VisibleEquipSlots mVisibleEquipSlots; + EquipSlotsInfo mEquipSlotsInfo; + // Map a string (name of slot) with (str-id, max-per-equip-slot) + // We only keep a pointer to it: The id map will take care of deletion. + utils::NameMap<EquipSlotInfo* > mNamedEquipSlotsInfo; std::string mItemsFile; std::string mEquipSlotsFile; - mutable unsigned int mVisibleEquipSlotCount; // Cache + unsigned int mVisibleEquipSlotCount; // Cache /** Version of the loaded items database file.*/ unsigned int mItemDatabaseVersion; diff --git a/src/game-server/main-game.cpp b/src/game-server/main-game.cpp index 0b786555..529e0690 100644 --- a/src/game-server/main-game.cpp +++ b/src/game-server/main-game.cpp @@ -88,6 +88,7 @@ utils::StringFilter *stringFilter; /**< Slang's Filter */ AttributeManager *attributeManager = new AttributeManager(DEFAULT_ATTRIBUTEDB_FILE); ItemManager *itemManager = new ItemManager(DEFAULT_ITEMSDB_FILE, DEFAULT_EQUIPDB_FILE); MonsterManager *monsterManager = new MonsterManager(DEFAULT_MONSTERSDB_FILE); +SkillManager *skillManager = new SkillManager(DEFAULT_SKILLSDB_FILE); /** Core game message handler */ GameHandler *gameHandler; @@ -193,7 +194,7 @@ static void initializeServer() exit(EXIT_MAP_FILE_NOT_FOUND); } attributeManager->initialize(); - SkillManager::initialize(DEFAULT_SKILLSDB_FILE); + skillManager->initialize(); itemManager->initialize(); monsterManager->initialize(); StatusManager::initialize(DEFAULT_STATUSDB_FILE); @@ -248,8 +249,9 @@ static void deinitializeServer() // Destroy Managers delete stringFilter; - monsterManager->deinitialize(); - itemManager->deinitialize(); + delete monsterManager; monsterManager = 0; + delete itemManager; itemManager = 0; + delete skillManager; skillManager = 0; MapManager::deinitialize(); StatusManager::deinitialize(); diff --git a/src/game-server/monstermanager.h b/src/game-server/monstermanager.h index f04a5733..07ebb58f 100644 --- a/src/game-server/monstermanager.h +++ b/src/game-server/monstermanager.h @@ -30,8 +30,13 @@ class MonsterClass; class MonsterManager { public: + MonsterManager(const std::string &file): + mMonsterReferenceFile(file) + {} + + ~MonsterManager() + { deinitialize(); } - MonsterManager(const std::string &file) : mMonsterReferenceFile(file) {} /** * Loads monster reference file. */ diff --git a/src/game-server/skillmanager.cpp b/src/game-server/skillmanager.cpp index 75f5f53c..66d9b939 100644 --- a/src/game-server/skillmanager.cpp +++ b/src/game-server/skillmanager.cpp @@ -20,174 +20,169 @@ #include "game-server/skillmanager.h" -#include "utils/string.h" // for the toUpper function #include "utils/logger.h" -#include "utils/xml.h" #include <map> -typedef std::map< std::string, int > SkillMap; -static SkillMap skillMap; -static std::string skillReferenceFile; -static std::string defaultSkillKey = std::string(); - -void SkillManager::initialize(const std::string &file) +void SkillManager::clear() { - skillReferenceFile = file; - reload(); + for (SkillsInfo::iterator it = mSkillsInfo.begin(), + it_end = mSkillsInfo.end(); it != it_end; ++it) + { + delete it->second; + } + + mSkillsInfo.clear(); + mNamedSkillsInfo.clear(); } -void SkillManager::reload() +void SkillManager::initialize() { - /* - skillMap["UNARMED"] = 100; - skillMap["KNIFE"] = 101; - */ + clear(); - XML::Document doc(skillReferenceFile); + XML::Document doc(mSkillFile); xmlNodePtr rootNode = doc.rootNode(); if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "skills")) { - LOG_ERROR("Skill Manager: " << skillReferenceFile + LOG_ERROR("Skill Manager: " << mSkillFile << " is not a valid database file!"); return; } - LOG_INFO("Loading skill reference: " << skillReferenceFile); + LOG_INFO("Loading skill reference: " << mSkillFile); - for_each_xml_child_node(setnode, rootNode) + for_each_xml_child_node(setNode, rootNode) { - if (!xmlStrEqual(setnode->name, BAD_CAST "set")) + // The server will prefix the core name with the set, so we need one. + if (!xmlStrEqual(setNode->name, BAD_CAST "set")) continue; - // we don't care about sets server-sided (yet?) - for_each_xml_child_node(skillnode, setnode) + std::string setName = XML::getProperty(setNode, "name", std::string()); + if (setName.empty()) { - if (xmlStrEqual(skillnode->name, BAD_CAST "skill")) - { - std::string name = XML::getProperty(skillnode, "name", - std::string()); - name = utils::toUpper(name); - int id = XML::getProperty(skillnode, "id", 0); - if (id > 0 && !name.empty()) - { - bool duplicateKey = false; - for (SkillMap::iterator i = skillMap.begin(); - i != skillMap.end(); i++) - { - if (id == i->second) - { - LOG_ERROR("SkillManager: The same id: " << id - << " is given for skill names: " << i->first - << " and " << name); - LOG_ERROR("The skill reference: " << "'" << name - << "': " << id << " will be ignored."); - - duplicateKey = true; - break; - } - } - - if (!duplicateKey) - { - if (XML::getBoolProperty(skillnode, "default", false)) - { - if (!defaultSkillKey.empty()) - { - LOG_WARN("SkillManager: " - "Default Skill Key already defined as " - << defaultSkillKey - << ". Redefinit it as: " << name); - } - else - { - LOG_INFO("SkillManager: Defining " << name - << " as default weapon-type key."); - } - defaultSkillKey = name; - } - skillMap[name] = id; - } - } - } + LOG_WARN("The " << mSkillFile << " file is containing unamed <set> " + "tags and will be ignored."); + continue; } + + setName = utils::toLower(setName); + + for_each_xml_child_node(skillNode, setNode) + readSkillNode(skillNode, setName); } - if (::utils::Logger::mVerbosity >= ::utils::Logger::Debug) + printDebugSkillTable(); + + if (!mDefaultSkillId) + LOG_WARN("SkillManager: No default weapon-type id was given during " + "Skill map loading. " + "Players won't be able to earn XP when unarmed."); + + LOG_INFO("Loaded " << mSkillsInfo.size() << " skills from " + << mSkillFile); +} + +void SkillManager::readSkillNode(xmlNodePtr skillNode, + const std::string& setName) +{ + if (!xmlStrEqual(skillNode->name, BAD_CAST "skill")) + return; + + SkillInfo *skillInfo = new SkillInfo; + skillInfo->setName = setName; + skillInfo->skillName = XML::getProperty(skillNode, "name", std::string()); + skillInfo->skillName = utils::toLower(skillInfo->skillName); + int id = XML::getProperty(skillNode, "id", 0); + + if (id <= 0 || skillInfo->skillName.empty()) { - LOG_DEBUG("Skill map in " << skillReferenceFile << ":" - << std::endl << "-----"); - for (SkillMap::iterator i = skillMap.begin(); i != skillMap.end(); i++) + LOG_WARN("Invalid skill (empty name or id <= 0) in set: " << setName); + return; + } + skillInfo->id = (unsigned)id; + + SkillsInfo::iterator it = mSkillsInfo.find(skillInfo->id); + if (it != mSkillsInfo.end()) + { + LOG_WARN("SkillManager: The same id: " << skillInfo->id + << " is given for skill names: " << it->first + << " and " << skillInfo->skillName); + LOG_WARN("The skill reference: " << skillInfo->id + << ": '" << skillInfo->skillName << "' will be ignored."); + return; + } + + if (XML::getBoolProperty(skillNode, "default", false)) + { + if (mDefaultSkillId) { - if (!defaultSkillKey.compare(i->first)) - { - LOG_DEBUG("'" << i->first << "': " << i->second - << " (Default)"); - } - else - { - LOG_DEBUG("'" << i->first << "': " << i->second); - } + LOG_WARN("SkillManager: " + "Default Skill id already defined as " + << mDefaultSkillId + << ". Redefinit it as: " << skillInfo->id); } - LOG_DEBUG("-----"); + else + { + LOG_INFO("SkillManager: Defining skill id: " << skillInfo->id + << " as default weapon-type id."); + } + mDefaultSkillId = skillInfo->id; } - if (defaultSkillKey.empty()) - LOG_WARN("SkillManager: No default weapon-type id was given during " - "Skill map loading. Defaults will fall back to id 0."); + mSkillsInfo.insert( + std::make_pair<unsigned int, SkillInfo*>(skillInfo->id, skillInfo)); - LOG_INFO("Loaded " << skillMap.size() << " skill references from " - << skillReferenceFile); + std::string keyName = setName + "_" + skillInfo->skillName; + mNamedSkillsInfo.insert(keyName, skillInfo); } -int SkillManager::getIdFromString(const std::string &name) +void SkillManager::printDebugSkillTable() { - // Check if the name is an integer value. - if (utils::isNumeric(name)) + if (::utils::Logger::mVerbosity >= ::utils::Logger::Debug) { - int val = 0; - val = utils::stringToInt(name); - if (val) + std::string lastSet; + LOG_DEBUG("Skill map in " << mSkillFile << ":" + << std::endl << "-----"); + for (SkillsInfo::iterator it = mSkillsInfo.begin(); + it != mSkillsInfo.end(); ++it) { - for (SkillMap::iterator i = skillMap.begin(); i != skillMap.end(); i++) + if (!lastSet.compare(it->second->setName)) { - if (i->second == val) - return val; + lastSet = it->second->setName; + LOG_DEBUG("Skill set: " << lastSet); } - LOG_WARN("SkillManager::getIdFromString(): Numeric weapon-type id " - << val << " not found into " << skillReferenceFile); - SkillMap::iterator i = skillMap.find(defaultSkillKey); - if (i != skillMap.end()) + if (it->first == mDefaultSkillId) { - LOG_WARN("Id defaulted to " << defaultSkillKey << ": " - << i->second); - return i->second; + LOG_DEBUG("'" << it->first << "': " << it->second->skillName + << " (Default)"); } else { - LOG_WARN("Id defaulted to 0."); - return 0; + LOG_DEBUG("'" << it->first << "': " << it->second->skillName); } } - else - { - LOG_WARN("SkillManager: Invalid skill id " << name); - return 0; - } + LOG_DEBUG("-----"); } +} - // Convert to upper case for easier finding - SkillMap::iterator i = skillMap.find(utils::toUpper(name)); - if (i == skillMap.end()) - { - LOG_WARN("SkillManager: No weapon-type name corresponding to " - << utils::toUpper(name) << " into " << skillReferenceFile); - return 0; - } - else - { - return i->second; - } +unsigned int SkillManager::getId(const std::string& set, + const std::string &name) const +{ + std::string key = utils::toLower(set) + "_" + utils::toLower(name); + SkillInfo *skillInfo = mNamedSkillsInfo.find(key); + return skillInfo ? skillInfo->id : 0; +} + +const std::string SkillManager::getSkillName(unsigned int id) const +{ + SkillsInfo::const_iterator it = mSkillsInfo.find(id); + return it != mSkillsInfo.end() ? it->second->skillName : ""; +} + +const std::string SkillManager::getSetName(unsigned int id) const +{ + SkillsInfo::const_iterator it = mSkillsInfo.find(id); + return it != mSkillsInfo.end() ? it->second->setName : ""; } diff --git a/src/game-server/skillmanager.h b/src/game-server/skillmanager.h index c6a73a5c..1912e2fc 100644 --- a/src/game-server/skillmanager.h +++ b/src/game-server/skillmanager.h @@ -22,14 +22,24 @@ #ifndef SKILLMANAGER_H #define SKILLMANAGER_H -#include <string> +#include "utils/string.h" +#include "utils/xml.h" -namespace SkillManager +class SkillManager { + public: + SkillManager(const std::string & skillFile): + mSkillFile(skillFile), + mDefaultSkillId(0) + {} + + ~SkillManager() + { clear(); } + /** * Loads skill reference file. */ - void initialize(const std::string &); + void initialize(); /** * Reloads skill reference file. @@ -37,12 +47,43 @@ namespace SkillManager void reload(); /** - * Gets the skill ID of a skill string - * (not case-sensitive to reduce wall-bashing) + * Gets the skill Id from a set and a skill string. */ - int getIdFromString(const std::string &name); -} + unsigned int getId(const std::string& set, const std::string &name) const; + const std::string getSkillName(unsigned int id) const; + const std::string getSetName(unsigned int id) const; + + private: + struct SkillInfo { + SkillInfo(): + id(0) + {} + + unsigned int id; + std::string setName; + std::string skillName; + }; + + /* + * Clears up the skill maps. + */ + void clear(); + + void readSkillNode(xmlNodePtr skillNode, const std::string& setName); + + void printDebugSkillTable(); + + // The skill file (skills.xml) + std::string mSkillFile; + // The skill map + typedef std::map<unsigned int, SkillInfo*> SkillsInfo; + SkillsInfo mSkillsInfo; + // A map used to get skills per name. + utils::NameMap<SkillInfo*> mNamedSkillsInfo; + // The default skill id + unsigned int mDefaultSkillId; +}; #endif // SKILLMANAGER_H diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index 03d4b71c..30b57cae 100644 --- a/src/game-server/state.cpp +++ b/src/game-server/state.cpp @@ -106,59 +106,50 @@ static void updateMap(MapComposite *map) /** * Sets message fields describing character look. */ -static void serializeLooks(Character *ch, MessageOut &msg, bool full) +static void serializeLooks(Character *ch, MessageOut &msg) { - const Possessions &poss = ch->getPossessions(); - unsigned int nb_slots = itemManager->getVisibleSlotCount(); + const EquipData &equipData = ch->getPossessions().getEquipment(); - // Bitmask describing the changed entries. - int changed = (1 << nb_slots) - 1; - if (!full) - { - // TODO: do not assume the whole equipment changed, - // when an update is asked for. - changed = (1 << nb_slots) - 1; - } + // We'll use a set to check whether we already sent the update for the given + // item instance. + std::set<unsigned int> itemInstances; + + // The map storing the info about the look changes to send + //{ slot type id, item id } + std::map <unsigned int, unsigned int> lookChanges; - std::vector<unsigned int> items; - items.resize(nb_slots, 0); - // Partially build both kinds of packet, to get their sizes. - unsigned int mask_full = 0, mask_diff = 0; - unsigned int nb_full = 0, nb_diff = 0; - std::map<unsigned int, unsigned int>::const_iterator it = - poss.equipSlots.begin(); - for (unsigned int i = 0; i < nb_slots; ++i) + // Note that we can send several updates on the same slot type as different + // items may have been equipped. + for (EquipData::const_iterator it = equipData.begin(), + it_end = equipData.end(); it != it_end; ++it) { - if (changed & (1 << i)) - { - // Skip slots that have not changed, when sending an update. - ++nb_diff; - mask_diff |= 1 << i; - } - if (it == poss.equipSlots.end() || it->first > i) continue; - ItemClass *eq; - items[i] = it->first && (eq = itemManager->getItem(it->first)) ? - eq->getSpriteID() : 0; - if (items[i]) + if (!itemManager->isEquipSlotVisible(it->first)) + continue; + + if (!it->second.itemInstance + || itemInstances.insert(it->second.itemInstance).second) { - /* If we are sending the whole equipment, only filled slots have to - be accounted for, as the other ones will be automatically cleared. */ - ++nb_full; - mask_full |= 1 << i; + // When the insertion succeeds, its the first time + // we encounter the item, so we can send the look change. + // We also send empty slots for unequipment handling. + lookChanges.insert( + std::make_pair<unsigned int, unsigned int>(it->first, + it->second.itemId)); } } - // Choose the smaller payload. - if (nb_full <= nb_diff) full = true; - - /* Bitmask enumerating the sent slots. - Setting the upper bit tells the client to clear the slots beforehand. */ - int mask = full ? mask_full | (1 << 7) : mask_diff; - - msg.writeInt8(mask); - for (unsigned int i = 0; i < nb_slots; ++i) + if (lookChanges.size() > 0) { - if (mask & (1 << i)) msg.writeInt16(items[i]); + // Number of look changes to send + msg.writeInt8(lookChanges.size()); + + for (std::map<unsigned int, unsigned int>::const_iterator it2 = + lookChanges.begin(), it2_end = lookChanges.end(); + it2 != it2_end; ++it2) + { + msg.writeInt8(it2->first); + msg.writeInt16(it2->second); + } } } @@ -222,7 +213,7 @@ static void informPlayer(MapComposite *map, Character *p, int worldTime) MessageOut LooksMsg(GPMSG_BEING_LOOKS_CHANGE); LooksMsg.writeInt16(oid); Character * c = static_cast<Character * >(o); - serializeLooks(c, LooksMsg, false); + serializeLooks(c, LooksMsg); LooksMsg.writeInt16(c->getHairStyle()); LooksMsg.writeInt16(c->getHairColor()); LooksMsg.writeInt16(c->getGender()); @@ -286,7 +277,7 @@ static void informPlayer(MapComposite *map, Character *p, int worldTime) enterMsg.writeInt8(q->getHairStyle()); enterMsg.writeInt8(q->getHairColor()); enterMsg.writeInt8(q->getGender()); - serializeLooks(q, enterMsg, true); + serializeLooks(q, enterMsg); } break; case OBJECT_MONSTER: diff --git a/src/game-server/trade.cpp b/src/game-server/trade.cpp index 51509307..e1779d2f 100644 --- a/src/game-server/trade.cpp +++ b/src/game-server/trade.cpp @@ -130,7 +130,7 @@ void Trade::agree(Character *c) // Check if both player has the objects in their inventories // and enouth money, then swap them. - Inventory v1(mChar1, true), v2(mChar2, true); + Inventory v1(mChar1), v2(mChar2); if (mChar1->getAttribute(mCurrencyId) >= mMoney1 - mMoney2 && mChar2->getAttribute(mCurrencyId) >= mMoney2 - mMoney1 && perform(mItems1, v1, v2) && @@ -143,8 +143,6 @@ void Trade::agree(Character *c) } else { - v1.cancel(); - v2.cancel(); cancel(); return; } diff --git a/src/game-server/trigger.cpp b/src/game-server/trigger.cpp index f25b00b8..c4cec2f4 100644 --- a/src/game-server/trigger.cpp +++ b/src/game-server/trigger.cpp @@ -52,10 +52,15 @@ void TriggerArea::update() std::set<Actor*> insideNow; for (BeingIterator i(getMap()->getInsideRectangleIterator(mZone)); i; ++i) { - //skip garbage - if (!(*i) || (*i)->getPublicID() == 0) continue; + // Don't deal with unitialized actors. + if (!(*i) || !(*i)->isPublicIdValid()) + continue; - if (mZone.contains((*i)->getPosition())) //<-- Why is this additional condition necessary? Shouldn't getInsideRectangleIterator already exclude those outside of the zone? --Crush + // The BeingIterator returns the mapZones in touch with the rectangle + // area. On the other hand, the beings contained in the map zones + // may not be within the rectangle area. Hence, this additional + // contains() condition. + if (mZone.contains((*i)->getPosition())) { insideNow.insert(*i); |