From 42ee97d467db8c3e53841108cd325fde9c64d013 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 2 Jun 2019 16:57:46 -0400 Subject: [PATCH] Refactored inventory to "ReferencedItemPackage" as this is a more general approach closer to what the FFXIV client uses. Added itempackage code to the LinkedItemList packets (also renamed from EquipmentList). Cleaned up trade code. --- FFXIVClassic Map Server/Database.cs | 13 +- .../FFXIVClassic Map Server.csproj | 12 +- .../actors/chara/Character.cs | 4 +- .../actors/chara/Equipment.cs | 271 --------------- .../actors/chara/ItemPackage.cs | 168 +++++----- .../actors/chara/ReferencedItemPackage.cs | 312 ++++++++++++++++++ .../actors/chara/player/Player.cs | 211 ++++++------ .../dataobjects/InventoryItem.cs | 101 +++--- .../Actor/inventory/EquipmentListX01Packet.cs | 29 -- .../inventory/LinkedItemListX01Packet.cs | 46 +++ ...08Packet.cs => LinkedItemListX08Packet.cs} | 8 +- ...16Packet.cs => LinkedItemListX16Packet.cs} | 8 +- ...32Packet.cs => LinkedItemListX32Packet.cs} | 8 +- ...64Packet.cs => LinkedItemListX64Packet.cs} | 8 +- data/scripts/commands/EquipCommand.lua | 22 +- data/scripts/commands/TradeExecuteCommand.lua | 16 +- 16 files changed, 646 insertions(+), 591 deletions(-) delete mode 100644 FFXIVClassic Map Server/actors/chara/Equipment.cs create mode 100644 FFXIVClassic Map Server/actors/chara/ReferencedItemPackage.cs delete mode 100644 FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs create mode 100644 FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX01Packet.cs rename FFXIVClassic Map Server/packets/send/Actor/inventory/{EquipmentListX08Packet.cs => LinkedItemListX08Packet.cs} (82%) rename FFXIVClassic Map Server/packets/send/Actor/inventory/{EquipmentListX16Packet.cs => LinkedItemListX16Packet.cs} (81%) rename FFXIVClassic Map Server/packets/send/Actor/inventory/{EquipmentListX32Packet.cs => LinkedItemListX32Packet.cs} (81%) rename FFXIVClassic Map Server/packets/send/Actor/inventory/{EquipmentListX64Packet.cs => LinkedItemListX64Packet.cs} (81%) diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index 872c6e74..96877f0e 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -1117,7 +1117,7 @@ namespace FFXIVClassic_Map_Server player.GetItemPackage(ItemPackage.MELDREQUEST).InitList(GetItemPackage(player, 0, ItemPackage.MELDREQUEST)); player.GetItemPackage(ItemPackage.LOOT).InitList(GetItemPackage(player, 0, ItemPackage.LOOT)); - player.GetEquipment().SetEquipment(GetEquipment(player, player.charaWork.parameterSave.state_mainSkill[0])); + player.GetEquipment().SetList(GetEquipment(player, player.charaWork.parameterSave.state_mainSkill[0])); } catch (MySqlException e) { @@ -1131,9 +1131,12 @@ namespace FFXIVClassic_Map_Server } - public static InventoryItem[] GetEquipment(Player player, ushort classId) + public static uint[] GetEquipment(Player player, ushort classId) { - InventoryItem[] equipment = new InventoryItem[player.GetEquipment().GetCapacity()]; + uint[] equipment = new uint[player.GetEquipment().GetCapacity()]; + + for (int i = 0; i < equipment.Length; i++) + equipment[i] = 0xFFFFFFFF; using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) { @@ -1159,7 +1162,7 @@ namespace FFXIVClassic_Map_Server ushort equipSlot = reader.GetUInt16(0); ulong uniqueItemId = reader.GetUInt16(1); InventoryItem item = player.GetItemPackage(ItemPackage.NORMAL).GetItemByUniqueId(uniqueItemId); - equipment[equipSlot] = item; + equipment[equipSlot] = (uint)((item.itemPackage << 16) | item.slot); } } } @@ -1195,7 +1198,7 @@ namespace FFXIVClassic_Map_Server MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@characterId", player.actorId); - cmd.Parameters.AddWithValue("@classId", (equipSlot == Equipment.SLOT_UNDERSHIRT || equipSlot == Equipment.SLOT_UNDERGARMENT) ? 0 : player.charaWork.parameterSave.state_mainSkill[0]); + cmd.Parameters.AddWithValue("@classId", (equipSlot == Player.SLOT_UNDERSHIRT || equipSlot == Player.SLOT_UNDERGARMENT) ? 0 : player.charaWork.parameterSave.state_mainSkill[0]); cmd.Parameters.AddWithValue("@equipSlot", equipSlot); cmd.Parameters.AddWithValue("@uniqueItemId", uniqueItemId); diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index 00b528b2..5105d83c 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -139,8 +139,8 @@ - + @@ -240,9 +240,6 @@ - - - @@ -254,14 +251,17 @@ - + + + + + - diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs index 94e0ca78..3246848a 100644 --- a/FFXIVClassic Map Server/actors/chara/Character.cs +++ b/FFXIVClassic Map Server/actors/chara/Character.cs @@ -135,7 +135,7 @@ namespace FFXIVClassic_Map_Server.Actors //Inventory protected Dictionary itemPackages = new Dictionary(); - protected Equipment equipment; + protected ReferencedItemPackage equipment; public Character(uint actorID) : base(actorID) @@ -1147,7 +1147,7 @@ namespace FFXIVClassic_Map_Server.Actors return; player.QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId, true)); - itemPackages[(ushort)id].SendFullInventory(player); + itemPackages[(ushort)id].SendUpdate(player); player.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); } diff --git a/FFXIVClassic Map Server/actors/chara/Equipment.cs b/FFXIVClassic Map Server/actors/chara/Equipment.cs deleted file mode 100644 index fae856ef..00000000 --- a/FFXIVClassic Map Server/actors/chara/Equipment.cs +++ /dev/null @@ -1,271 +0,0 @@ -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/ItemPackage.cs b/FFXIVClassic Map Server/actors/chara/ItemPackage.cs index dd543dc6..ed964671 100644 --- a/FFXIVClassic Map Server/actors/chara/ItemPackage.cs +++ b/FFXIVClassic Map Server/actors/chara/ItemPackage.cs @@ -6,7 +6,6 @@ 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 { @@ -22,7 +21,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.player public const ushort EQUIPMENT = 0x00FE; //Max 0x23 public const ushort TRADE = 0x00FD; //Max 0x04 public const ushort EQUIPMENT_OTHERPLAYER = 0x00F9; //Max 0x23 - + + public const ushort MAXSIZE_NORMAL = 200; + public const ushort MAXSIZE_CURRANCY = 320; + public const ushort MAXSIZE_KEYITEMS = 500; + public const ushort MAXSIZE_LOOT = 10; + public const ushort MAXSIZE_TRADE = 4; + public const ushort MAXSIZE_MELDREQUEST = 4; + public const ushort MAXSIZE_BAZAAR = 10; + public const ushort MAXSIZE_EQUIPMENT = 35; + public const ushort MAXSIZE_EQUIPMENT_OTHERPLAYER = 0x23; + public enum INV_ERROR { SUCCESS = 0, INVENTORY_FULL, @@ -170,7 +179,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player list[endOfListIndex++] = itemRef; DoDatabaseAdd(itemRef); - SendUpdatePackets(); + SendUpdate(); return INV_ERROR.SUCCESS; } @@ -233,7 +242,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player DoDatabaseAdd(addedItem); } - SendUpdatePackets(); + SendUpdate(); return INV_ERROR.SUCCESS; } @@ -241,7 +250,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player public void SetItem(ushort slot, InventoryItem item) { list[slot] = item; - SendUpdatePackets(); + SendUpdate(); item.RefreshPositioning(owner, itemPackageCode, slot); } @@ -300,7 +309,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player } DoRealign(); - SendUpdatePackets(); + SendUpdate(); } public void RemoveItem(InventoryItem item) @@ -349,7 +358,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player isDirty[slot] = true; DoRealign(); - SendUpdatePackets(); + SendUpdate(); } public void RemoveItemAtSlot(ushort slot) @@ -364,7 +373,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player isDirty[slot] = true; DoRealign(); - SendUpdatePackets(); + SendUpdate(); } public void RemoveItemAtSlot(ushort slot, int quantity) @@ -388,7 +397,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player DoDatabaseQuantity(list[slot].uniqueId, list[slot].quantity); isDirty[slot] = true; - SendUpdatePackets(); + SendUpdate(); } } @@ -402,7 +411,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.player } endOfListIndex = 0; - SendUpdatePackets(); + SendUpdate(); + } + + public void MarkDirty(InventoryItem item) + { + if (item.itemPackage != itemPackageCode || list[item.slot] == null) + return; + + isDirty[item.slot] = true; + } + + public void MarkDirty(ushort slot) + { + isDirty[slot] = true; } public InventoryItem[] GetRawList() @@ -427,19 +449,61 @@ namespace FFXIVClassic_Map_Server.actors.chara.player #endregion #region Packet Functions - public void SendFullInventory(Player player) + public void SendFullPackage(Player player) { player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); - SendInventoryPackets(player, 0); + SendItemPackets(player, 0); player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); } - private void SendInventoryPackets(Player player, InventoryItem item) + public void SendUpdate() + { + if (owner is Player && !holdingUpdates) + { + SendUpdate((Player)owner); + } + } + + public void SendUpdate(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(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + //Send Updated Slots + SendItemPackets(player, items); + //Send Remove packets for tail end + SendItemPackets(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().SendUpdate(); + } + + private void SendItemPackets(Player player, InventoryItem item) { player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, item)); } - private void SendInventoryPackets(Player player, List items) + private void SendItemPackets(Player player, List items) { int currentIndex = 0; @@ -463,7 +527,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player } } - private void SendInventoryPackets(Player player, int startOffset) + private void SendItemPackets(Player player, int startOffset) { int currentIndex = startOffset; @@ -491,12 +555,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.player } } - private void SendInventoryRemovePackets(Player player, ushort index) + private void SendItemPackets(Player player, ushort index) { player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, index)); } - private void SendInventoryRemovePackets(Player player, List indexes) + private void SendItemPackets(Player player, List indexes) { int currentIndex = 0; @@ -519,28 +583,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.player 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 @@ -584,51 +626,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player 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; @@ -637,7 +635,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player public void DoneSendUpdate() { holdingUpdates = false; - SendUpdatePackets(); + SendUpdate(); Array.Clear(isDirty, 0, isDirty.Length); } diff --git a/FFXIVClassic Map Server/actors/chara/ReferencedItemPackage.cs b/FFXIVClassic Map Server/actors/chara/ReferencedItemPackage.cs new file mode 100644 index 00000000..2a8e492b --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ReferencedItemPackage.cs @@ -0,0 +1,312 @@ +using FFXIVClassic_Map_Server.actors.chara.player; +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 +{ + + class ReferencedItemPackage + { + const uint EMPTY = 0xFFFFFFFF; + + private readonly Player owner; + private readonly uint[] contentList; + private readonly ushort itemPackageCode; + private readonly ushort itemPackageCapacity; + private bool writeToDB = false; + + public ReferencedItemPackage(Player owner, ushort capacity, ushort code) + { + this.owner = owner; + itemPackageCode = code; + itemPackageCapacity = capacity; + contentList = new uint[capacity]; + + if (code == ItemPackage.EQUIPMENT) + writeToDB = true; + + for (int i = 0; i < contentList.Length; i++) + contentList[i] = EMPTY; + } + + public void ToggleDBWrite(bool flag) + { + writeToDB = flag; + } + + #region Package Management + public void SetList(uint[] toSet) + { + Debug.Assert(contentList.Length == itemPackageCapacity); + toSet.CopyTo(contentList, 0); + } + + public void SetList(ushort[] positions, uint[] values) + { + Debug.Assert(positions.Length == values.Length); + + for (int i = 0; i < positions.Length; i++) + { + InventoryItem item = GetItem(values[i]); + + if (item == null) + continue; + + //Database.EquipItem(owner, positions[i], item.uniqueId); + contentList[positions[i]] = values[i]; + } + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + SendUpdate(); + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + } + + public void Set(ushort position, ushort itemPackagePosition, ushort itemPackageCode) + { + InventoryItem item = owner.GetItemPackage(itemPackageCode).GetItemAtSlot(itemPackagePosition); + + if (item == null) + return; + + Set(position, item); + } + + public void Set(ushort position, InventoryItem item) + { + if (position >= contentList.Length) + return; + + if (writeToDB) + Database.EquipItem(owner, position, item.uniqueId); + + ItemPackage newPackage = owner.GetItemPackage(item.itemPackage); + ItemPackage oldPackage = GetItemPackage(contentList[position]); + InventoryItem oldItem = GetItem(contentList[position]); + + if (oldPackage != null && oldItem != null) + oldPackage.MarkDirty(oldItem); + newPackage.MarkDirty(item); + + contentList[position] = GetValue(item.itemPackage, item.slot); + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + if (oldPackage != null) + oldPackage.SendUpdate(); + newPackage.SendUpdate(); + SendSingleUpdate(position); + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + + owner.CalculateBaseStats();// RecalculateStats(); + } + + public void Clear(ushort position) + { + if (position >= contentList.Length) + return; + + if (writeToDB) + Database.UnequipItem(owner, position); + + ItemPackage oldItemPackage = GetItemPackage(contentList[position]); + + oldItemPackage.MarkDirty(GetItem(contentList[position])); + contentList[position] = EMPTY; + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + oldItemPackage.SendUpdate(); + SendSingleUpdate(position); + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + + owner.RecalculateStats(); + } + + public void ClearAll() + { + List packagesToRefresh = new List(); + + for (int i = 0; i < contentList.Length; i++) + { + if (contentList[i] == EMPTY) + continue; + + if (writeToDB) + Database.UnequipItem(owner, (ushort)i); + + ItemPackage package = GetItemPackage(contentList[i]); + package.MarkDirty(GetItem(contentList[i])); + packagesToRefresh.Add(package); + + contentList[i] = EMPTY; + } + + owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + for (int i = 0; i < packagesToRefresh.Count; i++) + packagesToRefresh[i].SendUpdate(); + SendUpdate(); + owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + + owner.RecalculateStats(); + } + #endregion + + #region Send Update Functions + public void SendSingleUpdate(ushort position) + { + owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + SendSingleLinkedItemPacket(owner, position); + owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + public void SendUpdate() + { + SendUpdate(owner); + } + + public void SendUpdate(Player targetPlayer) + { + List slotsToUpdate = new List(); + + for (ushort i = 0; i < contentList.Length; i++) + { + if (contentList[i] != EMPTY) + slotsToUpdate.Add(i); + } + + targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, itemPackageCapacity, itemPackageCode)); + SendLinkedItemPackets(targetPlayer, slotsToUpdate); + targetPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + public void SendUpdateAsItemPackage(Player targetPlayer) + { + SendUpdateAsItemPackage(targetPlayer, itemPackageCapacity, itemPackageCode); + } + + public void SendUpdateAsItemPackage(Player targetPlayer, ushort destinationCapacity, ushort destinationCode) + { + List items = new List(); + + for (int i = 0; i < contentList.Length; i++) + { + if (contentList[i] == EMPTY) + continue; + items.Add(GetItem(contentList[i])); + } + + targetPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, destinationCapacity, destinationCode)); + SendItemPackets(targetPlayer, items); + targetPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + #endregion + + #region Packet Functions (Private) + private void SendSingleLinkedItemPacket(Player targetPlayer, ushort position) + { + if (contentList[position] == EMPTY) + targetPlayer.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, position)); + else + targetPlayer.QueuePacket(LinkedItemListX01Packet.BuildPacket(owner.actorId, position, contentList[position])); + } + + private void SendLinkedItemPackets(Player targetPlayer, List slotsToUpdate) + { + int currentIndex = 0; + + while (true) + { + if (slotsToUpdate.Count - currentIndex >= 64) + targetPlayer.QueuePacket(LinkedItemListX64Packet.BuildPacket(owner.actorId, contentList, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex >= 32) + targetPlayer.QueuePacket(LinkedItemListX32Packet.BuildPacket(owner.actorId, contentList, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex >= 16) + targetPlayer.QueuePacket(LinkedItemListX16Packet.BuildPacket(owner.actorId, contentList, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex > 1) + targetPlayer.QueuePacket(LinkedItemListX08Packet.BuildPacket(owner.actorId, contentList, slotsToUpdate, ref currentIndex)); + else if (slotsToUpdate.Count - currentIndex == 1) + { + targetPlayer.QueuePacket(LinkedItemListX01Packet.BuildPacket(owner.actorId, slotsToUpdate[currentIndex], contentList[slotsToUpdate[currentIndex]])); + currentIndex++; + } + else + break; + } + } + + private void SendItemPackets(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; + } + } + #endregion + + #region Getters/Setters + public ushort GetCode() + { + return itemPackageCode; + } + + public int GetCapacity() + { + return itemPackageCapacity; + } + + public Player GetOwner() + { + return owner; + } + + public InventoryItem GetItemAtSlot(ushort position) + { + if (position < contentList.Length) + return GetItem(contentList[position]); + else + return null; + } + #endregion + + #region Utils + private ItemPackage GetItemPackage(uint value) + { + if (value == EMPTY) + return null; + return owner.GetItemPackage((ushort)((value >> 16) & 0xFFFF)); + } + + private InventoryItem GetItem(uint value) + { + if (value == EMPTY) + return null; + ItemPackage package = GetItemPackage(value); + if (package != null) + return package.GetItemAtSlot((ushort)(value & 0xFFFF)); + return null; + } + + private uint GetValue(ushort code, ushort slot) + { + return (uint) ((code << 16) | slot); + } + #endregion + } +} diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index 00b85cdf..8c213784 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -23,19 +23,12 @@ using FFXIVClassic_Map_Server.packets.send.actor.inventory; using FFXIVClassic_Map_Server.packets.send.player; using FFXIVClassic_Map_Server.packets.send.actor.battle; using FFXIVClassic_Map_Server.packets.receive.events; +using static FFXIVClassic_Map_Server.LuaUtils; namespace FFXIVClassic_Map_Server.Actors { class Player : Character { - public const int MAXSIZE_INVENTORY_NORMAL = 200; - public const int MAXSIZE_INVENTORY_CURRANCY = 320; - public const int MAXSIZE_INVENTORY_KEYITEMS = 500; - public const int MAXSIZE_INVENTORY_LOOT = 10; - public const int MAXSIZE_INVENTORY_MELDREQUEST = 4; - public const int MAXSIZE_INVENTORY_BAZAAR = 10; - public const int MAXSIZE_INVENTORY_EQUIPMENT = 35; - public const int TIMER_TOTORAK = 0; public const int TIMER_DZEMAEL = 1; public const int TIMER_BOWL_OF_EMBERS_HARD = 2; @@ -62,6 +55,25 @@ namespace FFXIVClassic_Map_Server.Actors public const int NPCLS_ACTIVE = 2; public const int NPCLS_ALERT = 3; + 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; + public static int[] MAXEXP = {570, 700, 880, 1100, 1500, 1800, 2300, 3200, 4300, 5000, //Level <= 10 5900, 6800, 7700, 8700, 9700, 11000, 12000, 13000, 15000, 16000, //Level <= 20 20000, 22000, 23000, 25000, 27000, 29000, 31000, 33000, 35000, 38000, //Level <= 30 @@ -85,9 +97,8 @@ namespace FFXIVClassic_Map_Server.Actors //Trading private Player otherTrader = null; - private ItemPackage myOfferings; + private ReferencedItemPackage myOfferings; private bool isTradeAccepted = false; - private bool isTradeLocked = false; //GC Related public byte gcCurrent; @@ -143,14 +154,13 @@ namespace FFXIVClassic_Map_Server.Actors moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN; moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE; - itemPackages[ItemPackage.NORMAL] = new ItemPackage(this, MAXSIZE_INVENTORY_NORMAL, ItemPackage.NORMAL); - itemPackages[ItemPackage.KEYITEMS] = new ItemPackage(this, MAXSIZE_INVENTORY_KEYITEMS, ItemPackage.KEYITEMS); - itemPackages[ItemPackage.CURRENCY_CRYSTALS] = new ItemPackage(this, MAXSIZE_INVENTORY_CURRANCY, ItemPackage.CURRENCY_CRYSTALS); - itemPackages[ItemPackage.MELDREQUEST] = new ItemPackage(this, MAXSIZE_INVENTORY_MELDREQUEST, ItemPackage.MELDREQUEST); - 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]); + itemPackages[ItemPackage.NORMAL] = new ItemPackage(this, ItemPackage.MAXSIZE_NORMAL, ItemPackage.NORMAL); + itemPackages[ItemPackage.KEYITEMS] = new ItemPackage(this, ItemPackage.MAXSIZE_KEYITEMS, ItemPackage.KEYITEMS); + itemPackages[ItemPackage.CURRENCY_CRYSTALS] = new ItemPackage(this, ItemPackage.MAXSIZE_CURRANCY, ItemPackage.CURRENCY_CRYSTALS); + itemPackages[ItemPackage.MELDREQUEST] = new ItemPackage(this, ItemPackage.MAXSIZE_MELDREQUEST, ItemPackage.MELDREQUEST); + itemPackages[ItemPackage.BAZAAR] = new ItemPackage(this, ItemPackage.MAXSIZE_BAZAAR, ItemPackage.BAZAAR); + itemPackages[ItemPackage.LOOT] = new ItemPackage(this, ItemPackage.MAXSIZE_LOOT, ItemPackage.LOOT); + equipment = new ReferencedItemPackage(this, ItemPackage.MAXSIZE_EQUIPMENT, ItemPackage.EQUIPMENT); //Set the Skill level caps of all FFXIV (classes)skills to 50 for (int i = 0; i < charaWork.battleSave.skillLevelCap.Length; i++) @@ -531,13 +541,13 @@ namespace FFXIVClassic_Map_Server.Actors #region Inventory & Equipment QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId, true)); - itemPackages[ItemPackage.NORMAL].SendFullInventory(this); - itemPackages[ItemPackage.CURRENCY_CRYSTALS].SendFullInventory(this); - itemPackages[ItemPackage.KEYITEMS].SendFullInventory(this); - itemPackages[ItemPackage.BAZAAR].SendFullInventory(this); - itemPackages[ItemPackage.MELDREQUEST].SendFullInventory(this); - itemPackages[ItemPackage.LOOT].SendFullInventory(this); - equipment.SendFullEquipment(); + itemPackages[ItemPackage.NORMAL].SendFullPackage(this); + itemPackages[ItemPackage.CURRENCY_CRYSTALS].SendFullPackage(this); + itemPackages[ItemPackage.KEYITEMS].SendFullPackage(this); + itemPackages[ItemPackage.BAZAAR].SendFullPackage(this); + itemPackages[ItemPackage.MELDREQUEST].SendFullPackage(this); + itemPackages[ItemPackage.LOOT].SendFullPackage(this); + equipment.SendUpdate(this); playerSession.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); #endregion @@ -972,7 +982,7 @@ namespace FFXIVClassic_Map_Server.Actors return max; } - public InventoryItem[] GetGearset(ushort classId) + public uint[] GetGearset(ushort classId) { return Database.GetEquipment(this, classId); } @@ -1190,7 +1200,7 @@ namespace FFXIVClassic_Map_Server.Actors return isZoneChanging; } - public Equipment GetEquipment() + public ReferencedItemPackage GetEquipment() { return equipment; } @@ -1674,18 +1684,10 @@ namespace FFXIVClassic_Map_Server.Actors else return; - QueuePacket(InventoryBeginChangePacket.BuildPacket(toBeExamined.actorId)); - toBeExamined.GetEquipment().SendFullEquipment(this); + QueuePacket(InventoryBeginChangePacket.BuildPacket(toBeExamined.actorId, true)); + toBeExamined.GetEquipment().SendUpdateAsItemPackage(this, ItemPackage.MAXSIZE_EQUIPMENT_OTHERPLAYER, ItemPackage.EQUIPMENT_OTHERPLAYER); QueuePacket(InventoryEndChangePacket.BuildPacket(toBeExamined.actorId)); - } - - public void SendMyTradeToPlayer(Player player) - { - ItemPackage tradeInventory = new ItemPackage(this, 4, ItemPackage.TRADE); - player.QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId, true)); - tradeInventory.SendFullInventory(player); - player.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); - } + } public void SendDataPacket(params object[] parameters) { @@ -2618,7 +2620,7 @@ namespace FFXIVClassic_Map_Server.Actors base.CalculateBaseStats(); //Add weapon property mod var equip = GetEquipment(); - var mainHandItem = equip.GetItemAtSlot(Equipment.SLOT_MAINHAND); + var mainHandItem = equip.GetItemAtSlot(SLOT_MAINHAND); var damageAttribute = 0; var attackDelay = 3000; var hitCount = 1; @@ -2631,7 +2633,7 @@ namespace FFXIVClassic_Map_Server.Actors hitCount = mainHandWeapon.frequency; } - var hasShield = equip.GetItemAtSlot(Equipment.SLOT_OFFHAND) != null ? 1 : 0; + var hasShield = equip.GetItemAtSlot(SLOT_OFFHAND) != null ? 1 : 0; SetMod((uint)Modifier.HasShield, hasShield); SetMod((uint)Modifier.AttackType, damageAttribute); @@ -2699,14 +2701,7 @@ namespace FFXIVClassic_Map_Server.Actors public void StartTradeTransaction(Player otherPlayer) { - myOfferings = new ItemPackage(this, 4, ItemPackage.TRADE, true); - ItemPackage otherPlayerOfferings = new ItemPackage(otherPlayer, 4, ItemPackage.TRADE, true); - - myOfferings.StartSendUpdate(); - myOfferings.SendUpdatePackets(this); - myOfferings.SendUpdatePackets(otherPlayer); - myOfferings.DoneSendUpdate(); - + myOfferings = new ReferencedItemPackage(this, ItemPackage.MAXSIZE_TRADE, ItemPackage.TRADE); otherTrader = otherPlayer; isTradeAccepted = false; } @@ -2716,7 +2711,7 @@ namespace FFXIVClassic_Map_Server.Actors return otherTrader; } - public ItemPackage GetTradeOfferings() + public ReferencedItemPackage GetTradeOfferings() { return myOfferings; } @@ -2730,53 +2725,59 @@ namespace FFXIVClassic_Map_Server.Actors { return isTradeAccepted; } - - public void AddTradeItem(ushort slot, ushort linkedSlot, int subquantity) - { - if (!IsTrading()) - return; - - InventoryItem mine = itemPackages[ItemPackage.NORMAL].GetItemAtSlot(linkedSlot); - - InventoryItem tradeItem = new InventoryItem(mine, slot); - - myOfferings.StartSendUpdate(); - myOfferings.AddItem(mine.itemId, mine.quantity, mine.quality); - myOfferings.SendUpdatePackets(otherTrader); - myOfferings.DoneSendUpdate(); - } - - public void AddTradeGil(int quantity) + + public void AddTradeItem(ushort slot, ItemRefParam chosenItem, int tradeQuantity) { if (!IsTrading()) return; - myOfferings.StartSendUpdate(); - myOfferings.AddItem(1000001, quantity, 1); - myOfferings.SendUpdatePackets(otherTrader); - myOfferings.DoneSendUpdate(); + //Get chosen item + InventoryItem offeredItem = itemPackages[chosenItem.itemPackage].GetItemAtSlot(chosenItem.slot); + offeredItem.SetTradeQuantity(tradeQuantity); + + myOfferings.Set(slot, offeredItem); + SendTradePackets(); } - + public void RemoveTradeItem(ushort slot) - { - if (!IsTrading()) - return; - - myOfferings.StartSendUpdate(); - myOfferings.RemoveItemAtSlot(slot); - myOfferings.SendUpdatePackets(otherTrader); - myOfferings.DoneSendUpdate(); - } - - public void ClearTradeItems(ushort slot) { if (!IsTrading()) return; - myOfferings.StartSendUpdate(); - myOfferings.Clear(); - myOfferings.SendUpdatePackets(otherTrader); - myOfferings.DoneSendUpdate(); + InventoryItem offeredItem = myOfferings.GetItemAtSlot(slot); + offeredItem.SetNormal(); + + myOfferings.Clear(slot); + SendTradePackets(); + } + + public void ClearTradeItems() + { + if (!IsTrading()) + return; + + for (ushort i = 0; i < myOfferings.GetCapacity(); i++) + { + InventoryItem offeredItem = myOfferings.GetItemAtSlot(i); + if (offeredItem != null) + offeredItem.SetNormal(); + } + + myOfferings.ClearAll(); + SendTradePackets(); + } + + private void SendTradePackets() + { + //Send to self + QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId, true)); + myOfferings.SendUpdate(this); + QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); + + //Send to other trader + otherTrader.QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId, true)); + myOfferings.SendUpdateAsItemPackage(otherTrader); + otherTrader.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); } public void AcceptTrade(bool accepted) @@ -2788,31 +2789,25 @@ namespace FFXIVClassic_Map_Server.Actors public void FinishTradeTransaction() { + if (myOfferings != null) + { + myOfferings.ClearAll(); + for (ushort i = 0; i < myOfferings.GetCapacity(); i++) + { + InventoryItem offeredItem = myOfferings.GetItemAtSlot(i); + if (offeredItem != null) + offeredItem.SetNormal(); + } + + QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId, true)); + myOfferings.SendUpdate(this); + QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); + } + isTradeAccepted = false; myOfferings = null; otherTrader = null; } - - public void Test() - { - QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId)); - QueuePacket(InventorySetBeginPacket.BuildPacket(actorId, 4, ItemPackage.TRADE)); - - QueuePacket(InventoryRemoveX01Packet.BuildPacket(actorId, 1)); - - QueuePacket(InventorySetEndPacket.BuildPacket(actorId)); - QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); - } - public void Test2() - { - QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId)); - QueuePacket(InventorySetBeginPacket.BuildPacket(actorId, 4, ItemPackage.TRADE)); - - QueuePacket(EquipmentListX01Packet.BuildPacket(actorId, 1, 1)); - - QueuePacket(InventorySetEndPacket.BuildPacket(actorId)); - QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); - } - + } } diff --git a/FFXIVClassic Map Server/dataobjects/InventoryItem.cs b/FFXIVClassic Map Server/dataobjects/InventoryItem.cs index 284ecb57..a299c016 100644 --- a/FFXIVClassic Map Server/dataobjects/InventoryItem.cs +++ b/FFXIVClassic Map Server/dataobjects/InventoryItem.cs @@ -1,5 +1,4 @@ using FFXIVClassic_Map_Server.Actors; -using FFXIVClassic_Map_Server.packets.send.actor.inventory; using System; using System.IO; @@ -27,9 +26,9 @@ namespace FFXIVClassic_Map_Server.dataobjects public byte dealingVal = 0; public byte dealingMode = DEALINGMODE_NONE; - public uint dealingAttached1 = 0; - public uint dealingAttached2 = 0; - public uint dealingAttached3 = 0; + public int dealingAttached1 = 0; + public int dealingAttached2 = 0; + public int dealingAttached3 = 0; public byte[] tags = new byte[4]; public byte[] tagValues = new byte[4]; @@ -131,25 +130,21 @@ namespace FFXIVClassic_Map_Server.dataobjects this.itemData = data; this.quantity = 1; - ItemData gItem = Server.GetItemGamedata(itemId); - tags[1] = gItem.isExclusive ? (byte)0x3 : (byte)0x0; + tags[1] = itemData.isExclusive ? TAG_EXCLUSIVE : (byte)0; } - //For check command - public InventoryItem(InventoryItem item, ushort equipSlot) + //For check and trade commands + public InventoryItem(InventoryItem item, ushort slot) { this.uniqueId = item.uniqueId; this.itemData = item.itemData; this.itemId = item.itemId; this.quantity = item.quantity; - this.slot = equipSlot; - + this.slot = slot; this.tags = item.tags; this.tagValues = item.tagValues; - this.quality = item.quality; - - this.modifiers = item.modifiers; + this.modifiers = item.modifiers; } public InventoryItem(uint uniqueId, ItemData itemData, int quantity, byte qualityNumber, ItemModifier modifiers = null) @@ -160,6 +155,8 @@ namespace FFXIVClassic_Map_Server.dataobjects this.quantity = quantity; this.quality = qualityNumber; this.modifiers = modifiers; + + tags[1] = itemData.isExclusive ? TAG_EXCLUSIVE : (byte)0; } public byte[] ToPacketBytes() @@ -209,8 +206,8 @@ namespace FFXIVClassic_Map_Server.dataobjects quantity = 0; Database.SetQuantity(uniqueId, this.quantity); - if (owner != null && owner is Player) - owner.GetItemPackage(itemPackage).RefreshItem((Player)owner, this); + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); } } @@ -230,13 +227,8 @@ namespace FFXIVClassic_Map_Server.dataobjects Database.SetQuantity(uniqueId, this.quantity); - if (owner != null && owner is Player) - { - - ((Player)owner).QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId, false)); - owner.GetItemPackage(itemPackage).RefreshItem((Player)owner, this); - ((Player)owner).QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - } + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); } } @@ -245,12 +237,7 @@ namespace FFXIVClassic_Map_Server.dataobjects this.owner = owner; this.itemPackage = itemPackage; this.slot = slot; - } - - public void SetExclusive(bool isExclusive) - { - tags[1] = isExclusive ? TAG_EXCLUSIVE : (byte)0; - } + } public void SetHasAttached(bool isAttached) { @@ -258,21 +245,18 @@ namespace FFXIVClassic_Map_Server.dataobjects } public void SetNormal() - { - for (int i = 0; i < 4; i++) - { - if (tags[i] == TAG_DEALING || tags[i] == TAG_ATTACHED) - { - tags[i] = 0; - tagValues[i] = 0; - attachedTo = 0; - dealingVal = 0; - dealingMode = 0; - dealingAttached1 = 0; - dealingAttached2 = 0; - dealingAttached3 = 0; - } - } + { + tags[0] = 0; + tagValues[0] = 0; + attachedTo = 0; + dealingVal = 0; + dealingMode = 0; + dealingAttached1 = 0; + dealingAttached2 = 0; + dealingAttached3 = 0; + + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); } public void SetDealing(byte mode, int price) @@ -285,9 +269,12 @@ namespace FFXIVClassic_Map_Server.dataobjects dealingVal = 1; dealingMode = DEALINGMODE_PRICED; dealingAttached1 = 1; - dealingAttached2 = (uint) price; + dealingAttached2 = price; dealingAttached3 = 0; } + + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); } public void SetDealingAttached(byte mode, ulong attached) @@ -295,6 +282,9 @@ namespace FFXIVClassic_Map_Server.dataobjects tags[0] = TAG_DEALING; tagValues[0] = mode; attachedTo = attached; + + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); } public ulong GetAttached() @@ -306,9 +296,20 @@ namespace FFXIVClassic_Map_Server.dataobjects { dealingVal = 1; dealingMode = DEALINGMODE_REFERENCED; - dealingAttached1 = (uint)((package << 16) | index); + dealingAttached1 = ((package << 16) | index); dealingAttached2 = 0; - dealingAttached3 = 0; + dealingAttached3 = 0; + + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); + } + + public void SetTradeQuantity(int quantity) + { + dealingAttached3 = quantity; + + if (owner != null) + owner.GetItemPackage(itemPackage).MarkDirty(this); } public ItemData GetItemData() @@ -318,12 +319,8 @@ namespace FFXIVClassic_Map_Server.dataobjects public byte GetBazaarMode() { - for (int i = 0; i < tags.Length; i++) - { - if (tags[i] == 0xC9) - return tagValues[i]; - } - + if (tags[0] == 0xC9) + return tagValues[0]; return 0; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs deleted file mode 100644 index 925758d7..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.IO; - -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor.inventory -{ - class EquipmentListX01Packet - { - public const ushort OPCODE = 0x014D; - public const uint PACKET_SIZE = 0x28; - - public static SubPacket BuildPacket(uint playerActorID, ushort equipSlot, uint itemSlot) - { - byte[] data = new byte[PACKET_SIZE - 0x20]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - binWriter.Write((UInt16)equipSlot); - binWriter.Write((UInt32)itemSlot); - } - } - - return new SubPacket(OPCODE, playerActorID, data); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX01Packet.cs new file mode 100644 index 00000000..59a5c9d2 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX01Packet.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; + +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.packets.send.actor.inventory +{ + class LinkedItemListX01Packet + { + public const ushort OPCODE = 0x014D; + public const uint PACKET_SIZE = 0x28; + + public static SubPacket BuildPacket(uint playerActorID, ushort position, uint linkedItem) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write((UInt16)position); + binWriter.Write((UInt32)linkedItem); + } + } + + return new SubPacket(OPCODE, playerActorID, data); + } + + public static SubPacket BuildPacket(uint playerActorID, ushort position, ushort itemSlot, ushort itemPackageCode) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write((UInt16)position); + binWriter.Write((UInt16)itemSlot); + binWriter.Write((UInt16)itemPackageCode); + } + } + + return new SubPacket(OPCODE, playerActorID, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX08Packet.cs similarity index 82% rename from FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX08Packet.cs index c3f94f37..41f344f0 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX08Packet.cs @@ -7,12 +7,12 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { - class EquipmentListX08Packet + class LinkedItemListX08Packet { public const ushort OPCODE = 0x14E; public const uint PACKET_SIZE = 0x58; - public static SubPacket BuildPacket(uint playerActorId, InventoryItem[] equipment, List slotsToUpdate, ref int listOffset) + public static SubPacket BuildPacket(uint playerActorId, uint[] linkedItemList, List slotsToUpdate, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -28,8 +28,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory for (int i = 0; i < max; i++) { - binWriter.Write((UInt16)slotsToUpdate[i]); - binWriter.Write((UInt32)equipment[slotsToUpdate[i]].slot); + binWriter.Write((UInt16)slotsToUpdate[i]); //LinkedItemPackageSlot + binWriter.Write((UInt32)linkedItemList[slotsToUpdate[i]]); //ItemPackage Slot + ItemPackage Code listOffset++; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX16Packet.cs similarity index 81% rename from FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX16Packet.cs index fe347718..77375726 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX16Packet.cs @@ -7,12 +7,12 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { - class EquipmentListX16Packet + class LinkedItemListX16Packet { public const ushort OPCODE = 0x14F; public const uint PACKET_SIZE = 0x80; - public static SubPacket BuildPacket(uint playerActorId, InventoryItem[] equipment, List slotsToUpdate, ref int listOffset) + public static SubPacket BuildPacket(uint playerActorId, uint[] linkedItemList, List slotsToUpdate, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -28,8 +28,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory for (int i = 0; i < max; i++) { - binWriter.Write((UInt16)slotsToUpdate[i]); - binWriter.Write((UInt32)equipment[slotsToUpdate[i]].slot); + binWriter.Write((UInt16)slotsToUpdate[i]); //LinkedItemPackageSlot + binWriter.Write((UInt32)linkedItemList[slotsToUpdate[i]]); //ItemPackage Slot + ItemPackage Code listOffset++; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX32Packet.cs similarity index 81% rename from FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX32Packet.cs index 82614765..417a085f 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX32Packet.cs @@ -7,12 +7,12 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { - class EquipmentListX32Packet + class LinkedItemListX32Packet { public const ushort OPCODE = 0x150; public const uint PACKET_SIZE = 0xE0; - public static SubPacket BuildPacket(uint playerActorId, InventoryItem[] equipment, List slotsToUpdate, ref int listOffset) + public static SubPacket BuildPacket(uint playerActorId, uint[] linkedItemList, List slotsToUpdate, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -28,8 +28,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory for (int i = 0; i < max; i++) { - binWriter.Write((UInt16)slotsToUpdate[i]); - binWriter.Write((UInt32)equipment[slotsToUpdate[i]].slot); + binWriter.Write((UInt16)slotsToUpdate[i]); //LinkedItemPackageSlot + binWriter.Write((UInt32)linkedItemList[slotsToUpdate[i]]); //ItemPackage Slot + ItemPackage Code listOffset++; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX64Packet.cs similarity index 81% rename from FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX64Packet.cs index 05cb42c4..edc7378a 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/LinkedItemListX64Packet.cs @@ -7,12 +7,12 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { - class EquipmentListX64Packet + class LinkedItemListX64Packet { public const ushort OPCODE = 0x151; public const uint PACKET_SIZE = 0x194; - public static SubPacket BuildPacket(uint playerActorId, InventoryItem[] equipment, List slotsToUpdate, ref int listOffset) + public static SubPacket BuildPacket(uint playerActorId, uint[] linkedItemList, List slotsToUpdate, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -28,8 +28,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory for (int i = 0; i < max; i++) { - binWriter.Write((UInt16)slotsToUpdate[i]); - binWriter.Write((UInt32)equipment[slotsToUpdate[i]].slot); + binWriter.Write((UInt16)slotsToUpdate[i]); //LinkedItemPackageSlot + binWriter.Write((UInt32)linkedItemList[slotsToUpdate[i]]); //ItemPackage Slot + ItemPackage Code listOffset++; } diff --git a/data/scripts/commands/EquipCommand.lua b/data/scripts/commands/EquipCommand.lua index a73142cf..6c66c03a 100644 --- a/data/scripts/commands/EquipCommand.lua +++ b/data/scripts/commands/EquipCommand.lua @@ -6,7 +6,7 @@ Notes: Gearset activating could be optimized a bit more by doing the item packets in one go. -The param "invActionInfo" has the vars: actorId, unknown, slot, and inventoryType. +The param "equippedItem" has the vars: actorId, unknown, slot, and inventoryType. The param "itemDBIds" has the vars: item1 and item2. --]] @@ -53,12 +53,12 @@ GRAPHICSLOT_L_RINGFINGER = 24; GRAPHICSLOT_R_INDEXFINGER = 25; GRAPHICSLOT_L_INDEXFINGER = 26; -function onEventStarted(player, actor, triggerName, invActionInfo, param1, param2, param3, param4, param5, param6, param7, equipSlot, itemDBIds) +function onEventStarted(player, actor, triggerName, equippedItem, param1, param2, param3, param4, param5, param6, param7, equipSlot, itemDBIds) equipSlot = equipSlot-1; --Equip Item - if (invActionInfo ~= nil) then - item = player:GetItemPackage(0):GetItemAtSlot(invActionInfo.slot); + if (equippedItem ~= nil) then + item = player:GetItemPackage(equippedItem.itemPackage):GetItemAtSlot(equippedItem.slot); equipItem(player, equipSlot, item); player:SendAppearance(); --Unequip Item @@ -83,7 +83,7 @@ function loadGearset(player, classId) for slot = 0, 34 do if (slot ~= EQUIPSLOT_MAINHAND and slot ~= EQUIPSLOT_UNDERSHIRT and slot ~= EQUIPSLOT_UNDERGARMENT) then - itemAtSlot = player:GetEquipment():GetItemAtSlot(slot); + itemAtSlot = slot; itemAtGearsetSlot = gearset[slot]; if (itemAtSlot ~= nil or itemAtGearsetSlot ~= nil) then @@ -98,10 +98,8 @@ function loadGearset(player, classId) end end - end - - player:GetEquipment():ToggleDBWrite(true); - + end + player:GetEquipment():ToggleDBWrite(true); end function equipItem(player, equipSlot, item) @@ -151,7 +149,7 @@ function equipItem(player, equipSlot, item) player:DoClassChange(classId); end - player:GetEquipment():Equip(equipSlot, item); + player:GetEquipment():Set(equipSlot, item); if (equipSlot == EQUIPSLOT_MAINHAND and gItem:IsNailWeapon() == false) then graphicSlot = GRAPHICSLOT_MAINHAND; elseif (equipSlot == EQUIPSLOT_OFFHAND) then graphicSlot = GRAPHICSLOT_OFFHAND; @@ -167,7 +165,7 @@ function equipItem(player, equipSlot, item) elseif (equipSlot == EQUIPSLOT_RFINGER) then graphicSlot = GRAPHICSLOT_R_RINGFINGER; elseif (equipSlot == EQUIPSLOT_LFINGER) then graphicSlot = GRAPHICSLOT_L_RINGFINGER; end - + --Graphic Slot was set, otherwise it's a special case if (graphicSlot ~= nil) then player:GraphicChange(graphicSlot, item); @@ -191,7 +189,7 @@ function unequipItem(player, equipSlot, item) player:SendGameMessage(player, worldMaster, 30730, 0x20, equipSlot+1, item.itemId, item.quality, 0, 0, 1); --Unable to unequip elseif (item ~= nil) then player:SendGameMessage(player, worldMaster, 30602, 0x20, equipSlot+1, item.itemId, item.quality, 0, 0, 1); --Item Removed - player:GetEquipment():Unequip(equipSlot); + player:GetEquipment():Clear(equipSlot); if (equipSlot == EQUIPSLOT_BODY) then --Show Undershirt item = player:GetEquipment():GetItemAtSlot(EQUIPSLOT_UNDERSHIRT); diff --git a/data/scripts/commands/TradeExecuteCommand.lua b/data/scripts/commands/TradeExecuteCommand.lua index 730f491f..849202e1 100644 --- a/data/scripts/commands/TradeExecuteCommand.lua +++ b/data/scripts/commands/TradeExecuteCommand.lua @@ -30,16 +30,22 @@ function onEventStarted(player, actor, triggerName) tradeOffering = player:GetTradeOfferings(); + if (player.actorId == 0xA6) then + return; + end + while (true) do - widgetOpen, chosenOperation, tradeSlot, type7, quantity, packageId, quality = callClientFunction(player, "delegateCommand", GetStaticActor("TradeExecuteCommand"), "processUpdateTradeCommandTrayData"); - + widgetOpen, chosenOperation, tradeSlot, itemActor, quantity, itemPackageId, itemSlot = callClientFunction(player, "delegateCommand", GetStaticActor("TradeExecuteCommand"), "processUpdateTradeCommandTrayData"); + --Abort script if client script dead if (widgetOpen == false or widgetOpen == nil) then + player:FinishTradeTransaction(); break; end --Handle you/target canceling/finishing the trade if (not player:IsTrading() or not player:GetOtherTrader():IsTrading()) then + player:FinishTradeTransaction(); break; end @@ -63,15 +69,15 @@ function onEventStarted(player, actor, triggerName) callClientFunction(player, "delegateCommand", GetStaticActor("TradeExecuteCommand"), "processTradeCommandReply", "set"); --Clear All elseif (chosenOperation == 2) then - player:ClearTradeItems(1); + player:ClearTradeItems(); callClientFunction(player, "delegateCommand", GetStaticActor("TradeExecuteCommand"), "processTradeCommandReply", "set"); --Item Chosen elseif (chosenOperation == 3) then - player:AddTradeItem(tradeSlot - 1, type7.slot, quantity); + player:AddTradeItem(tradeSlot - 1, itemActor, quantity); callClientFunction(player, "delegateCommand", GetStaticActor("TradeExecuteCommand"), "processTradeCommandReply", "set", 2, 2, 2, 2); --Gil Chosen elseif (chosenOperation == 4) then - player:AddTradeGil(quantity); + player:AddTradeItem(tradeSlot - 1, itemActor, quantity); callClientFunction(player, "delegateCommand", GetStaticActor("TradeExecuteCommand"), "processTradeCommandReply", "set"); --Cancel elseif (chosenOperation == 11) then