diff options
| author | Freeyorp <Freeyorp101@hotmail.com> | 2010-05-17 20:55:06 +1200 |
|---|---|---|
| committer | Freeyorp <Freeyorp101@hotmail.com> | 2010-07-10 21:51:07 +1200 |
| commit | 98cdcb1de4f422255aa5ef924042ae7d00a5b968 (patch) | |
| tree | 1746776580502fb007581f171fa89638ab6bc64f /src/game-server/inventory.cpp | |
| parent | 26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2 (diff) | |
| download | manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.tar.gz manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.tar.xz manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.zip | |
New attribute system and major changes to many low-level areas.
Attribute system:
Structure is no longer completely hardcoded. Attributes and structure is
defined by new xml file (defaulting to stats.xml)
Structure defines non-base modifications to an attribute, to be used by
modifiers from items, effects, etc.
Calculating the base value for core attributes is still done in C++ (and for
such fundamental elements the only reason I can think of to do it any other
way is perhaps being able to quickly change scripts without a compile could be
useful for testing, but such things are a low priority anyway)
Item structure:
Modifiers are now through triggers rather than single events. This also
removes hardcoded types - an item could be both able to be equipped and be
able to be activated.
Item activation no longer consumes by default, this must be specified by the
property <consumes /> inside the trigger.
Currently only attribute modifications, autoattacks, and consumes are defined
as effects, but stubs for others do exist. Autoattacks are currently
non-functional, and this should be rectified with some urgency.
Auto Attacks:
AutoAttacks are now separate entities, though not fully complete, nor fully
integrated with all beings yet. Integration with the Character class is
urgent, integration with other Being children less so.
When fully integrated this will allow for multiple autoattacks, through
equipping multiple items with this as an equip effect or even through other
means if needed.
Equipment structure:
As ItemClass types are no longer hardcoded, so too are equip types. An item
have multiple ways to be equipped across multiple equipment slots with any
number in each slot. Character maximums are global but configurable.
Miscellaneous:
Speed, money, and weight are now attributes.
Some managers have been changed into classes such that their associated
classes can have them as friends, to avoid (ab)use of public accessors.
The serialise procedure should also be set as a friend of Character (both in
the account- and game- server) as well; having public accessors returning
iterators is simply ridiculous.
Some start for such cleanups have been made, but this is not the primary focus
here. Significant work will need to be done before this is resolved
completely, but the start is there.
BuySell::registerPlayerItems() has been completely disabled temporarily. The
previous function iterated through equipment, yet in the context I think it is
intended to fill items? I have been unable to update this function to fit the
modifications made to the Inventory/Equipment/Possessions, as I am unsure what
exactly what it should be doing.
ItemClass::mSpriteId was previously unused, so had been removed, but I
notice that it was used when transmitting equipment to nearby clients.
Experimentation showed that this value was never set to anything other than
0, and so has been left out of the ItemManager rewrite.
I am not entirely sure what is happening here, but it should be worth looking
into at a later time, as I am not sure how equipment appearences would be sent
otherwise.
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 b90354b..f560ce5 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->modifiedAttribute(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; } |
