From 67af89b5fe14d2d0431cefb9b714f72873a74e16 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Fri, 19 Aug 2011 00:56:22 +0200 Subject: Used the client base to make a final bugfix on the server code. Made the server handle only one slot type requirement since it's irrelevant to have more anyway. Plus, it simplifies the code for both equipping/unequipping. I also added a dagger to show how equipping/unequipping is working. --- src/game-server/inventory.cpp | 225 ++++++++++++++++++++-------------------- src/game-server/inventory.h | 8 +- src/game-server/item.h | 25 +++-- src/game-server/itemmanager.cpp | 21 ++-- 4 files changed, 147 insertions(+), 132 deletions(-) (limited to 'src/game-server') diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index c3d2bfcb..55986106 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -600,58 +600,66 @@ bool Inventory::equip(int inventorySlot) return false; // Test the equip requirements. If none, it's not an equipable item. - const ItemEquipsInfo &equipInfoList = - itemManager->getItem(it->second.itemId)->getItemEquipData(); - if (equipInfoList.empty()) + 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; } - // Iterate through all slots requirements. - std::list equipSlotsToUnequipFirst; - for (ItemEquipsInfo::const_iterator it2 = equipInfoList.begin(), - it2_end = equipInfoList.end(); it2 != it2_end; ++it2) - { - // We first check the equipment slots for: - // - 1. whether enough total equip slot space is available. - // - 2. whether some other equipment is to be unequipped first. + // List of potential unique itemInstances to unequip first. + std::set equipInstancesToUnequipFirst; - // If not enough total space in the equipment slot is available, - // we cannot equip. - if (itemManager->getEquipSlotCapacity(it2->first) < it2->second) - { - LOG_DEBUG("Not enough equip capacity at slot: " << it2->first - << ", total available: " - << itemManager->getEquipSlotCapacity(it2->first) - << ", required: " << it2->second); - return false; - } + // 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; + } - // Test whether some item(s) is(are) to be unequipped first. - if (!checkEquipmentCapacity(it2->first, it2->second)) + // Test whether some item(s) is(are) to be unequipped first. + if (!checkEquipmentCapacity(equipReq.equipSlotId, + equipReq.capacityRequired)) + { + // And test whether the unequip action would succeed first. + if (testUnequipScriptRequirements(equipReq.equipSlotId) + && hasInventoryEnoughSpace(equipReq.equipSlotId)) { - // And test whether the unequip action would succeed first. - if (testUnequipScriptRequirements(it2->first) - && hasInventoryEnoughSpace(it2->first)) + // Then, we unequip each iteminstance of the equip slot + for (EquipData::iterator iter = + mPoss->equipSlots.begin(); + iter != mPoss->equipSlots.end(); ++iter) { - equipSlotsToUnequipFirst.push_back(it2->first); - } - 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; + if (iter->first == equipReq.equipSlotId + && iter->second.itemInstance) + equipInstancesToUnequipFirst.insert( + iter->second.itemInstance); } } + 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::list::const_iterator it3 = - equipSlotsToUnequipFirst.begin(); - it3 != equipSlotsToUnequipFirst.end(); ++it3) + for (std::set::const_iterator it3 = + equipInstancesToUnequipFirst.begin(); + it3 != equipInstancesToUnequipFirst.end(); ++it3) { if (!unequip(*it3)) { @@ -667,69 +675,65 @@ bool Inventory::equip(int inventorySlot) //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. + equipMsg.writeInt16(1); // 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 = 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) { - 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; + if (!capacityLeft) + break; - // We've found an existing equip slot - if (it4->first == it2->first) + // We've found an existing equip slot + if (it4->first == equipReq.equipSlotId) + { + // We've found an empty slot + if (it4->second.itemInstance == 0) { - // 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; - } + it4->second.itemId = it->second.itemId; + it4->second.itemInstance = itemInstance; + --capacityLeft; + } + else // The slot is already in use. + { + ++capacityUsed; } } + } - // 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); + // 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(equipReq.equipSlotId); - // A should never happen case - assert(maxCapacity >= capacityUsed + capacityLeft); + // A should never happen case + assert(maxCapacity >= capacityUsed + capacityLeft); - while (capacityLeft) - { - EquipmentItem equipItem(it->second.itemId, itemInstance); - mPoss->equipSlots.insert( - std::make_pair - (it2->first, equipItem)); - --capacityLeft; - } + while (capacityLeft) + { + EquipmentItem equipItem(it->second.itemId, itemInstance); + mPoss->equipSlots.insert( + std::make_pair + (equipReq.equipSlotId, equipItem)); + --capacityLeft; } - - // Equip slot - equipMsg.writeInt16(it2->first); - // Capacity used - equipMsg.writeInt16(it2->second); - // Item instance - equipMsg.writeInt16(itemInstance); } + // 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); @@ -744,54 +748,47 @@ bool Inventory::equip(int inventorySlot) bool Inventory::unequip(unsigned int itemInstance) { - // map of { itemInstance, itemId } - std::map itemIdListToInventory; + if (!itemInstance) + return false; MessageOut equipMsg(GPMSG_EQUIP); equipMsg.writeInt16(0); // Item Id, useless in case of unequip. + // The itemId to unequip + unsigned int itemId = 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.itemInstance == itemInstance) + if (it->second.itemInstance == itemInstance && it->second.itemId) { // Add the item to the inventory list if not already present there - std::map::const_iterator it2 = - itemIdListToInventory.find(it->second.itemInstance); - if (it2 == itemIdListToInventory.end()) - { - itemIdListToInventory.insert( - std::make_pair - (it->second.itemInstance, it->second.itemId)); - } - + itemId = it->second.itemId; it->second.itemId = 0; it->second.itemInstance = 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(itemIdListToInventory.size()); + equipMsg.writeInt16(1); - // Apply unequip trigger(s), and move the item(s) back to inventory. - for (std::map::const_iterator it2 = - itemIdListToInventory.begin(), it2_end = itemIdListToInventory.end(); - it2 != it2_end; ++it2) - { - updateEquipmentTrigger(it2->second, 0); - insert(it2->second, 1); + // Move the item back to inventory. + insert(itemId, 1); - equipMsg.writeInt16(it2->first); - equipMsg.writeInt16(0); // Capacity used, set to 0 to unequip. - } + equipMsg.writeInt16(itemInstance); + equipMsg.writeInt16(0); // Capacity used, set to 0 to unequip. - if (equipMsg.getLength() > 2) - { - gameHandler->sendTo(mCharacter, equipMsg); - return true; - } + gameHandler->sendTo(mCharacter, equipMsg); + + // Apply unequip trigger + updateEquipmentTrigger(itemId, 0); - return false; + return true; } diff --git a/src/game-server/inventory.h b/src/game-server/inventory.h index 39f83216..1eaf0527 100644 --- a/src/game-server/inventory.h +++ b/src/game-server/inventory.h @@ -120,7 +120,7 @@ class Inventory * @todo */ bool hasInventoryEnoughSpace(unsigned int equipmentSlot) - { return false; } + { return true; } /** * Test the items unequipment requirements. @@ -152,6 +152,12 @@ class Inventory */ void checkInventorySize(); + /** + * Check whether the equipment change has visible consequence + * on the character sprite. + */ + void checkLookUpdate(unsigned slotTypeId); + /** * Apply equipment triggers. */ diff --git a/src/game-server/item.h b/src/game-server/item.h index caef386a..f7c380f1 100644 --- a/src/game-server/item.h +++ b/src/game-server/item.h @@ -28,10 +28,15 @@ class Being; class Script; -// A pair indicating: Equipment slot id -> how much slots required. -typedef std::pair< unsigned int, unsigned int> ItemEquipInfo; -// The list of required slots to equip. -typedef std::list< ItemEquipInfo > ItemEquipsInfo; +// Indicates the equip slot "cost" to equip an item. +struct ItemEquipRequirement { + ItemEquipRequirement(): + equipSlotId(0), + capacityRequired(0) + {} + + unsigned int equipSlotId, capacityRequired; +}; /** * State effects to beings, and actors. @@ -226,9 +231,10 @@ class ItemClass { return mSpriteID; } /** - * Returns equip requirements. + * Returns equip requirement. */ - const ItemEquipsInfo &getItemEquipData() const { return mEquip; } + const ItemEquipRequirement &getItemEquipRequirement() const + { return mEquipReq; } private: /** @@ -274,12 +280,9 @@ class ItemClass std::multimap< ItemTriggerType, ItemEffectInfo * > mDispells; /** - * List of list of requirements for equipping. Only one inner list - * need be satisfied to sucessfully equip. Checks occur in order - * from outer front to back. - * All conditions in an inner list must be met for success. + * Requirement for equipping. */ - ItemEquipsInfo mEquip; + ItemEquipRequirement mEquipReq; friend class ItemManager; }; diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index 074b8343..d41212bb 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -264,6 +264,13 @@ void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item) { 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()) { @@ -273,21 +280,23 @@ void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item) 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))); + item->mEquipReq.equipSlotId = utils::stringToInt(slot); } else { // When its name is given - item->mEquip.push_back(std::make_pair(getEquipSlotIdFromName(slot), - XML::getProperty(subNode, "required", 1))); + item->mEquipReq.equipSlotId = getEquipSlotIdFromName(slot); } + item->mEquipReq.capacityRequired = + XML::getProperty(subNode, "required", 1); } } - if (item->mEquip.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; } } -- cgit v1.2.3-70-g09d2 From 22fc8d2c06a73e0d29b03c5f6c98992ba331615b Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Fri, 19 Aug 2011 01:39:59 +0200 Subject: Changed the sprite layer protocol to support an arbitrary number of layers. (up to 255). --- src/common/manaserv_protocol.h | 4 +- src/game-server/inventory.cpp | 19 +++++++++- src/game-server/inventory.h | 5 +-- src/game-server/itemmanager.cpp | 16 ++++---- src/game-server/state.cpp | 81 ++++++++++++++++++----------------------- 5 files changed, 66 insertions(+), 59 deletions(-) (limited to 'src/game-server') diff --git a/src/common/manaserv_protocol.h b/src/common/manaserv_protocol.h index 25dd7c77..2448a357 100644 --- a/src/common/manaserv_protocol.h +++ b/src/common/manaserv_protocol.h @@ -106,12 +106,12 @@ enum { 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 }* + // character: S name, B hair style, B hair color, B gender, B sprite layers changed, { B slot type, W item id }* // monster: W type id // npc: W type id GPMSG_BEING_LEAVE = 0x0201, // W being id GPMSG_ITEM_APPEAR = 0x0202, // W item id, W*2 position - GPMSG_BEING_LOOKS_CHANGE = 0x0210, // W weapon, W hat, W top clothes, W bottom clothes + GPMSG_BEING_LOOKS_CHANGE = 0x0210, // B sprite layers changed, { B slot type, W item id }* PGMSG_WALK = 0x0260, // W*2 destination PGMSG_ACTION_CHANGE = 0x0270, // B Action GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index 55986106..e486f7c0 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -740,8 +740,10 @@ bool Inventory::equip(int inventorySlot) // Remove item from inventory removeFromSlot(inventorySlot, 1); - if (equipMsg.getLength() > 2) - gameHandler->sendTo(mCharacter, equipMsg); + gameHandler->sendTo(mCharacter, equipMsg); + + // Update look when necessary + checkLookchanges(equipReq.equipSlotId); return true; } @@ -756,6 +758,7 @@ bool Inventory::unequip(unsigned int itemInstance) // 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! @@ -768,6 +771,10 @@ bool Inventory::unequip(unsigned int itemInstance) 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; } } @@ -790,5 +797,13 @@ bool Inventory::unequip(unsigned int itemInstance) // 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 1eaf0527..547abdf0 100644 --- a/src/game-server/inventory.h +++ b/src/game-server/inventory.h @@ -153,10 +153,9 @@ class Inventory void checkInventorySize(); /** - * Check whether the equipment change has visible consequence - * on the character sprite. + * Check potential visible character sprite changes. */ - void checkLookUpdate(unsigned slotTypeId); + void checkLookchanges(unsigned int slotTypeId); /** * Apply equipment triggers. diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index d41212bb..6840368a 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -132,16 +132,18 @@ void ItemManager::readEquipSlotsFile() continue; } - bool visible = XML::getBoolProperty(node, "visible", false); - if (visible) + if (slotId > 255) { - if (++mVisibleEquipSlotCount > 7) - { - LOG_WARN("Item Manager: More than 7 visible equip slot!" - "This will not work with current netcode!"); - } + 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()) diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index d8f9b183..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 EquipData &equipData = ch->getPossessions().getEquipment(); - unsigned int nb_slots = itemManager->getVisibleEquipSlotCount(); - // 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 itemInstances; + + // The map storing the info about the look changes to send + //{ slot type id, item id } + std::map lookChanges; - std::vector 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::const_iterator it = - equipData.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 == equipData.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(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::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(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: -- cgit v1.2.3-70-g09d2