Files
test/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
T
2025-12-08 16:15:01 +07:00

1007 lines
36 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
using System.Text;
using CSNetwork.GPDataType;
using BrewMonster.Network;
using BrewMonster;
using BrewMonster.Common;
using ModelRenderer.Scripts.GameData;
using PerfectWorld.Scripts.Managers;
using BrewMonster.Scripts;
namespace BrewMonster.Scripts.Managers
{
public class EC_InventoryUI : MonoBehaviour
{
[Header("Pack Buttons (assign in Inspector)")]
[SerializeField] private List<Button> inventoryPackButtons = new List<Button>(); // byPackage: 0
[SerializeField] private List<Button> equipmentPackButtons = new List<Button>(); // byPackage: 1
[SerializeField] private List<Button> fashionPackButtons = new List<Button>(); // byPackage: 3
[Header("Detail Panel (assign in Inspector)")]
[SerializeField] private GameObject detailPanelRoot;
[SerializeField] private Vector2 detailPanelOffset = new Vector2(20f, 0f);
[SerializeField] private bool hideDetailOnStart = true;
[SerializeField] private TextOutlet nameText;
[SerializeField] private TextOutlet descriptionText;
[SerializeField] private TextOutlet extendedDescText;
[SerializeField] private Button equipButton;
[SerializeField] private Button dropButton;
[Header("Inventory Settings")]
[SerializeField] private bool autoRefresh = true;
[SerializeField] private float refreshInterval = 1.0f;
[SerializeField] private bool showEquipmentDetails = true;
[Header("Money UI (assign any text fields to mirror money amount)")]
[SerializeField] private List<UnityEngine.UI.Text> moneyTextsLegacy = new List<UnityEngine.UI.Text>();
[SerializeField] private List<TMPro.TextMeshProUGUI> moneyTextsTMP = new List<TMPro.TextMeshProUGUI>();
[Header("Cash UI (assign any text fields to mirror cash amount)")]
[SerializeField] private List<UnityEngine.UI.Text> cashTextsLegacy = new List<UnityEngine.UI.Text>();
[SerializeField] private List<TMPro.TextMeshProUGUI> cashTextsTMP = new List<TMPro.TextMeshProUGUI>();
private float lastRefreshTime;
// Pending currency cache for when UI is not yet active
private static bool s_hasPendingMoney;
private static ulong s_pendingMoneyAmount;
private static ulong s_pendingMoneyMaxAmount;
private static bool s_hasPendingCash;
private static int s_pendingCashAmount;
// Flags to prevent log spam for extended description warnings
// 防止扩展描述警告日志刷屏的标志
private static bool m_HasLoggedExtDescNull = false;
private static bool m_HasLoggedExtDescNotInit = false;
private static bool m_HasLoggedExtDescError = false;
private InventoryModel model;
private InventoryView view;
// === Text Formatting Methods ===
/// <summary>
/// Format text for TextMeshPro components with rich text support
/// </summary>
/// <param name="text">Raw text with formatting codes</param>
/// <returns>Formatted text for TextMeshPro</returns>
private static string FormatForTextMeshPro(string text)
{
return EC_Utility.FormatForTextMeshPro(text);
}
// Current selected item for equip/unequip operations
private byte currentSelectedPackage;
private int currentSelectedSlot;
private EC_IvtrItem currentSelectedItem;
private EC_IvtrEquip currentSelectedEquipment;
private const byte PKG_INVENTORY = 0;
private const byte PKG_EQUIPMENT = 1;
private const byte PKG_FASHION = 3; // Note: byPackage 3 used for Fashion
private void Awake()
{
model = new InventoryModel();
view = new InventoryView();
}
private void Start()
{
RefreshAll();
if (hideDetailOnStart)
{
ShowDetailPanel(false);
}
// Apply any pending currency values captured before the UI became active
ApplyPendingCurrency();
}
private void OnEnable()
{
// Ensure cached values are pushed when the UI is enabled
ApplyPendingCurrency();
}
private void Update()
{
if (autoRefresh && Time.time - lastRefreshTime >= refreshInterval)
{
RefreshAll();
}
}
public void RefreshAll()
{
lastRefreshTime = Time.time;
var invItems = model.GetInventoryData(PKG_INVENTORY);
var eqpItems = model.GetInventoryData(PKG_EQUIPMENT);
var fshItems = model.GetInventoryData(PKG_FASHION);
view.RenderPackage(inventoryPackButtons, invItems, PKG_INVENTORY, OnInventoryButtonClicked, GetDisplayTextForItem);
view.RenderPackage(equipmentPackButtons, eqpItems, PKG_EQUIPMENT, OnInventoryButtonClicked, GetDisplayTextForItem);
view.RenderPackage(fashionPackButtons, fshItems, PKG_FASHION, OnInventoryButtonClicked, GetDisplayTextForItem);
}
/// <summary>
/// Update all configured money text components with the current amount.
/// Call this when GET_OWN_MONEY arrives.
/// </summary>
public void UpdateMoney(ulong amount, ulong maxAmount)
{
string text = amount.ToString();
if (moneyTextsLegacy != null)
{
for (int i = 0; i < moneyTextsLegacy.Count; i++)
{
var t = moneyTextsLegacy[i];
if (t != null) t.text = text;
}
}
if (moneyTextsTMP != null)
{
for (int i = 0; i < moneyTextsTMP.Count; i++)
{
var t = moneyTextsTMP[i];
if (t != null) t.text = text;
}
}
}
/// <summary>
/// Update all configured cash text components with the current boutique cash amount.
/// Call this when PLAYER_CASH arrives.
/// </summary>
public void UpdateCash(int amount)
{
string text = amount.ToString();
if (cashTextsLegacy != null)
{
for (int i = 0; i < cashTextsLegacy.Count; i++)
{
var t = cashTextsLegacy[i];
if (t != null) t.text = text;
}
}
if (cashTextsTMP != null)
{
for (int i = 0; i < cashTextsTMP.Count; i++)
{
var t = cashTextsTMP[i];
if (t != null) t.text = text;
}
}
}
// Public static entry points to cache values when UI is unavailable
public static void CacheMoney(ulong amount, ulong maxAmount)
{
s_pendingMoneyAmount = amount;
s_pendingMoneyMaxAmount = maxAmount;
s_hasPendingMoney = true;
// If an instance exists (even inactive), push immediately so the value is ready
var all = Resources.FindObjectsOfTypeAll<EC_InventoryUI>();
if (all != null)
{
for (int i = 0; i < all.Length; i++)
{
var ui = all[i];
if (ui != null && ui.gameObject.scene.IsValid())
{
ui.ApplyPendingCurrency();
break;
}
}
}
}
public static void CacheCash(int amount)
{
s_pendingCashAmount = amount;
s_hasPendingCash = true;
// If an instance exists (even inactive), push immediately so the value is ready
var all = Resources.FindObjectsOfTypeAll<EC_InventoryUI>();
if (all != null)
{
for (int i = 0; i < all.Length; i++)
{
var ui = all[i];
if (ui != null && ui.gameObject.scene.IsValid())
{
ui.ApplyPendingCurrency();
break;
}
}
}
}
private void ApplyPendingCurrency()
{
if (s_hasPendingMoney)
{
UpdateMoney(s_pendingMoneyAmount, s_pendingMoneyMaxAmount);
}
if (s_hasPendingCash)
{
UpdateCash(s_pendingCashAmount);
}
}
private void OnInventoryButtonClicked(byte package, int slot)
{
UnityGameSession.RequestCheckSecurityPassWd("");
var data = model.GetInventoryData(package);
if (data != null && data.TryGetValue(slot, out var itemData))
{
// Store current selection for equip/unequip operations
currentSelectedPackage = package;
currentSelectedSlot = slot;
currentSelectedItem = itemData;
// Create equipment object if this is equipment
currentSelectedEquipment = CreateEquipmentFromItemData(itemData);
// Position detail panel near the clicked item button
PositionDetailPanelNearButton(package, slot);
FillDetailPanel(package, itemData);
}
else
{
ShowDetailPanel(false);
}
}
/// <summary>
/// Create EC_IvtrEquip object from InventoryItemData
/// </summary>
private EC_IvtrEquip CreateEquipmentFromItemData(EC_IvtrItem itemData)
{
if (itemData == null)
return null;
var equipment = new EC_IvtrEquip(itemData.m_tid, itemData.m_expire_date);
// Set basic properties (use default values since InventoryItemData doesn't have these)
equipment.Price = 0;
equipment.Count = itemData.m_iCount;
equipment.PriceScale = 1.0f;
equipment.ScaleType = 0;
// Parse item info if available (use Content field)
if (itemData.Content != null && itemData.Content.Length > 0)
{
equipment.SetItemInfo(itemData.Content, itemData.Content.Length);
}
return equipment;
}
private string GetDisplayTextForItem(int slot, EC_IvtrItem itemData)
{
if (itemData == null || itemData.m_iCount <= 0)
{
return string.Empty;
}
string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(itemData.m_tid);
string displayText = string.IsNullOrEmpty(itemName) ? $"Item {itemData.m_tid}" : itemName;
if (itemData.m_iCount > 1)
{
displayText += $" x{itemData.m_iCount}";
}
return displayText;
}
public void ToggleAutoRefresh()
{
autoRefresh = !autoRefresh;
}
public void SetRefreshInterval(float interval)
{
refreshInterval = Mathf.Max(0.1f, interval);
}
public void ToggleEquipmentDetails()
{
showEquipmentDetails = !showEquipmentDetails;
if (currentSelectedItem != null)
{
FillDetailPanel(currentSelectedPackage, currentSelectedItem);
}
}
public void OnEquipButtonClicked()
{
if (currentSelectedItem == null)
{
Debug.LogWarning("[InventoryUI] No item selected for equip/unequip operation");
return;
}
if (currentSelectedPackage == PKG_INVENTORY)
{
// Equipping from inventory
EquipItem();
}
else if (currentSelectedPackage == PKG_EQUIPMENT)
{
// Unequipping from equipment
UnequipItem();
}
else
{
Debug.LogWarning($"[InventoryUI] Equip/Unequip not supported for package {currentSelectedPackage}");
}
}
public void OnDropButtonClicked()
{
if (currentSelectedItem == null)
{
Debug.LogWarning("[InventoryUI] No item selected for drop operation");
return;
}
if (currentSelectedPackage == PKG_INVENTORY)
{
// Dropping from inventory
DropInventoryItem();
}
else if (currentSelectedPackage == PKG_EQUIPMENT)
{
// Dropping from equipment
DropEquipItem();
}
else
{
Debug.LogWarning($"[InventoryUI] Drop not supported for package {currentSelectedPackage}");
}
}
private void EquipItem()
{
if (currentSelectedItem == null) return;
// For equipping, we need to find an empty equipment slot
// Use the new method that checks for available slots (especially for finger items)
byte equipLocation = EC_IvtrType.GetAvailableEquipLocationForItem(currentSelectedItem.m_tid);
if (equipLocation >= (byte)IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR)
{
Debug.LogWarning($"[InventoryUI] Could not determine equip location for item {currentSelectedItem.m_tid}");
return;
}
// Call RequestEquipItemAsync with inventory slot and equip location
UnityGameSession.RequestEquipItemAsync((byte)currentSelectedSlot, equipLocation, () =>
{
Debug.Log($"[InventoryUI] Equip request sent for item {currentSelectedItem.m_tid} from slot {currentSelectedSlot} to equip location {equipLocation}");
// Refresh inventory after equip
RefreshAll();
});
}
private void UnequipItem()
{
if (currentSelectedItem == null) return;
// Find empty slot in PACK_INVENTORY
int emptySlot = FindEmptyInventorySlot();
if (emptySlot == -1)
{
Debug.LogWarning("[InventoryUI] No empty slots available in inventory for unequipping");
return;
}
// For unequipping, the equip location is the current equipment slot
// We can use the slot number as the equip location
byte equipLocation = (byte)currentSelectedSlot;
// Call RequestEquipItemAsync with empty inventory slot and current equip location
UnityGameSession.RequestEquipItemAsync((byte)emptySlot, equipLocation, () =>
{
Debug.Log($"[InventoryUI] Unequip request sent for item {currentSelectedItem.m_tid} from equip location {equipLocation} to inventory slot {emptySlot}");
// Refresh inventory after unequip
RefreshAll();
});
}
private void DropInventoryItem()
{
if (currentSelectedItem == null) return;
// Call RequestDropIvrtItem with slot index and amount
UnityGameSession.RequestDropIvrtItem((byte)currentSelectedSlot, 1);
Debug.Log($"[InventoryUI] Drop request sent for inventory item {currentSelectedItem.m_tid} from slot {currentSelectedSlot} with amount {currentSelectedItem.m_iCount}");
// Refresh inventory after drop
RefreshAll();
}
private void DropEquipItem()
{
if (currentSelectedItem == null) return;
// Call RequestDropEquipItem with slot index
UnityGameSession.RequestDropEquipItem((byte)currentSelectedSlot);
Debug.Log($"[InventoryUI] Drop request sent for equipment item {currentSelectedItem.m_tid} from slot {currentSelectedSlot}");
// Refresh inventory after drop
RefreshAll();
}
private int FindEmptyInventorySlot()
{
var inventoryData = model.GetInventoryData(PKG_INVENTORY);
if (inventoryData == null) return -1;
// Find first empty slot (assuming slots are numbered 0, 1, 2, ...)
for (int i = 0; i < 100; i++) // Assuming max 100 inventory slots
{
if (!inventoryData.ContainsKey(i))
{
return i;
}
}
return -1;
}
/// <summary>
/// Get item description from string table
/// </summary>
/// <param name="templateId">Item template ID</param>
/// <returns>Item description text or null if not found</returns>
private string GetItemDescription(int templateId)
{
try
{
// Prefer mapped message id if available
if (EC_Game.TryGetItemMsg(templateId, out int messageId, out int displayMode))
{
var itemDesc = EC_Game.GetItemDesc();
if (itemDesc != null && itemDesc.IsInitialized())
{
string description = itemDesc.GetWideString(messageId);
if (!string.IsNullOrEmpty(description))
{
return description;
}
}
}
// Fallback: try direct template id
{
var itemDesc = EC_Game.GetItemDesc();
if (itemDesc != null && itemDesc.IsInitialized())
{
string description = itemDesc.GetWideString(templateId);
if (!string.IsNullOrEmpty(description))
return description;
}
}
}
catch (System.Exception ex)
{
Debug.LogWarning($"[InventoryUI] Error getting item description for ID {templateId}: {ex.Message}");
}
return null;
}
/// <summary>
/// Get extended item description from string table
/// </summary>
/// <param name="templateId">Item template ID</param>
/// <returns>Extended item description text or null if not found</returns>
private string GetItemExtendedDescription(int templateId)
{
try
{
// Prefer mapped message id if available
if (EC_Game.TryGetItemMsg(templateId, out int messageId, out int displayMode))
{
var itemExtDesc = EC_Game.GetItemExtDesc();
if (itemExtDesc != null && itemExtDesc.IsInitialized())
{
string extendedDesc = itemExtDesc.GetWideString(messageId);
if (!string.IsNullOrEmpty(extendedDesc))
{
return extendedDesc;
}
}
}
// Fallback: direct template id
{
var itemExtDesc = EC_Game.GetItemExtDesc();
if (itemExtDesc != null && itemExtDesc.IsInitialized())
{
string extendedDesc = itemExtDesc.GetWideString(templateId);
if (!string.IsNullOrEmpty(extendedDesc))
return extendedDesc;
}
}
}
catch (System.Exception ex)
{
Debug.LogWarning($"[InventoryUI] Error getting extended item description for ID {templateId}: {ex.Message}");
}
return null;
}
/// <summary>
/// Get user-friendly text for item state
/// </summary>
/// <param name="state">Item state value</param>
/// <returns>Human-readable state text</returns>
private string GetItemStateText(int state)
{
switch (state)
{
case 0: return "Normal";
case 1: return "Equipped";
case 2: return "Broken";
case 3: return "Locked";
default: return $"State: {state}";
}
}
// Equipment indices and resolution helpers moved to EC_IvtrType
// === MVC: Model ===
private class InventoryModel
{
public Dictionary<int, EC_IvtrItem> GetInventoryData(byte package)
{
// Read from host player's per-package CECInventory instance
var host = CECGameRun.Instance?.GetHostPlayer();
var inv = host?.GetInventory(package);
var result = new Dictionary<int, EC_IvtrItem>();
if (inv == null)
return result;
int size = inv.GetSize();
for (int i = 0; i < size; i++)
{
var item = inv.GetItem(i, false);
if (item != null)
result[i] = item;
}
return result;
}
}
// === MVC: View ===
private class InventoryView
{
private static readonly Dictionary<Image, Sprite> _defaultSprites = new Dictionary<Image, Sprite>();
public void RenderPackage(List<Button> buttons, Dictionary<int, EC_IvtrItem> items, byte package, System.Action<byte, int> onClick, System.Func<int, EC_IvtrItem, string> getDisplayText)
{
if (buttons == null)
{
return;
}
for (int slot = 0; slot < buttons.Count; slot++)
{
var button = buttons[slot];
if (button == null)
{
continue;
}
EC_IvtrItem itemData = null;
bool hasItem = items != null && items.TryGetValue(slot, out itemData);
button.onClick.RemoveAllListeners();
int capturedSlot = slot;
button.onClick.AddListener(() => onClick(package, capturedSlot));
// Optional visual tweaks based on state/count
var image = button.GetComponent<Image>();
if (image != null)
{
// Store the default sprite if we haven't seen this image before
if (!_defaultSprites.ContainsKey(image))
{
_defaultSprites[image] = image.sprite;
}
// Set icon sprite based on item TemplateId
if (hasItem && itemData != null && itemData.m_iCount > 0)
{
var sprite = EC_IvtrItemUtils.Instance.ResolveItemIconSprite(itemData.m_tid);
image.sprite = sprite;
image.enabled = true;
}
else
{
// Restore the default sprite instead of setting to null
image.sprite = _defaultSprites[image];
image.enabled = true;
}
}
}
}
}
// === Detail Panel Helpers ===
[System.Serializable]
private class TextOutlet
{
[SerializeField] public Text legacy;
[SerializeField] public TMPro.TextMeshProUGUI tmp;
public void Set(string value)
{
if (legacy != null)
{
legacy.text = EC_Utility.FormatForLegacyText(value ?? string.Empty);
}
if (tmp != null)
{
tmp.text = FormatForTextMeshPro(value ?? string.Empty);
}
}
/// <summary>
/// Set text with explicit formatting preference
/// </summary>
/// <param name="value">Raw text with formatting codes</param>
/// <param name="preferTextMeshPro">Whether to prefer TextMeshPro formatting</param>
public void SetFormatted(string value, bool preferTextMeshPro = true)
{
string formattedText = preferTextMeshPro ?
FormatForTextMeshPro(value ?? string.Empty) :
EC_Utility.FormatForLegacyText(value ?? string.Empty);
if (legacy != null)
{
legacy.text = formattedText;
}
if (tmp != null)
{
tmp.text = formattedText;
}
}
}
public void RefreshLayout(GameObject gameObject)
{
var parent = gameObject.GetComponent<RectTransform>();
// Force Unity to rebuild layout immediately
parent.ForceUpdateRectTransforms();
LayoutRebuilder.ForceRebuildLayoutImmediate(parent);
}
private void ShowDetailPanel(bool show)
{
if (detailPanelRoot != null)
{
detailPanelRoot.SetActive(show);
RefreshLayout(detailPanelRoot);
}
}
private Button GetButtonForSlot(byte package, int slot)
{
List<Button> list = null;
switch (package)
{
case PKG_INVENTORY:
list = inventoryPackButtons;
break;
case PKG_EQUIPMENT:
list = equipmentPackButtons;
break;
case PKG_FASHION:
list = fashionPackButtons;
break;
}
if (list == null || slot < 0 || slot >= list.Count)
{
return null;
}
return list[slot];
}
private void PositionDetailPanelNearButton(byte package, int slot)
{
if (detailPanelRoot == null)
{
return;
}
var panelRect = detailPanelRoot.transform as RectTransform;
if (panelRect == null)
{
return;
}
var button = GetButtonForSlot(package, slot);
if (button == null)
{
return;
}
var buttonRect = button.transform as RectTransform;
if (buttonRect == null)
{
return;
}
var canvas = panelRect.GetComponentInParent<Canvas>();
if (canvas == null)
{
return;
}
var parentRect = panelRect.parent as RectTransform;
if (parentRect == null)
{
return;
}
Camera eventCamera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
Vector3 worldCenter = buttonRect.TransformPoint(buttonRect.rect.center);
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(eventCamera, worldCenter);
Vector2 localPoint;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRect, screenPoint, eventCamera, out localPoint))
{
return;
}
float btnHalfW = buttonRect.rect.width * 0.5f;
float panelW = panelRect.rect.width;
float panelH = panelRect.rect.height;
float pivotX = panelRect.pivot.x;
float pivotY = panelRect.pivot.y;
// Compute right-placement candidate (panel's left edge at button's right edge + offset)
float leftEdgeRightPlacement = localPoint.x + btnHalfW + detailPanelOffset.x;
float candidateXRight = leftEdgeRightPlacement + pivotX * panelW;
// Compute left-placement candidate (panel's right edge at button's left edge - offset)
float rightEdgeLeftPlacement = localPoint.x - btnHalfW - detailPanelOffset.x;
float candidateXLeft = rightEdgeLeftPlacement - (1f - pivotX) * panelW;
// Vertical clamping honoring pivot
float minY = parentRect.rect.yMin + pivotY * panelH;
float maxY = parentRect.rect.yMax - (1f - pivotY) * panelH;
float candidateY = Mathf.Clamp(localPoint.y + detailPanelOffset.y, minY, maxY);
// Choose side based on available space
float rightEdgeOfRight = candidateXRight + (1f - pivotX) * panelW;
float canvasRight = parentRect.rect.xMax;
float canvasLeft = parentRect.rect.xMin;
float leftEdgeOfLeft = candidateXLeft - pivotX * panelW;
Vector2 finalPos;
if (rightEdgeOfRight <= canvasRight)
{
finalPos = new Vector2(candidateXRight, candidateY);
}
else if (leftEdgeOfLeft >= canvasLeft)
{
finalPos = new Vector2(candidateXLeft, candidateY);
}
else
{
// Fallback: clamp within canvas horizontally
float minX = canvasLeft + pivotX * panelW;
float maxX = canvasRight - (1f - pivotX) * panelW;
finalPos = new Vector2(Mathf.Clamp(candidateXRight, minX, maxX), candidateY);
}
panelRect.anchoredPosition = finalPos;
}
private void FillDetailPanel(byte package, EC_IvtrItem item)
{
if (item == null)
{
ShowDetailPanel(false);
return;
}
// Get user-friendly name
string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(item.m_tid);
nameText?.Set(string.IsNullOrEmpty(itemName) ? $"Item {item.m_tid}" : itemName);
// Centralised description:
// - For equipment, prefer EC_IvtrEquip description (includes stats, addons, sockets, etc.)
// - For other items, use EC_IvtrItem.GetDesc which reads from string tables.
string fullDesc = null;
if (showEquipmentDetails && currentSelectedEquipment != null)
{
fullDesc = currentSelectedEquipment.GetNormalDesc();
}
else
{
fullDesc = item.GetDesc();
}
if (!string.IsNullOrEmpty(fullDesc))
{
// Replace C++ style "\r" line separators with real newlines for TMP
fullDesc = fullDesc.Replace("\\r", "\n");
descriptionText?.Set(fullDesc);
}
else
{
// Fallback to legacy string-table description if centralised pipeline returns empty
string itemDescription = GetItemDescription(item.m_tid);
descriptionText?.Set(itemDescription ?? "No description available");
}
// Get extended description exactly like C++ code: g_pGame->GetItemExtDesc(m_tid)
// C++ code doesn't check IsInitialized() - it just calls GetWideString() directly
// 完全按照C++代码获取扩展描述:g_pGame->GetItemExtDesc(m_tid)
// C++代码不检查IsInitialized() - 它直接调用GetWideString()
string szExtDesc = null;
try
{
var itemExtDescTab = EC_Game.GetItemExtDesc();
if (itemExtDescTab != null)
{
// First try to get mapped message ID (like TryGetItemExtDesc does)
// 首先尝试获取映射的消息ID(如TryGetItemExtDesc所做)
if (EC_Game.TryGetItemMsg(item.m_tid, out int messageId, out int displayMode))
{
szExtDesc = itemExtDescTab.GetWideString(messageId);
}
// Fallback: direct lookup using tid (exactly like C++: m_ItemExtDesc.GetWideString(tid))
// 回退:直接使用tid查找(完全像C++m_ItemExtDesc.GetWideString(tid)
if (string.IsNullOrEmpty(szExtDesc))
{
szExtDesc = itemExtDescTab.GetWideString(item.m_tid);
}
}
}
catch (System.Exception ex)
{
// Only log once to avoid spam
// 仅记录一次以避免垃圾日志
if (!m_HasLoggedExtDescError)
{
Debug.LogWarning($"[InventoryUI] Error getting extended description: {ex.Message}");
m_HasLoggedExtDescError = true;
}
}
// Display extended description if found (exactly like C++ checks: if (!szExtDesc || !szExtDesc[0]))
// 如果找到扩展描述则显示(完全像C++检查:if (!szExtDesc || !szExtDesc[0])
string displayText = !string.IsNullOrEmpty(szExtDesc)
? szExtDesc.Replace("\\r", "\n")
: "";
// Debug logging to diagnose issues
// 调试日志以诊断问题
if (string.IsNullOrEmpty(displayText))
{
Debug.Log($"[InventoryUI] Extended description is empty for tid={item.m_tid}. szExtDesc was null/empty.");
}
else
{
Debug.Log($"[InventoryUI] Found extended description for tid={item.m_tid}, length={displayText.Length}");
}
// Setup equip and drop buttons
SetupEquipButton(package, item);
SetupDropButton(package, item);
// Show panel first
// 先显示面板
ShowDetailPanel(true);
// Set text directly - if this causes rebuild issues, we'll use coroutine
// 直接设置文本 - 如果这导致重建问题,我们将使用协程
if (extendedDescText != null)
{
extendedDescText.Set(displayText);
Debug.Log($"[InventoryUI] Set extended description text, extendedDescText is {(extendedDescText == null ? "null" : "not null")}");
}
else
{
Debug.LogWarning("[InventoryUI] extendedDescText is null! Check Inspector assignment.");
}
}
private void SetupEquipButton(byte package, EC_IvtrItem item)
{
if (equipButton == null) return;
// Clear previous listeners
equipButton.onClick.RemoveAllListeners();
equipButton.onClick.AddListener(OnEquipButtonClicked);
// Set button text and visibility based on package
var buttonText = equipButton.GetComponentInChildren<UnityEngine.UI.Text>();
if (buttonText == null)
{
var tmpText = equipButton.GetComponentInChildren<TMPro.TextMeshProUGUI>();
if (tmpText != null)
{
if (package == PKG_INVENTORY)
{
tmpText.text = "Equip";
equipButton.gameObject.SetActive(true);
}
else if (package == PKG_EQUIPMENT)
{
tmpText.text = "UnEquip";
equipButton.gameObject.SetActive(true);
}
else
{
equipButton.gameObject.SetActive(false);
}
}
}
else
{
if (package == PKG_INVENTORY)
{
buttonText.text = "Equip";
equipButton.gameObject.SetActive(true);
}
else if (package == PKG_EQUIPMENT)
{
buttonText.text = "UnEquip";
equipButton.gameObject.SetActive(true);
}
else
{
equipButton.gameObject.SetActive(false);
}
}
}
private void SetupDropButton(byte package, EC_IvtrItem item)
{
if (dropButton == null) return;
// Clear previous listeners
dropButton.onClick.RemoveAllListeners();
dropButton.onClick.AddListener(OnDropButtonClicked);
// Set button text and visibility based on package
var buttonText = dropButton.GetComponentInChildren<UnityEngine.UI.Text>();
if (buttonText == null)
{
var tmpText = dropButton.GetComponentInChildren<TMPro.TextMeshProUGUI>();
if (tmpText != null)
{
if (package == PKG_INVENTORY || package == PKG_EQUIPMENT)
{
tmpText.text = "Drop";
dropButton.gameObject.SetActive(true);
}
else
{
dropButton.gameObject.SetActive(false);
}
}
}
else
{
if (package == PKG_INVENTORY || package == PKG_EQUIPMENT)
{
buttonText.text = "Drop";
dropButton.gameObject.SetActive(true);
}
else
{
dropButton.gameObject.SetActive(false);
}
}
}
}
}