using System; using System.Collections.Generic; using UnityEngine; namespace PerfectWorld.Scripts.Managers { public static class EC_Inventory { private static readonly Dictionary _packSizeByPackage = new Dictionary(); private static readonly Dictionary> _itemsByPackage = new Dictionary>(); private const int MaxContentHexToLog = 64; // Package constants to mirror legacy C++ names public const byte PACK_INVENTORY = 0; public const byte PACK_EQUIPMENT = 1; public const byte PACK_TASKINVENTORY = 2; private static string GetPackageName(byte pkg) { switch (pkg) { case 0: return "PACK_INVENTORY"; case 1: return "PACK_EQUIPMENT"; case 2: return "PACK_TASKINVENTORY"; default: return "PACK_UNKNOWN"; } } private static string BytesToHex(byte[] bytes, int max) { if (bytes == null || bytes.Length == 0) return ""; int len = Math.Min(bytes.Length, max); string hex = BitConverter.ToString(bytes, 0, len); if (bytes.Length > len) hex += "-..."; return hex; } public static InventoryItemData GetItem(int iSlot, bool bRemove) { // Backward-compatible overload defaulting to inventory package return GetItem(PACK_INVENTORY, iSlot, bRemove); } public static InventoryItemData GetItem(byte byPackage, int slot, bool remove) { if (!_itemsByPackage.TryGetValue(byPackage, out var slots) || slot < 0) return null; if (!slots.TryGetValue(slot, out var item)) return null; if (remove) slots.Remove(slot); return item; } private static Dictionary EnsureSlots(byte byPackage) { if (!_itemsByPackage.TryGetValue(byPackage, out var slots) || slots == null) { slots = new Dictionary(); _itemsByPackage[byPackage] = slots; } return slots; } public static void Resize(byte byPackage, int newSize) { _packSizeByPackage[byPackage] = Math.Max(0, newSize); EnsureSlots(byPackage); } public static InventoryItemData PutItem(byte byPackage, int slot, InventoryItemData item) { var slots = EnsureSlots(byPackage); if (slot < 0) return null; slots.TryGetValue(slot, out var oldItem); slots[slot] = item; return oldItem; } public static void SetItem(byte byPackage, int slot, InventoryItemData item) { var slots = EnsureSlots(byPackage); if (slot < 0) return; slots[slot] = item; } public static void ExchangeItem(byte byPackage, int slot1, int slot2) { if (slot1 == slot2) return; var slots = EnsureSlots(byPackage); slots.TryGetValue(slot1, out var i1); slots.TryGetValue(slot2, out var i2); if (i1 != null) slots[slot2] = i1; else slots.Remove(slot2); if (i2 != null) slots[slot1] = i2; else slots.Remove(slot1); } public static bool RemoveItem(byte byPackage, int slot, int amount) { var slots = EnsureSlots(byPackage); if (!slots.TryGetValue(slot, out var item) || item == null) return true; int newCount = Math.Max(0, item.Count - Math.Max(0, amount)); item.Count = newCount; if (newCount <= 0) slots.Remove(slot); else slots[slot] = item; return true; } public static void RemoveAllItems(byte byPackage) { var slots = EnsureSlots(byPackage); slots.Clear(); } public static int SearchEmpty(byte byPackage) { int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; var slots = EnsureSlots(byPackage); for (int i = 0; i < size; i++) if (!slots.ContainsKey(i)) return i; return -1; } public static int GetEmptySlotNum(byte byPackage) { int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; int occupied = EnsureSlots(byPackage).Count; int empty = Math.Max(0, size - occupied); return empty; } public static int FindItem(byte byPackage, int templateId, int baseIdx = 0) { var slots = EnsureSlots(byPackage); int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; if (baseIdx < 0) baseIdx = 0; for (int i = baseIdx; i < size; i++) { if (slots.TryGetValue(i, out var it) && it != null && it.TemplateId == templateId) return i; } return -1; } public static int GetItemTotalNum(byte byPackage, int templateId) { int total = 0; foreach (var kv in EnsureSlots(byPackage)) { var it = kv.Value; if (it != null && it.TemplateId == templateId) total += Math.Max(0, it.Count); } return total; } public static int GetItemCanPileCount(byte byPackage, int templateId) { int ret = 0; foreach (var kv in EnsureSlots(byPackage)) { var it = kv.Value; if (it != null && it.TemplateId == templateId) { int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId)); ret += Math.Max(0, pileLimit - Math.Max(0, it.Count)); } } return ret; } public static int CanAddItem(byte byPackage, int templateId, int amount, bool tryPile) { int firstEmpty = -1; int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId)); int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; var slots = EnsureSlots(byPackage); for (int i = 0; i < size; i++) { if (!slots.TryGetValue(i, out var it) || it == null) { if (!tryPile) return i; if (firstEmpty < 0) firstEmpty = i; } else if (it.TemplateId == templateId && it.Count + amount <= pileLimit) { return i; } } return firstEmpty; } public static bool MergeItem(byte byPackage, int templateId, int expireDate, int amount, out int lastSlot, out int slotAmount) { lastSlot = -1; slotAmount = 0; var slots = EnsureSlots(byPackage); int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId)); int firstEmpty = -1; int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; for (int i = 0; i < size && amount > 0; i++) { slots.TryGetValue(i, out var slotItem); if (slotItem == null) { if (firstEmpty < 0) firstEmpty = i; continue; } if (slotItem.TemplateId != templateId) continue; int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.Count)); if (canAdd <= 0) continue; int add = Math.Min(canAdd, amount); slotItem.Count += add; amount -= add; lastSlot = i; slotAmount = slotItem.Count; } if (amount <= 0) return true; if (firstEmpty < 0) return false; var newItem = new InventoryItemData { Package = byPackage, Slot = firstEmpty, TemplateId = templateId, ExpireDate = expireDate, State = 0, Count = amount, Crc = 0, Content = null }; slots[firstEmpty] = newItem; lastSlot = firstEmpty; slotAmount = amount; return true; } public static bool MoveItem(byte byPackage, int src, int dest, int amount) { if (src == dest) return false; if (amount <= 0) return false; int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; if (src < 0 || src >= size || dest < 0 || dest >= size) return false; var slots = EnsureSlots(byPackage); if (!slots.TryGetValue(src, out var srcItem) || srcItem == null) return false; slots.TryGetValue(dest, out var dstItem); if (dstItem == null) { var clone = new InventoryItemData { Package = byPackage, Slot = dest, TemplateId = srcItem.TemplateId, ExpireDate = srcItem.ExpireDate, State = srcItem.State, Count = amount, Crc = srcItem.Crc, Content = srcItem.Content != null ? (byte[])srcItem.Content.Clone() : null }; slots[dest] = clone; } else { if (dstItem.TemplateId != srcItem.TemplateId) return false; int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(dstItem.TemplateId)); int canAdd = Math.Max(0, pileLimit - Math.Max(0, dstItem.Count)); int add = Math.Min(canAdd, amount); if (add <= 0) return false; dstItem.Count += add; amount = add; } RemoveItem(byPackage, src, amount); return true; } public static void UpdatePack(byte byPackage, int ivtrSize, IEnumerable items) { _packSizeByPackage[byPackage] = ivtrSize; if (!_itemsByPackage.TryGetValue(byPackage, out var slots)) { slots = new Dictionary(); _itemsByPackage[byPackage] = slots; } slots.Clear(); if (items != null) { foreach (var it in items) { if (it != null && it.Slot >= 0) { slots[it.Slot] = it; } } } // Log this pack's items LogPackInternal(byPackage, ivtrSize, slots); } public static bool ResetWithDetailData(byte byPackage, int ivtrSize, byte[] data) { // Uses EC_IvtrItem.TryParseInventoryDetail format if (data == null) { Resize(byPackage, ivtrSize); RemoveAllItems(byPackage); return true; } if (!EC_IvtrItem.TryParseInventoryDetail(data, out var pkg, out var size, out var items)) return false; // Prefer header values when valid byte finalPkg = byPackage; if (pkg == byPackage) finalPkg = pkg; int finalSize = ivtrSize > 0 ? ivtrSize : size; UpdatePack(finalPkg, finalSize, items); return true; } private static void LogPackInternal(byte byPackage, int ivtrSize, IReadOnlyDictionary slots) { Debug.Log($"[Inventory] === Pack {GetPackageName(byPackage)}({byPackage}) size={ivtrSize}, items={(slots?.Count ?? 0)} ==="); if (slots == null || slots.Count == 0) { Debug.Log("[Inventory] (empty)"); return; } foreach (var kv in slots) { var it = kv.Value; string itemName = EC_IvtrItem.ResolveItemName(it.TemplateId); string extraHex = it.Content != null && it.Content.Length > 0 ? EC_IvtrItem.BytesToHex(it.Content, MaxContentHexToLog) : ""; int extraLen = it.Content?.Length ?? 0; Debug.Log( $"[Inventory] pkg={GetPackageName(it.Package)}({it.Package}) slot={it.Slot} tid={it.TemplateId}{(string.IsNullOrEmpty(itemName) ? "" : " \"" + itemName + "\"")} count={it.Count} state={it.State} expire={it.ExpireDate} crc={it.Crc} content_len={extraLen}{(extraLen > 0 ? ", content_hex=" + extraHex : "")}" ); } } public static void LogInventoryPacket(string tag, byte[] buffer, int hostId) { if (buffer == null) { Debug.LogWarning($"[Inventory] {tag}: buffer is null (hostId={hostId})"); return; } int index = 0; if (buffer.Length < 6) { Debug.LogWarning($"[Inventory] {tag}: buffer too small: {buffer.Length} bytes (hostId={hostId})"); LogInventoryRaw(tag, buffer); return; } byte byPackage = buffer[index++]; byte ivtrSize = buffer[index++]; uint contentLength = BitConverter.ToUInt32(buffer, index); index += 4; int remaining = buffer.Length - index; int contentBytes = remaining; if (contentLength < (uint)remaining) { contentBytes = (int)contentLength; } Debug.Log($"[Inventory] {tag}: hostId={hostId}, totalBytes={buffer.Length}, byPackage={byPackage}, ivtrSize={ivtrSize}, contentLength={contentLength}, actualContentBytes={contentBytes}"); if (contentBytes > 0) { byte[] content = new byte[contentBytes]; Buffer.BlockCopy(buffer, index, content, 0, contentBytes); Debug.Log($"[Inventory] {tag}: content HEX=\n{BitConverter.ToString(content)}"); } int trailing = buffer.Length - (index + contentBytes); if (trailing > 0) { byte[] tail = new byte[trailing]; Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing); Debug.Log($"[Inventory] {tag}: trailing {trailing} byte(s) HEX=\n{BitConverter.ToString(tail)}"); } } public static void LogInventoryRaw(string tag, byte[] buffer) { Debug.Log($"[Inventory] {tag}: RAW HEX (len={buffer?.Length ?? 0})=\n{(buffer == null ? "" : BitConverter.ToString(buffer))}"); } } }