diff --git a/Assets/PerfectWorld/Scripts/Inventory/EC_IvtrType.cs b/Assets/PerfectWorld/Scripts/Inventory/EC_IvtrType.cs index c42244873d..a1d0ea8fe1 100644 --- a/Assets/PerfectWorld/Scripts/Inventory/EC_IvtrType.cs +++ b/Assets/PerfectWorld/Scripts/Inventory/EC_IvtrType.cs @@ -61,5 +61,10 @@ namespace BrewMonster.Scripts public const int IVTRSIZE_CLIENTCARDPACK = 32; // Client pack for general card collection public const int NUM_NPCIVTR = 8; // NPC inventory number + + // Inventory package types (match legacy C++ IVTRTYPE_*) + public const byte IVTRTYPE_PACK = 0; + public const byte IVTRTYPE_EQUIPPACK = 1; + public const byte IVTRTYPE_TASKPACK = 2; } } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/CECInventory.cs b/Assets/PerfectWorld/Scripts/Managers/CECInventory.cs deleted file mode 100644 index 107493f88f..0000000000 --- a/Assets/PerfectWorld/Scripts/Managers/CECInventory.cs +++ /dev/null @@ -1,798 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace BrewMonster.Scripts.Managers -{ - /// - /// C# mirror of C++ CECInventory (EC_Inventory.h / EC_Inventory.cpp). - /// This is an instance-based inventory using a fixed-size item array. - /// - public class CECInventory - { - // Item array: index is slot, null means empty. - private EC_IvtrItem[] m_aItems = Array.Empty(); - - public CECInventory() - { - } - - public bool Init(int iSize) - { - Resize(iSize); - return true; - } - - public void Release() - { - // In C++ this deletes all heap-allocated items. - // Here we simply clear references so GC can collect them. - RemoveAllItems(); - m_aItems = Array.Empty(); - } - - public void RemoveAllItems() - { - for (int i = 0; i < m_aItems.Length; i++) - { - m_aItems[i] = null; - } - } - - public void Resize(int iNewSize) - { - int oldSize = m_aItems.Length; - if (iNewSize < 0) iNewSize = 0; - - if (iNewSize == oldSize) - return; - - var newArray = iNewSize > 0 ? new EC_IvtrItem[iNewSize] : Array.Empty(); - - if (oldSize > 0 && iNewSize > 0) - { - Array.Copy(m_aItems, newArray, Math.Min(oldSize, iNewSize)); - } - - m_aItems = newArray; - } - - public EC_IvtrItem PutItem(int iSlot, EC_IvtrItem pItem) - { - if (iSlot < 0 || iSlot >= m_aItems.Length) - { - return null; - } - - var old = m_aItems[iSlot]; - m_aItems[iSlot] = pItem; - return old; - } - - public void SetItem(int iSlot, EC_IvtrItem pItem) - { - if (iSlot < 0 || iSlot >= m_aItems.Length) - { - return; - } - - m_aItems[iSlot] = pItem; - } - - public EC_IvtrItem GetItem(int iSlot, bool bRemove = false) - { - if (iSlot < 0 || iSlot >= m_aItems.Length) - { - return null; - } - - var pItem = m_aItems[iSlot]; - if (bRemove) - m_aItems[iSlot] = null; - return pItem; - } - - public void ExchangeItem(int iSlot1, int iSlot2) - { - if (iSlot1 < 0 || iSlot1 >= m_aItems.Length || - iSlot2 < 0 || iSlot2 >= m_aItems.Length) - { - return; - } - - if (iSlot1 == iSlot2) - return; - - var tmp = m_aItems[iSlot1]; - m_aItems[iSlot1] = m_aItems[iSlot2]; - m_aItems[iSlot2] = tmp; - } - - public bool MergeItem(int tid, int iExpireDate, int iAmount, out int piLastSlot, out int piLastAmount) - { - piLastSlot = -1; - piLastAmount = 0; - - int firstEmpty = -1; - - for (int i = 0; i < m_aItems.Length; i++) - { - var slotItem = m_aItems[i]; - if (slotItem != null) - { - if (slotItem.GetTemplateID() != tid) - continue; - - int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(tid)); - int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.GetCount())); - if (canAdd <= 0) continue; - - int add = Math.Min(canAdd, iAmount); - slotItem.AddAmount(add); - iAmount -= add; - - if (iAmount == 0) - { - piLastSlot = i; - piLastAmount = slotItem.GetCount(); - return true; - } - } - else if (firstEmpty < 0) - { - firstEmpty = i; - } - } - - if (firstEmpty < 0 || iAmount <= 0) - { - return false; - } - - var newItem = new EC_IvtrItem(tid, iExpireDate) - { - Slot = firstEmpty, - State = 0, - Crc = 0, - Content = null - }; - newItem.SetCount(iAmount); - - m_aItems[firstEmpty] = newItem; - piLastSlot = firstEmpty; - piLastAmount = iAmount; - return true; - } - - public bool MoveItem(int iSrc, int iDest, int iAmount) - { - if (iSrc < 0 || iSrc >= m_aItems.Length || - iDest < 0 || iDest >= m_aItems.Length) - { - return false; - } - - var pSrc = m_aItems[iSrc]; - var pDst = m_aItems[iDest]; - - if (pSrc == null) - return false; - - if (iAmount == 0) - return false; - - if (pDst == null) - { - var clone = new EC_IvtrItem(pSrc.GetTemplateID(), pSrc.GetExpireDate()) - { - Slot = iDest, - Package = pSrc.Package, - State = pSrc.State, - Crc = pSrc.Crc, - Content = pSrc.Content != null ? (byte[])pSrc.Content.Clone() : null - }; - clone.SetCount(iAmount); - m_aItems[iDest] = clone; - } - else - { - if (pSrc.GetTemplateID() != pDst.GetTemplateID()) - return false; - - int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(pDst.GetTemplateID())); - int canAdd = Math.Max(0, pileLimit - Math.Max(0, pDst.GetCount())); - int add = Math.Min(canAdd, iAmount); - if (add <= 0) return false; - pDst.AddAmount(add); - iAmount = add; - } - - RemoveItem(iSrc, iAmount); - return true; - } - - public bool RemoveItem(int iSlot, int iAmount) - { - if (iSlot < 0 || iSlot >= m_aItems.Length) - { - return false; - } - - var pItem = m_aItems[iSlot]; - if (pItem == null) - return true; - - int newCount = pItem.AddAmount(-Math.Max(0, iAmount)); - if (newCount <= 0) - m_aItems[iSlot] = null; - - return true; - } - - public int FindItem(int idItem, int baseIdx = 0) - { - if (baseIdx < 0) baseIdx = 0; - for (int i = baseIdx; i < m_aItems.Length; i++) - { - var pItem = m_aItems[i]; - if (pItem != null && pItem.GetTemplateID() == idItem) - return i; - } - return -1; - } - - public int GetItemTotalNum(int idItem) - { - int count = 0; - for (int i = 0; i < m_aItems.Length; i++) - { - var pItem = m_aItems[i]; - if (pItem != null && pItem.GetTemplateID() == idItem) - count += Math.Max(0, pItem.GetCount()); - } - return count; - } - - public int SearchEmpty() - { - for (int i = 0; i < m_aItems.Length; i++) - { - if (m_aItems[i] == null) - return i; - } - return -1; - } - - public int GetEmptySlotNum() - { - int count = 0; - for (int i = 0; i < m_aItems.Length; i++) - { - if (m_aItems[i] == null) - count++; - } - return count; - } - - public int CanAddItem(int idItem, int iAmount, bool tryPile) - { - int foundEmpty = -1; - for (int i = 0; i < m_aItems.Length; i++) - { - var pItem = m_aItems[i]; - if (pItem == null) - { - if (!tryPile) return i; - if (foundEmpty < 0) foundEmpty = i; - } - else if (pItem.GetTemplateID() == idItem) - { - int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(idItem)); - if (pItem.GetCount() + iAmount <= pileLimit) - return i; - } - } - return foundEmpty; - } - - public int GetSize() - { - return m_aItems.Length; - } - } - - /// - /// Static inventory facade used by the current client code. - /// This implementation was originally in EC_Inventory.cs and is now colocated - /// with CECInventory so both static-style and instance-style code continue to work. - /// - public static class EC_Inventory - { - // We currently support exactly three inventory packs, matching legacy C++ semantics: - // 0 = normal pack, 1 = equip pack, 2 = task pack. - private const int PackageCount = 3; - - // Fixed-size arrays per package, mimicking C++ CECInventory::m_aItems (APtrArray). - // Index is the slot index; a null entry means the slot is empty. - private static readonly int[] _packSizeByPackage = new int[PackageCount]; - private static readonly EC_IvtrItem[][] _itemsByPackage = new EC_IvtrItem[PackageCount][]; - private const int MaxContentHexToLog = 64; - - // Package constants to mirror legacy C++ names - public const byte IVTRTYPE_PACK = 0; - public const byte IVTRTYPE_EQUIPPACK = 1; - public const byte IVTRTYPE_TASKPACK = 2; - - private static int GetPackageIndex(byte pkg) - { - switch (pkg) - { - case IVTRTYPE_PACK: - case IVTRTYPE_EQUIPPACK: - case IVTRTYPE_TASKPACK: - return pkg; - default: - // Only three legacy packages are currently supported; ignore others safely. - Debug.LogWarning($"[Inventory] Unsupported package id={pkg}, expected 0..2."); - return -1; - } - } - - private static int GetPackSize(byte byPackage) - { - int idx = GetPackageIndex(byPackage); - if (idx < 0) return 0; - return _packSizeByPackage[idx]; - } - - private static EC_IvtrItem[] EnsureSlots(byte byPackage) - { - int idx = GetPackageIndex(byPackage); - if (idx < 0) - return Array.Empty(); - - int size = Math.Max(0, _packSizeByPackage[idx]); - var slots = _itemsByPackage[idx]; - - if (slots == null || slots.Length != size) - { - var newSlots = size > 0 ? new EC_IvtrItem[size] : Array.Empty(); - - // Preserve items that still fit into the resized pack, similar to C++ Resize. - if (slots != null && slots.Length > 0 && newSlots.Length > 0) - { - Array.Copy(slots, newSlots, Math.Min(slots.Length, newSlots.Length)); - } - - _itemsByPackage[idx] = newSlots; - slots = newSlots; - } - - return slots; - } - - private static string GetPackageName(byte pkg) - { - switch (pkg) - { - case 0: return "IVTRTYPE_PACK"; - case 1: return "IVTRTYPE_EQUIPPACK"; - case 2: return "IVTRTYPE_TASKPACK"; - 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; - } - - // C++ has CECInventory per pack; this helper returns a snapshot Dictionary view - // of the current slots for convenience where a map-like API is easier to use. - public static Dictionary GetPack(byte byPackage) - { - var slots = EnsureSlots(byPackage); - var result = new Dictionary(slots.Length); - for (int i = 0; i < slots.Length; i++) - { - var it = slots[i]; - if (it != null) - result[i] = it; - } - return result; - } - - public static EC_IvtrItem GetItem(int iSlot, bool bRemove) - { - // Backward-compatible overload defaulting to inventory package - return GetItem(IVTRTYPE_PACK, iSlot, bRemove); - } - - public static EC_IvtrItem GetItem(byte byPackage, int slot, bool remove) - { - var slots = EnsureSlots(byPackage); - if (slot < 0 || slot >= slots.Length) - return null; - - var item = slots[slot]; - if (remove && slot >= 0 && slot < slots.Length) - slots[slot] = null; - return item; - } - - public static void Resize(byte byPackage, int newSize) - { - int idx = GetPackageIndex(byPackage); - if (idx < 0) - return; - - newSize = Math.Max(0, newSize); - int oldSize = _packSizeByPackage[idx]; - _packSizeByPackage[idx] = newSize; - - var oldSlots = _itemsByPackage[idx]; - - if (oldSlots == null) - { - _itemsByPackage[idx] = newSize > 0 ? new EC_IvtrItem[newSize] : Array.Empty(); - return; - } - - if (oldSize == newSize && oldSlots.Length == newSize) - return; - - var newSlots = newSize > 0 ? new EC_IvtrItem[newSize] : Array.Empty(); - if (oldSlots.Length > 0 && newSlots.Length > 0) - { - Array.Copy(oldSlots, newSlots, Math.Min(oldSlots.Length, newSlots.Length)); - } - _itemsByPackage[idx] = newSlots; - } - - public static EC_IvtrItem PutItem(byte byPackage, int slot, EC_IvtrItem item) - { - var slots = EnsureSlots(byPackage); - if (slot < 0 || slot >= slots.Length) - return null; - - var oldItem = slots[slot]; - slots[slot] = item; - return oldItem; - } - - public static void SetItem(byte byPackage, int slot, EC_IvtrItem item) - { - var slots = EnsureSlots(byPackage); - if (slot < 0 || slot >= slots.Length) - return; - slots[slot] = item; - } - - public static void ExchangeItem(byte byPackage, int slot1, int slot2) - { - if (slot1 == slot2) return; - var slots = EnsureSlots(byPackage); - if (slot1 < 0 || slot1 >= slots.Length || slot2 < 0 || slot2 >= slots.Length) - return; - - var i1 = slots[slot1]; - var i2 = slots[slot2]; - slots[slot1] = i2; - slots[slot2] = i1; - } - - public static bool RemoveItem(byte byPackage, int slot, int amount) - { - var slots = EnsureSlots(byPackage); - if (slot < 0 || slot >= slots.Length) - return false; - - var item = slots[slot]; - if (item == null) - return true; - - int newCount = Math.Max(0, item.m_iCount - Math.Max(0, amount)); - item.m_iCount = newCount; - if (newCount <= 0) - slots[slot] = null; - - return true; - } - - public static void RemoveAllItems(byte byPackage) - { - var slots = EnsureSlots(byPackage); - for (int i = 0; i < slots.Length; i++) - { - slots[i] = null; - } - } - - public static int SearchEmpty(byte byPackage) - { - var slots = EnsureSlots(byPackage); - for (int i = 0; i < slots.Length; i++) - { - if (slots[i] == null) - return i; - } - return -1; - } - - public static int GetEmptySlotNum(byte byPackage) - { - var slots = EnsureSlots(byPackage); - int empty = 0; - for (int i = 0; i < slots.Length; i++) - { - if (slots[i] == null) - empty++; - } - return empty; - } - - public static int FindItem(byte byPackage, int templateId, int baseIdx = 0) - { - var slots = EnsureSlots(byPackage); - if (baseIdx < 0) baseIdx = 0; - for (int i = baseIdx; i < slots.Length; i++) - { - var it = slots[i]; - if (it != null && it.m_tid == templateId) - return i; - } - return -1; - } - - public static int GetItemTotalNum(byte byPackage, int templateId) - { - int total = 0; - var slots = EnsureSlots(byPackage); - for (int i = 0; i < slots.Length; i++) - { - var it = slots[i]; - if (it != null && it.m_tid == templateId) - total += Math.Max(0, it.m_iCount); - } - return total; - } - - public static int GetItemCanPileCount(byte byPackage, int templateId) - { - int ret = 0; - var slots = EnsureSlots(byPackage); - for (int i = 0; i < slots.Length; i++) - { - var it = slots[i]; - if (it != null && it.m_tid == templateId) - { - int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId)); - ret += Math.Max(0, pileLimit - Math.Max(0, it.m_iCount)); - } - } - 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)); - var slots = EnsureSlots(byPackage); - for (int i = 0; i < slots.Length; i++) - { - var it = slots[i]; - if (it == null) - { - // return first empty slot if not trying to pile item - if (!tryPile) return i; - if (firstEmpty < 0) firstEmpty = i; - } - else if (it.m_tid == templateId && it.m_iCount + 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; - - for (int i = 0; i < slots.Length && amount > 0; i++) - { - var slotItem = slots[i]; - if (slotItem == null) - { - if (firstEmpty < 0) firstEmpty = i; - continue; - } - if (slotItem.m_tid != templateId) continue; - int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.m_iCount)); - if (canAdd <= 0) continue; - int add = Math.Min(canAdd, amount); - slotItem.m_iCount += add; - amount -= add; - lastSlot = i; - slotAmount = slotItem.m_iCount; - } - if (amount <= 0) return true; - if (firstEmpty < 0) return false; - - var newItem = new EC_IvtrItem - { - Package = byPackage, - Slot = firstEmpty, - m_tid = templateId, - m_expire_date = expireDate, - State = 0, - m_iCount = 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; - - var slots = EnsureSlots(byPackage); - if (src < 0 || src >= slots.Length || dest < 0 || dest >= slots.Length) return false; - - var srcItem = slots[src]; - var dstItem = slots[dest]; - if (srcItem == null) return false; - - if (dstItem == null) - { - var clone = new EC_IvtrItem - { - Package = byPackage, - Slot = dest, - m_tid = srcItem.m_tid, - m_expire_date = srcItem.m_expire_date, - State = srcItem.State, - m_iCount = amount, - Crc = srcItem.Crc, - Content = srcItem.Content != null ? (byte[])srcItem.Content.Clone() : null - }; - slots[dest] = clone; - } - else - { - if (dstItem.m_tid != srcItem.m_tid) return false; - int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(dstItem.m_tid)); - int canAdd = Math.Max(0, pileLimit - Math.Max(0, dstItem.m_iCount)); - int add = Math.Min(canAdd, amount); - if (add <= 0) return false; - dstItem.m_iCount += add; - amount = add; - } - RemoveItem(byPackage, src, amount); - return true; - } - - public static void UpdatePack(byte byPackage, int ivtrSize, IEnumerable items) - { - Resize(byPackage, ivtrSize); - var slots = EnsureSlots(byPackage); - - // Clear existing entries; keep size. - for (int i = 0; i < slots.Length; i++) - slots[i] = null; - - if (items != null) - { - foreach (var it in items) - { - if (it != null && it.Slot >= 0 && it.Slot < slots.Length) - { - 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_IvtrItemUtils.Instance.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, EC_IvtrItem[] slots) - { - //Debug.Log($"[Inventory] === Pack {GetPackageName(byPackage)}({byPackage}) size={ivtrSize}, items={slots?.Length ?? 0} ==="); - if (slots == null || slots.Length == 0) - { - //Debug.Log("[Inventory] (empty)"); - return; - } - for (int i = 0; i < slots.Length; i++) - { - var it = slots[i]; - if (it == null) - continue; - - string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(it.m_tid); - string extraHex = it.Content != null && it.Content.Length > 0 ? EC_IvtrItemUtils.Instance.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) - { - return; - } - - int index = 0; - if (buffer.Length < 6) - { - //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; - } - - if (contentBytes > 0) - { - byte[] content = new byte[contentBytes]; - Buffer.BlockCopy(buffer, index, content, 0, contentBytes); - } - - int trailing = buffer.Length - (index + contentBytes); - if (trailing > 0) - { - byte[] tail = new byte[trailing]; - Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing); - } - } - - public static void LogInventoryRaw(string tag, byte[] buffer) - { - Debug.Log($"[Inventory] {tag}: RAW HEX (len={buffer?.Length ?? 0})=\n{(buffer == null ? "" : BitConverter.ToString(buffer))}"); - } - } -} - diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs new file mode 100644 index 0000000000..2109586757 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace BrewMonster.Scripts.Managers +{ + /// + /// C# mirror of C++ CECInventory (EC_Inventory.h / EC_Inventory.cpp). + /// Instance-based inventory core plus static helpers used by existing client code. + /// + public class EC_Inventory + { + // ===== Instance-based inventory (per-pack) ===== + + // Item array: index is slot, null means empty. + private EC_IvtrItem[] m_aItems = Array.Empty(); + + public EC_Inventory() + { + } + + public bool Init(int iSize) + { + Resize(iSize); + return true; + } + + public void Release() + { + // In C++ this deletes all heap-allocated items. + // Here we simply clear references so GC can collect them. + RemoveAllItems(); + m_aItems = Array.Empty(); + } + + public void RemoveAllItems() + { + for (int i = 0; i < m_aItems.Length; i++) + { + m_aItems[i] = null; + } + } + + public void Resize(int iNewSize) + { + int oldSize = m_aItems.Length; + if (iNewSize < 0) iNewSize = 0; + + if (iNewSize == oldSize) + return; + + var newArray = iNewSize > 0 ? new EC_IvtrItem[iNewSize] : Array.Empty(); + + if (oldSize > 0 && iNewSize > 0) + { + Array.Copy(m_aItems, newArray, Math.Min(oldSize, iNewSize)); + } + + m_aItems = newArray; + } + + public EC_IvtrItem PutItem(int iSlot, EC_IvtrItem pItem) + { + if (iSlot < 0 || iSlot >= m_aItems.Length) + { + return null; + } + + var old = m_aItems[iSlot]; + m_aItems[iSlot] = pItem; + return old; + } + + public void SetItem(int iSlot, EC_IvtrItem pItem) + { + if (iSlot < 0 || iSlot >= m_aItems.Length) + { + return; + } + + m_aItems[iSlot] = pItem; + } + + public EC_IvtrItem GetItem(int iSlot, bool bRemove = false) + { + if (iSlot < 0 || iSlot >= m_aItems.Length) + { + return null; + } + + var pItem = m_aItems[iSlot]; + if (bRemove) + m_aItems[iSlot] = null; + return pItem; + } + + public void ExchangeItem(int iSlot1, int iSlot2) + { + if (iSlot1 < 0 || iSlot1 >= m_aItems.Length || + iSlot2 < 0 || iSlot2 >= m_aItems.Length) + { + return; + } + + if (iSlot1 == iSlot2) + return; + + var tmp = m_aItems[iSlot1]; + m_aItems[iSlot1] = m_aItems[iSlot2]; + m_aItems[iSlot2] = tmp; + } + + public bool MergeItem(int tid, int iExpireDate, int iAmount, out int piLastSlot, out int piLastAmount) + { + piLastSlot = -1; + piLastAmount = 0; + + int firstEmpty = -1; + + for (int i = 0; i < m_aItems.Length; i++) + { + var slotItem = m_aItems[i]; + if (slotItem != null) + { + if (slotItem.GetTemplateID() != tid) + continue; + + int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(tid)); + int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.GetCount())); + if (canAdd <= 0) continue; + + int add = Math.Min(canAdd, iAmount); + slotItem.AddAmount(add); + iAmount -= add; + + if (iAmount == 0) + { + piLastSlot = i; + piLastAmount = slotItem.GetCount(); + return true; + } + } + else if (firstEmpty < 0) + { + firstEmpty = i; + } + } + + if (firstEmpty < 0 || iAmount <= 0) + { + return false; + } + + var newItem = new EC_IvtrItem(tid, iExpireDate) + { + Slot = firstEmpty, + State = 0, + Crc = 0, + Content = null + }; + newItem.SetCount(iAmount); + + m_aItems[firstEmpty] = newItem; + piLastSlot = firstEmpty; + piLastAmount = iAmount; + return true; + } + + public bool MoveItem(int iSrc, int iDest, int iAmount) + { + if (iSrc < 0 || iSrc >= m_aItems.Length || + iDest < 0 || iDest >= m_aItems.Length) + { + return false; + } + + var pSrc = m_aItems[iSrc]; + var pDst = m_aItems[iDest]; + + if (pSrc == null) + return false; + + if (iAmount == 0) + return false; + + if (pDst == null) + { + var clone = new EC_IvtrItem(pSrc.GetTemplateID(), pSrc.GetExpireDate()) + { + Slot = iDest, + Package = pSrc.Package, + State = pSrc.State, + Crc = pSrc.Crc, + Content = pSrc.Content != null ? (byte[])pSrc.Content.Clone() : null + }; + clone.SetCount(iAmount); + m_aItems[iDest] = clone; + } + else + { + if (pSrc.GetTemplateID() != pDst.GetTemplateID()) + return false; + + int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(pDst.GetTemplateID())); + int canAdd = Math.Max(0, pileLimit - Math.Max(0, pDst.GetCount())); + int add = Math.Min(canAdd, iAmount); + if (add <= 0) return false; + pDst.AddAmount(add); + iAmount = add; + } + + RemoveItem(iSrc, iAmount); + return true; + } + + public bool RemoveItem(int iSlot, int iAmount) + { + if (iSlot < 0 || iSlot >= m_aItems.Length) + { + return false; + } + + var pItem = m_aItems[iSlot]; + if (pItem == null) + return true; + + int newCount = pItem.AddAmount(-Math.Max(0, iAmount)); + if (newCount <= 0) + m_aItems[iSlot] = null; + + return true; + } + + public int FindItem(int idItem, int baseIdx = 0) + { + if (baseIdx < 0) baseIdx = 0; + for (int i = baseIdx; i < m_aItems.Length; i++) + { + var pItem = m_aItems[i]; + if (pItem != null && pItem.GetTemplateID() == idItem) + return i; + } + return -1; + } + + public int GetItemTotalNum(int idItem) + { + int count = 0; + for (int i = 0; i < m_aItems.Length; i++) + { + var pItem = m_aItems[i]; + if (pItem != null && pItem.GetTemplateID() == idItem) + count += Math.Max(0, pItem.GetCount()); + } + return count; + } + + public int SearchEmpty() + { + for (int i = 0; i < m_aItems.Length; i++) + { + if (m_aItems[i] == null) + return i; + } + return -1; + } + + public int GetEmptySlotNum() + { + int count = 0; + for (int i = 0; i < m_aItems.Length; i++) + { + if (m_aItems[i] == null) + count++; + } + return count; + } + + public int CanAddItem(int idItem, int iAmount, bool tryPile) + { + int foundEmpty = -1; + for (int i = 0; i < m_aItems.Length; i++) + { + var pItem = m_aItems[i]; + if (pItem == null) + { + if (!tryPile) return i; + if (foundEmpty < 0) foundEmpty = i; + } + else if (pItem.GetTemplateID() == idItem) + { + int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(idItem)); + if (pItem.GetCount() + iAmount <= pileLimit) + return i; + } + } + return foundEmpty; + } + + public int GetSize() + { + return m_aItems.Length; + } + } +} diff --git a/Assets/PerfectWorld/Scripts/Managers/CECInventory.cs.meta b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs.meta similarity index 100% rename from Assets/PerfectWorld/Scripts/Managers/CECInventory.cs.meta rename to Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs index 29f0904155..181114ab42 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs @@ -10,6 +10,7 @@ using BrewMonster; using BrewMonster.Common; using ModelRenderer.Scripts.GameData; using PerfectWorld.Scripts.Managers; +using BrewMonster.Scripts; namespace BrewMonster.Scripts.Managers { @@ -654,34 +655,23 @@ namespace BrewMonster.Scripts.Managers // === MVC: Model === private class InventoryModel { - private readonly FieldInfo itemsByPackageField; - - public InventoryModel() - { - var inventoryType = typeof(EC_Inventory); - itemsByPackageField = inventoryType.GetField("_itemsByPackage", BindingFlags.NonPublic | BindingFlags.Static); - if (itemsByPackageField == null) - { - Debug.LogError("[InventoryUI] Could not access _itemsByPackage field from EC_Inventory"); - } - } - public Dictionary GetInventoryData(byte package) { - if (itemsByPackageField == null) + // Read from host player's per-package CECInventory instance + var host = CECGameRun.Instance?.GetHostPlayer(); + var inv = host?.GetInventory(package); + var result = new Dictionary(); + if (inv == null) + return result; + + int size = inv.GetSize(); + for (int i = 0; i < size; i++) { - return new Dictionary(); + var item = inv.GetItem(i, false); + if (item != null) + result[i] = item; } - var itemsByPackage = itemsByPackageField.GetValue(null) as Dictionary>; - if (itemsByPackage == null) - { - return new Dictionary(); - } - if (itemsByPackage.TryGetValue(package, out var packageItems)) - { - return packageItems; - } - return new Dictionary(); + return result; } } diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs index bac2feae4a..dcf525698d 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs @@ -708,7 +708,7 @@ namespace BrewMonster.Scripts.Managers public bool m_bIsInNPCPack; // true, this item is in NPC package public bool m_bLocalDetailData; // true, data from GetDetailDataFromLocal - public CECInventory m_pDescIvtr; // Inventory only used to get item description + public EC_Inventory m_pDescIvtr; // Inventory only used to get item description #region Constructors @@ -979,7 +979,7 @@ namespace BrewMonster.Scripts.Managers /// Get item description text (normal / booth-buy / reward / repair). /// Mirrors the inline C++ GetDesc. /// - public string GetDesc(DescType iDescType = DescType.DESC_NORMAL, CECInventory pInventory = null) + public string GetDesc(DescType iDescType = DescType.DESC_NORMAL, EC_Inventory pInventory = null) { m_pDescIvtr = pInventory; diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrType.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrType.cs index bde0c2132f..b42a4e4aba 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrType.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrType.cs @@ -442,15 +442,18 @@ namespace BrewMonster.Scripts.Managers /// private static IndexOfIteminEquipmentInventory GetAvailableFingerSlot() { + var host = CECGameRun.Instance?.GetHostPlayer(); + var equipInv = host?.GetInventory(InventoryConst.IVTRTYPE_EQUIPPACK); + // Check if FINGER1 slot is empty - var finger1Item = EC_Inventory.GetItem(EC_Inventory.IVTRTYPE_EQUIPPACK, (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1, false); + var finger1Item = equipInv?.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1, false); if (finger1Item == null) { return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1; } // Check if FINGER2 slot is empty - var finger2Item = EC_Inventory.GetItem(EC_Inventory.IVTRTYPE_EQUIPPACK, (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2, false); + var finger2Item = equipInv?.GetItem((int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2, false); if (finger2Item == null) { return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2; diff --git a/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs b/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs index f3c3990cd2..3d3b764b3e 100644 --- a/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs +++ b/Assets/PerfectWorld/Scripts/Task/CECTaskInterface.cs @@ -454,12 +454,10 @@ namespace BrewMonster.Scripts.Task public int GetCommonItemCount(uint ulCommonItem) { // CECInventory pPack = m_pHost.GetPack(); - //EC_Inventory pPack = m_pHost.GetPack(); - - // return pPack != null ? EC_Inventory.GetItemTotalNum( 0, (int)ulCommonItem) : 0; + // return pPack != null ? pPack.GetItemTotalNum((int)ulCommonItem) : 0; - // paramater 1 to get type inventory (0:inventoty, 1:equip,...) - return EC_Inventory.GetItemTotalNum(EC_Inventory.IVTRTYPE_PACK, (int)ulCommonItem); + var inv = m_pHost?.GetInventory(InventoryConst.IVTRTYPE_PACK); + return inv != null ? inv.GetItemTotalNum((int)ulCommonItem) : 0; } public int GetTaskItemCount(uint ulTaskItem) @@ -467,7 +465,8 @@ namespace BrewMonster.Scripts.Task // CECInventory* pPack = m_pHost->GetTaskPack(); // return pPack ? pPack->GetItemTotalNum((int)ulTaskItem) : 0; - return EC_Inventory.GetItemTotalNum(EC_Inventory.IVTRTYPE_TASKPACK, (int)ulTaskItem); + var inv = m_pHost?.GetInventory(InventoryConst.IVTRTYPE_TASKPACK); + return inv != null ? inv.GetItemTotalNum((int)ulTaskItem) : 0; } private ATaskTemplMan GetTaskTemplMan() @@ -725,7 +724,8 @@ namespace BrewMonster.Scripts.Task public uint GetInvEmptySlot() { - return (uint)EC_Inventory.GetEmptySlotNum(EC_Inventory.IVTRTYPE_PACK); + var inv = m_pHost?.GetInventory(InventoryConst.IVTRTYPE_PACK); + return inv != null ? (uint)inv.GetEmptySlotNum() : 0u; } public int GetFactionContrib() diff --git a/Assets/Prefabs/UI/InventoryUI.prefab b/Assets/Prefabs/UI/InventoryUI.prefab index 8fead78cbd..4693a4da1b 100644 --- a/Assets/Prefabs/UI/InventoryUI.prefab +++ b/Assets/Prefabs/UI/InventoryUI.prefab @@ -718,7 +718,6 @@ GameObject: m_Component: - component: {fileID: 5275130098495308601} - component: {fileID: 7874153745862569389} - - component: {fileID: 112648634020396885} - component: {fileID: 3332719603249310962} m_Layer: 5 m_Name: PreviewCamera @@ -797,14 +796,6 @@ Camera: m_OcclusionCulling: 1 m_StereoConvergence: 10 m_StereoSeparation: 0.022 ---- !u!81 &112648634020396885 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 633515748786992396} - m_Enabled: 1 --- !u!114 &3332719603249310962 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index f9cb9061a6..e532a65bd2 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -116,6 +116,31 @@ public partial class CECHostPlayer : CECPlayer public bool IsChangingFace() { return m_bChangingFace; } + // ===== Inventory packs (instance-based) ===== + // 0 = normal pack, 1 = equip pack, 2 = task pack (see InventoryConst.IVTRTYPE_*) + private readonly EC_Inventory m_packInventory = new EC_Inventory(); + private readonly EC_Inventory m_equipInventory = new EC_Inventory(); + private readonly EC_Inventory m_taskInventory = new EC_Inventory(); + + public EC_Inventory PackInventory => m_packInventory; + public EC_Inventory EquipInventory => m_equipInventory; + public EC_Inventory TaskInventory => m_taskInventory; + + public EC_Inventory GetInventory(byte byPackage) + { + switch (byPackage) + { + case InventoryConst.IVTRTYPE_PACK: + return m_packInventory; + case InventoryConst.IVTRTYPE_EQUIPPACK: + return m_equipInventory; + case InventoryConst.IVTRTYPE_TASKPACK: + return m_taskInventory; + default: + return null; + } + } + private void Awake() { base.Awake(); @@ -773,8 +798,9 @@ public partial class CECHostPlayer : CECPlayer Content = null }; - // Add item to inventory - EC_Inventory.SetItem(where, index, newItem); + // Add item to inventory (instance-based) + var inv = GetInventory(where); + inv?.SetItem(index, newItem); Debug.Log( $"[HOST_OBTAIN_ITEM] Successfully added item {type} to package {where}, slot {index} with count {amount}"); @@ -824,8 +850,9 @@ public partial class CECHostPlayer : CECPlayer Content = null }; - // Add item to inventory - EC_Inventory.SetItem(byPackage, bySlot, newItem); + // Add item to inventory (instance-based) + var inv = GetInventory(byPackage); + inv?.SetItem(bySlot, newItem); //Debug.Log($"[Inventory] Successfully added item {tid} to package {byPackage}, slot {bySlot} with count {iAmount}"); @@ -865,7 +892,8 @@ public partial class CECHostPlayer : CECPlayer $"[Inventory] PLAYER_DROP_ITEM: package={byPackage}, slot={bySlot}, count={count}, tid={tid}, reason={reason}"); // Update the inventory by removing the item - bool success = EC_Inventory.RemoveItem(byPackage, bySlot, count); + var inv = GetInventory(byPackage); + bool success = inv != null && inv.RemoveItem(bySlot, count); if (success) { @@ -896,20 +924,22 @@ public partial class CECHostPlayer : CECPlayer byte index_inv = data[0]; byte index_equip = data[1]; // Update client-side data: move item between PACK_INVENTORY and PACK_EQUIPMENT - var invItem = EC_Inventory.GetItem(EC_Inventory.IVTRTYPE_PACK, index_inv, true); - var equipItem = EC_Inventory.GetItem(EC_Inventory.IVTRTYPE_EQUIPPACK, index_equip, true); + var packInv = GetInventory(InventoryConst.IVTRTYPE_PACK); + var equipInv = GetInventory(InventoryConst.IVTRTYPE_EQUIPPACK); + var invItem = packInv?.GetItem(index_inv, true); + var equipItem = equipInv?.GetItem(index_equip, true); if (invItem != null) { - invItem.Package = EC_Inventory.IVTRTYPE_EQUIPPACK; + invItem.Package = InventoryConst.IVTRTYPE_EQUIPPACK; invItem.Slot = index_equip; - EC_Inventory.SetItem(EC_Inventory.IVTRTYPE_EQUIPPACK, index_equip, invItem); + equipInv?.SetItem(index_equip, invItem); } if (equipItem != null) { - equipItem.Package = EC_Inventory.IVTRTYPE_PACK; + equipItem.Package = InventoryConst.IVTRTYPE_PACK; equipItem.Slot = index_inv; - EC_Inventory.SetItem(EC_Inventory.IVTRTYPE_PACK, index_inv, equipItem); + packInv?.SetItem(index_inv, equipItem); } // Trigger UI refresh if an EC_InventoryUI is present in scene @@ -934,7 +964,7 @@ public partial class CECHostPlayer : CECPlayer Debug.Log("[Inventory] OWN_ITEM_INFO received"); var data = Msg.dwParam1 as byte[]; int hostId = Convert.ToInt32(Msg.dwParam3); - EC_Inventory.LogInventoryPacket("OWN_ITEM_INFO", data, hostId); + LogInventoryPacket("OWN_ITEM_INFO", data, hostId); break; } } @@ -950,7 +980,7 @@ public partial class CECHostPlayer : CECPlayer { case CommandID.OWN_IVTR_DATA: { - EC_Inventory.LogInventoryPacket("OWN_IVTR_DATA", data, hostId); + LogInventoryPacket("OWN_IVTR_DATA", data, hostId); break; } case CommandID.OWN_IVTR_DETAIL_DATA: @@ -964,11 +994,25 @@ public partial class CECHostPlayer : CECPlayer if (EC_IvtrItemUtils.Instance.TryParseInventoryDetail(data, out var pkg, out var size, out var items)) { - EC_Inventory.UpdatePack(pkg, size, items); + var inv = GetInventory(pkg); + if (inv != null) + { + inv.Resize(size); + inv.RemoveAllItems(); + + if (items != null) + { + foreach (var it in items) + { + if (it != null && it.Slot >= 0 && it.Slot < size) + inv.SetItem(it.Slot, it); + } + } + } } // check if we got the item from the Equipment Pack. If so, we have to load the equipment items - if (byPackage == EC_Inventory.IVTRTYPE_EQUIPPACK) + if (byPackage == InventoryConst.IVTRTYPE_EQUIPPACK) { UpdateEquipSkins(); } @@ -1063,6 +1107,49 @@ public partial class CECHostPlayer : CECPlayer } } + // ===== Inventory packet logging helpers (was static on EC_Inventory/CECInventory) ===== + private void LogInventoryPacket(string tag, byte[] buffer, int hostId) + { + if (buffer == null) + return; + + int index = 0; + if (buffer.Length < 6) + { + //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; + } + + if (contentBytes > 0) + { + byte[] content = new byte[contentBytes]; + Buffer.BlockCopy(buffer, index, content, 0, contentBytes); + } + + int trailing = buffer.Length - (index + contentBytes); + if (trailing > 0) + { + byte[] tail = new byte[trailing]; + Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing); + } + } + + private void LogInventoryRaw(string tag, byte[] buffer) + { + Debug.Log($"[Inventory] {tag}: RAW HEX (len={buffer?.Length ?? 0})=\n{(buffer == null ? "" : BitConverter.ToString(buffer))}"); + } + public void OnMsgHstCorrectPos(in ECMSG Msg) { Debug.LogWarning("HoangDev : OnMsgHstCorrectPos"); diff --git a/Assets/Scripts/CECPlayer_Inventory.cs b/Assets/Scripts/CECPlayer_Inventory.cs index fb27db140a..516a114e86 100644 --- a/Assets/Scripts/CECPlayer_Inventory.cs +++ b/Assets/Scripts/CECPlayer_Inventory.cs @@ -129,17 +129,16 @@ public partial class CECPlayer public bool UpdateEquipSkins() { - var equipPack = EC_Inventory.GetPack(EC_Inventory.IVTRTYPE_EQUIPPACK); - int[] aNewEquips = new int[InventoryConst.IVTRSIZE_EQUIPPACK]; EC_IvtrItem pItem = null; for (int i = 0; i < InventoryConst.IVTRSIZE_EQUIPPACK; i++) { - - if (equipPack.TryGetValue(i, out pItem)) - { + // Use host player's equipment inventory (per-instance CECInventory) + var host = CECGameRun.Instance?.GetHostPlayer(); + var equipInv = host?.EquipInventory; + pItem = equipInv?.GetItem(i, false); + if (pItem != null) aNewEquips[i] = pItem.m_tid; - } } ShowEquipments(aNewEquips, true, true);