diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index 2ff05a68..75541a42 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -1225,18 +1225,28 @@ namespace FFXIVClassic_Map_Server SELECT serverItemId, itemId, + modifierId, quantity, - itemType, quality, + durability, - spiritBind, + mainQuality, + subQuality1, + subQuality2, + subQuality3, + param1, + param2, + param3, + spiritbind, materia1, materia2, materia3, materia4, materia5 + FROM characters_inventory INNER JOIN server_items ON serverItemId = server_items.id + LEFT JOIN server_items_modifiers ON server_items.modifierId = server_items_modifiers.id WHERE characterId = @charId AND inventoryType = @type"; MySqlCommand cmd = new MySqlCommand(query, conn); @@ -1252,19 +1262,15 @@ namespace FFXIVClassic_Map_Server uint itemId = reader.GetUInt32("itemId"); int quantity = reader.GetInt32("quantity"); - byte itemType = reader.GetByte("itemType"); byte qualityNumber = reader.GetByte("quality"); - int durability = reader.GetInt32("durability"); - ushort spiritBind = reader.GetUInt16("spiritBind"); + bool hasModifier = !reader.IsDBNull(reader.GetOrdinal("modifierId")); + InventoryItem.ItemModifier modifier = null; - byte materia1 = reader.GetByte("materia1"); - byte materia2 = reader.GetByte("materia2"); - byte materia3 = reader.GetByte("materia3"); - byte materia4 = reader.GetByte("materia4"); - byte materia5 = reader.GetByte("materia5"); + if (hasModifier) + modifier = new InventoryItem.ItemModifier(reader); - InventoryItem item = new InventoryItem(uniqueId, itemId, quantity, itemType, qualityNumber, durability, spiritBind, materia1, materia2, materia3, materia4, materia5); + InventoryItem item = new InventoryItem(uniqueId, itemId, quantity, new byte[4], new byte[4], qualityNumber, modifier); item.slot = slot; slot++; items.Add(item); @@ -1298,19 +1304,29 @@ namespace FFXIVClassic_Map_Server SELECT serverItemId, itemId, + modifierId, quantity, - itemType, quality, + durability, - spiritBind, + mainQuality, + subQuality1, + subQuality2, + subQuality3, + param1, + param2, + param3, + spiritbind, materia1, materia2, materia3, materia4, materia5 + FROM retainers_inventory INNER JOIN server_items ON serverItemId = server_items.id - WHERE retainerId = @retainerId AND inventoryType = @type"; + LEFT JOIN server_items_modifiers ON server_items.modifierId = server_items_modifiers.id + WHERE characterId = @charId AND inventoryType = @type"; MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@retainerId", retainer.getRetainerId()); @@ -1328,16 +1344,13 @@ namespace FFXIVClassic_Map_Server byte itemType = reader.GetByte("itemType"); byte qualityNumber = reader.GetByte("quality"); - int durability = reader.GetInt32("durability"); - ushort spiritBind = reader.GetUInt16("spiritBind"); + bool hasModifier = reader.IsDBNull(reader.GetOrdinal("modifierId")); + InventoryItem.ItemModifier modifier = null; - byte materia1 = reader.GetByte("materia1"); - byte materia2 = reader.GetByte("materia2"); - byte materia3 = reader.GetByte("materia3"); - byte materia4 = reader.GetByte("materia4"); - byte materia5 = reader.GetByte("materia5"); + if (hasModifier) + modifier = new InventoryItem.ItemModifier(reader); - InventoryItem item = new InventoryItem(uniqueId, itemId, quantity, itemType, qualityNumber, durability, spiritBind, materia1, materia2, materia3, materia4, materia5); + InventoryItem item = new InventoryItem(uniqueId, itemId, quantity, new byte[4], new byte[4], qualityNumber, modifier); item.slot = slot; slot++; items.Add(item); @@ -1385,7 +1398,7 @@ namespace FFXIVClassic_Map_Server cmd.ExecuteNonQuery(); - insertedItem = new InventoryItem((uint)cmd.LastInsertedId, itemId, quantity, itemType, quality, durability, 0, 0, 0, 0, 0, 0); + insertedItem = new InventoryItem((uint)cmd.LastInsertedId, itemId, quantity, new byte[4], new byte[4], quality, new InventoryItem.ItemModifier()); } catch (MySqlException e) { diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index e092b465..16267ea2 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -46,6 +46,7 @@ namespace FFXIVClassic_Map_Server public Dictionary mRelationGroups = new Dictionary(); public Dictionary mTradeGroups = new Dictionary(); private Object groupLock = new Object(); + private Object tradeLock = new Object(); public ulong groupIndexId = 1; public WorldManager(Server server) @@ -902,7 +903,7 @@ namespace FFXIVClassic_Map_Server } public TradeGroup CreateTradeGroup(Player inviter, Player invitee) - { + { lock (groupLock) { groupIndexId = groupIndexId | 0x0000000000000000; @@ -946,6 +947,11 @@ namespace FFXIVClassic_Map_Server } } + public void TradeTEST(Player player) + { + player.KickEventSpecial(Server.GetStaticActors("TradeExecuteCommand"), 0, "commandContent", null, null, null, 16, null, null, null, null, null); + } + public void AcceptTrade(Player invitee) { TradeGroup group = GetTradeGroup(invitee.actorId); @@ -958,13 +964,13 @@ namespace FFXIVClassic_Map_Server Player inviter = (Player)invitee.GetZone().FindActorInArea(group.GetHost()); - DeleteTradeGroup(group.groupIndex); + //DeleteTradeGroup(group.groupIndex); inviter.StartTradeTransaction(invitee); invitee.StartTradeTransaction(inviter); - inviter.KickEvent(Server.GetStaticActors("TradeExecuteCommand"), "commandContent", null, null, null, 16, null, null, null, null, null); - invitee.KickEvent(Server.GetStaticActors("TradeExecuteCommand"), "commandContent", null, null, null, 16, null, null, null, null, null); + inviter.KickEventSpecial(Server.GetStaticActors("TradeExecuteCommand"), 0, "commandContent", null, null, null, 16, null, null, null, null, null); + invitee.KickEventSpecial(Server.GetStaticActors("TradeExecuteCommand"), 0, "commandContent", null, null, null, 16, null, null, null, null, null); } public void CancelTradeTooFar(Player inviter) @@ -1023,6 +1029,20 @@ namespace FFXIVClassic_Map_Server DeleteTradeGroup(group.groupIndex); } + public void SwapTradedItems(Player p1, Player p2) + { + lock (tradeLock) + { + if (p1.IsTradeAccepted() && p2.IsTradeAccepted()) + { + //move items around + + p1.FinishTradeTransaction(); + p2.FinishTradeTransaction(); + } + } + } + public bool SendGroupInit(Session session, ulong groupId) { if (mContentGroups.ContainsKey(groupId)) diff --git a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs b/FFXIVClassic Map Server/actors/chara/player/Inventory.cs index 5cc25122..0a93ba92 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Inventory.cs @@ -37,6 +37,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player private bool isTemporary; private InventoryItem[] list; private bool[] isDirty; + private bool holdingUpdates = false; private int endOfListIndex = 0; @@ -135,7 +136,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) { int oldQuantity = item.quantity; - item.quantity = Math.Min(item.quantity + quantityCount, gItem.maxStack); + item.quantity = Math.Min(item.quantity + quantityCount, gItem.maxStack); isDirty[i] = true; quantityCount -= (gItem.maxStack - oldQuantity); @@ -165,6 +166,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.player SendUpdatePackets(); return INV_ERROR.SUCCESS; + } + + public void AddItemSpecial(ushort slot, InventoryItem item) + { + list[slot] = item; + SendUpdatePackets(); } public void RemoveItem(uint itemId) @@ -306,6 +313,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.player SendUpdatePackets(); } + public InventoryItem[] GetRawList() + { + return list; + } + public void ChangeDurability(uint slot, uint durabilityChange) { isDirty[slot] = true; @@ -481,11 +493,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.player { if (owner is Player) { - SendUpdatePackets((Player)owner, true); + SendUpdatePackets((Player)owner); } } - public void SendUpdatePackets(Player player, bool doneImmediate = false) + public void SendUpdatePackets(Player player) { List items = new List(); List slotsToRemove = new List(); @@ -504,7 +516,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player slotsToRemove.Add((ushort)i); } - if (doneImmediate) + if (!holdingUpdates) DoneSendUpdate(); player.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); @@ -517,8 +529,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.player player.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); } + public void StartSendUpdate() + { + holdingUpdates = true; + } + public void DoneSendUpdate() { + holdingUpdates = false; Array.Clear(isDirty, 0, isDirty.Length); } diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index f08ce14a..bf7ff311 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -111,6 +111,8 @@ namespace FFXIVClassic_Map_Server.Actors //Trading private Player otherTrader = null; private Inventory myOfferings; + private bool isTradeAccepted = false; + private bool isTradeLocked = false; //GC Related public byte gcCurrent; @@ -1600,7 +1602,18 @@ namespace FFXIVClassic_Map_Server.Actors return; List lParams = LuaUtils.CreateLuaParamList(parameters); - SubPacket spacket = KickEventPacket.BuildPacket(actorId, actor.actorId, conditionName, lParams); + SubPacket spacket = KickEventPacket.BuildPacket(actorId, actor.actorId, 0x75dc1, conditionName, lParams); + spacket.DebugPrintSubPacket(); + QueuePacket(spacket); + } + + public void KickEventSpecial(Actor actor, uint unknown, string conditionName, params object[] parameters) + { + if (actor == null) + return; + + List lParams = LuaUtils.CreateLuaParamList(parameters); + SubPacket spacket = KickEventPacket.BuildPacket(actorId, actor.actorId, unknown, conditionName, lParams); spacket.DebugPrintSubPacket(); QueuePacket(spacket); } @@ -1786,19 +1799,98 @@ namespace FFXIVClassic_Map_Server.Actors myOfferings = new Inventory(this, 4, Inventory.TRADE, true); Inventory otherPlayerOfferings = new Inventory(otherPlayer, 4, Inventory.TRADE, true); - QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId)); - QueuePacket(InventorySetBeginPacket.BuildPacket(actorId, 4, Inventory.TRADE)); - - QueuePacket(EquipmentListX01Packet.BuildPacket(actorId, 1, 1)); - - QueuePacket(InventorySetEndPacket.BuildPacket(actorId)); - QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); - - myOfferings.SendFullInventory(this); + myOfferings.StartSendUpdate(); + myOfferings.SendUpdatePackets(this); + myOfferings.SendUpdatePackets(otherPlayer); + myOfferings.DoneSendUpdate(); otherTrader = otherPlayer; + isTradeAccepted = false; } + public Player GetOtherTrader() + { + return otherTrader; + } + + public Inventory GetTradeOfferings() + { + return myOfferings; + } + + public bool IsTrading() + { + return otherTrader != null; + } + + public bool IsTradeAccepted() + { + return isTradeAccepted; + } + + public void AddTradeItem(ushort slot, ushort linkedSlot, int subquantity) + { + if (!IsTrading()) + return; + + InventoryItem mine = inventories[Inventory.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) + { + if (!IsTrading()) + return; + + myOfferings.StartSendUpdate(); + myOfferings.AddItem(1000001, quantity, 1); + myOfferings.SendUpdatePackets(otherTrader); + myOfferings.DoneSendUpdate(); + } + + 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(); + } + + public void AcceptTrade(bool accepted) + { + if (!IsTrading()) + return; + isTradeAccepted = accepted; + } + + public void FinishTradeTransaction() + { + isTradeAccepted = false; + myOfferings = null; + otherTrader = null; + } + + public void Test() { QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId)); @@ -1820,22 +1912,5 @@ namespace FFXIVClassic_Map_Server.Actors QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); } - - public Inventory GetTradeOfferings() - { - return myOfferings; - } - - public void FinishTradeTransaction() - { - myOfferings = null; - otherTrader = null; - } - - public void CancelTradeTransaction() - { - myOfferings = null; - otherTrader = null; - } } } diff --git a/FFXIVClassic Map Server/dataobjects/InventoryItem.cs b/FFXIVClassic Map Server/dataobjects/InventoryItem.cs index 13c51499..3add2bdb 100644 --- a/FFXIVClassic Map Server/dataobjects/InventoryItem.cs +++ b/FFXIVClassic Map Server/dataobjects/InventoryItem.cs @@ -10,17 +10,94 @@ namespace FFXIVClassic_Map_Server.dataobjects public int quantity = 1; public ushort slot; - public byte itemType; + public uint dealingAttached1 = 0; + public uint dealingAttached2 = 0; + public uint dealingAttached3 = 0; + + public byte[] tags = new byte[4]; + public byte[] tagValues = new byte[4]; + public byte quality = 1; - public int durability = 0; - public ushort spiritbind = 0; + public ItemModifier modifiers; - public byte materia1 = 0; - public byte materia2 = 0; - public byte materia3 = 0; - public byte materia4 = 0; - public byte materia5 = 0; + public class ItemModifier + { + public uint durability; + public ushort use = 0; + public uint materiaId = 0; + public uint materiaLife = 0; + public byte mainQuality; + public byte[] subQuality = new byte[3]; + public uint polish; + public uint param1; + public uint param2; + public uint param3; + public ushort spiritbind; + public byte[] materiaType = new byte[5]; + public byte[] materiaGrade = new byte[5]; + + public ItemModifier() + { + } + + public ItemModifier(MySql.Data.MySqlClient.MySqlDataReader reader) + { + durability = reader.GetUInt32("durability"); + mainQuality = reader.GetByte("mainQuality"); + subQuality[0] = reader.GetByte("subQuality1"); + subQuality[1] = reader.GetByte("subQuality2"); + subQuality[2] = reader.GetByte("subQuality3"); + param1 = reader.GetUInt32("param1"); + param2 = reader.GetUInt32("param2"); + param3 = reader.GetUInt32("param3"); + spiritbind = reader.GetUInt16("spiritbind"); + + ushort materia1 = reader.GetUInt16("materia1"); + ushort materia2 = reader.GetUInt16("materia2"); + ushort materia3 = reader.GetUInt16("materia3"); + ushort materia4 = reader.GetUInt16("materia4"); + ushort materia5 = reader.GetUInt16("materia5"); + + materiaType[0] = (byte)(materia1 & 0xFF); + materiaGrade[0] = (byte)((materia1 >> 8) & 0xFF); + materiaType[1] = (byte)(materia2 & 0xFF); + materiaGrade[1] = (byte)((materia2 >> 8) & 0xFF); + materiaType[2] = (byte)(materia3 & 0xFF); + materiaGrade[2] = (byte)((materia3 >> 8) & 0xFF); + materiaType[3] = (byte)(materia4 & 0xFF); + materiaGrade[3] = (byte)((materia4 >> 8) & 0xFF); + materiaType[4] = (byte)(materia5 & 0xFF); + materiaGrade[4] = (byte)((materia5 >> 8) & 0xFF); + } + + public void WriteBytes(BinaryWriter binWriter) + { + binWriter.Write((UInt32) durability); + binWriter.Write((UInt16) use); + binWriter.Write((UInt32) materiaId); + binWriter.Write((UInt32) materiaLife); + binWriter.Write((Byte) mainQuality); + binWriter.Write((Byte) subQuality[0]); + binWriter.Write((Byte) subQuality[1]); + binWriter.Write((Byte) subQuality[2]); + binWriter.Write((UInt32) polish); + binWriter.Write((UInt32) param1); + binWriter.Write((UInt32) param2); + binWriter.Write((UInt32) param3); + binWriter.Write((UInt16) spiritbind); + binWriter.Write((Byte) materiaType[0]); + binWriter.Write((Byte) materiaType[1]); + binWriter.Write((Byte) materiaType[2]); + binWriter.Write((Byte) materiaType[3]); + binWriter.Write((Byte) materiaType[4]); + binWriter.Write((Byte) materiaGrade[0]); + binWriter.Write((Byte) materiaGrade[1]); + binWriter.Write((Byte) materiaGrade[2]); + binWriter.Write((Byte) materiaGrade[3]); + binWriter.Write((Byte) materiaGrade[4]); + } + } //Bare Minimum public InventoryItem(uint id, uint itemId) @@ -30,7 +107,7 @@ namespace FFXIVClassic_Map_Server.dataobjects this.quantity = 1; ItemData gItem = Server.GetItemGamedata(itemId); - itemType = gItem.isExclusive ? (byte)0x3 : (byte)0x0; + tags[1] = gItem.isExclusive ? (byte)0x3 : (byte)0x0; } //For check command @@ -41,33 +118,27 @@ namespace FFXIVClassic_Map_Server.dataobjects this.quantity = item.quantity; this.slot = equipSlot; - this.itemType = item.itemType; + this.tags = item.tags; + this.tagValues = item.tagValues; + this.quality = item.quality; - this.durability = item.durability; - this.spiritbind = item.spiritbind; - - this.materia1 = item.materia1; - this.materia2 = item.materia2; - this.materia3 = item.materia3; - this.materia4 = item.materia4; - this.materia5 = item.materia5; + this.modifiers = item.modifiers; } - public InventoryItem(uint uniqueId, uint itemId, int quantity, byte itemType, byte qualityNumber, int durability, ushort spiritbind, byte materia1, byte materia2, byte materia3, byte materia4, byte materia5) + public InventoryItem(uint uniqueId, uint itemId, int quantity, byte[] tags, byte[] tagValues, byte qualityNumber, ItemModifier modifiers = null) { this.uniqueId = uniqueId; this.itemId = itemId; this.quantity = quantity; - this.itemType = itemType; + + if (tags != null) + this.tags = tags; + if (tagValues != null) + this.tagValues = tagValues; + this.quality = qualityNumber; - this.durability = durability; - this.spiritbind = spiritbind; - this.materia1 = materia1; - this.materia2 = materia2; - this.materia3 = materia3; - this.materia4 = materia4; - this.materia5 = materia5; + this.modifiers = modifiers; } public byte[] ToPacketBytes() @@ -83,35 +154,25 @@ namespace FFXIVClassic_Map_Server.dataobjects binWriter.Write((UInt32)itemId); binWriter.Write((UInt16)slot); - binWriter.Write((UInt16)0x0001); - binWriter.Write((UInt32)0x00000000); - binWriter.Write((UInt32)0x00000000); - binWriter.Write((UInt32)0x00000000); + binWriter.Write((Byte)0x01); + binWriter.Write((Byte)0x00); - binWriter.Write((UInt32)itemType); + binWriter.Write((UInt32)dealingAttached1); + binWriter.Write((UInt32)dealingAttached2); + binWriter.Write((UInt32)dealingAttached3); - binWriter.Write((UInt32)0x00000000); + for (int i = 0; i < tags.Length; i++) + binWriter.Write((Byte) tags[i]); + for (int i = 0; i < tagValues.Length; i++) + binWriter.Write((Byte) tagValues[i]); - binWriter.Write((byte)quality); - binWriter.Write((byte)0x01); - binWriter.Write((uint)durability); - - binWriter.BaseStream.Seek(0x10-0x06, SeekOrigin.Current); - - binWriter.Write((byte)0x01); - binWriter.Write((byte)0x01); - binWriter.Write((byte)0x01); - binWriter.Write((byte)0x01); - - binWriter.BaseStream.Seek(0x10, SeekOrigin.Current); - - binWriter.Write((ushort)spiritbind); - - binWriter.Write((byte)materia1); - binWriter.Write((byte)materia2); - binWriter.Write((byte)materia3); - binWriter.Write((byte)materia4); - binWriter.Write((byte)materia5); + binWriter.Write((Byte)quality); + + if (modifiers != null) + { + binWriter.Write((Byte)0x01); + modifiers.WriteBytes(binWriter); + } } } diff --git a/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs b/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs index fe0a2d19..7fc79d64 100644 --- a/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs +++ b/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs @@ -13,7 +13,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events public const ushort OPCODE = 0x012F; public const uint PACKET_SIZE = 0x90; - public static SubPacket BuildPacket(uint sourcePlayerActorId, uint targetEventActorId, string conditionName, List luaParams) + public static SubPacket BuildPacket(uint sourcePlayerActorId, uint targetEventActorId, uint unknown, string conditionName, List luaParams) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,10 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events { binWriter.Write((UInt32)sourcePlayerActorId); binWriter.Write((UInt32)targetEventActorId); - - int test = 0x75dc1705; //This will crash if set to 0 on pushCommand but not for mining which has to be 0???? - - binWriter.Write((UInt32)test); + binWriter.Write((UInt32)unknown); binWriter.Write((UInt32)0x30400000); binWriter.Write(Encoding.ASCII.GetBytes(conditionName), 0, Encoding.ASCII.GetByteCount(conditionName) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(conditionName));