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; }