From 6392baf369675feaaf8ae076463987bfa13953c3 Mon Sep 17 00:00:00 2001
From: HungDK <>
Date: Mon, 8 Dec 2025 16:15:01 +0700
Subject: [PATCH] Add ext description text
---
.../Scripts/Managers/EC_InventoryUI.cs | 75 +++-
.../Scripts/Managers/EC_IvtrItem.cs | 350 +++++++++++++++++-
.../Scripts/UI/NPCShopDetailPanel.cs | 91 +++--
Assets/Prefabs/UI/DialogNPCShop.prefab | 4 +-
Assets/Prefabs/UI/InventoryUI.prefab | 162 +++++++-
Assets/Prefabs/UI/SkillUI.prefab | 18 +-
Assets/Scripts/CECStringTab.cs | 89 ++++-
7 files changed, 722 insertions(+), 67 deletions(-)
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
index a36210c880..70d4966f54 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
@@ -53,6 +53,12 @@ namespace BrewMonster.Scripts.Managers
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;
@@ -828,16 +834,81 @@ namespace BrewMonster.Scripts.Managers
{
// Fallback to legacy string-table description if centralised pipeline returns empty
string itemDescription = GetItemDescription(item.m_tid);
- string itemExtendedDesc = GetItemExtendedDescription(item.m_tid);
descriptionText?.Set(itemDescription ?? "No description available");
- extendedDescText?.Set(itemExtendedDesc ?? "");
+ }
+
+ // 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)
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs
index 16b78930e5..8c2734eada 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs
@@ -1250,14 +1250,180 @@ namespace BrewMonster.Scripts.Managers
m_strDesc += "\n";
}
+ // Add extend description to description string / 添加扩展描述到描述字符串
protected void AddExtDescText()
{
- // Extend description from item_ext_desc.txt via EC_Game
- string ext = TryGetItemExtDesc();
- if (!string.IsNullOrEmpty(ext))
+ // Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述
+ string szExtDesc = TryGetItemExtDesc();
+ // Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了
+ // if (!szExtDesc || !szExtDesc[0])
+ // return;
+
+ m_strDesc += "\\r";
+
+ bool bAddLine = true;
+ // Add special properties description / 添加特殊属性描述
+ var pDescTab = EC_Game.GetItemDesc();
+ // Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整
+ int green = 1000; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value
+
+ if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性
{
- AddDescText(0, true, ext);
+ // Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
+ // 精确的C++逻辑:(PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
+ if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0
+ || (!((m_iProcType & (int)ProcType.PROC_BINDING) != 0)
+ && ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0
+ || (m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0
+ || (m_iProcType & (int)ProcType.PROC_SELLABLE) != 0
+ || (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0
+ || (m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0
+ || (m_iProcType & (int)ProcType.PROC_USE) != 0
+ || (m_iProcType & (int)ProcType.PROC_DEADDROP) != 0
+ || (m_iProcType & (int)ProcType.PROC_OFFLINE) != 0
+ || (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)))
+ {
+ bAddLine = false;
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ string szCol = pDescTab.GetWideString(green);
+ if (!string.IsNullOrEmpty(szCol))
+ {
+ m_strDesc += szCol;
+ }
+ }
+
+ // Note: These message IDs are placeholders - adjust based on actual string table / 注意:这些消息ID是占位符 - 根据实际字符串表调整
+ if ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_DEAD_PROTECT placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2000); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_DROP placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2001); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_SELLABLE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_TRADE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2002); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_PLAYER_TRADE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2003); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_LEAVE_SCENE_DISAPEAR placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2004); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_USE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_USE_AFTER_PICK_UP placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2005); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_DEADDROP) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_DROP_WHEN_DEAD placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2006); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_OFFLINE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_DROP_WHEN_OFFLINE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2007); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_UNREPAIRABLE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2008); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0)
+ {
+ m_strDesc += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_USER_TRASH placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2009); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ m_strDesc += desc;
+ }
+ }
+ }
+ else
+ {
+ TrimLastReturn();
+ }
}
+ else
+ {
+ TrimLastReturn();
+ }
+
+ if (string.IsNullOrEmpty(szExtDesc))
+ return;
+
+ // Extend description is below special properties / 扩展描述在特殊属性下方
+ if (bAddLine)
+ m_strDesc += "\\r\\r";
+ else
+ m_strDesc += "\\r";
+ m_strDesc += szExtDesc;
}
protected void AddExpireTimeDesc()
@@ -1401,6 +1567,182 @@ namespace BrewMonster.Scripts.Managers
return string.Empty;
}
+ ///
+ /// Get extended description text for UI display.
+ /// This method mirrors AddExtDescText() logic but returns a string instead of modifying m_strDesc.
+ /// 此方法镜像AddExtDescText()逻辑,但返回字符串而不是修改m_strDesc
+ ///
+ public string GetExtendedDescText()
+ {
+ string result = string.Empty;
+
+ // Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述
+ string szExtDesc = TryGetItemExtDesc();
+ // Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了
+ // if (!szExtDesc || !szExtDesc[0])
+ // return;
+
+ result += "\\r";
+
+ bool bAddLine = true;
+ // Add special properties description / 添加特殊属性描述
+ var pDescTab = EC_Game.GetItemDesc();
+ // Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整
+ int green = 1000; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value
+
+ if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性
+ {
+ // Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
+ // 精确的C++逻辑:(PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
+ if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0
+ || (!((m_iProcType & (int)ProcType.PROC_BINDING) != 0)
+ && ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0
+ || (m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0
+ || (m_iProcType & (int)ProcType.PROC_SELLABLE) != 0
+ || (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0
+ || (m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0
+ || (m_iProcType & (int)ProcType.PROC_USE) != 0
+ || (m_iProcType & (int)ProcType.PROC_DEADDROP) != 0
+ || (m_iProcType & (int)ProcType.PROC_OFFLINE) != 0
+ || (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)))
+ {
+ bAddLine = false;
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ string szCol = pDescTab.GetWideString(green);
+ if (!string.IsNullOrEmpty(szCol))
+ {
+ result += szCol;
+ }
+ }
+
+ // Note: These message IDs are placeholders - adjust based on actual string table / 注意:这些消息ID是占位符 - 根据实际字符串表调整
+ if ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_DEAD_PROTECT placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2000); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_DROP placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2001); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_SELLABLE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_TRADE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2002); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_PLAYER_TRADE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2003); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_LEAVE_SCENE_DISAPEAR placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2004); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_USE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_USE_AFTER_PICK_UP placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2005); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_DEADDROP) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_DROP_WHEN_DEAD placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2006); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_OFFLINE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_DROP_WHEN_OFFLINE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2007); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_UNREPAIRABLE placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2008); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0)
+ {
+ result += "\\r";
+ if (pDescTab != null && pDescTab.IsInitialized())
+ {
+ // ITEMDESC_NO_USER_TRASH placeholder - adjust this value
+ string desc = pDescTab.GetWideString(2009); // Placeholder ID
+ if (!string.IsNullOrEmpty(desc))
+ result += desc;
+ }
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(szExtDesc))
+ return result;
+
+ // Extend description is below special properties / 扩展描述在特殊属性下方
+ if (bAddLine)
+ result += "\\r\\r";
+ else
+ result += "\\r";
+ result += szExtDesc;
+
+ return result;
+ }
+
#endregion
}
diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs
index eb2f69fa6c..1af4021df2 100644
--- a/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs
+++ b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs
@@ -6,6 +6,7 @@ using UnityEngine;
using UnityEngine.UI;
using TMPro;
using PerfectWorld.Scripts.Shop;
+using PerfectWorld.Scripts.Managers;
using BrewMonster.Scripts.Managers;
using BrewMonster.Network;
using CSNetwork.C2SCommand;
@@ -13,10 +14,9 @@ using CSNetwork.C2SCommand;
public class NPCShopDetailPanel : MonoBehaviour
{
[Header("UI Components")]
- public TextMeshProUGUI itemNameText;
- public TextMeshProUGUI itemDescriptionText;
+ public TextOutlet itemDescriptionText;
public Image itemIconImage;
- public TextMeshProUGUI itemPriceText;
+ //public TextMeshProUGUI itemPriceText;
[Header("Action Buttons")]
public Button closeButton;
@@ -55,15 +55,11 @@ public class NPCShopDetailPanel : MonoBehaviour
return;
}
- // Set item name
- if (itemNameText != null)
- itemNameText.text = currentItem.name;
-
// Set item description
if (itemDescriptionText != null)
{
string description = GetItemDescription((int)currentItem.id);
- itemDescriptionText.text = description;
+ itemDescriptionText.Set(description);
}
// Set price (use first available price)
@@ -80,8 +76,8 @@ public class NPCShopDetailPanel : MonoBehaviour
}
}
- if (itemPriceText != null)
- itemPriceText.text = price > 0 ? $"Price: {price}" : "Price: N/A";
+ // if (itemPriceText != null)
+ // itemPriceText.text = price > 0 ? $"Price: {price}" : "Price: N/A";
// Load icon
if (itemIconImage != null)
@@ -114,7 +110,7 @@ public class NPCShopDetailPanel : MonoBehaviour
}
}
- string GetItemDescription(int itemId)
+ string GetItemDescription(int itemId)
{
// First check if description is already in the item data
if (!string.IsNullOrEmpty(currentItem.desc))
@@ -122,27 +118,48 @@ public class NPCShopDetailPanel : MonoBehaviour
return currentItem.desc;
}
- // Otherwise, build description using the same central pipeline as inventory:
- try
+ // Try to use EC_IvtrEquip description for equipment items (like EC_InventoryUI does)
+ try
+ {
+ // Create EC_IvtrEquip for equipment items - this will work for weapons and armor
+ EC_IvtrEquip equipment = new EC_IvtrEquip(itemId, 0);
+
+ // For NPC shop items, we typically have static data only
+ // Try to get description using GetNormalDesc() which works with base stats
+ string equipDesc = equipment.GetNormalDesc();
+ if (!string.IsNullOrEmpty(equipDesc))
{
- EC_IvtrItem item = EC_IvtrItem.CreateItem(itemId, 0, 1);
- if (item != null)
+ // Replace C++ style "\r" line separators with real newlines for TMP
+ return equipDesc.Replace("\\r", "\n");
+ }
+ }
+ catch (System.Exception ex)
+ {
+ // If EC_IvtrEquip fails, fall through to EC_IvtrItem method
+ Debug.LogWarning($"[NPCShopDetailPanel] Failed to get equipment description for item {itemId}: {ex.Message}");
+ }
+
+ // Fallback: build description using EC_IvtrItem (for non-equipment items)
+ try
+ {
+ EC_IvtrItem item = EC_IvtrItem.CreateItem(itemId, 0, 1);
+ if (item != null)
+ {
+ // For NPC shop, we typically have static data only, so load from local DB
+ item.GetDetailDataFromLocal();
+ string description = item.GetDesc();
+ if (!string.IsNullOrEmpty(description))
{
- // For NPC shop, we typically have static data only, so load from local DB
- item.GetDetailDataFromLocal();
- string description = item.GetDesc();
- if (!string.IsNullOrEmpty(description))
- {
- return description.Replace("\\r", "\n");
- }
+ return description.Replace("\\r", "\n");
}
}
- catch (System.Exception ex)
- {
- Debug.LogWarning($"[NPCShopDetailPanel] Failed to get description for item {itemId}: {ex.Message}");
- }
+ }
+ catch (System.Exception ex)
+ {
+ Debug.LogWarning($"[NPCShopDetailPanel] Failed to get description for item {itemId}: {ex.Message}");
+ }
- return "No description available.";
+ return "No description available.";
}
void OnCloseClicked()
@@ -197,5 +214,25 @@ public class NPCShopDetailPanel : MonoBehaviour
if (buyButton != null)
buyButton.onClick.RemoveListener(OnBuyButtonClicked);
}
+
+ // === Text Outlet Helper ===
+ [System.Serializable]
+ public 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 = EC_Utility.FormatForTextMeshPro(value ?? string.Empty);
+ }
+ }
+ }
}
diff --git a/Assets/Prefabs/UI/DialogNPCShop.prefab b/Assets/Prefabs/UI/DialogNPCShop.prefab
index 847811f599..0ead223757 100644
--- a/Assets/Prefabs/UI/DialogNPCShop.prefab
+++ b/Assets/Prefabs/UI/DialogNPCShop.prefab
@@ -896,7 +896,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
itemNameText: {fileID: 2676848775259769377}
- itemDescriptionText: {fileID: 1509498505529622136}
+ itemDescriptionText:
+ legacy: {fileID: 0}
+ tmp: {fileID: 1509498505529622136}
itemIconImage: {fileID: 5729739685989188916}
itemPriceText: {fileID: 3533247489785110134}
closeButton: {fileID: 0}
diff --git a/Assets/Prefabs/UI/InventoryUI.prefab b/Assets/Prefabs/UI/InventoryUI.prefab
index bf0d1f3230..be567511ba 100644
--- a/Assets/Prefabs/UI/InventoryUI.prefab
+++ b/Assets/Prefabs/UI/InventoryUI.prefab
@@ -1899,7 +1899,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
- m_AnchoredPosition: {x: 21, y: -22.97}
+ m_AnchoredPosition: {x: 21, y: -0}
m_SizeDelta: {x: 472.5032, y: 0}
m_Pivot: {x: 0, y: 0.5}
--- !u!222 &2043307214318211948
@@ -1930,7 +1930,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_text: aaa
+ m_text:
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
@@ -4442,6 +4442,7 @@ RectTransform:
m_Children:
- {fileID: 9106031791145292554}
- {fileID: 1001152567372181051}
+ - {fileID: 1333165094145940333}
m_Father: {fileID: 5834405183358786743}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -7163,6 +7164,157 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+--- !u!1 &5371522206622176611
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1333165094145940333}
+ - component: {fileID: 7370426642012978972}
+ - component: {fileID: 37862130938576806}
+ - component: {fileID: 9183113038478938762}
+ m_Layer: 5
+ m_Name: Text (TMP) (2)
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1333165094145940333
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5371522206622176611}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 7205431771786927886}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 21, y: -57.98}
+ m_SizeDelta: {x: 465.7476, y: 0}
+ m_Pivot: {x: 0, y: 0.5}
+--- !u!222 &7370426642012978972
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5371522206622176611}
+ m_CullTransparentMesh: 1
+--- !u!114 &37862130938576806
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5371522206622176611}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_text:
+ m_isRightToLeft: 0
+ m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
+ m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
+ m_fontSharedMaterials: []
+ m_fontMaterial: {fileID: 0}
+ m_fontMaterials: []
+ m_fontColor32:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontColor: {r: 1, g: 1, b: 1, a: 1}
+ m_enableVertexGradient: 0
+ m_colorMode: 3
+ m_fontColorGradient:
+ topLeft: {r: 1, g: 1, b: 1, a: 1}
+ topRight: {r: 1, g: 1, b: 1, a: 1}
+ bottomLeft: {r: 1, g: 1, b: 1, a: 1}
+ bottomRight: {r: 1, g: 1, b: 1, a: 1}
+ m_fontColorGradientPreset: {fileID: 0}
+ m_spriteAsset: {fileID: 0}
+ m_tintAllSprites: 0
+ m_StyleSheet: {fileID: 0}
+ m_TextStyleHashCode: -1183493901
+ m_overrideHtmlColors: 0
+ m_faceColor:
+ serializedVersion: 2
+ rgba: 4294967295
+ m_fontSize: 36
+ m_fontSizeBase: 36
+ m_fontWeight: 400
+ m_enableAutoSizing: 0
+ m_fontSizeMin: 18
+ m_fontSizeMax: 72
+ m_fontStyle: 0
+ m_HorizontalAlignment: 1
+ m_VerticalAlignment: 512
+ m_textAlignment: 65535
+ m_characterSpacing: 0
+ m_wordSpacing: 0
+ m_lineSpacing: 0
+ m_lineSpacingMax: 0
+ m_paragraphSpacing: 0
+ m_charWidthMaxAdj: 0
+ m_TextWrappingMode: 1
+ m_wordWrappingRatios: 0.4
+ m_overflowMode: 0
+ m_linkedTextComponent: {fileID: 0}
+ parentLinkedComponent: {fileID: 0}
+ m_enableKerning: 0
+ m_ActiveFontFeatures: 6e72656b
+ m_enableExtraPadding: 0
+ checkPaddingRequired: 0
+ m_isRichText: 1
+ m_EmojiFallbackSupport: 1
+ m_parseCtrlCharacters: 1
+ m_isOrthographic: 1
+ m_isCullingEnabled: 0
+ m_horizontalMapping: 0
+ m_verticalMapping: 0
+ m_uvLineOffset: 0
+ m_geometrySortingOrder: 0
+ m_IsTextObjectScaleStatic: 0
+ m_VertexBufferAutoSizeReduction: 0
+ m_useMaxVisibleDescender: 1
+ m_pageToDisplay: 1
+ m_margin: {x: 0, y: 0, z: 0, w: 0}
+ m_isUsingLegacyAnimationComponent: 0
+ m_isVolumetricText: 0
+ m_hasFontAssetChanged: 0
+ m_baseMaterial: {fileID: 0}
+ m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+--- !u!114 &9183113038478938762
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5371522206622176611}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_HorizontalFit: 0
+ m_VerticalFit: 2
--- !u!1 &5460797107099608431
GameObject:
m_ObjectHideFlags: 0
@@ -8046,13 +8198,13 @@ MonoBehaviour:
hideDetailOnStart: 1
nameText:
legacy: {fileID: 0}
- tmp: {fileID: 7082730707602873357}
+ tmp: {fileID: 0}
descriptionText:
legacy: {fileID: 0}
tmp: {fileID: 6020258894941961325}
extendedDescText:
legacy: {fileID: 0}
- tmp: {fileID: 0}
+ tmp: {fileID: 37862130938576806}
equipButton: {fileID: 472698755110594484}
dropButton: {fileID: 540159372834342487}
autoRefresh: 1
@@ -11506,7 +11658,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
- m_AnchoredPosition: {x: 21, y: -68.909996}
+ m_AnchoredPosition: {x: 21, y: -28.989998}
m_SizeDelta: {x: 465.7476, y: 0}
m_Pivot: {x: 0, y: 0.5}
--- !u!222 &5347950336050242333
diff --git a/Assets/Prefabs/UI/SkillUI.prefab b/Assets/Prefabs/UI/SkillUI.prefab
index f3a3ab076f..3b6992f31c 100644
--- a/Assets/Prefabs/UI/SkillUI.prefab
+++ b/Assets/Prefabs/UI/SkillUI.prefab
@@ -1943,9 +1943,9 @@ RectTransform:
- {fileID: 4504331075840543341}
m_Father: {fileID: 1361524257611413148}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 0}
- m_AnchorMax: {x: 0, y: 0}
- m_AnchoredPosition: {x: 0, y: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 108.9562, y: -31.05}
m_SizeDelta: {x: 179.9124, y: 68.0217}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8804506040386004496
@@ -5768,9 +5768,9 @@ RectTransform:
- {fileID: 2027606699309904338}
m_Father: {fileID: 1361524257611413148}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 0}
- m_AnchorMax: {x: 0, y: 0}
- m_AnchoredPosition: {x: 0, y: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 468.781, y: -31.05}
m_SizeDelta: {x: 179.9124, y: 68.0217}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6741821173640675138
@@ -18122,9 +18122,9 @@ RectTransform:
- {fileID: 911293677621153352}
m_Father: {fileID: 1361524257611413148}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 0}
- m_AnchorMax: {x: 0, y: 0}
- m_AnchoredPosition: {x: 0, y: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 288.8686, y: -31.05}
m_SizeDelta: {x: 179.9124, y: 68.0217}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5623009994815814977
diff --git a/Assets/Scripts/CECStringTab.cs b/Assets/Scripts/CECStringTab.cs
index 5a3ba316a4..ae5f28ff3d 100644
--- a/Assets/Scripts/CECStringTab.cs
+++ b/Assets/Scripts/CECStringTab.cs
@@ -54,35 +54,74 @@ namespace BrewMonster
protected bool LoadANSIStrings(string resourceName)
{
- TextAsset textAsset = Resources.Load(resourceName);
- if (textAsset == null)
+ try
{
- Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
+ // If a real file path is provided (e.g. StreamingAssets), read directly from disk.
+ // 如果提供的是实际文件路径(例如 StreamingAssets),则直接从磁盘读取。
+ if (File.Exists(resourceName))
+ {
+ // ANSI tables are in CP936 in original PW; keep using CP936 decoder.
+ // 原版完美世界的ANSI表是CP936编码,这里保持一致。
+ byte[] bytes = File.ReadAllBytes(resourceName);
+ string content = ByteToStringUtils.ByteArrayToCP936String(bytes);
+ using var srFile = new StringReader(content);
+ return ParseIntoDict(srFile, isWide: false);
+ }
+
+ // Fallback to Resources (old behaviour).
+ // 回退到 Resources 加载(旧行为)。
+ TextAsset textAsset = Resources.Load(resourceName);
+ if (textAsset == null)
+ {
+ Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
+ return false;
+ }
+
+ string resContent = ByteToStringUtils.ByteArrayToCP936String(textAsset.bytes);
+ using var srRes = new StringReader(resContent);
+ return ParseIntoDict(srRes, isWide: false);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[CECStringTab] LoadANSIStrings failed for '{resourceName}': {e}");
return false;
}
-
- // Giải mã bytes -> string (ANSI: dùng Encoding.Default)
- string content = ByteToStringUtils.ByteArrayToCP936String(textAsset.bytes);
- using var sr = new StringReader(content);
- return ParseIntoDict(sr, isWide: false);
}
protected bool LoadWideStrings(string resourceName)
{
- TextAsset textAsset = Resources.Load(resourceName);
- if (textAsset == null)
+ try
{
- Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
+ // Support absolute / relative filesystem paths (e.g. StreamingAssets/configs/*.txt)
+ // 支持文件系统路径(例如 StreamingAssets/configs/*.txt)
+ if (File.Exists(resourceName))
+ {
+ // String tables we ship in StreamingAssets are saved as UTF-8.
+ // 我们放在 StreamingAssets 里的字符串表保存为 UTF-8。
+ string content = File.ReadAllText(resourceName, Encoding.UTF8);
+ using var srFile = new StringReader(content);
+ return ParseIntoDict(srFile, isWide: true);
+ }
+
+ // Fallback to Resources-based loading (old behaviour)
+ // 回退到基于 Resources 的加载(旧行为)
+ TextAsset textAsset = Resources.Load(resourceName);
+ if (textAsset == null)
+ {
+ Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
+ return false;
+ }
+
+ // Unity TextAsset.text is already UTF-8 decoded.
+ string resContent = textAsset.text;
+ using var srRes = new StringReader(resContent);
+ return ParseIntoDict(srRes, isWide: true);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"[CECStringTab] LoadWideStrings failed for '{resourceName}': {e}");
return false;
}
-
- // Unity TextAsset mặc định đã decode text UTF8 -> textAsset.text
- // nhưng để chắc chắn BOM/Unicode thì đọc từ bytes
- string content;
- content = textAsset.text;
-
- using var sr = new StringReader(content);
- return ParseIntoDict(sr, isWide: true);
}
private static Encoding DetectEncoding(byte[] bom)
@@ -183,6 +222,18 @@ namespace BrewMonster
private void PutString(int id, string value, bool isWide)
{
+ if (string.IsNullOrEmpty(value))
+ return;
+
+ // Many PW string tables wrap the payload in double quotes, e.g.:
+ // 12345 "^ffcb4aSome text\rMore text"
+ // Strip a single leading/trailing quote pair to avoid showing raw quotes in UI.
+ // 许多字符串表会用双引号包裹内容,这里去掉首尾各一个引号以避免在UI中显示多余的引号。
+ if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"')
+ {
+ value = value.Substring(1, value.Length - 2);
+ }
+
if (isWide) m_WStrTab[id] = value;
else m_AStrTab[id] = value;
}