diff options
Diffstat (limited to 'src/game-server/inventory.cpp')
-rw-r--r-- | src/game-server/inventory.cpp | 1169 |
1 files changed, 568 insertions, 601 deletions
diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index 33f09264..f560ce50 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -28,26 +28,27 @@ #include "net/messageout.hpp" #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()), msg(GPMSG_INVENTORY), mClient(p), - mDelayed(d), mChangedLook(false) + mPoss(&p->getPossessions()), mInvMsg(GPMSG_INVENTORY), + mEqmMsg(GPMSG_EQUIP), mClient(p), mDelayed(d) { } Inventory::~Inventory() { - if (msg.getLength() > 2) - { - update(); - gameHandler->sendTo(mClient, msg); - } + commit(false); } void Inventory::restart() { - msg.clear(); - msg.writeShort(GPMSG_INVENTORY); - mChangedLook = false; + mInvMsg.clear(); + mInvMsg.writeShort(GPMSG_INVENTORY); } void Inventory::cancel() @@ -62,736 +63,702 @@ void Inventory::cancel() restart(); } -void Inventory::update() +void Inventory::commit(bool doRestart) { - if (mDelayed) + Possessions &poss = mClient->getPossessions(); + /* Sends changes, whether delayed or not. */ + if (mInvMsg.getLength() > 2) { - Possessions &poss = mClient->getPossessions(); - if (mPoss != &poss) - { - poss = *mPoss; - delete mPoss; - mPoss = &poss; - } + /* Send the message to the client directly. Perhaps this should be + done through an update flag, too? */ + gameHandler->sendTo(mClient, mInvMsg); } - if (mChangedLook) + if (mPoss != &poss) { - mClient->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); + 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::commit() +void Inventory::equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it) { - if (msg.getLength() > 2) + const unsigned int invSlot = it->first; + unsigned int count = 0, eqSlot = it->second; + mEqmMsg.writeShort(invSlot); + mEqmMsg.writeByte(newCount); + do { + if (newCount) + { + if (it->second != eqSlot) + { + mEqmMsg.writeByte(eqSlot); + mEqmMsg.writeByte(count); + count = 1; + eqSlot = it->second; + } + ++count; + } + if (itemManager->isEquipSlotVisible(it->second)) + mClient->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); + } while ((++it)->first == invSlot); + if (count) { - update(); - gameHandler->sendTo(mClient, msg); - restart(); + mEqmMsg.writeByte(eqSlot); + mEqmMsg.writeByte(count); } + mEqmMsg.writeShort(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); - } + if (!mDelayed) return; + Possessions *poss = &mClient->getPossessions(); + if (mPoss == poss) + mPoss = new Possessions(*poss); } void Inventory::sendFull() const { + /* Sends all the information needed to construct inventory + and equipment to the client */ MessageOut m(GPMSG_INVENTORY_FULL); - for (int i = 0; i < EQUIPMENT_SLOTS; ++i) + m.writeShort(mPoss->inventory.size()); + for (InventoryData::const_iterator l = mPoss->inventory.begin(), + l_end = mPoss->inventory.end(); l != l_end; ++l) { - if (int id = mPoss->equipment[i]) - { - m.writeByte(i); - m.writeShort(id); - } + assert(l->second.itemId); + m.writeShort(l->first); // Slot id + m.writeShort(l->second.itemId); + m.writeShort(l->second.amount); } - int slot = EQUIP_CLIENT_INVENTORY; - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) + for (EquipData::const_iterator k = mPoss->equipSlots.begin(), + k_end = mPoss->equipSlots.end(); + k != k_end; + ++k) { - if (i->itemId) - { - m.writeByte(slot); - m.writeShort(i->itemId); - m.writeByte(i->amount); - ++slot; - } - else - { - slot += i->amount; - } + m.writeByte(k->first); // equip slot + m.writeShort(k->second); // inventory slot } - m.writeByte(255); - m.writeLong(mPoss->money); - gameHandler->sendTo(mClient, m); } -void Inventory::initialize() +void Inventory::initialise() { assert(!mDelayed); - // First, check the equipment and apply its modifiers. - for (int i = 0; i < EQUIP_PROJECTILE_SLOT; ++i) - { - int itemId = mPoss->equipment[i]; - if (!itemId) continue; - if (ItemClass *ic = ItemManager::getItem(itemId)) - { - ic->getModifiers().applyAttributes(mClient); - } - else - { - mPoss->equipment[i] = 0; - LOG_WARN("Removed unknown item " << itemId << " from equipment " - "of character " << mClient->getDatabaseID() << '.'); - } - } + InventoryData::iterator it1; + EquipData::const_iterator it2, it2_end = mPoss->equipSlots.end(); + /* + * Apply all exists triggers. + * Remove unknown inventory items. + */ - // Second, remove unknown inventory items. - int i = 0; - while (i < (int)mPoss->inventory.size()) - { - int itemId = mPoss->inventory[i].itemId; - if (itemId) - { - ItemClass *ic = ItemManager::getItem(itemId); - if (!ic) - { - LOG_WARN("Removed unknown item " << itemId << " from inventory" - " of character " << mClient->getDatabaseID() << '.'); - freeIndex(i); - continue; - } - } - ++i; - } -} + ItemIdSet itemIds; -int Inventory::getItem(int slot) const -{ - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) + /* + * Construct a set of itemIds to keep track of duplicate itemIds. + */ + for (it1 = mPoss->inventory.begin(); it1 != mPoss->inventory.end(); ++it1) { - if (slot == 0) + ItemClass *item = itemManager->getItem(it1->second.itemId); + if (item) { - return i->itemId; + if (itemIds.insert(it1->second.itemId).second) + item->useTrigger(mClient, ITT_IN_INVY); } - - slot -= i->itemId ? 1 : i->amount; - - if (slot < 0) + else { - return 0; + LOG_WARN("Inventory: deleting unknown item type " + << it1->second.itemId << " from the inventory of '" + << mClient->getName() + << "'!"); + removeFromSlot(it1->first, + it1->second.amount); } } - return 0; -} -int Inventory::getIndex(int slot) const -{ - int index = 0; + itemIds.clear(); + + typedef std::set<unsigned int> SlotSet; + SlotSet equipment; - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i, ++index) + /* + * Construct a set of slot references from equipment to keep track of + * duplicate slot usage. + */ + for (it2 = mPoss->equipSlots.begin(); it2 != it2_end; ++it2) { - if (slot == 0) + if (equipment.insert(it2->second).second) { - return i->itemId ? index : -1; + /* + * 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); } + } - slot -= i->itemId ? 1 : i->amount; + equipment.clear(); - if (slot < 0) - { - return -1; - } - } - return -1; + checkSize(); } -int Inventory::getSlot(int index) const +void Inventory::checkSize() { - int slot = 0; - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.begin() + index; i != i_end; ++i) - { - slot += i->itemId ? 1 : i->amount; + /* + * 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. + * Check that inventory capacity is greater than or equal to zero. + * If not, forcibly delete (drop?) items from the end until it is. + */ + while (mPoss->inventory.size() > INVENTORY_SLOTS + || mClient->getModifiedAttribute(ATTR_INV_CAPACITY) < 0) { + LOG_WARN("Inventory: oversize inventory! Deleting '" + << mPoss->inventory.rbegin()->second.amount + << "' items of type '" + << mPoss->inventory.rbegin()->second.itemId + << "' from slot '" + << mPoss->inventory.rbegin()->first + << "' of character '" + << mClient->getName() + << "'!"); + // FIXME Should probably be dropped rather than deleted. + removeFromSlot(mPoss->inventory.rbegin()->first, + mPoss->inventory.rbegin()->second.amount); } - return slot; } -int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot) +unsigned int Inventory::getItem(unsigned int slot) const { - int slot = 0; - for (int i = 0, i_end = mPoss->inventory.size(); i < i_end; ++i) - { - InventoryItem &it = mPoss->inventory[i]; - if (it.itemId == 0) - { - int nb = std::min(amount, maxPerSlot); - if (it.amount <= 1) - { - it.itemId = itemId; - it.amount = nb; - } - else - { - --it.amount; - InventoryItem iu = { itemId, nb }; - mPoss->inventory.insert(mPoss->inventory.begin() + i, iu); - ++i_end; - } - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - msg.writeShort(itemId); - msg.writeByte(nb); - - amount -= nb; - if (amount == 0) - { - return 0; - } - } - ++slot; - } - - while (slot < INVENTORY_SLOTS - 1 && amount > 0) - { - int nb = std::min(amount, maxPerSlot); - amount -= nb; - InventoryItem it = { itemId, nb }; - mPoss->inventory.push_back(it); - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - msg.writeShort(itemId); - msg.writeByte(nb); - ++slot; - } - - return amount; + InventoryData::iterator item = mPoss->inventory.find(slot); + return item != mPoss->inventory.end() ? item->second.itemId : 0; } -int Inventory::insert(int itemId, int amount) +unsigned int Inventory::insert(unsigned int itemId, unsigned int amount) { - if (itemId == 0 || amount == 0) - { + unsigned int maxPerSlot = itemManager->getItem(itemId)->getMaxPerSlot(); + if (!itemId || !amount) return 0; - } - prepare(); - - int maxPerSlot = ItemManager::getItem(itemId)->getMaxPerSlot(); - if (maxPerSlot == 1) - { - return fillFreeSlot(itemId, amount, maxPerSlot); - } - - int slot = 0; - for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) - { - if (i->itemId == itemId && i->amount < maxPerSlot) + 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) { - int nb = std::min(maxPerSlot - i->amount, amount); - i->amount += nb; - amount -= nb; - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - msg.writeShort(itemId); - msg.writeByte(i->amount); - - if (amount == 0) - { + if (it->second.amount >= maxPerSlot) + continue; + unsigned short additions = std::min(amount, maxPerSlot) + - it->second.amount; + amount -= additions; + it->second.amount += additions; + mInvMsg.writeShort(it->first); + mInvMsg.writeShort(itemId); + mInvMsg.writeShort(it->second.amount); + if (!amount) return 0; - } - ++slot; } - else - { - slot += i->itemId ? 1 : i->amount; - } - } - return fillFreeSlot(itemId, amount, maxPerSlot); -} - -int Inventory::count(int itemId) const -{ - int nb = 0; - - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) + int slot = 0; + // We still have some left, so add to blank slots. + for (it = mPoss->inventory.begin();; ++it) { - if (i->itemId == itemId) + if (!amount) + return 0; + int lim = it == it_end ? INVENTORY_SLOTS : it->first; + while (amount && slot < lim) { - nb += i->amount; + int additions = std::min(amount, maxPerSlot); + mPoss->inventory[slot].itemId = itemId; + mPoss->inventory[slot].amount = additions; + amount -= additions; + mInvMsg.writeShort(slot++); // Last read, so also increment + mInvMsg.writeShort(itemId); + mInvMsg.writeShort(additions); } + ++slot; // Skip the slot that the iterator points to + if (it == it_end) break; } - return nb; -} - -bool Inventory::changeMoney(int amount) -{ - if (amount == 0) - { - return true; - } - - int money = mPoss->money + amount; - if (money < 0) - { - return false; - } - - prepare(); + checkSize(); - mPoss->money = money; - msg.writeByte(255); - msg.writeLong(money); - return true; + return amount; } -void Inventory::freeIndex(int i) +unsigned int Inventory::count(unsigned int itemId) const { - InventoryItem &it = mPoss->inventory[i]; - - // Is it the last slot? - if (i == (int)mPoss->inventory.size() - 1) - { - mPoss->inventory.pop_back(); - if (i > 0 && mPoss->inventory[i - 1].itemId == 0) - { - mPoss->inventory.pop_back(); - } - return; - } - - it.itemId = 0; - - // First concatenate with an empty slot on the right. - if (mPoss->inventory[i + 1].itemId == 0) - { - it.amount = mPoss->inventory[i + 1].amount + 1; - mPoss->inventory.erase(mPoss->inventory.begin() + i + 1); - } - else - { - it.amount = 1; - } - - // Then concatenate with an empty slot on the left. - if (i > 0 && mPoss->inventory[i - 1].itemId == 0) - { - // Note: "it" is no longer a valid reference, hence inventory[i] below. - mPoss->inventory[i - 1].amount += mPoss->inventory[i].amount; - mPoss->inventory.erase(mPoss->inventory.begin() + i); - } + unsigned int nb = 0; + for (InventoryData::iterator it = mPoss->inventory.begin(), + it_end = mPoss->inventory.end(); + it != it_end; ++it) + if (it->second.itemId == itemId) + nb += it->second.amount; + return nb; } -int Inventory::remove(int itemId, int amount) +unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool force) { - if (itemId == 0 || amount == 0) - { - return 0; - } - prepare(); - - for (int i = mPoss->inventory.size() - 1; i >= 0; --i) - { - InventoryItem &it = mPoss->inventory[i]; - if (it.itemId == itemId) + bool inv = false, + eq = !itemManager->getItem(itemId)->getItemEquipData().empty(); + for (InventoryData::iterator it = mPoss->inventory.begin(), + it_end = mPoss->inventory.end(); + it != it_end; ++it) + if (it->second.itemId == itemId) { - int nb = std::min((int)it.amount, amount); - it.amount -= nb; - amount -= nb; - - msg.writeByte(getSlot(i) + EQUIP_CLIENT_INVENTORY); - if (it.amount == 0) + if (amount) { - msg.writeShort(0); - freeIndex(i); + 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.writeShort(it->first); + if (it->second.amount) + { + mInvMsg.writeShort(it->second.itemId); + mInvMsg.writeShort(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; + } + else + { + mInvMsg.writeShort(0); + mPoss->inventory.erase(it); + } } else - { - msg.writeShort(itemId); - msg.writeByte(it.amount); - } - - if (amount == 0) - { + // We found an instance of them existing and have none left to + // remove, so no need to run leave invy triggers. return 0; - } } - } - - return amount; + 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; } -int Inventory::move(int slot1, int slot2, int amount) +unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned int amount) { - if (amount == 0 || slot1 == slot2 || slot2 >= INVENTORY_SLOTS) - { + 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(); - int i1 = getIndex(slot1); - if (i1 < 0) - { + if (it1 == inv_end) return amount; - } - - prepare(); - InventoryItem &it1 = mPoss->inventory[i1]; - int i2 = getIndex(slot2); + 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; + + unsigned int nb = std::min(amount, it1->second.amount); + if (it2 == inv_end) + { + // Slot2 does not yet exist. + mPoss->inventory[slot2].itemId = it1->second.itemId; + nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot(), + nb); + + mPoss->inventory[slot2].amount = nb; + it1->second.amount -= nb; + amount -= nb; - if (i2 >= 0) - { - InventoryItem &it2 = mPoss->inventory[i2]; - if (it1.itemId == it2.itemId) + mInvMsg.writeShort(slot1); // Slot + if (it1->second.amount) { - // Move between two stacks of the same kind. - int maxPerSlot = ItemManager::getItem(it1.itemId)->getMaxPerSlot(); - int nb = std::min(std::min(amount, (int)it1.amount), maxPerSlot - it2.amount); - if (nb == 0) - { - return amount; - } - - it1.amount -= nb; - it2.amount += nb; - amount -= nb; - - msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(it2.itemId); - msg.writeByte(it2.amount); - - msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY); - if (it1.amount == 0) - { - msg.writeShort(0); - freeIndex(i1); - } - else - { - msg.writeShort(it1.itemId); - msg.writeByte(it1.amount); - } - return amount; + mInvMsg.writeShort(it1->second.itemId); // Item Id + mInvMsg.writeShort(it1->second.amount); // Amount } - - // Swap between two different stacks. - if (it1.amount != amount) + else { - return amount; + mInvMsg.writeShort(0); + mPoss->inventory.erase(it1); } - - std::swap(it1, it2); - - msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(it1.itemId); - msg.writeByte(it1.amount); - msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(it2.itemId); - msg.writeByte(it2.amount); - return 0; - } - - // Move some items to an empty slot. - int id = it1.itemId; - int nb = std::min((int)it1.amount, amount); - it1.amount -= nb; - amount -= nb; - - msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY); - if (it1.amount == 0) - { - msg.writeShort(0); - freeIndex(i1); + mInvMsg.writeShort(slot2); // Slot + mInvMsg.writeShort(it1->second.itemId); // Item Id (same as slot 1) + mInvMsg.writeShort(nb); // Amount } else { - msg.writeShort(id); - msg.writeByte(it1.amount); - } - - // Fill second slot. - msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(id); - msg.writeByte(nb); - - for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) - { - if (i->itemId) - { - --slot2; - continue; - } - - if (slot2 >= i->amount) - { - slot2 -= i->amount; - continue; - } - - assert(slot2 >= 0 && i + 1 != i_end); + // 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); - if (i->amount == 1) - { - // One single empty slot in the range. - i->itemId = id; - i->amount = nb; - return amount; - } - - InventoryItem it = { id, nb }; - --i->amount; + it1->second.amount -= nb; + it2->second.amount += nb; + amount -= nb; - if (slot2 == 0) + mInvMsg.writeShort(slot1); // Slot + if (it1->second.amount) { - // First slot in an empty range. - mPoss->inventory.insert(i, it); - return amount; + mInvMsg.writeShort(it1->second.itemId); // Item Id + mInvMsg.writeShort(it1->second.amount); // Amount } - - if (slot2 == i->amount) + else { - // Last slot in an empty range. - mPoss->inventory.insert(i + 1, it); - return amount; + mInvMsg.writeShort(0); + mPoss->inventory.erase(it1); } - - InventoryItem it3 = { 0, slot2 }; - i->amount -= slot2; - i = mPoss->inventory.insert(i, it); - mPoss->inventory.insert(i, it3); - return amount; + mInvMsg.writeShort(slot2); // Slot + mInvMsg.writeShort(it2->second.itemId); // Item Id + mInvMsg.writeShort(it2->second.amount); // Amount } - - // The second slot does not yet exist. - assert(slot2 >= 0); - if (slot2 != 0) - { - InventoryItem it = { 0, slot2 }; - mPoss->inventory.insert(mPoss->inventory.end(), it); - } - InventoryItem it = { id, nb }; - mPoss->inventory.insert(mPoss->inventory.end(), it); return amount; } -int Inventory::removeFromSlot(int slot, int amount) +unsigned int Inventory::removeFromSlot(unsigned int slot, unsigned int amount) { - if (amount == 0) - { - return 0; - } - - int i = getIndex(slot); - if (i < 0) - { + prepare(); + InventoryData::iterator it = mPoss->inventory.find(slot); + if (it == mPoss->inventory.end()) return amount; + unequip(slot); + { + bool exists = false; + for (InventoryData::const_iterator it2 = mPoss->inventory.begin(), + it2_end = mPoss->inventory.end(); + it2 != it2_end; + ++it2) + if (it2->second.itemId == it->second.itemId + && it->first != it2->first) + { + exists = true; + break; + } + if (!exists && it->second.itemId) + itemManager->getItem(it->second.itemId) + ->useTrigger(mClient, ITT_LEAVE_INVY); } - - prepare(); - - InventoryItem &it = mPoss->inventory[i]; - int nb = std::min((int)it.amount, amount); - it.amount -= nb; - amount -= nb; - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - if (it.amount == 0) + unsigned int sub = std::min(amount, it->second.amount); + amount -= sub; + it->second.amount -= sub; + mInvMsg.writeShort(it->first); + if (it->second.amount) { - msg.writeShort(0); - freeIndex(i); + mInvMsg.writeShort(it->second.itemId); + mInvMsg.writeShort(it->second.amount); } else { - msg.writeShort(it.itemId); - msg.writeByte(it.amount); + mInvMsg.writeShort(0); + mPoss->inventory.erase(it); } - return amount; } -void Inventory::replaceInSlot(int slot, int itemId, int amount) -{ - int i = getIndex(slot); - assert(i >= 0); - prepare(); - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - if (itemId == 0 || amount == 0) - { - msg.writeShort(0); - freeIndex(i); - } - else - { - InventoryItem &it = mPoss->inventory[i]; - it.itemId = itemId; - it.amount = amount; - msg.writeShort(itemId); - msg.writeByte(amount); - } -} -void Inventory::changeEquipment(int slot, int itemId) +void Inventory::changeEquipment(unsigned int oldId, unsigned int newId) { - // FIXME: Changes are applied now, so it does not work in delayed mode. - assert(!mDelayed); - - int oldId = mPoss->equipment[slot]; - if (oldId == itemId) - { + if (!oldId && !newId) return; - } - - if (oldId) - { - ItemManager::getItem(oldId)->getModifiers().cancelAttributes(mClient); - } - - if (itemId) - { - ItemManager::getItem(itemId)->getModifiers().applyAttributes(mClient); - } - - msg.writeByte(slot); - msg.writeShort(itemId); - mPoss->equipment[slot] = itemId; - mChangedLook = true; - - //mark evade as modified because it depends on equipment weight - mClient->updateDerivedAttributes(BASE_ATTR_EVADE); + changeEquipment(oldId ? itemManager->getItem(oldId) : 0, + newId ? itemManager->getItem(newId) : 0); } -void Inventory::equip(int slot) +void Inventory::changeEquipment(ItemClass *oldI, ItemClass *newI) { - int itemId = getItem(slot); - if (!itemId) - { + // 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; - } - - prepare(); - - int availableSlots = 0, firstSlot = 0, secondSlot = 0; + if (oldI && newI) + oldI->useTrigger(mClient, ITT_EQUIPCHG); + else if (oldI) + oldI->useTrigger(mClient, ITT_UNEQUIP); + else if (newI) + newI->useTrigger(mClient, ITT_EQUIP); +} - switch (ItemManager::getItem(itemId)->getType()) - { - case ITEM_EQUIPMENT_TWO_HANDS_WEAPON: +bool Inventory::equip(int slot, bool override) +{ + if (mPoss->equipSlots.count(slot)) + return false; + InventoryData::iterator it; + if ((it = mPoss->inventory.find(slot)) == mPoss->inventory.end()) + return false; + const ItemEquipsInfo &eq = itemManager->getItem(it->second.itemId)->getItemEquipData(); + if (eq.empty()) + 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) + { + // 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) { - // The one-handed weapons are to be placed back in the inventory. - int id1 = mPoss->equipment[EQUIP_FIGHT1_SLOT], - id2 = mPoss->equipment[EQUIP_FIGHT2_SLOT]; - - if (id2) + // 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) { - if (id1 && insert(id1, 1) != 0) - { - return; + fail |= 1; + if (override) + continue; + else + break; + } + else + { + fail |= 2; + break; + } + } + switch (fail) + { + case 0: + /* + * Clean fit. Equip and apply immediately. + */ + if (!mDelayed) { + mEqmMsg.writeShort(slot); // Inventory slot + mEqmMsg.writeByte(it2->size()); // Equip slot type count + } + for (it3 = it2->begin(), + it3_end = it2->end(); + it3 != it3_end; + ++it3) + { + if (!mDelayed) { + mEqmMsg.writeByte(it3->first); // Equip slot + mEqmMsg.writeByte(it3->second); // How many are used } - id1 = id2; + /* + * 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)); } - - replaceInSlot(slot, id1, 1); - changeEquipment(EQUIP_FIGHT1_SLOT, itemId); - changeEquipment(EQUIP_FIGHT2_SLOT, 0); - return; + 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; } - - case ITEM_EQUIPMENT_AMMO: - msg.writeByte(EQUIP_PROJECTILE_SLOT); - msg.writeShort(itemId); - mPoss->equipment[EQUIP_PROJECTILE_SLOT] = itemId; - return; - - case ITEM_EQUIPMENT_ONE_HAND_WEAPON: - case ITEM_EQUIPMENT_SHIELD: - availableSlots = 2; - firstSlot = EQUIP_FIGHT1_SLOT; - secondSlot = EQUIP_FIGHT2_SLOT; - break; - case ITEM_EQUIPMENT_RING: - availableSlots = 2; - firstSlot = EQUIP_RING1_SLOT; - secondSlot = EQUIP_RING2_SLOT; - break; - case ITEM_EQUIPMENT_TORSO: - availableSlots = 1; - firstSlot = EQUIP_TORSO_SLOT; - break; - case ITEM_EQUIPMENT_ARMS: - availableSlots = 1; - firstSlot = EQUIP_ARMS_SLOT; - break; - case ITEM_EQUIPMENT_HEAD: - availableSlots = 1; - firstSlot = EQUIP_HEAD_SLOT; - break; - case ITEM_EQUIPMENT_LEGS: - availableSlots = 1; - firstSlot = EQUIP_LEGS_SLOT; - break; - case ITEM_EQUIPMENT_NECKLACE: - availableSlots = 1; - firstSlot = EQUIP_NECKLACE_SLOT; - break; - case ITEM_EQUIPMENT_FEET: - availableSlots = 1; - firstSlot = EQUIP_FEET_SLOT; - break; - - case ITEM_UNUSABLE: - case ITEM_USABLE: - default: - return; } + // We didn't find a clean equip. + if (ovd) + { + /* + * We did find an equip that works if we unequip other items, and we can override. + * Process unequip triggers for all items we have to unequip. + * Process equip triggers for new item. + * Attempt to reequip any equipment we had to remove, but disallowing override. + */ - int id = mPoss->equipment[firstSlot]; + // TODO - this would increase ease of use substatially, add as soon as + // there is time to do so. - if (availableSlots == 2 && id && !mPoss->equipment[secondSlot] && - ItemManager::getItem(id)->getType() != ITEM_EQUIPMENT_TWO_HANDS_WEAPON) - { - // The first equipment slot is full, but the second one is empty. - id = 0; - firstSlot = secondSlot; + return false; // Return true when this section is complete } - - // Put the item in the first equipment slot. - replaceInSlot(slot, id, 1); - changeEquipment(firstSlot, itemId); + /* + * 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; } -void Inventory::unequip(int slot) +bool Inventory::unequip(EquipData::iterator it) { - int itemId = mPoss->equipment[slot]; - if (!itemId) - { - return; - } - // No need to prepare. + return unequip(it->second, &it); +} - if (insert(itemId, 1) == 0) +bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) +{ + prepare(); + EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); + bool changed = false; + for (it = mPoss->equipSlots.begin(); + it != it_end; + ++it) + if (it->second == slot) + { + changed = true; + mPoss->equipSlots.erase(it); + } + if (changed && !mDelayed) { - changeEquipment(slot, 0); + changeEquipment(mPoss->inventory.at(it->second).itemId, 0); + mEqmMsg.writeShort(slot); + mEqmMsg.writeByte(0); } + return changed; } |