diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index 14905e16..00b528b2 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -139,8 +139,8 @@ - - + + diff --git a/FFXIVClassic Map Server/actors/chara/Equipment.cs b/FFXIVClassic Map Server/actors/chara/Equipment.cs new file mode 100644 index 00000000..fae856ef --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/Equipment.cs @@ -0,0 +1,271 @@ +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.packets.send.actor.inventory; +using System.Collections.Generic; +using System.Diagnostics; + +namespace FFXIVClassic_Map_Server.actors.chara.player +{ + /// + /// This class stores the current equipment that a Player has equipped on themselves. Technically + /// it is an ItemPackage like other inventories, however due to how this one operates, it is in + /// it's own class. While on the server this is stored as a list of InventoryItems like other + /// ItemPackages, on the client it exists as a link to the "normal inventory" package. The Equipment list + /// therefore is a list of slot values (the slots in the inventory), with each position in the list being + /// a position on the paper doll (in the game's Gear menu). + /// + class Equipment + { + public const int SLOT_MAINHAND = 0; + public const int SLOT_OFFHAND = 1; + public const int SLOT_THROWINGWEAPON = 4; + public const int SLOT_PACK = 5; + public const int SLOT_POUCH = 6; + public const int SLOT_HEAD = 8; + public const int SLOT_UNDERSHIRT = 9; + public const int SLOT_BODY = 10; + public const int SLOT_UNDERGARMENT = 11; + public const int SLOT_LEGS = 12; + public const int SLOT_HANDS = 13; + public const int SLOT_BOOTS = 14; + public const int SLOT_WAIST = 15; + public const int SLOT_NECK = 16; + public const int SLOT_EARS = 17; + public const int SLOT_WRISTS = 19; + public const int SLOT_RIGHTFINGER = 21; + public const int SLOT_LEFTFINGER = 22; + + private const ushort EQUIP_ITEMPACKAGE_CAPACITY = 0x23; + private const ushort EQUIP_ITEMPACKAGE_CODE = ItemPackage.EQUIPMENT; + + readonly private InventoryItem[] list = new InventoryItem[EQUIP_ITEMPACKAGE_CAPACITY]; + + readonly private Player owner; + readonly private ItemPackage normalInventory; + + private bool writeToDB = true; + + /// The player client that owns this ItemPackage. + /// A reference to the normal inventory ItemPackage this equipment ItemPackage links to. + public Equipment(Player ownerPlayer, ItemPackage normalInventory) + { + owner = ownerPlayer; + this.normalInventory = normalInventory; + } + + /// + /// Sets the full equipment ItemPackage to the given list. 's length must + /// equal EQUIP_ITEMPACKAGE_CAPACITY. Used to initialize the list when loading from the DB. + /// + /// The list of inventory items to set the full list to. + public void SetEquipment(InventoryItem[] toEquip) + { + Debug.Assert(toEquip.Length == EQUIP_ITEMPACKAGE_CAPACITY); + toEquip.CopyTo(list, 0); + } + + /// + /// Sets the given equipSlots to link to the given itemSlots. The lengths of both must be equal. + /// + /// The list of equipmentSlots that get linked. + /// The list of itemSlots that the equipSlots will link to. + public void SetEquipment(ushort[] equipSlots, ushort[] itemSlots) + { + if (equipSlots.Length != itemSlots.Length) + return; + + for (int i = 0; i < equipSlots.Length; i++) + { + InventoryItem item = normalInventory.GetItemAtSlot(itemSlots[i]); + + if (item == null) + continue; + + Database.EquipItem(owner, equipSlots[i], item.uniqueId); + list[equipSlots[i]] = normalInventory.GetItemAtSlot(itemSlots[i]); + } + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + SendFullEquipment(owner); + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + } + + /// + /// Equips the item at the given item slot to the given equipment slot. + /// + /// The equipment slot being equipped. + /// The inventory slot of where the equipped item is. + public void Equip(ushort equipSlot, ushort invSlot) + { + InventoryItem item = normalInventory.GetItemAtSlot(invSlot); + + if (item == null) + return; + + Equip(equipSlot, item); + } + + /// + /// Equips the given inventory item to the given equipment slot. + /// + /// The equipment slot being equipped. + /// The inventory item being equiped. + public void Equip(ushort equipSlot, InventoryItem item) + { + if (equipSlot >= list.Length) + return; + + if (writeToDB) + Database.EquipItem(owner, equipSlot, item.uniqueId); + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + + if (list[equipSlot] != null) + normalInventory.RefreshItem(owner, list[equipSlot], item); + else + normalInventory.RefreshItem(owner, item); + + list[equipSlot] = item; + + owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, EQUIP_ITEMPACKAGE_CAPACITY, EQUIP_ITEMPACKAGE_CODE)); + SendSingleEquipmentUpdatePacket(equipSlot); + owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + + owner.CalculateBaseStats();// RecalculateStats(); + } + + /// + /// Toggles if equipment changes are saved to the DB. + /// + /// If true, equipment changes are saved to the db. + public void ToggleDBWrite(bool flag) + { + writeToDB = flag; + } + + /// + /// Removes the linked item at the given . + /// + /// The slot that is being cleared. + public void Unequip(ushort equipSlot) + { + if (equipSlot >= list.Length) + return; + + if (writeToDB) + Database.UnequipItem(owner, equipSlot); + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + normalInventory.RefreshItem(owner, list[equipSlot]); + list[equipSlot] = null; + SendSingleEquipmentUpdatePacket(equipSlot); + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + + owner.RecalculateStats(); + } + + /// + /// Returns the item equipped at the given . + /// + /// The slot to retrieve from. + public InventoryItem GetItemAtSlot(ushort equipSlot) + { + if (equipSlot < list.Length) + return list[equipSlot]; + else + return null; + } + + /// + /// Returns the capacity of this ItemPackage. + /// + public int GetCapacity() + { + return list.Length; + } + + #region Packet Functions + + /// + /// Syncs the client the link status of a single equipment slot. If the item was null, + /// sends a delete packet instead. + /// + /// The slot we are updating the client about. + private void SendSingleEquipmentUpdatePacket(ushort equipSlot) + { + owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, EQUIP_ITEMPACKAGE_CAPACITY, EQUIP_ITEMPACKAGE_CODE)); + if (list[equipSlot] == null) + owner.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, equipSlot)); + else + owner.QueuePacket(EquipmentListX01Packet.BuildPacket(owner.actorId, equipSlot, list[equipSlot].slot)); + owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + /// + /// Syncs the full list of equipped items to the client owner of this ItemPackage. Used on login/zone in. + /// + public void SendFullEquipment() + { + SendFullEquipment(owner); + } + + /// + /// Syncs the full list of equipped items to a given target. Used to send the equipment list of this ItemPackage to the owner, + /// or in the case of examining another player, sends the list of this ItemPackage to the player client examining. A different + /// ItemPackage Code is used for /checking. + /// + /// The player client that is being synced. + public void SendFullEquipment(Player targetPlayer) + { + List slotsToUpdate = new List(); + + for (ushort i = 0; i < list.Length; i++) + { + if (list[i] != null) + slotsToUpdate.Add(i); + } + + if (targetPlayer.Equals(owner)) + targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, EQUIP_ITEMPACKAGE_CAPACITY, EQUIP_ITEMPACKAGE_CODE)); + else + targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, 0x23, ItemPackage.EQUIPMENT_OTHERPLAYER)); + + SendEquipmentPackets(slotsToUpdate, targetPlayer); + + targetPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + /// + /// Main sync function. Syncs the given targetPlayer client the link status of multiple equipment slots. + /// + /// The slots that will be synced. + /// The player client that is being synced. + private void SendEquipmentPackets(List slotsToUpdate, Player targetPlayer) + { + int currentIndex = 0; + + while (true) + { + if (slotsToUpdate.Count - currentIndex >= 64) + targetPlayer.QueuePacket(EquipmentListX64Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex >= 32) + targetPlayer.QueuePacket(EquipmentListX32Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex >= 16) + targetPlayer.QueuePacket(EquipmentListX16Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex > 1) + targetPlayer.QueuePacket(EquipmentListX08Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex == 1) + { + targetPlayer.QueuePacket(EquipmentListX01Packet.BuildPacket(owner.actorId, slotsToUpdate[currentIndex], list[slotsToUpdate[currentIndex]].slot)); + currentIndex++; + } + else + break; + } + } + + #endregion + } +} diff --git a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs b/FFXIVClassic Map Server/actors/chara/ItemPackage.cs similarity index 97% rename from FFXIVClassic Map Server/actors/chara/player/Inventory.cs rename to FFXIVClassic Map Server/actors/chara/ItemPackage.cs index 9573bb0f..dd543dc6 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs +++ b/FFXIVClassic Map Server/actors/chara/ItemPackage.cs @@ -1,744 +1,744 @@ -using FFXIVClassic.Common; -using FFXIVClassic_Map_Server.actors.chara.npc; -using FFXIVClassic_Map_Server.Actors; -using FFXIVClassic_Map_Server.dataobjects; -using FFXIVClassic_Map_Server.packets.send.actor.inventory; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace FFXIVClassic_Map_Server.actors.chara.player -{ - class ItemPackage - { - public const ushort NORMAL = 0; //Max 0xC8 - public const ushort UNKNOWN = 1; //Max 0x96 - public const ushort LOOT = 4; //Max 0xA - public const ushort MELDREQUEST = 5; //Max 0x04 - public const ushort BAZAAR = 7; //Max 0x0A - public const ushort CURRENCY_CRYSTALS = 99; //Max 0x140 - public const ushort KEYITEMS = 100; //Max 0x500 - public const ushort EQUIPMENT = 0x00FE; //Max 0x23 - public const ushort TRADE = 0x00FD; //Max 0x04 - public const ushort EQUIPMENT_OTHERPLAYER = 0x00F9; //Max 0x23 - - public enum INV_ERROR { - SUCCESS = 0, - INVENTORY_FULL, - ALREADY_HAS_UNIQUE, - SYSTEM_ERROR - }; - - private Character owner; - private ushort itemPackageCapacity; - private ushort itemPackageCode; - private bool isTemporary; - private InventoryItem[] list; - private bool[] isDirty; - private bool holdingUpdates = false; - - private int endOfListIndex = 0; - - public ItemPackage(Character ownerPlayer, ushort capacity, ushort code, bool temporary = false) - { - owner = ownerPlayer; - itemPackageCapacity = capacity; - itemPackageCode = code; - isTemporary = temporary; - list = new InventoryItem[capacity]; - isDirty = new bool[capacity]; - } - - #region Inventory Management - public void InitList(List itemsFromDB) - { - int i = 0; - foreach (InventoryItem item in itemsFromDB) - { - item.RefreshPositioning(owner, itemPackageCode, (ushort) i); - list[i++] = item; - } - endOfListIndex = i; - } - - public InventoryItem GetItemAtSlot(ushort slot) - { - if (slot < list.Length) - return list[slot]; - else - return null; - } - - public InventoryItem GetItemByUniqueId(ulong uniqueItemId) - { - for (int i = 0; i < endOfListIndex; i++) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (item.uniqueId == uniqueItemId) - return item; - } - return null; - } - - public InventoryItem GetItemByCatelogId(ulong catelogId) - { - for (int i = 0; i < endOfListIndex; i++) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (item.itemId == catelogId) - return item; - } - return null; - } - - public InventoryItem GetItemAttachedTo(InventoryItem attachedTo) - { - for (int i = 0; i < endOfListIndex; i++) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (attachedTo.GetAttached() == item.uniqueId) - return item; - } - return null; - } - - public INV_ERROR AddItem(uint itemId) - { - return AddItem(itemId, 1, 1); - } - - public INV_ERROR AddItem(uint itemId, int quantity) - { - return AddItem(itemId, quantity, 1); - } - - public bool AddItems(uint[] itemIds) - { - bool canAdd = GetFreeSlots() - itemIds.Length >= 0; - if (canAdd) - { - foreach (uint id in itemIds) - { - ItemData gItem = Server.GetItemGamedata(id); - InventoryItem.ItemModifier modifiers = null; - if (gItem.durability != 0) - { - modifiers = new InventoryItem.ItemModifier(); - modifiers.durability = (uint)gItem.durability; - } - - InventoryItem addedItem = Database.CreateItem(id, Math.Min(1, gItem.maxStack), 0, modifiers); - addedItem.RefreshPositioning(owner, itemPackageCode, (ushort)endOfListIndex); - isDirty[endOfListIndex] = true; - list[endOfListIndex++] = addedItem; - DoDatabaseAdd(addedItem); - } - } - return canAdd; - } - - public INV_ERROR AddItem(InventoryItem itemRef) - { - //If it isn't a single item (ie: armor) just add like normal (not valid for BAZAAR) - if (itemPackageCode != BAZAAR && itemRef.GetItemData().maxStack > 1) - return AddItem(itemRef.itemId, itemRef.quantity, itemRef.quality); - - if (!IsSpaceForAdd(itemRef.itemId, itemRef.quantity, itemRef.quality)) - return INV_ERROR.INVENTORY_FULL; - - ItemData gItem = Server.GetItemGamedata(itemRef.itemId); - - if (gItem == null) - { - Program.Log.Error("Inventory.AddItem: unable to find item %u", itemRef.itemId); - return INV_ERROR.SYSTEM_ERROR; - } - - itemRef.RefreshPositioning(owner, itemPackageCode, (ushort)endOfListIndex); - - isDirty[endOfListIndex] = true; - list[endOfListIndex++] = itemRef; - DoDatabaseAdd(itemRef); - - SendUpdatePackets(); - - return INV_ERROR.SUCCESS; - } - - public INV_ERROR AddItem(uint itemId, int quantity, byte quality) - { - if (!IsSpaceForAdd(itemId, quantity, quality)) - return INV_ERROR.INVENTORY_FULL; - - ItemData gItem = Server.GetItemGamedata(itemId); - - //If it's unique, abort - if (HasItem(itemId) && gItem.isExclusive) - return INV_ERROR.ALREADY_HAS_UNIQUE; - - if (gItem == null) - { - Program.Log.Error("Inventory.AddItem: unable to find item %u", itemId); - return INV_ERROR.SYSTEM_ERROR; - } - - //Check if item id exists - int quantityCount = quantity; - for (int i = 0; i < endOfListIndex; i++) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) - { - int oldQuantity = item.quantity; - item.quantity = Math.Min(item.quantity + quantityCount, gItem.maxStack); - isDirty[i] = true; - quantityCount -= (gItem.maxStack - oldQuantity); - - DoDatabaseQuantity(item.uniqueId, item.quantity); - - if (quantityCount <= 0) - break; - } - } - - //New item that spilled over - while (quantityCount > 0) - { - InventoryItem.ItemModifier modifiers = null; - if (gItem.durability != 0) - { - modifiers = new InventoryItem.ItemModifier(); - modifiers.durability = (uint)gItem.durability; - } - - InventoryItem addedItem = Database.CreateItem(itemId, Math.Min(quantityCount, gItem.maxStack), quality, modifiers); - addedItem.RefreshPositioning(owner, itemPackageCode, (ushort)endOfListIndex); - isDirty[endOfListIndex] = true; - list[endOfListIndex++] = addedItem; - quantityCount -= gItem.maxStack; - - DoDatabaseAdd(addedItem); - } - - SendUpdatePackets(); - - return INV_ERROR.SUCCESS; - } - - public void SetItem(ushort slot, InventoryItem item) - { - list[slot] = item; - SendUpdatePackets(); - item.RefreshPositioning(owner, itemPackageCode, slot); - } - - public void RemoveItem(uint itemId) - { - RemoveItem(itemId, 1); - } - - public void RemoveItem(uint itemId, int quantity) - { - RemoveItem(itemId, quantity, 1); - } - - public void RemoveItem(uint itemId, int quantity, int quality) - { - if (!HasItem(itemId, quantity, quality)) - return; - - List slotsToUpdate = new List(); - List itemsToRemove = new List(); - List slotsToRemove = new List(); - List AddItemPackets = new List(); - - //Remove as we go along - int quantityCount = quantity; - ushort lowestSlot = 0; - for (int i = endOfListIndex - 1; i >= 0; i--) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (item.itemId == itemId && item.quality == quality) - { - int oldQuantity = item.quantity; - //Stack nomnomed - if (item.quantity - quantityCount <= 0) - { - DoDatabaseRemove(list[i].uniqueId); - list[i] = null; - } - //Stack reduced - else - { - item.quantity -= quantityCount; - DoDatabaseQuantity(list[i].uniqueId, list[i].quantity);} - - isDirty[i] = true; - - quantityCount -= oldQuantity; - lowestSlot = item.slot; - - if (quantityCount <= 0) - break; - } - } - - DoRealign(); - SendUpdatePackets(); - } - - public void RemoveItem(InventoryItem item) - { - RemoveItemByUniqueId(item.uniqueId, item.quantity); - } - - public void RemoveItem(InventoryItem item, int quantity) - { - RemoveItemByUniqueId(item.uniqueId, quantity); - } - - public void RemoveItemByUniqueId(ulong itemDBId, int quantity) - { - ushort slot = 0; - InventoryItem toDelete = null; - for (int i = 0; i < endOfListIndex; i++) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (item.uniqueId == itemDBId) - { - toDelete = item; - break; - } - slot++; - } - - if (toDelete == null) - return; - - if (quantity >= toDelete.quantity) - { - DoDatabaseRemove(toDelete.uniqueId); - list[slot].RefreshPositioning(null, 0xFFFF, 0xFFFF); - list[slot] = null; - } - else - { - list[slot].quantity -= quantity; - DoDatabaseQuantity(list[slot].uniqueId, list[slot].quantity); - } - - isDirty[slot] = true; - - DoRealign(); - SendUpdatePackets(); - } - - public void RemoveItemAtSlot(ushort slot) - { - if (slot >= endOfListIndex) - return; - - DoDatabaseRemove(list[slot].uniqueId); - - list[slot].RefreshPositioning(null, 0xFFFF, 0xFFFF); - list[slot] = null; - isDirty[slot] = true; - - DoRealign(); - SendUpdatePackets(); - } - - public void RemoveItemAtSlot(ushort slot, int quantity) - { - if (slot >= endOfListIndex) - return; - - if (list[slot] != null) - { - list[slot].quantity -= quantity; - - if (list[slot].quantity <= 0) - { - DoDatabaseRemove(list[slot].uniqueId); - - list[slot].RefreshPositioning(null, 0xFFFF, 0xFFFF); - list[slot] = null; - DoRealign(); - } - else - DoDatabaseQuantity(list[slot].uniqueId, list[slot].quantity); - - isDirty[slot] = true; - SendUpdatePackets(); - } - } - - public void Clear() - { - for (int i = 0; i < endOfListIndex; i++) - { - list[i].RefreshPositioning(null, 0xFFFF, 0xFFFF); - list[i] = null; - isDirty[i] = true; - } - endOfListIndex = 0; - - SendUpdatePackets(); - } - - public InventoryItem[] GetRawList() - { - return list; - } - - public void ChangeDurability(uint slot, uint durabilityChange) - { - isDirty[slot] = true; - } - - public void ChangeSpiritBind(uint slot, uint spiritBindChange) - { - isDirty[slot] = true; - } - - public void ChangeMateria(uint slot, byte materiaSlot, byte materiaId) - { - isDirty[slot] = true; - } - #endregion - - #region Packet Functions - public void SendFullInventory(Player player) - { - player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); - SendInventoryPackets(player, 0); - player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - private void SendInventoryPackets(Player player, InventoryItem item) - { - player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, item)); - } - - private void SendInventoryPackets(Player player, List items) - { - int currentIndex = 0; - - while (true) - { - if (items.Count - currentIndex >= 64) - player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex >= 32) - player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex >= 16) - player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex > 1) - player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex == 1) - { - player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, items[currentIndex])); - currentIndex++; - } - else - break; - } - } - - private void SendInventoryPackets(Player player, int startOffset) - { - int currentIndex = startOffset; - - List lst = new List(); - for (int i = 0; i < endOfListIndex; i++) - lst.Add(list[i]); - - while (true) - { - if (endOfListIndex - currentIndex >= 64) - player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); - else if (endOfListIndex - currentIndex >= 32) - player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); - else if (endOfListIndex - currentIndex >= 16) - player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); - else if (endOfListIndex - currentIndex > 1) - player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); - else if (endOfListIndex - currentIndex == 1) - { - player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, list[currentIndex])); - currentIndex++; - } - else - break; - } - } - - private void SendInventoryRemovePackets(Player player, ushort index) - { - player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, index)); - } - - private void SendInventoryRemovePackets(Player player, List indexes) - { - int currentIndex = 0; - - while (true) - { - if (indexes.Count - currentIndex >= 64) - player.QueuePacket(InventoryRemoveX64Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex >= 32) - player.QueuePacket(InventoryRemoveX32Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex >= 16) - player.QueuePacket(InventoryRemoveX16Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex > 1) - player.QueuePacket(InventoryRemoveX08Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex == 1) - { - player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, indexes[currentIndex])); - currentIndex++; - } - else - break; - } - } - - public void RefreshItem(Player player, InventoryItem item) - { - player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); - SendInventoryPackets(player, item); - player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - public void RefreshItem(Player player, params InventoryItem[] items) - { - player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); - SendInventoryPackets(player, items.ToList()); - player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - public void RefreshItem(Player player, List items) - { - player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); - SendInventoryPackets(player, items); - player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - #endregion - - #region Automatic Client and DB Updating - - private void DoDatabaseAdd(InventoryItem addedItem) - { - if (isTemporary) - return; - - if (itemPackageCode == BAZAAR) - return; - - if (owner is Player) - Database.AddItem((Player)owner, addedItem, itemPackageCode); - else if (owner is Retainer) - Database.AddItem((Retainer)owner, addedItem, itemPackageCode); - } - - private void DoDatabaseQuantity(ulong itemDBId, int quantity) - { - if (isTemporary) - return; - - - if (itemPackageCode == BAZAAR) - return; - - Database.SetQuantity(itemDBId, quantity); - } - - private void DoDatabaseRemove(ulong itemDBId) - { - if (isTemporary) - return; - - if (itemPackageCode == BAZAAR) - return; - - if (owner is Player) - Database.RemoveItem((Player)owner, itemDBId); - else if (owner is Retainer) - Database.RemoveItem((Retainer)owner, itemDBId); - } - - private void SendUpdatePackets() - { - if (owner is Player && !holdingUpdates) - { - SendUpdatePackets((Player)owner); - } - } - - public void SendUpdatePackets(Player player) - { - List items = new List(); - List slotsToRemove = new List(); - - for (int i = 0; i < list.Length; i++) - { - if (i == endOfListIndex) - break; - if (isDirty[i]) - items.Add(list[i]); - } - - for (int i = endOfListIndex; i < list.Length; i++) - { - if (isDirty[i]) - slotsToRemove.Add((ushort)i); - } - - if (!holdingUpdates) - Array.Clear(isDirty, 0, isDirty.Length); - - player.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); - //Send Updated Slots - SendInventoryPackets(player, items); - //Send Remove packets for tail end - SendInventoryRemovePackets(player, slotsToRemove); - player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - //If player is updating their normal inventory, we need to send - //an equip update as well to resync the slots. - if (player.Equals(owner) && itemPackageCode == NORMAL) - player.GetEquipment().SendFullEquipment(); - player.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - } - - public void StartSendUpdate() - { - holdingUpdates = true; - } - - public void DoneSendUpdate() - { - holdingUpdates = false; - SendUpdatePackets(); - Array.Clear(isDirty, 0, isDirty.Length); - } - - #endregion - - #region Inventory Utils - - public bool IsFull() - { - return endOfListIndex >= itemPackageCapacity; - } - - public int GetFreeSlots() - { - return itemPackageCapacity - endOfListIndex; - } - - public bool IsSpaceForAdd(uint itemId, int quantity, int quality) - { - int quantityCount = quantity; - for (int i = 0; i < endOfListIndex; i++) - { - InventoryItem item = list[i]; - ItemData gItem = Server.GetItemGamedata(item.itemId); - if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) - { - quantityCount -= (gItem.maxStack - item.quantity); - if (quantityCount <= 0) - break; - } - } - - return quantityCount <= 0 || (quantityCount > 0 && !IsFull()); - } - - public bool HasItem(uint itemId) - { - return HasItem(itemId, 1); - } - - public bool HasItem(uint itemId, int minQuantity) - { - return HasItem(itemId, minQuantity, 1); - } - - public bool HasItem(uint itemId, int minQuantity, int quality) - { - int count = 0; - - for (int i = endOfListIndex - 1; i >= 0; i--) - { - InventoryItem item = list[i]; - - Debug.Assert(item != null, "Item slot was null!!!"); - - if (item.itemId == itemId && item.quality == quality) - count += item.quantity; - - if (count >= minQuantity) - return true; - } - - return false; - } - - public int GetNextEmptySlot() - { - return endOfListIndex; - } - - private void DoRealign() - { - int lastNullSlot = -1; - - for (int i = 0; i < endOfListIndex; i++) - { - if (list[i] == null && lastNullSlot == -1) - { - lastNullSlot = i; - continue; - } - else if (list[i] != null && lastNullSlot != -1) - { - list[lastNullSlot] = list[i]; - list[lastNullSlot].slot = (ushort)lastNullSlot; - list[i] = null; - isDirty[lastNullSlot] = true; - isDirty[i] = true; - lastNullSlot++; - } - } - - if (lastNullSlot != -1) - endOfListIndex = lastNullSlot; - } - - #endregion - - public int GetCount() - { - return endOfListIndex; - } - } -} +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.packets.send.actor.inventory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace FFXIVClassic_Map_Server.actors.chara.player +{ + class ItemPackage + { + public const ushort NORMAL = 0; //Max 0xC8 + public const ushort UNKNOWN = 1; //Max 0x96 + public const ushort LOOT = 4; //Max 0xA + public const ushort MELDREQUEST = 5; //Max 0x04 + public const ushort BAZAAR = 7; //Max 0x0A + public const ushort CURRENCY_CRYSTALS = 99; //Max 0x140 + public const ushort KEYITEMS = 100; //Max 0x500 + public const ushort EQUIPMENT = 0x00FE; //Max 0x23 + public const ushort TRADE = 0x00FD; //Max 0x04 + public const ushort EQUIPMENT_OTHERPLAYER = 0x00F9; //Max 0x23 + + public enum INV_ERROR { + SUCCESS = 0, + INVENTORY_FULL, + ALREADY_HAS_UNIQUE, + SYSTEM_ERROR + }; + + private Character owner; + private ushort itemPackageCapacity; + private ushort itemPackageCode; + private bool isTemporary; + private InventoryItem[] list; + private bool[] isDirty; + private bool holdingUpdates = false; + + private int endOfListIndex = 0; + + public ItemPackage(Character ownerPlayer, ushort capacity, ushort code, bool temporary = false) + { + owner = ownerPlayer; + itemPackageCapacity = capacity; + itemPackageCode = code; + isTemporary = temporary; + list = new InventoryItem[capacity]; + isDirty = new bool[capacity]; + } + + #region Inventory Management + public void InitList(List itemsFromDB) + { + int i = 0; + foreach (InventoryItem item in itemsFromDB) + { + item.RefreshPositioning(owner, itemPackageCode, (ushort) i); + list[i++] = item; + } + endOfListIndex = i; + } + + public InventoryItem GetItemAtSlot(ushort slot) + { + if (slot < list.Length) + return list[slot]; + else + return null; + } + + public InventoryItem GetItemByUniqueId(ulong uniqueItemId) + { + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.uniqueId == uniqueItemId) + return item; + } + return null; + } + + public InventoryItem GetItemByCatelogId(ulong catelogId) + { + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == catelogId) + return item; + } + return null; + } + + public InventoryItem GetItemAttachedTo(InventoryItem attachedTo) + { + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (attachedTo.GetAttached() == item.uniqueId) + return item; + } + return null; + } + + public INV_ERROR AddItem(uint itemId) + { + return AddItem(itemId, 1, 1); + } + + public INV_ERROR AddItem(uint itemId, int quantity) + { + return AddItem(itemId, quantity, 1); + } + + public bool AddItems(uint[] itemIds) + { + bool canAdd = GetFreeSlots() - itemIds.Length >= 0; + if (canAdd) + { + foreach (uint id in itemIds) + { + ItemData gItem = Server.GetItemGamedata(id); + InventoryItem.ItemModifier modifiers = null; + if (gItem.durability != 0) + { + modifiers = new InventoryItem.ItemModifier(); + modifiers.durability = (uint)gItem.durability; + } + + InventoryItem addedItem = Database.CreateItem(id, Math.Min(1, gItem.maxStack), 0, modifiers); + addedItem.RefreshPositioning(owner, itemPackageCode, (ushort)endOfListIndex); + isDirty[endOfListIndex] = true; + list[endOfListIndex++] = addedItem; + DoDatabaseAdd(addedItem); + } + } + return canAdd; + } + + public INV_ERROR AddItem(InventoryItem itemRef) + { + //If it isn't a single item (ie: armor) just add like normal (not valid for BAZAAR) + if (itemPackageCode != BAZAAR && itemRef.GetItemData().maxStack > 1) + return AddItem(itemRef.itemId, itemRef.quantity, itemRef.quality); + + if (!IsSpaceForAdd(itemRef.itemId, itemRef.quantity, itemRef.quality)) + return INV_ERROR.INVENTORY_FULL; + + ItemData gItem = Server.GetItemGamedata(itemRef.itemId); + + if (gItem == null) + { + Program.Log.Error("Inventory.AddItem: unable to find item %u", itemRef.itemId); + return INV_ERROR.SYSTEM_ERROR; + } + + itemRef.RefreshPositioning(owner, itemPackageCode, (ushort)endOfListIndex); + + isDirty[endOfListIndex] = true; + list[endOfListIndex++] = itemRef; + DoDatabaseAdd(itemRef); + + SendUpdatePackets(); + + return INV_ERROR.SUCCESS; + } + + public INV_ERROR AddItem(uint itemId, int quantity, byte quality) + { + if (!IsSpaceForAdd(itemId, quantity, quality)) + return INV_ERROR.INVENTORY_FULL; + + ItemData gItem = Server.GetItemGamedata(itemId); + + //If it's unique, abort + if (HasItem(itemId) && gItem.isExclusive) + return INV_ERROR.ALREADY_HAS_UNIQUE; + + if (gItem == null) + { + Program.Log.Error("Inventory.AddItem: unable to find item %u", itemId); + return INV_ERROR.SYSTEM_ERROR; + } + + //Check if item id exists + int quantityCount = quantity; + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) + { + int oldQuantity = item.quantity; + item.quantity = Math.Min(item.quantity + quantityCount, gItem.maxStack); + isDirty[i] = true; + quantityCount -= (gItem.maxStack - oldQuantity); + + DoDatabaseQuantity(item.uniqueId, item.quantity); + + if (quantityCount <= 0) + break; + } + } + + //New item that spilled over + while (quantityCount > 0) + { + InventoryItem.ItemModifier modifiers = null; + if (gItem.durability != 0) + { + modifiers = new InventoryItem.ItemModifier(); + modifiers.durability = (uint)gItem.durability; + } + + InventoryItem addedItem = Database.CreateItem(itemId, Math.Min(quantityCount, gItem.maxStack), quality, modifiers); + addedItem.RefreshPositioning(owner, itemPackageCode, (ushort)endOfListIndex); + isDirty[endOfListIndex] = true; + list[endOfListIndex++] = addedItem; + quantityCount -= gItem.maxStack; + + DoDatabaseAdd(addedItem); + } + + SendUpdatePackets(); + + return INV_ERROR.SUCCESS; + } + + public void SetItem(ushort slot, InventoryItem item) + { + list[slot] = item; + SendUpdatePackets(); + item.RefreshPositioning(owner, itemPackageCode, slot); + } + + public void RemoveItem(uint itemId) + { + RemoveItem(itemId, 1); + } + + public void RemoveItem(uint itemId, int quantity) + { + RemoveItem(itemId, quantity, 1); + } + + public void RemoveItem(uint itemId, int quantity, int quality) + { + if (!HasItem(itemId, quantity, quality)) + return; + + List slotsToUpdate = new List(); + List itemsToRemove = new List(); + List slotsToRemove = new List(); + List AddItemPackets = new List(); + + //Remove as we go along + int quantityCount = quantity; + ushort lowestSlot = 0; + for (int i = endOfListIndex - 1; i >= 0; i--) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == itemId && item.quality == quality) + { + int oldQuantity = item.quantity; + //Stack nomnomed + if (item.quantity - quantityCount <= 0) + { + DoDatabaseRemove(list[i].uniqueId); + list[i] = null; + } + //Stack reduced + else + { + item.quantity -= quantityCount; + DoDatabaseQuantity(list[i].uniqueId, list[i].quantity);} + + isDirty[i] = true; + + quantityCount -= oldQuantity; + lowestSlot = item.slot; + + if (quantityCount <= 0) + break; + } + } + + DoRealign(); + SendUpdatePackets(); + } + + public void RemoveItem(InventoryItem item) + { + RemoveItemByUniqueId(item.uniqueId, item.quantity); + } + + public void RemoveItem(InventoryItem item, int quantity) + { + RemoveItemByUniqueId(item.uniqueId, quantity); + } + + public void RemoveItemByUniqueId(ulong itemDBId, int quantity) + { + ushort slot = 0; + InventoryItem toDelete = null; + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.uniqueId == itemDBId) + { + toDelete = item; + break; + } + slot++; + } + + if (toDelete == null) + return; + + if (quantity >= toDelete.quantity) + { + DoDatabaseRemove(toDelete.uniqueId); + list[slot].RefreshPositioning(null, 0xFFFF, 0xFFFF); + list[slot] = null; + } + else + { + list[slot].quantity -= quantity; + DoDatabaseQuantity(list[slot].uniqueId, list[slot].quantity); + } + + isDirty[slot] = true; + + DoRealign(); + SendUpdatePackets(); + } + + public void RemoveItemAtSlot(ushort slot) + { + if (slot >= endOfListIndex) + return; + + DoDatabaseRemove(list[slot].uniqueId); + + list[slot].RefreshPositioning(null, 0xFFFF, 0xFFFF); + list[slot] = null; + isDirty[slot] = true; + + DoRealign(); + SendUpdatePackets(); + } + + public void RemoveItemAtSlot(ushort slot, int quantity) + { + if (slot >= endOfListIndex) + return; + + if (list[slot] != null) + { + list[slot].quantity -= quantity; + + if (list[slot].quantity <= 0) + { + DoDatabaseRemove(list[slot].uniqueId); + + list[slot].RefreshPositioning(null, 0xFFFF, 0xFFFF); + list[slot] = null; + DoRealign(); + } + else + DoDatabaseQuantity(list[slot].uniqueId, list[slot].quantity); + + isDirty[slot] = true; + SendUpdatePackets(); + } + } + + public void Clear() + { + for (int i = 0; i < endOfListIndex; i++) + { + list[i].RefreshPositioning(null, 0xFFFF, 0xFFFF); + list[i] = null; + isDirty[i] = true; + } + endOfListIndex = 0; + + SendUpdatePackets(); + } + + public InventoryItem[] GetRawList() + { + return list; + } + + public void ChangeDurability(uint slot, uint durabilityChange) + { + isDirty[slot] = true; + } + + public void ChangeSpiritBind(uint slot, uint spiritBindChange) + { + isDirty[slot] = true; + } + + public void ChangeMateria(uint slot, byte materiaSlot, byte materiaId) + { + isDirty[slot] = true; + } + #endregion + + #region Packet Functions + public void SendFullInventory(Player player) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + SendInventoryPackets(player, 0); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + private void SendInventoryPackets(Player player, InventoryItem item) + { + player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, item)); + } + + private void SendInventoryPackets(Player player, List items) + { + int currentIndex = 0; + + while (true) + { + if (items.Count - currentIndex >= 64) + player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex >= 32) + player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex >= 16) + player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex > 1) + player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex == 1) + { + player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, items[currentIndex])); + currentIndex++; + } + else + break; + } + } + + private void SendInventoryPackets(Player player, int startOffset) + { + int currentIndex = startOffset; + + List lst = new List(); + for (int i = 0; i < endOfListIndex; i++) + lst.Add(list[i]); + + while (true) + { + if (endOfListIndex - currentIndex >= 64) + player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex >= 32) + player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex >= 16) + player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex > 1) + player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex == 1) + { + player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, list[currentIndex])); + currentIndex++; + } + else + break; + } + } + + private void SendInventoryRemovePackets(Player player, ushort index) + { + player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, index)); + } + + private void SendInventoryRemovePackets(Player player, List indexes) + { + int currentIndex = 0; + + while (true) + { + if (indexes.Count - currentIndex >= 64) + player.QueuePacket(InventoryRemoveX64Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex >= 32) + player.QueuePacket(InventoryRemoveX32Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex >= 16) + player.QueuePacket(InventoryRemoveX16Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex > 1) + player.QueuePacket(InventoryRemoveX08Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex == 1) + { + player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, indexes[currentIndex])); + currentIndex++; + } + else + break; + } + } + + public void RefreshItem(Player player, InventoryItem item) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + SendInventoryPackets(player, item); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + public void RefreshItem(Player player, params InventoryItem[] items) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + SendInventoryPackets(player, items.ToList()); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + public void RefreshItem(Player player, List items) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + SendInventoryPackets(player, items); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + #endregion + + #region Automatic Client and DB Updating + + private void DoDatabaseAdd(InventoryItem addedItem) + { + if (isTemporary) + return; + + if (itemPackageCode == BAZAAR) + return; + + if (owner is Player) + Database.AddItem((Player)owner, addedItem, itemPackageCode); + else if (owner is Retainer) + Database.AddItem((Retainer)owner, addedItem, itemPackageCode); + } + + private void DoDatabaseQuantity(ulong itemDBId, int quantity) + { + if (isTemporary) + return; + + + if (itemPackageCode == BAZAAR) + return; + + Database.SetQuantity(itemDBId, quantity); + } + + private void DoDatabaseRemove(ulong itemDBId) + { + if (isTemporary) + return; + + if (itemPackageCode == BAZAAR) + return; + + if (owner is Player) + Database.RemoveItem((Player)owner, itemDBId); + else if (owner is Retainer) + Database.RemoveItem((Retainer)owner, itemDBId); + } + + private void SendUpdatePackets() + { + if (owner is Player && !holdingUpdates) + { + SendUpdatePackets((Player)owner); + } + } + + public void SendUpdatePackets(Player player) + { + List items = new List(); + List slotsToRemove = new List(); + + for (int i = 0; i < list.Length; i++) + { + if (i == endOfListIndex) + break; + if (isDirty[i]) + items.Add(list[i]); + } + + for (int i = endOfListIndex; i < list.Length; i++) + { + if (isDirty[i]) + slotsToRemove.Add((ushort)i); + } + + if (!holdingUpdates) + Array.Clear(isDirty, 0, isDirty.Length); + + player.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + //Send Updated Slots + SendInventoryPackets(player, items); + //Send Remove packets for tail end + SendInventoryRemovePackets(player, slotsToRemove); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + //If player is updating their normal inventory, we need to send + //an equip update as well to resync the slots. + if (player.Equals(owner) && itemPackageCode == NORMAL) + player.GetEquipment().SendFullEquipment(); + player.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + } + + public void StartSendUpdate() + { + holdingUpdates = true; + } + + public void DoneSendUpdate() + { + holdingUpdates = false; + SendUpdatePackets(); + Array.Clear(isDirty, 0, isDirty.Length); + } + + #endregion + + #region Inventory Utils + + public bool IsFull() + { + return endOfListIndex >= itemPackageCapacity; + } + + public int GetFreeSlots() + { + return itemPackageCapacity - endOfListIndex; + } + + public bool IsSpaceForAdd(uint itemId, int quantity, int quality) + { + int quantityCount = quantity; + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + ItemData gItem = Server.GetItemGamedata(item.itemId); + if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) + { + quantityCount -= (gItem.maxStack - item.quantity); + if (quantityCount <= 0) + break; + } + } + + return quantityCount <= 0 || (quantityCount > 0 && !IsFull()); + } + + public bool HasItem(uint itemId) + { + return HasItem(itemId, 1); + } + + public bool HasItem(uint itemId, int minQuantity) + { + return HasItem(itemId, minQuantity, 1); + } + + public bool HasItem(uint itemId, int minQuantity, int quality) + { + int count = 0; + + for (int i = endOfListIndex - 1; i >= 0; i--) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == itemId && item.quality == quality) + count += item.quantity; + + if (count >= minQuantity) + return true; + } + + return false; + } + + public int GetNextEmptySlot() + { + return endOfListIndex; + } + + private void DoRealign() + { + int lastNullSlot = -1; + + for (int i = 0; i < endOfListIndex; i++) + { + if (list[i] == null && lastNullSlot == -1) + { + lastNullSlot = i; + continue; + } + else if (list[i] != null && lastNullSlot != -1) + { + list[lastNullSlot] = list[i]; + list[lastNullSlot].slot = (ushort)lastNullSlot; + list[i] = null; + isDirty[lastNullSlot] = true; + isDirty[i] = true; + lastNullSlot++; + } + } + + if (lastNullSlot != -1) + endOfListIndex = lastNullSlot; + } + + #endregion + + public int GetCount() + { + return endOfListIndex; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/player/Equipment.cs b/FFXIVClassic Map Server/actors/chara/player/Equipment.cs deleted file mode 100644 index 49181e09..00000000 --- a/FFXIVClassic Map Server/actors/chara/player/Equipment.cs +++ /dev/null @@ -1,212 +0,0 @@ -using FFXIVClassic_Map_Server.Actors; -using FFXIVClassic_Map_Server.dataobjects; -using FFXIVClassic_Map_Server.packets.send.actor.inventory; -using System.Collections.Generic; - -namespace FFXIVClassic_Map_Server.actors.chara.player -{ - class Equipment - { - public const int SLOT_MAINHAND = 0; - public const int SLOT_OFFHAND = 1; - public const int SLOT_THROWINGWEAPON = 4; - public const int SLOT_PACK = 5; - public const int SLOT_POUCH = 6; - public const int SLOT_HEAD = 8; - public const int SLOT_UNDERSHIRT = 9; - public const int SLOT_BODY = 10; - public const int SLOT_UNDERGARMENT = 11; - public const int SLOT_LEGS = 12; - public const int SLOT_HANDS = 13; - public const int SLOT_BOOTS = 14; - public const int SLOT_WAIST = 15; - public const int SLOT_NECK = 16; - public const int SLOT_EARS = 17; - public const int SLOT_WRISTS = 19; - public const int SLOT_RIGHTFINGER = 21; - public const int SLOT_LEFTFINGER = 22; - - readonly private Player owner; - readonly private ushort inventoryCapacity; - readonly private ushort inventoryCode; - private InventoryItem[] list; - readonly private ItemPackage normalInventory; - - private bool writeToDB = true; - - public Equipment(Player ownerPlayer, ItemPackage normalInventory, ushort capacity, ushort code) - { - owner = ownerPlayer; - inventoryCapacity = capacity; - inventoryCode = code; - list = new InventoryItem[inventoryCapacity]; - this.normalInventory = normalInventory; - } - - public void SetEquipment(ushort[] slots, ushort[] itemSlots) - { - if (slots.Length != itemSlots.Length) - return; - - for (int i = 0; i < slots.Length; i++) - { - InventoryItem item = normalInventory.GetItemAtSlot(itemSlots[i]); - - if (item == null) - continue; - - Database.EquipItem(owner, slots[i], item.uniqueId); - list[slots[i]] = normalInventory.GetItemAtSlot(itemSlots[i]); - } - - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - SendFullEquipment(owner); - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - } - - public void SetEquipment(InventoryItem[] toEquip) - { - List slotsToUpdate = new List(); - for (ushort i = 0; i < toEquip.Length; i++) - { - if (toEquip[i] != null) - slotsToUpdate.Add(i); - } - list = toEquip; - } - - public void Equip(ushort slot, ushort invSlot) - { - InventoryItem item = normalInventory.GetItemAtSlot(invSlot); - - if (item == null) - return; - - Equip(slot, item); - } - - public void Equip(ushort slot, InventoryItem item) - { - if (slot >= list.Length) - return; - - if (writeToDB) - Database.EquipItem(owner, slot, item.uniqueId); - - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - - if (list[slot] != null) - normalInventory.RefreshItem(owner, list[slot], item); - else - normalInventory.RefreshItem(owner, item); - - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - SendSingleEquipmentUpdatePacket(slot, item); - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - - list[slot] = item; - owner.CalculateBaseStats();// RecalculateStats(); - } - - public void ToggleDBWrite(bool flag) - { - writeToDB = flag; - } - - public void Unequip(ushort slot) - { - if (slot >= list.Length) - return; - - if (writeToDB) - Database.UnequipItem(owner, slot); - - - normalInventory.RefreshItem(owner, list[slot]); - - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - SendSingleEquipmentUpdatePacket(slot, null); - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - - list[slot] = null; - owner.RecalculateStats(); - } - - public InventoryItem GetItemAtSlot(ushort slot) - { - if (slot < list.Length) - return list[slot]; - else - return null; - } - - public int GetCapacity() - { - return list.Length; - } - - #region Packet Functions - private void SendSingleEquipmentUpdatePacket(ushort equipSlot, InventoryItem item) - { - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - if (item == null) - owner.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, equipSlot)); - else - owner.QueuePacket(EquipmentListX01Packet.BuildPacket(owner.actorId, equipSlot, item.slot)); - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - private void SendEquipmentPackets(List slotsToUpdate, Player targetPlayer) - { - int currentIndex = 0; - - while (true) - { - if (slotsToUpdate.Count - currentIndex >= 64) - targetPlayer.QueuePacket(EquipmentListX64Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); - else if (slotsToUpdate.Count - currentIndex >= 32) - targetPlayer.QueuePacket(EquipmentListX32Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); - else if (slotsToUpdate.Count - currentIndex >= 16) - targetPlayer.QueuePacket(EquipmentListX16Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); - else if (slotsToUpdate.Count - currentIndex > 1) - targetPlayer.QueuePacket(EquipmentListX08Packet.BuildPacket(owner.actorId, list, slotsToUpdate, ref currentIndex)); - else if (slotsToUpdate.Count - currentIndex == 1) - { - targetPlayer.QueuePacket(EquipmentListX01Packet.BuildPacket(owner.actorId, slotsToUpdate[currentIndex], list[slotsToUpdate[currentIndex]].slot)); - currentIndex++; - } - else - break; - } - } - - public void SendFullEquipment() - { - SendFullEquipment(owner); - } - - public void SendFullEquipment(Player targetPlayer) - { - List slotsToUpdate = new List(); - - for (ushort i = 0; i < list.Length; i++) - { - if (list[i] != null) - slotsToUpdate.Add(i); - } - - if (targetPlayer.Equals(owner)) - targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - else - targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, 0x23, ItemPackage.EQUIPMENT_OTHERPLAYER)); - - SendEquipmentPackets(slotsToUpdate, targetPlayer); - - targetPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - #endregion - } -} diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index 0d7f9606..00b85cdf 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -150,7 +150,7 @@ namespace FFXIVClassic_Map_Server.Actors itemPackages[ItemPackage.BAZAAR] = new ItemPackage(this, MAXSIZE_INVENTORY_BAZAAR, ItemPackage.BAZAAR); itemPackages[ItemPackage.LOOT] = new ItemPackage(this, MAXSIZE_INVENTORY_LOOT, ItemPackage.LOOT); - equipment = new Equipment(this, itemPackages[ItemPackage.NORMAL], MAXSIZE_INVENTORY_EQUIPMENT, ItemPackage.EQUIPMENT); + equipment = new Equipment(this, itemPackages[ItemPackage.NORMAL]); //Set the Skill level caps of all FFXIV (classes)skills to 50 for (int i = 0; i < charaWork.battleSave.skillLevelCap.Length; i++)