diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index cd7cf087..6b90a874 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -589,7 +589,7 @@ namespace FFXIVClassic_Lobby_Server return insertedItem; } - public static void addQuantity(Player player, uint slot, int quantity) + public static void setQuantity(Player player, uint slot, int quantity) { 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))) { @@ -597,9 +597,9 @@ namespace FFXIVClassic_Lobby_Server { conn.Open(); - string query = @" + string query = @" UPDATE characters_inventory - SET quantity = quantity + @quantity + SET quantity = @quantity WHERE serverItemId = (SELECT id FROM server_items WHERE characterId = @charId AND slot = @slot LIMIT 1) "; @@ -620,7 +620,41 @@ namespace FFXIVClassic_Lobby_Server } - public static void removeItem(Player player, uint uniqueItemId) + public static void removeItem(Player player, ulong serverItemId) + { + 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))) + { + try + { + conn.Open(); + + //Load Last 5 Completed + string query = @" + SELECT slot INTO @slotToDelete FROM characters_inventory WHERE serverItemId = @serverItemId; + UPDATE characters_inventory + SET slot = slot - 1 + WHERE characterId = 108 AND slot > @slotToDelete; + DELETE FROM characters_inventory + WHERE serverItemId = @serverItemId; + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@serverItemId", serverItemId); + cmd.ExecuteNonQuery(); + + } + catch (MySqlException e) + { Console.WriteLine(e); } + finally + { + conn.Dispose(); + } + } + + } + + public static void removeItem(Player player, ushort slot) { 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))) { @@ -631,7 +665,7 @@ namespace FFXIVClassic_Lobby_Server //Load Last 5 Completed string query = @" DELETE FROM server_items - WHERE id = @uniqueItemId; + WHERE slot = @slot; "; string query2 = @" @@ -639,13 +673,14 @@ namespace FFXIVClassic_Lobby_Server WHERE uniqueServerId = @uniqueItemId; UPDATE character_inventory SET slot = slot - 1 - WHERE characterId = @charId AND slot > X + WHERE characterId = @charId AND slot > @slot "; query += query2; MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@slot", slot); cmd.ExecuteNonQuery(); } diff --git a/FFXIVClassic Map Server/Server.cs b/FFXIVClassic Map Server/Server.cs index 2961810d..ec626973 100644 --- a/FFXIVClassic Map Server/Server.cs +++ b/FFXIVClassic Map Server/Server.cs @@ -407,19 +407,36 @@ namespace FFXIVClassic_Lobby_Server } } - private void giveItem(ConnectedPlayer client, uint itemId) + private void giveItem(ConnectedPlayer client, uint itemId, int quantity) { if (client != null) { Player p = client.getActor(); - p.inventories[Inventory.NORMAL].addItem(itemId, 0, 1, 1); + p.inventories[Inventory.NORMAL].addItem(itemId, 0, quantity, 1); } else { foreach (KeyValuePair entry in mConnectedPlayerList) { Player p = entry.Value.getActor(); - p.inventories[Inventory.NORMAL].addItem(itemId, 0, 1, 1); + p.inventories[Inventory.NORMAL].addItem(itemId, 0, quantity, 1); + } + } + } + + private void removeItem(ConnectedPlayer client, ushort slot) + { + if (client != null) + { + Player p = client.getActor(); + p.inventories[Inventory.NORMAL].removeItem(slot); + } + else + { + foreach (KeyValuePair entry in mConnectedPlayerList) + { + Player p = entry.Value.getActor(); + p.inventories[Inventory.NORMAL].removeItem(slot); } } } @@ -455,11 +472,11 @@ namespace FFXIVClassic_Lobby_Server } mWorldManager.reloadZone(client.getActor().zoneId); } - } - if (split.Length >= 2) - { - if (split[0].Equals("sendpacket")) + else if (split[0].Equals("sendpacket")) { + if (split.Length < 2) + return; + try { sendPacket(client, "./packets/" + split[1]); @@ -470,10 +487,27 @@ namespace FFXIVClassic_Lobby_Server } } else if (split[0].Equals("giveitem")) - { + { try { - giveItem(client, UInt32.Parse(split[1])); + if (split.Length == 2) + giveItem(client, UInt32.Parse(split[1]), 1); + else if (split.Length == 3) + giveItem(client, UInt32.Parse(split[1]), Int32.Parse(split[2])); + } + catch (Exception e) + { + Log.error("Could not give item."); + } + } + else if (split[0].Equals("removeitem")) + { + if (split.Length < 2) + return; + + try + { + removeItem(client, UInt16.Parse(split[1])); } catch (Exception e) { @@ -482,6 +516,9 @@ namespace FFXIVClassic_Lobby_Server } else if (split[0].Equals("music")) { + if (split.Length < 2) + return; + try { doMusic(client, split[1]); @@ -493,20 +530,17 @@ namespace FFXIVClassic_Lobby_Server } else if (split[0].Equals("warp")) { - doWarp(client, split[1]); - } - } - if (split.Length >= 5) - { - if (split[0].Equals("warp")) - { - doWarp(client, split[1], split[2], split[3], split[4]); + if (split.Length == 2) + doWarp(client, split[1]); + else if (split.Length == 5) + doWarp(client, split[1], split[2], split[3], split[4]); } else if (split[0].Equals("property")) { - testCodePacket(Utils.MurmurHash2(split[1], 0), Convert.ToUInt32(split[2], 16), split[3]); + if (split.Length == 5) + testCodePacket(Utils.MurmurHash2(split[1], 0), Convert.ToUInt32(split[2], 16), split[3]); } - } + } } } diff --git a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs b/FFXIVClassic Map Server/actors/chara/player/Inventory.cs index 48a45024..eba9225a 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Inventory.cs @@ -13,7 +13,7 @@ using System.Threading.Tasks; namespace FFXIVClassic_Map_Server.actors.chara.player { class Inventory - { + { public const ushort NORMAL = 0x0000; //Max 0xC8 public const ushort LOOT = 0x0004; //Max 0xA public const ushort MELDREQUEST = 0x0005; //Max 0x04 @@ -42,49 +42,154 @@ namespace FFXIVClassic_Map_Server.actors.chara.player public void addItem(uint itemId, ushort type, int quantity, byte quality) { + if (!isSpaceForAdd(itemId, quantity)) + return; + + List slotsToUpdate = new List(); List addItemPackets = new List(); - Item storedItem = null; //Check if item id exists - foreach (Item item in list) + int quantityCount = quantity; + for (int i = 0; i < list.Count; i++) { - if (item.itemId == itemId && item.quantity < 99) + Item item = list[i]; + if (item.itemId == itemId && item.quantity < item.maxStack) { - storedItem = item; - break; + slotsToUpdate.Add(item.slot); + int oldQuantity = item.quantity; + item.quantity = Math.Min(item.quantity + quantityCount, item.maxStack); + quantityCount -= (item.maxStack - oldQuantity); + if (quantityCount <= 0) + break; } } //If it's unique, abort - // if (storedItem != null && storedItem.isUnique) + //if (quantityCount > 0 && storedItem.isUnique) // return ITEMERROR_UNIQUE; //If Inventory is full - // if (storedItem == null && isInventoryFull()) + //if (quantityCount > 0 && isInventoryFull()) // return ITEMERROR_FULL; //Update lists and db owner.queuePacket(InventoryBeginChangePacket.buildPacket(owner.actorId)); owner.queuePacket(InventorySetBeginPacket.buildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - if (storedItem == null) + + //These had their quantities changed + foreach (ushort slot in slotsToUpdate) { - Item addedItem = Database.addItem(owner, itemId, quantity, quality, false, 100, type); + Database.setQuantity(owner, slot, list[slot].quantity); + sendInventoryPackets(list[slot]); + } + + //New item that spilled over + while (quantityCount > 0) + { + Item addedItem = Database.addItem(owner, itemId, Math.Min(quantityCount, 5), quality, false, 100, type); list.Add(addedItem); sendInventoryPackets(addedItem); + + quantityCount -= addedItem.maxStack; } - else - { - Database.addQuantity(owner, storedItem.slot, quantity); - storedItem.quantity += quantity; - sendInventoryPackets(storedItem); - } + owner.queuePacket(InventorySetEndPacket.buildPacket(owner.actorId)); owner.queuePacket(InventoryEndChangePacket.buildPacket(owner.actorId)); } - public void removeItem(uint itemId, uint quantity) + public void removeItem(uint itemId, int quantity) { + if (!hasItem(itemId)) + return; + List slotsToRemove = new List(); + List addItemPackets = new List(); + + //Remove as we go along + int quantityCount = quantity; + ushort lowestSlot = 0; + for (int i = list.Count - 1; i >= 0; i--) + { + Item item = list[i]; + if (item.itemId == itemId) + { + //Stack nomnomed + if (item.quantity - quantityCount <= 0) + slotsToRemove.Add(item.slot); + else + item.quantity -= quantityCount; //Stack reduced + + quantityCount -= item.quantity; + lowestSlot = item.slot; + + if (quantityCount <= 0) + break; + } + } + + list.Sort(); + int numSlotsRemoved = 0; + for (int i = 0; i < slotsToRemove.Count; i++) + { + Database.removeItem(owner, (ushort)(slotsToRemove[i] - numSlotsRemoved)); + list.RemoveAt(slotsToRemove[i] - numSlotsRemoved); + numSlotsRemoved++; + } + + owner.queuePacket(InventoryBeginChangePacket.buildPacket(owner.actorId)); + owner.queuePacket(InventorySetBeginPacket.buildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + + sendInventoryPackets(lowestSlot); + sendInventoryRemovePackets(slotsToRemove); + + owner.queuePacket(InventorySetEndPacket.buildPacket(owner.actorId)); + owner.queuePacket(InventoryEndChangePacket.buildPacket(owner.actorId)); + } + + public void removeItem(uint itemDBId) + { + ushort slot = 0; + Item toDelete = null; + foreach (Item item in list) + { + if (item.uniqueId == itemDBId) + { + toDelete = item; + break; + } + slot++; + } + + if (toDelete == null) + return; + + list.RemoveAt(slot); + Database.removeItem(owner, itemDBId); + + owner.queuePacket(InventoryBeginChangePacket.buildPacket(owner.actorId)); + owner.queuePacket(InventorySetBeginPacket.buildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + + sendInventoryPackets(slot); + sendInventoryRemovePackets(slot); + + owner.queuePacket(InventorySetEndPacket.buildPacket(owner.actorId)); + owner.queuePacket(InventoryEndChangePacket.buildPacket(owner.actorId)); + + } + + public void removeItem(ushort slot) + { + list.RemoveAt((int)slot); + Database.removeItem(owner, slot); + + owner.queuePacket(InventoryBeginChangePacket.buildPacket(owner.actorId)); + owner.queuePacket(InventorySetBeginPacket.buildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + + sendInventoryPackets(slot); + sendInventoryRemovePackets(slot); + + owner.queuePacket(InventorySetEndPacket.buildPacket(owner.actorId)); + owner.queuePacket(InventoryEndChangePacket.buildPacket(owner.actorId)); } public void changeDurability(uint slot, uint durabilityChange) @@ -107,7 +212,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player public void sendFullInventory() { owner.queuePacket(InventorySetBeginPacket.buildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - sendInventoryPackets(list); + sendInventoryPackets(0); owner.queuePacket(InventorySetEndPacket.buildPacket(owner.actorId)); } @@ -116,23 +221,23 @@ namespace FFXIVClassic_Map_Server.actors.chara.player owner.queuePacket(InventoryListX01Packet.buildPacket(owner.actorId, item)); } - private void sendInventoryPackets(List items) + private void sendInventoryPackets(int startOffset) { - int currentIndex = 0; + int currentIndex = startOffset; while (true) { - if (items.Count - currentIndex >= 64) - owner.queuePacket(InventoryListX64Packet.buildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex >= 32) - owner.queuePacket(InventoryListX32Packet.buildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex >= 16) - owner.queuePacket(InventoryListX16Packet.buildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex > 1) - owner.queuePacket(InventoryListX08Packet.buildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex == 1) + if (list.Count - currentIndex >= 64) + owner.queuePacket(InventoryListX64Packet.buildPacket(owner.actorId, list, ref currentIndex)); + else if (list.Count - currentIndex >= 32) + owner.queuePacket(InventoryListX32Packet.buildPacket(owner.actorId, list, ref currentIndex)); + else if (list.Count - currentIndex >= 16) + owner.queuePacket(InventoryListX16Packet.buildPacket(owner.actorId, list, ref currentIndex)); + else if (list.Count - currentIndex > 1) + owner.queuePacket(InventoryListX08Packet.buildPacket(owner.actorId, list, ref currentIndex)); + else if (list.Count - currentIndex == 1) { - owner.queuePacket(InventoryListX01Packet.buildPacket(owner.actorId, items[currentIndex])); + owner.queuePacket(InventoryListX01Packet.buildPacket(owner.actorId, list[currentIndex])); currentIndex++; } else @@ -140,6 +245,37 @@ namespace FFXIVClassic_Map_Server.actors.chara.player } } + + private void sendInventoryRemovePackets(ushort index) + { + owner.queuePacket(InventoryRemoveX01Packet.buildPacket(owner.actorId, index)); + } + + private void sendInventoryRemovePackets(List indexes) + { + int currentIndex = 0; + + while (true) + { + if (indexes.Count - currentIndex >= 64) + owner.queuePacket(InventoryRemoveX64Packet.buildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex >= 32) + owner.queuePacket(InventoryRemoveX32Packet.buildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex >= 16) + owner.queuePacket(InventoryRemoveX16Packet.buildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex > 1) + owner.queuePacket(InventoryRemoveX08Packet.buildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex == 1) + { + owner.queuePacket(InventoryRemoveX01Packet.buildPacket(owner.actorId, indexes[currentIndex])); + currentIndex++; + } + else + break; + } + + } + #endregion #region Inventory Utils @@ -148,6 +284,46 @@ namespace FFXIVClassic_Map_Server.actors.chara.player { return list.Count >= inventoryCapacity; } + + public bool isSpaceForAdd(uint itemId, int quantity) + { + int quantityCount = quantity; + for (int i = 0; i < list.Count; i++) + { + Item item = list[i]; + if (item.itemId == itemId && item.quantity < item.maxStack) + { + int oldQuantity = item.quantity; + item.quantity = Math.Min(item.quantity + quantityCount, item.maxStack); + quantityCount -= (item.maxStack - oldQuantity); + 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) + { + int count = 0; + + foreach (Item item in list) + { + if (item.itemId == itemId) + count += item.quantity; + + if (count >= minQuantity) + return true; + } + + return false; + } public int getNextEmptySlot() { diff --git a/FFXIVClassic Map Server/dataobjects/Item.cs b/FFXIVClassic Map Server/dataobjects/Item.cs index b1d10ddb..08e878e8 100644 --- a/FFXIVClassic Map Server/dataobjects/Item.cs +++ b/FFXIVClassic Map Server/dataobjects/Item.cs @@ -14,6 +14,8 @@ namespace FFXIVClassic_Map_Server.dataobjects public int quantity = 1; public ushort slot; + public int maxStack = 5; + public bool isUntradeable = false; public byte quality = 1;