summaryrefslogtreecommitdiff
path: root/src/game-server/inventory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game-server/inventory.cpp')
-rw-r--r--src/game-server/inventory.cpp1169
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;
}