From f615aab877b33843679e7d126d4df7517ccfb411 Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Sat, 13 Sep 2025 18:34:16 +0700 Subject: [PATCH 1/8] Add own item info data handle --- .../DataProcess/ElementDataManProvider.cs | 3 +- .../Common/DataProcess/elementdataman.cs | 4 +- .../Scripts/Managers/EC_IvtrItem.cs | 10 +++++ .../Scripts/Managers/EC_IvtrItem.cs.meta | 3 ++ .../Scripts/Network/UnityGameSession.cs | 6 +-- .../Scripts/UI/Login/LoginScreenUI.cs | 7 ++-- Assets/Scripts/CECHostPlayer.cs | 42 +++++++++++-------- 7 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs create mode 100644 Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs b/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs index c7a9d5def9..f40ab55507 100644 --- a/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs +++ b/Assets/PerfectWorld/Scripts/Common/DataProcess/ElementDataManProvider.cs @@ -1,5 +1,6 @@ using System; using ModelRenderer.Scripts.GameData; +using UnityEngine; namespace BrewMonster { @@ -24,7 +25,7 @@ namespace BrewMonster } catch (Exception ex) { - Logger.LogError($"ElementDataManProvider: Failed to load element data: {ex}"); + Logger.LogError($"ElementDataManProvider: Failed to load element data: {ex} - {ex.StackTrace}"); } } diff --git a/Assets/PerfectWorld/Scripts/Common/DataProcess/elementdataman.cs b/Assets/PerfectWorld/Scripts/Common/DataProcess/elementdataman.cs index a04b82e2a3..444ef356ba 100644 --- a/Assets/PerfectWorld/Scripts/Common/DataProcess/elementdataman.cs +++ b/Assets/PerfectWorld/Scripts/Common/DataProcess/elementdataman.cs @@ -586,11 +586,11 @@ namespace ModelRenderer.Scripts.GameData add_id_data(ID_SPACE.ID_SPACE_ESSENCE, item.id, item); } - foreach (var item in unionscroll_essence_array) + /*foreach (var item in unionscroll_essence_array) { add_id_index(ID_SPACE.ID_SPACE_ESSENCE, item.id, DATA_TYPE.DT_UNIONSCROLL_ESSENCE); add_id_data(ID_SPACE.ID_SPACE_ESSENCE, item.id, item); - } + }*/ } public void SaveDataToTextFile() diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs new file mode 100644 index 0000000000..1efb09b797 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs @@ -0,0 +1,10 @@ +using BrewMonster; +using ModelRenderer.Scripts.GameData; + +namespace PerfectWorld.Scripts.Managers +{ + public class EC_IvtrItem + { + + } +} \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs.meta b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs.meta new file mode 100644 index 0000000000..8045c9c72f --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 423a6efd71f143f08096d684ca414bba +timeCreated: 1757752654 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index 097587c041..aca8c53adb 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -102,11 +102,7 @@ namespace BrewMonster.Network { Instance._gameSession.SelectRoleAsync(roleInfo, callback); } - public static void EnterWorldAsync(RoleInfo roleInfo, Action callback = null) - { - Instance._gameSession.EnterWorldAsync(roleInfo, callback); - } - + public static void RequestInventoryAsync(Action callback = null) { Instance._gameSession.RequestInventoryAsync(callback); diff --git a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs index 9373b16535..f9b40066d9 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs @@ -86,7 +86,7 @@ namespace BrewMonster.UI Logger.Log($"OnSelectRoleComplete {roleInfo.name} - {roleInfo.roleid}"); // now we have to enter the world - /* UnityGameSession.SendProtocol( + UnityGameSession.SendProtocol( new enterworld() { Roleid = roleInfo.roleid, @@ -97,8 +97,9 @@ namespace BrewMonster.UI Settime = 0, Timeout = 0 } - );*/ - UnityGameSession.EnterWorldAsync(roleInfo, OnEnterWorldComplete); + + ); + OnEnterWorldComplete(); } private async void OnEnterWorldComplete() diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index dddc2c8f89..7e9df64e70 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -13,6 +13,7 @@ using UnityEngine.SceneManagement; using UnityEngine.UI; using Scene = UnityEngine.SceneManagement.Scene; using System.Runtime.InteropServices; +using BrewMonster; public class CECHostPlayer : MonoBehaviour { @@ -191,6 +192,27 @@ public class CECHostPlayer : MonoBehaviour OnMsgHstIvtrInfo(Msg); break; } + case int value when value == EC_MsgDef.MSG_HST_OWNITEMINFO: + { + OnMsgHstOwnItemInfo(Msg); + break; + } + } + } + public void OnMsgHstOwnItemInfo(ECMSG Msg) + { + int cmd = Convert.ToInt32(Msg.dwParam2); + switch (cmd) + { + case CommandID.OWN_ITEM_INFO: + { + Debug.Log("[Inventory] OWN_ITEM_INFO received"); + var data = Msg.dwParam1 as byte[]; + int hostId = Convert.ToInt32(Msg.dwParam3); + LogInventoryPacket("OWN_ITEM_INFO", data, hostId); + break; + } + } } public void OnMsgHstIvtrInfo(ECMSG Msg) @@ -211,26 +233,10 @@ public class CECHostPlayer : MonoBehaviour { Debug.Log("[Inventory] OWN_IVTR_DETAIL_DATA received"); LogInventoryPacket("OWN_IVTR_DETAIL_DATA", data, hostId); + //ElementDataManProvider.GetElementDataMan().armor_essence_array. break; } - case CommandID.GET_OWN_MONEY: - { - Debug.Log("[Inventory] GET_OWN_MONEY received"); - LogInventoryRaw("GET_OWN_MONEY", data); - break; - } - case CommandID.CHANGE_IVTR_SIZE: - { - Debug.Log("[Inventory] CHANGE_IVTR_SIZE received"); - LogInventoryRaw("CHANGE_IVTR_SIZE", data); - break; - } - default: - { - Debug.Log($"[Inventory] Unhandled inventory cmd={cmd}"); - LogInventoryRaw($"CMD_{cmd}", data); - break; - } + } } public void OnMsgHstCorrectPos(in ECMSG Msg) From c827dc6ed8a2f39bf04a7563905f4810cabf6c2f Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Sat, 13 Sep 2025 19:03:45 +0700 Subject: [PATCH 2/8] Log item by tid --- .../Scripts/Managers/InventoryManager.cs | 339 ++++++++++++++++++ .../Scripts/Managers/InventoryManager.cs.meta | 2 + .../Scripts/Network/UnityGameSession.cs | 31 ++ .../Scripts/UI/Login/LoginScreenUI.cs | 3 +- Assets/Scripts/CECHostPlayer.cs | 11 +- 5 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs create mode 100644 Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs b/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs new file mode 100644 index 0000000000..77d544018b --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using BrewMonster; +using ModelRenderer.Scripts.Common; +using ModelRenderer.Scripts.GameData; +using UnityEngine; + +namespace PerfectWorld.Scripts.Managers +{ + public class InventoryItemData + { + public byte Package; + public int Slot; + + public int TemplateId; + public int ExpireDate; + public int State; + public int Count; + public ushort Crc; + public byte[] Content; // variable-length item-specific payload (can be null) + } + + public static class InventoryManager + { + private static readonly Dictionary _packSizeByPackage = new Dictionary(); + private static readonly Dictionary> _itemsByPackage = new Dictionary>(); + private static readonly Dictionary _tidNameCache = new Dictionary(); + + public static event Action> OnPackUpdated; + + private const int MaxContentHexToLog = 64; + + 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; + } + + private static string ResolveItemName(int templateId) + { + if (templateId <= 0) return ""; + if (_tidNameCache.TryGetValue(templateId, out var cached)) return cached; + try + { + var edm = ElementDataManProvider.GetElementDataMan(); + if (edm == null) return CacheAndReturn(templateId, ""); + uint id = unchecked((uint)templateId); + object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE); + string name = ExtractNameFromElement(data); + if (string.IsNullOrEmpty(name)) + { + name = TryFindNameByScanningArrays(edm, id); + } + return CacheAndReturn(templateId, name ?? ""); + } + catch (Exception ex) + { + Debug.LogWarning($"[Inventory] ResolveItemName error for tid={templateId}: {ex.Message}"); + return CacheAndReturn(templateId, ""); + } + } + + private static string CacheAndReturn(int tid, string name) + { + _tidNameCache[tid] = name ?? ""; + return name ?? ""; + } + + private static string ExtractNameFromElement(object data) + { + if (data == null) return ""; + var t = data.GetType(); + var prop = t.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance); + if (prop != null && prop.PropertyType == typeof(string)) + { + var val = prop.GetValue(data) as string; + if (!string.IsNullOrEmpty(val)) return val; + } + var propReal = t.GetProperty("RealName", BindingFlags.Public | BindingFlags.Instance); + if (propReal != null && propReal.PropertyType == typeof(string)) + { + var val = propReal.GetValue(data) as string; + if (!string.IsNullOrEmpty(val)) return val; + } + var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance); + if (fieldName != null && fieldName.FieldType == typeof(ushort[])) + { + var arr = fieldName.GetValue(data) as ushort[]; + var s = ByteToStringUtils.UshortArrayToCP936String(arr); + if (!string.IsNullOrEmpty(s)) return s; + } + var fieldReal = t.GetField("realname", BindingFlags.Public | BindingFlags.Instance); + if (fieldReal != null && fieldReal.FieldType == typeof(byte[])) + { + var arr = fieldReal.GetValue(data) as byte[]; + var s = ByteToStringUtils.ByteArrayToCP936String(arr); + if (!string.IsNullOrEmpty(s)) return s; + } + return ""; + } + + private static string TryFindNameByScanningArrays(object edm, uint id) + { + try + { + var fields = edm.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var f in fields) + { + if (!f.FieldType.IsArray) continue; + var arr = f.GetValue(edm) as Array; + if (arr == null || arr.Length == 0) continue; + var elemType = f.FieldType.GetElementType(); + var idField = elemType.GetField("id", BindingFlags.Public | BindingFlags.Instance); + if (idField == null || idField.FieldType != typeof(uint)) continue; + for (int i = 0; i < arr.Length; i++) + { + var element = arr.GetValue(i); + if (element == null) continue; + var value = (uint)idField.GetValue(element); + if (value == id) + { + var name = ExtractNameFromElement(element); + if (!string.IsNullOrEmpty(name)) return name; + var toStr = element.ToString(); + if (!string.IsNullOrEmpty(toStr)) return toStr; + } + } + } + } + catch { } + return ""; + } + + public static bool TryParseInventoryDetail(byte[] buffer, out byte byPackage, out byte ivtrSize, out List items) + { + byPackage = 0; + ivtrSize = 0; + items = null; + if (buffer == null || buffer.Length < 6) + { + return false; + } + + int index = 0; + byPackage = buffer[index++]; + 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) + { + items = new List(); + return true; + } + + byte[] content = new byte[contentBytes]; + Buffer.BlockCopy(buffer, index, content, 0, contentBytes); + + // Parse S2C::cmd_own_ivtr_detail_info.content + int ci = 0; + if (contentBytes < 4) + { + return false; + } + int numItems = BitConverter.ToInt32(content, ci); ci += 4; + items = new List(numItems > 0 ? numItems : 0); + + for (int i = 0; i < numItems; i++) + { + // Ensure enough bytes for fixed part: index, tid, expire, state, amount, crc(2), len(2) => 4*5 + 2 + 2 = 24 bytes + if (ci + 24 > contentBytes) + { + return false; + } + int slotIndex = BitConverter.ToInt32(content, ci); ci += 4; + if (slotIndex < 0) + { + // Skip invalid slot but continue parsing to keep stream in sync + // Still need to consume fields even if slot is negative + int skipTid = BitConverter.ToInt32(content, ci); ci += 4; + int skipExpire = BitConverter.ToInt32(content, ci); ci += 4; + int skipState = BitConverter.ToInt32(content, ci); ci += 4; + int skipAmt = BitConverter.ToInt32(content, ci); ci += 4; + ushort skipCrc = BitConverter.ToUInt16(content, ci); ci += 2; + ushort skipLen = BitConverter.ToUInt16(content, ci); ci += 2; + if (skipLen > 0) + { + if (ci + skipLen > contentBytes) return false; + ci += skipLen; + } + continue; + } + + int tid = BitConverter.ToInt32(content, ci); ci += 4; + int expireDate = BitConverter.ToInt32(content, ci); ci += 4; + int state = BitConverter.ToInt32(content, ci); ci += 4; + int amount = BitConverter.ToInt32(content, ci); ci += 4; + ushort crc = BitConverter.ToUInt16(content, ci); ci += 2; + ushort extraLen = BitConverter.ToUInt16(content, ci); ci += 2; + + byte[] extra = null; + if (extraLen > 0) + { + if (ci + extraLen > contentBytes) + { + return false; + } + extra = new byte[extraLen]; + Buffer.BlockCopy(content, ci, extra, 0, extraLen); + ci += extraLen; + } + + var item = new InventoryItemData + { + Package = byPackage, + Slot = slotIndex, + TemplateId = tid, + ExpireDate = expireDate, + State = state, + Count = amount, + Crc = crc, + Content = extra + }; + items.Add(item); + } + + 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; + } + } + } + OnPackUpdated?.Invoke(byPackage, slots); + + // Log this pack's items + LogPackInternal(byPackage, ivtrSize, slots); + } + + public static void LogPack(byte byPackage) + { + if (_itemsByPackage.TryGetValue(byPackage, out var slots)) + { + int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; + LogPackInternal(byPackage, size, slots); + } + else + { + Debug.Log($"[Inventory] Pack {GetPackageName(byPackage)}({byPackage}) has no data yet."); + } + } + + public static void LogAllItems() + { + foreach (var kv in _itemsByPackage) + { + int size = _packSizeByPackage.TryGetValue(kv.Key, out var s) ? s : 0; + LogPackInternal(kv.Key, size, kv.Value); + } + } + + 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 = ResolveItemName(it.TemplateId); + string extraHex = it.Content != null && it.Content.Length > 0 ? 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 IReadOnlyDictionary GetPack(byte byPackage) + { + return _itemsByPackage.TryGetValue(byPackage, out var dict) ? dict : null; + } + + public static int GetPackSize(byte byPackage) + { + return _packSizeByPackage.TryGetValue(byPackage, out var size) ? size : 0; + } + + public static IEnumerable GetAllItems() + { + foreach (var pack in _itemsByPackage.Values) + { + foreach (var kv in pack) + { + yield return kv.Value; + } + } + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta b/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta new file mode 100644 index 0000000000..d2ca04007c --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 135967a674c33d64fb368288f9e719ef \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index aca8c53adb..296baacafd 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -1,6 +1,7 @@ using BrewMonster; using CSNetwork; using CSNetwork.Protocols; +using CSNetwork.C2SCommand; using CSNetwork.Protocols.RPCData; using CSNetwork.Security; using System; @@ -108,6 +109,36 @@ namespace BrewMonster.Network Instance._gameSession.RequestInventoryAsync(callback); } + public static void RequestInventoryByPackageAsync(byte byPackage, Action callback = null) + { + var req = new gamedatasend(); + req.Data = C2SCommandFactory.CreateGetInventoryDetail(byPackage); + SendProtocol(req, callback); + } + + public static void RequestAllInventoriesAsync(Action callback = null, params byte[] packages) + { + if (packages == null || packages.Length == 0) + { + packages = new byte[] { 0, 1, 2 }; + } + + int remaining = packages.Length; + Action onOneDone = () => + { + remaining--; + if (remaining <= 0) + { + callback?.Invoke(); + } + }; + + foreach (var p in packages) + { + RequestInventoryByPackageAsync(p, onOneDone); + } + } + public void LoadScene(string sceneName, LoadSceneMode mode, Action actDone) { StartCoroutine(LoadSceneCoroutine(sceneName, mode, actDone)); diff --git a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs index f9b40066d9..c92732df81 100644 --- a/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs +++ b/Assets/PerfectWorld/Scripts/UI/Login/LoginScreenUI.cs @@ -106,7 +106,8 @@ namespace BrewMonster.UI { await Task.Delay(2000); Logger.Log("Entered world successfully."); - UnityGameSession.RequestInventoryAsync(() => { Logger.Log("Sent Inventory Detail Request"); }); + // Request all known packages: 0=Inventory,1=Equipment,2=Task + UnityGameSession.RequestAllInventoriesAsync(() => { Logger.Log("Sent Inventory Detail Requests (all packs)"); }, 0, 1, 2); } //private void OnInventoryReceived(List inventoryData) diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 7e9df64e70..2c97cd5971 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -233,7 +233,16 @@ public class CECHostPlayer : MonoBehaviour { Debug.Log("[Inventory] OWN_IVTR_DETAIL_DATA received"); LogInventoryPacket("OWN_IVTR_DETAIL_DATA", data, hostId); - //ElementDataManProvider.GetElementDataMan().armor_essence_array. + // Parse and store + if (data != null && data.Length >= 6) + { + byte byPackage = data[0]; + byte ivtrSize = data[1]; + if (PerfectWorld.Scripts.Managers.InventoryManager.TryParseInventoryDetail(data, out var pkg, out var size, out var items)) + { + PerfectWorld.Scripts.Managers.InventoryManager.UpdatePack(pkg, size, items); + } + } break; } From dac52cb35e0bb370f9828dd1a07bf95212745732 Mon Sep 17 00:00:00 2001 From: hungdk Date: Mon, 15 Sep 2025 16:12:01 +0700 Subject: [PATCH 3/8] Change file name --- .../{InventoryManager.cs => EC_Inventory.cs} | 204 ++++++++++++++++-- .../Scripts/Managers/EC_Inventory.cs.meta | 2 + .../Scripts/Managers/InventoryManager.cs.meta | 2 - 3 files changed, 191 insertions(+), 17 deletions(-) rename Assets/PerfectWorld/Scripts/Managers/{InventoryManager.cs => EC_Inventory.cs} (70%) create mode 100644 Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs.meta delete mode 100644 Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta diff --git a/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs similarity index 70% rename from Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs rename to Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs index 77d544018b..92eb539a53 100644 --- a/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs @@ -21,7 +21,7 @@ namespace PerfectWorld.Scripts.Managers public byte[] Content; // variable-length item-specific payload (can be null) } - public static class InventoryManager + public static class EC_Inventory { private static readonly Dictionary _packSizeByPackage = new Dictionary(); private static readonly Dictionary> _itemsByPackage = new Dictionary>(); @@ -85,6 +85,28 @@ namespace PerfectWorld.Scripts.Managers { if (data == null) return ""; var t = data.GetType(); + // Prefer decoding the raw fields first to control encoding (Unicode for Vietnamese), + // then fall back to any string properties if needed. + var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance); + if (fieldName != null && fieldName.FieldType == typeof(ushort[])) + { + var arr = fieldName.GetValue(data) as ushort[]; + // Vietnamese names are stored as wide chars; decode as Unicode first. + var s = ByteToStringUtils.UshortArrayToUnicodeString(arr); + if (!string.IsNullOrEmpty(s)) return s; + // Fallback to legacy CP936 if Unicode was empty + s = ByteToStringUtils.UshortArrayToCP936String(arr); + if (!string.IsNullOrEmpty(s)) return s; + } + + var fieldReal = t.GetField("realname", BindingFlags.Public | BindingFlags.Instance); + if (fieldReal != null && fieldReal.FieldType == typeof(byte[])) + { + var arr = fieldReal.GetValue(data) as byte[]; + var s = ByteToStringUtils.ByteArrayToCP936String(arr); + if (!string.IsNullOrEmpty(s)) return s; + } + var prop = t.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance); if (prop != null && prop.PropertyType == typeof(string)) { @@ -97,20 +119,6 @@ namespace PerfectWorld.Scripts.Managers var val = propReal.GetValue(data) as string; if (!string.IsNullOrEmpty(val)) return val; } - var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance); - if (fieldName != null && fieldName.FieldType == typeof(ushort[])) - { - var arr = fieldName.GetValue(data) as ushort[]; - var s = ByteToStringUtils.UshortArrayToCP936String(arr); - if (!string.IsNullOrEmpty(s)) return s; - } - var fieldReal = t.GetField("realname", BindingFlags.Public | BindingFlags.Instance); - if (fieldReal != null && fieldReal.FieldType == typeof(byte[])) - { - var arr = fieldReal.GetValue(data) as byte[]; - var s = ByteToStringUtils.ByteArrayToCP936String(arr); - if (!string.IsNullOrEmpty(s)) return s; - } return ""; } @@ -334,6 +342,172 @@ namespace PerfectWorld.Scripts.Managers } } } + + // ====== Public query helpers for items/inventory properties ====== + public static bool TryGetItem(byte byPackage, int slot, out InventoryItemData item) + { + item = null; + if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) + { + if (dict.TryGetValue(slot, out var it)) + { + item = it; + return true; + } + } + return false; + } + + public static InventoryItemData GetItemOrNull(byte byPackage, int slot) + { + return (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict.TryGetValue(slot, out var it)) ? it : null; + } + + public static IEnumerable GetItemsInPack(byte byPackage) + { + if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) + { + return dict.Values; + } + return Array.Empty(); + } + + public static int GetUsedSlots(byte byPackage) + { + return _itemsByPackage.TryGetValue(byPackage, out var dict) ? dict.Count : 0; + } + + public static int GetFreeSlots(byte byPackage) + { + int size = GetPackSize(byPackage); + int used = GetUsedSlots(byPackage); + int free = size - used; + return free > 0 ? free : 0; + } + + public static IEnumerable GetSlotsOfTemplate(byte byPackage, int templateId) + { + if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) + { + foreach (var kv in dict) + { + if (kv.Value != null && kv.Value.TemplateId == templateId) + { + yield return kv.Key; + } + } + } + } + + public static InventoryItemData FindFirstItemByTemplate(byte byPackage, int templateId) + { + if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) + { + foreach (var kv in dict) + { + if (kv.Value != null && kv.Value.TemplateId == templateId) + { + return kv.Value; + } + } + } + return null; + } + + public static int GetCountOfTemplateInPack(byte byPackage, int templateId) + { + int total = 0; + if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) + { + foreach (var item in dict.Values) + { + if (item != null && item.TemplateId == templateId) + { + total += item.Count; + } + } + } + return total; + } + + public static int GetTotalUsedSlotsAcrossPacks() + { + int total = 0; + foreach (var dict in _itemsByPackage.Values) + { + total += dict?.Count ?? 0; + } + return total; + } + + public static int GetTotalPackSizeAcrossPacks() + { + int total = 0; + foreach (var kv in _packSizeByPackage) + { + total += kv.Value; + } + return total; + } + + public static int GetTotalFreeSlotsAcrossPacks() + { + int free = 0; + foreach (var kv in _packSizeByPackage) + { + byte pkg = kv.Key; + free += GetFreeSlots(pkg); + } + return free; + } + + public static IEnumerable GetAvailablePackages() + { + return _itemsByPackage.Keys; + } + + public static bool FindFirstTemplateAcrossPacks(int templateId, out byte byPackage, out int slot, out InventoryItemData item) + { + byPackage = 0; + slot = -1; + item = null; + foreach (var pkg in _itemsByPackage) + { + foreach (var kv in pkg.Value) + { + if (kv.Value != null && kv.Value.TemplateId == templateId) + { + byPackage = pkg.Key; + slot = kv.Key; + item = kv.Value; + return true; + } + } + } + return false; + } + + public static int GetTotalCountOfTemplateAcrossPacks(int templateId) + { + int total = 0; + foreach (var dict in _itemsByPackage.Values) + { + foreach (var it in dict.Values) + { + if (it != null && it.TemplateId == templateId) + { + total += it.Count; + } + } + } + return total; + } + + public static string GetTemplateName(int templateId) + { + return ResolveItemName(templateId) ?? string.Empty; + } } } + diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs.meta b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs.meta new file mode 100644 index 0000000000..2a2e71aaea --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b060723330d7f49409ca241f4e460bed \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta b/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta deleted file mode 100644 index d2ca04007c..0000000000 --- a/Assets/PerfectWorld/Scripts/Managers/InventoryManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 135967a674c33d64fb368288f9e719ef \ No newline at end of file From 535863cd97f24ca7a84158fdec2634ae8575c95d Mon Sep 17 00:00:00 2001 From: hungdk Date: Mon, 15 Sep 2025 16:12:16 +0700 Subject: [PATCH 4/8] Update ref --- Assets/Scripts/CECHostPlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 2c97cd5971..2480405fea 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -238,9 +238,9 @@ public class CECHostPlayer : MonoBehaviour { byte byPackage = data[0]; byte ivtrSize = data[1]; - if (PerfectWorld.Scripts.Managers.InventoryManager.TryParseInventoryDetail(data, out var pkg, out var size, out var items)) + if (PerfectWorld.Scripts.Managers.EC_Inventory.TryParseInventoryDetail(data, out var pkg, out var size, out var items)) { - PerfectWorld.Scripts.Managers.InventoryManager.UpdatePack(pkg, size, items); + PerfectWorld.Scripts.Managers.EC_Inventory.UpdatePack(pkg, size, items); } } break; From e2c272b3d5a5abde2ecee83b7b02f312e52a122f Mon Sep 17 00:00:00 2001 From: hungdk Date: Mon, 15 Sep 2025 16:14:33 +0700 Subject: [PATCH 5/8] Change encoding method --- .../Scripts/Common/ByteToStringUtils.cs | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs b/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs index 3175d971c3..755ae54c74 100644 --- a/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs +++ b/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs @@ -9,21 +9,33 @@ namespace ModelRenderer.Scripts.Common { if (ushortArray == null || ushortArray.Length == 0) return string.Empty; - - // First convert ushort array to byte array - // Each ushort (16 bits) can be up to two bytes in GBK - byte[] byteArray = new byte[ushortArray.Length * 2]; - + + // Determine actual length up to the first null terminator + int codeUnitCount = ushortArray.Length; + for (int i = 0; i < ushortArray.Length; i++) + { + if (ushortArray[i] == 0) + { + codeUnitCount = i; + break; + } + } + + if (codeUnitCount == 0) + return string.Empty; + + // Convert ushort code units to a byte array (UTF-16LE expected) + byte[] byteArray = new byte[codeUnitCount * 2]; Buffer.BlockCopy(ushortArray, 0, byteArray, 0, byteArray.Length); - - // Convert bytes to string using GBK encoding + + // Convert bytes to string using Unicode (UTF-16LE) try { return Encoding.Unicode.GetString(byteArray); } catch (Exception ex) { - UnityEngine.Debug.LogError($"Error converting bytes to GBK string: {ex.Message}"); + UnityEngine.Debug.LogError($"Error converting bytes to Unicode string: {ex.Message}"); return string.Empty; } } @@ -46,15 +58,29 @@ namespace ModelRenderer.Scripts.Common { if (byteArray == null || byteArray.Length == 0) return string.Empty; - + try { // Code page 936 is the code page for Simplified Chinese (GB2312/GBK) // You may need to import System.Text.Encoding.CodePages package for Unity/modern .NET Encoding cp936Encoding = Encoding.GetEncoding(936); - + + // Trim at first null terminator, if present + int length = byteArray.Length; + for (int i = 0; i < byteArray.Length; i++) + { + if (byteArray[i] == 0) + { + length = i; + break; + } + } + + if (length <= 0) + return string.Empty; + // Convert the byte array to a string using code page 936 encoding - return cp936Encoding.GetString(byteArray); + return cp936Encoding.GetString(byteArray, 0, length); } catch (Exception ex) { @@ -68,7 +94,21 @@ namespace ModelRenderer.Scripts.Common if (byteArray == null || byteArray.Length == 0) return string.Empty; - return Encoding.Unicode.GetString(byteArray); + // Trim at first null terminator (two-byte aligned not guaranteed; treat any 0 byte as terminator) + int length = byteArray.Length; + for (int i = 0; i < byteArray.Length; i++) + { + if (byteArray[i] == 0) + { + length = i; + break; + } + } + + if (length <= 0) + return string.Empty; + + return Encoding.Unicode.GetString(byteArray, 0, length); } } } \ No newline at end of file From 2b3af4037a5be00fce458dd385575bccf44cdff8 Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Mon, 15 Sep 2025 20:17:49 +0700 Subject: [PATCH 6/8] Add ui, font --- .../Scripts/Common/ByteToStringUtils.cs | 30 ++ .../Scripts/Managers/EC_Inventory.cs | 468 ++---------------- .../Scripts/Managers/EC_InventoryUI.cs | 238 +++++++++ .../Scripts/Managers/EC_InventoryUI.cs.meta | 11 + .../Scripts/Managers/EC_IvtrItem.cs | 304 +++++++++++- Assets/Prefabs/IvtrItem.prefab | 259 ++++++++++ Assets/Prefabs/IvtrItem.prefab.meta | 7 + Assets/Scenes/HoangTest.unity | 394 +++++++++++++++ Assets/Scripts/CECHostPlayer.cs | 57 +-- 9 files changed, 1288 insertions(+), 480 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs create mode 100644 Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs.meta create mode 100644 Assets/Prefabs/IvtrItem.prefab create mode 100644 Assets/Prefabs/IvtrItem.prefab.meta diff --git a/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs b/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs index 755ae54c74..9cf269881d 100644 --- a/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs +++ b/Assets/PerfectWorld/Scripts/Common/ByteToStringUtils.cs @@ -110,5 +110,35 @@ namespace ModelRenderer.Scripts.Common return Encoding.Unicode.GetString(byteArray, 0, length); } + + public static string ByteArrayToUTF8String(byte[] byteArray) + { + if (byteArray == null || byteArray.Length == 0) + return string.Empty; + + // Trim at first null terminator + int length = byteArray.Length; + for (int i = 0; i < byteArray.Length; i++) + { + if (byteArray[i] == 0) + { + length = i; + break; + } + } + + if (length <= 0) + return string.Empty; + + try + { + return Encoding.UTF8.GetString(byteArray, 0, length); + } + catch (Exception ex) + { + UnityEngine.Debug.LogError($"Error converting bytes to UTF-8 string: {ex.Message}"); + return string.Empty; + } + } } } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs index 92eb539a53..1597f99bbb 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_Inventory.cs @@ -1,34 +1,14 @@ using System; using System.Collections.Generic; -using System.Reflection; -using BrewMonster; -using ModelRenderer.Scripts.Common; -using ModelRenderer.Scripts.GameData; using UnityEngine; namespace PerfectWorld.Scripts.Managers { - public class InventoryItemData - { - public byte Package; - public int Slot; - - public int TemplateId; - public int ExpireDate; - public int State; - public int Count; - public ushort Crc; - public byte[] Content; // variable-length item-specific payload (can be null) - } public static class EC_Inventory { private static readonly Dictionary _packSizeByPackage = new Dictionary(); private static readonly Dictionary> _itemsByPackage = new Dictionary>(); - private static readonly Dictionary _tidNameCache = new Dictionary(); - - public static event Action> OnPackUpdated; - private const int MaxContentHexToLog = 64; private static string GetPackageName(byte pkg) @@ -51,209 +31,7 @@ namespace PerfectWorld.Scripts.Managers return hex; } - private static string ResolveItemName(int templateId) - { - if (templateId <= 0) return ""; - if (_tidNameCache.TryGetValue(templateId, out var cached)) return cached; - try - { - var edm = ElementDataManProvider.GetElementDataMan(); - if (edm == null) return CacheAndReturn(templateId, ""); - uint id = unchecked((uint)templateId); - object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE); - string name = ExtractNameFromElement(data); - if (string.IsNullOrEmpty(name)) - { - name = TryFindNameByScanningArrays(edm, id); - } - return CacheAndReturn(templateId, name ?? ""); - } - catch (Exception ex) - { - Debug.LogWarning($"[Inventory] ResolveItemName error for tid={templateId}: {ex.Message}"); - return CacheAndReturn(templateId, ""); - } - } - private static string CacheAndReturn(int tid, string name) - { - _tidNameCache[tid] = name ?? ""; - return name ?? ""; - } - - private static string ExtractNameFromElement(object data) - { - if (data == null) return ""; - var t = data.GetType(); - // Prefer decoding the raw fields first to control encoding (Unicode for Vietnamese), - // then fall back to any string properties if needed. - var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance); - if (fieldName != null && fieldName.FieldType == typeof(ushort[])) - { - var arr = fieldName.GetValue(data) as ushort[]; - // Vietnamese names are stored as wide chars; decode as Unicode first. - var s = ByteToStringUtils.UshortArrayToUnicodeString(arr); - if (!string.IsNullOrEmpty(s)) return s; - // Fallback to legacy CP936 if Unicode was empty - s = ByteToStringUtils.UshortArrayToCP936String(arr); - if (!string.IsNullOrEmpty(s)) return s; - } - - var fieldReal = t.GetField("realname", BindingFlags.Public | BindingFlags.Instance); - if (fieldReal != null && fieldReal.FieldType == typeof(byte[])) - { - var arr = fieldReal.GetValue(data) as byte[]; - var s = ByteToStringUtils.ByteArrayToCP936String(arr); - if (!string.IsNullOrEmpty(s)) return s; - } - - var prop = t.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance); - if (prop != null && prop.PropertyType == typeof(string)) - { - var val = prop.GetValue(data) as string; - if (!string.IsNullOrEmpty(val)) return val; - } - var propReal = t.GetProperty("RealName", BindingFlags.Public | BindingFlags.Instance); - if (propReal != null && propReal.PropertyType == typeof(string)) - { - var val = propReal.GetValue(data) as string; - if (!string.IsNullOrEmpty(val)) return val; - } - return ""; - } - - private static string TryFindNameByScanningArrays(object edm, uint id) - { - try - { - var fields = edm.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); - foreach (var f in fields) - { - if (!f.FieldType.IsArray) continue; - var arr = f.GetValue(edm) as Array; - if (arr == null || arr.Length == 0) continue; - var elemType = f.FieldType.GetElementType(); - var idField = elemType.GetField("id", BindingFlags.Public | BindingFlags.Instance); - if (idField == null || idField.FieldType != typeof(uint)) continue; - for (int i = 0; i < arr.Length; i++) - { - var element = arr.GetValue(i); - if (element == null) continue; - var value = (uint)idField.GetValue(element); - if (value == id) - { - var name = ExtractNameFromElement(element); - if (!string.IsNullOrEmpty(name)) return name; - var toStr = element.ToString(); - if (!string.IsNullOrEmpty(toStr)) return toStr; - } - } - } - } - catch { } - return ""; - } - - public static bool TryParseInventoryDetail(byte[] buffer, out byte byPackage, out byte ivtrSize, out List items) - { - byPackage = 0; - ivtrSize = 0; - items = null; - if (buffer == null || buffer.Length < 6) - { - return false; - } - - int index = 0; - byPackage = buffer[index++]; - 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) - { - items = new List(); - return true; - } - - byte[] content = new byte[contentBytes]; - Buffer.BlockCopy(buffer, index, content, 0, contentBytes); - - // Parse S2C::cmd_own_ivtr_detail_info.content - int ci = 0; - if (contentBytes < 4) - { - return false; - } - int numItems = BitConverter.ToInt32(content, ci); ci += 4; - items = new List(numItems > 0 ? numItems : 0); - - for (int i = 0; i < numItems; i++) - { - // Ensure enough bytes for fixed part: index, tid, expire, state, amount, crc(2), len(2) => 4*5 + 2 + 2 = 24 bytes - if (ci + 24 > contentBytes) - { - return false; - } - int slotIndex = BitConverter.ToInt32(content, ci); ci += 4; - if (slotIndex < 0) - { - // Skip invalid slot but continue parsing to keep stream in sync - // Still need to consume fields even if slot is negative - int skipTid = BitConverter.ToInt32(content, ci); ci += 4; - int skipExpire = BitConverter.ToInt32(content, ci); ci += 4; - int skipState = BitConverter.ToInt32(content, ci); ci += 4; - int skipAmt = BitConverter.ToInt32(content, ci); ci += 4; - ushort skipCrc = BitConverter.ToUInt16(content, ci); ci += 2; - ushort skipLen = BitConverter.ToUInt16(content, ci); ci += 2; - if (skipLen > 0) - { - if (ci + skipLen > contentBytes) return false; - ci += skipLen; - } - continue; - } - - int tid = BitConverter.ToInt32(content, ci); ci += 4; - int expireDate = BitConverter.ToInt32(content, ci); ci += 4; - int state = BitConverter.ToInt32(content, ci); ci += 4; - int amount = BitConverter.ToInt32(content, ci); ci += 4; - ushort crc = BitConverter.ToUInt16(content, ci); ci += 2; - ushort extraLen = BitConverter.ToUInt16(content, ci); ci += 2; - - byte[] extra = null; - if (extraLen > 0) - { - if (ci + extraLen > contentBytes) - { - return false; - } - extra = new byte[extraLen]; - Buffer.BlockCopy(content, ci, extra, 0, extraLen); - ci += extraLen; - } - - var item = new InventoryItemData - { - Package = byPackage, - Slot = slotIndex, - TemplateId = tid, - ExpireDate = expireDate, - State = state, - Count = amount, - Crc = crc, - Content = extra - }; - items.Add(item); - } - - return true; - } public static void UpdatePack(byte byPackage, int ivtrSize, IEnumerable items) { @@ -274,33 +52,10 @@ namespace PerfectWorld.Scripts.Managers } } } - OnPackUpdated?.Invoke(byPackage, slots); - // Log this pack's items LogPackInternal(byPackage, ivtrSize, slots); } - public static void LogPack(byte byPackage) - { - if (_itemsByPackage.TryGetValue(byPackage, out var slots)) - { - int size = _packSizeByPackage.TryGetValue(byPackage, out var s) ? s : 0; - LogPackInternal(byPackage, size, slots); - } - else - { - Debug.Log($"[Inventory] Pack {GetPackageName(byPackage)}({byPackage}) has no data yet."); - } - } - - public static void LogAllItems() - { - foreach (var kv in _itemsByPackage) - { - int size = _packSizeByPackage.TryGetValue(kv.Key, out var s) ? s : 0; - LogPackInternal(kv.Key, size, kv.Value); - } - } private static void LogPackInternal(byte byPackage, int ivtrSize, IReadOnlyDictionary slots) { @@ -313,8 +68,8 @@ namespace PerfectWorld.Scripts.Managers foreach (var kv in slots) { var it = kv.Value; - string itemName = ResolveItemName(it.TemplateId); - string extraHex = it.Content != null && it.Content.Length > 0 ? BytesToHex(it.Content, MaxContentHexToLog) : ""; + 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 : "")}" @@ -322,191 +77,56 @@ namespace PerfectWorld.Scripts.Managers } } - public static IReadOnlyDictionary GetPack(byte byPackage) + public static void LogInventoryPacket(string tag, byte[] buffer, int hostId) { - return _itemsByPackage.TryGetValue(byPackage, out var dict) ? dict : null; - } - - public static int GetPackSize(byte byPackage) - { - return _packSizeByPackage.TryGetValue(byPackage, out var size) ? size : 0; - } - - public static IEnumerable GetAllItems() - { - foreach (var pack in _itemsByPackage.Values) + if (buffer == null) { - foreach (var kv in pack) - { - yield return kv.Value; - } + 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 query helpers for items/inventory properties ====== - public static bool TryGetItem(byte byPackage, int slot, out InventoryItemData item) + public static void LogInventoryRaw(string tag, byte[] buffer) { - item = null; - if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) - { - if (dict.TryGetValue(slot, out var it)) - { - item = it; - return true; - } - } - return false; + Debug.Log($"[Inventory] {tag}: RAW HEX (len={buffer?.Length ?? 0})=\n{(buffer == null ? "" : BitConverter.ToString(buffer))}"); } - public static InventoryItemData GetItemOrNull(byte byPackage, int slot) - { - return (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict.TryGetValue(slot, out var it)) ? it : null; - } - - public static IEnumerable GetItemsInPack(byte byPackage) - { - if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) - { - return dict.Values; - } - return Array.Empty(); - } - - public static int GetUsedSlots(byte byPackage) - { - return _itemsByPackage.TryGetValue(byPackage, out var dict) ? dict.Count : 0; - } - - public static int GetFreeSlots(byte byPackage) - { - int size = GetPackSize(byPackage); - int used = GetUsedSlots(byPackage); - int free = size - used; - return free > 0 ? free : 0; - } - - public static IEnumerable GetSlotsOfTemplate(byte byPackage, int templateId) - { - if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) - { - foreach (var kv in dict) - { - if (kv.Value != null && kv.Value.TemplateId == templateId) - { - yield return kv.Key; - } - } - } - } - - public static InventoryItemData FindFirstItemByTemplate(byte byPackage, int templateId) - { - if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) - { - foreach (var kv in dict) - { - if (kv.Value != null && kv.Value.TemplateId == templateId) - { - return kv.Value; - } - } - } - return null; - } - - public static int GetCountOfTemplateInPack(byte byPackage, int templateId) - { - int total = 0; - if (_itemsByPackage.TryGetValue(byPackage, out var dict) && dict != null) - { - foreach (var item in dict.Values) - { - if (item != null && item.TemplateId == templateId) - { - total += item.Count; - } - } - } - return total; - } - - public static int GetTotalUsedSlotsAcrossPacks() - { - int total = 0; - foreach (var dict in _itemsByPackage.Values) - { - total += dict?.Count ?? 0; - } - return total; - } - - public static int GetTotalPackSizeAcrossPacks() - { - int total = 0; - foreach (var kv in _packSizeByPackage) - { - total += kv.Value; - } - return total; - } - - public static int GetTotalFreeSlotsAcrossPacks() - { - int free = 0; - foreach (var kv in _packSizeByPackage) - { - byte pkg = kv.Key; - free += GetFreeSlots(pkg); - } - return free; - } - - public static IEnumerable GetAvailablePackages() - { - return _itemsByPackage.Keys; - } - - public static bool FindFirstTemplateAcrossPacks(int templateId, out byte byPackage, out int slot, out InventoryItemData item) - { - byPackage = 0; - slot = -1; - item = null; - foreach (var pkg in _itemsByPackage) - { - foreach (var kv in pkg.Value) - { - if (kv.Value != null && kv.Value.TemplateId == templateId) - { - byPackage = pkg.Key; - slot = kv.Key; - item = kv.Value; - return true; - } - } - } - return false; - } - - public static int GetTotalCountOfTemplateAcrossPacks(int templateId) - { - int total = 0; - foreach (var dict in _itemsByPackage.Values) - { - foreach (var it in dict.Values) - { - if (it != null && it.TemplateId == templateId) - { - total += it.Count; - } - } - } - return total; - } - - public static string GetTemplateName(int templateId) - { - return ResolveItemName(templateId) ?? string.Empty; - } } } diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs new file mode 100644 index 0000000000..78b2ced562 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs @@ -0,0 +1,238 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + +namespace PerfectWorld.Scripts.Managers +{ + public class EC_InventoryUI : MonoBehaviour + { + [Header("UI References")] + [SerializeField] private GridLayoutGroup gridLayoutGroup; + [SerializeField] private GameObject inventoryButtonPrefab; + [SerializeField] private Transform inventoryContainer; + + [Header("Inventory Settings")] + [SerializeField] private byte targetPackage = 0; // 0 = PACK_INVENTORY, 1 = PACK_EQUIPMENT, 2 = PACK_TASKINVENTORY + [SerializeField] private bool autoRefresh = true; + [SerializeField] private float refreshInterval = 1.0f; + + private List inventoryButtons = new List(); + private float lastRefreshTime; + + private void Start() + { + InitializeUI(); + RefreshInventory(); + } + + private void Update() + { + if (autoRefresh && Time.time - lastRefreshTime >= refreshInterval) + { + RefreshInventory(); + } + } + + private void InitializeUI() + { + if (gridLayoutGroup == null) + { + Debug.LogError("[InventoryUI] GridLayoutGroup is not assigned!"); + return; + } + + if (inventoryButtonPrefab == null) + { + Debug.LogError("[InventoryUI] Inventory Button Prefab is not assigned!"); + return; + } + + if (inventoryContainer == null) + { + inventoryContainer = gridLayoutGroup.transform; + } + } + + public void RefreshInventory() + { + lastRefreshTime = Time.time; + ClearInventoryButtons(); + + // Get inventory data from EC_Inventory + var inventoryData = GetInventoryData(targetPackage); + + if (inventoryData == null || inventoryData.Count == 0) + { + Debug.Log($"[InventoryUI] No items found in package {targetPackage}"); + return; + } + + // Create buttons for each inventory item + foreach (var item in inventoryData.Values.OrderBy(x => x.Slot)) + { + CreateInventoryButton(item); + } + + Debug.Log($"[InventoryUI] Refreshed inventory with {inventoryData.Count} items"); + } + + private Dictionary GetInventoryData(byte package) + { + // Access the private _itemsByPackage dictionary from EC_Inventory using reflection + var inventoryType = typeof(EC_Inventory); + var itemsByPackageField = inventoryType.GetField("_itemsByPackage", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + + if (itemsByPackageField == null) + { + Debug.LogError("[InventoryUI] Could not access _itemsByPackage field from EC_Inventory"); + return new Dictionary(); + } + + var itemsByPackage = itemsByPackageField.GetValue(null) as Dictionary>; + if (itemsByPackage == null) + { + Debug.LogError("[InventoryUI] _itemsByPackage is null"); + return new Dictionary(); + } + + if (itemsByPackage.TryGetValue(package, out var packageItems)) + { + return packageItems; + } + + return new Dictionary(); + } + + private void CreateInventoryButton(InventoryItemData itemData) + { + if (inventoryButtonPrefab == null) return; + + // Instantiate button + GameObject buttonObj = Instantiate(inventoryButtonPrefab, inventoryContainer); + inventoryButtons.Add(buttonObj); + + // Get button components + Button button = buttonObj.GetComponent