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