From 9842d9960c30b084028831759de844f4e9518cb6 Mon Sep 17 00:00:00 2001
From: HungDK <>
Date: Tue, 21 Oct 2025 16:56:15 +0700
Subject: [PATCH] Update EC_InventoryUI.cs
---
.../Scripts/Managers/EC_InventoryUI.cs | 535 +++++++++++++++++-
1 file changed, 508 insertions(+), 27 deletions(-)
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
index 205716958a..ef41df0c57 100644
--- a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
@@ -3,8 +3,11 @@ using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
using BrewMonster.Network;
using BrewMonster;
+using BrewMonster.Common;
using ModelRenderer.Scripts.GameData;
namespace PerfectWorld.Scripts.Managers
@@ -20,30 +23,141 @@ namespace PerfectWorld.Scripts.Managers
[SerializeField] private GameObject detailPanelRoot;
[SerializeField] private bool hideDetailOnStart = true;
[SerializeField] private TextOutlet nameText;
- [SerializeField] private TextOutlet templateIdText;
+ [SerializeField] private TextOutlet descriptionText;
+ [SerializeField] private TextOutlet extendedDescText;
[SerializeField] private TextOutlet countText;
- [SerializeField] private TextOutlet slotText;
- [SerializeField] private TextOutlet packageText;
[SerializeField] private TextOutlet stateText;
[SerializeField] private TextOutlet expireText;
- [SerializeField] private TextOutlet crcText;
- [SerializeField] private TextOutlet contentLenText;
[SerializeField] private Button equipButton;
[SerializeField] private Button dropButton;
+ [Header("Equipment Details (assign in Inspector)")]
+ [SerializeField] private TextOutlet levelReqText;
+ [SerializeField] private TextOutlet statsReqText;
+ [SerializeField] private TextOutlet enduranceText;
+ [SerializeField] private TextOutlet repairCostText;
+ [SerializeField] private TextOutlet propertiesText;
+ [SerializeField] private TextOutlet refineText;
+ [SerializeField] private TextOutlet makerText;
+ [SerializeField] private TextOutlet priceText;
+ [SerializeField] private TextOutlet holesText;
+ [SerializeField] private TextOutlet suiteText;
+
[Header("Inventory Settings")]
[SerializeField] private bool autoRefresh = true;
[SerializeField] private float refreshInterval = 1.0f;
+ [SerializeField] private bool showEquipmentDetails = true;
private float lastRefreshTime;
private InventoryModel model;
private InventoryView view;
+ // === Text Formatting Methods ===
+
+ ///
+ /// Format text for TextMeshPro components with rich text support
+ ///
+ /// Raw text with formatting codes
+ /// Formatted text for TextMeshPro
+ private static string FormatForTextMeshPro(string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return string.Empty;
+
+ StringBuilder result = new StringBuilder(text);
+
+ // Handle line breaks (\r)
+ result.Replace("\\r", "\n");
+
+ // Handle color codes (^RRGGBB format)
+ string processedText = ProcessColorCodes(result);
+
+ return processedText;
+ }
+
+ ///
+ /// Format text for legacy Text components (limited rich text support)
+ ///
+ /// Raw text with formatting codes
+ /// Formatted text for legacy Text
+ private static string FormatForLegacyText(string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return string.Empty;
+
+ StringBuilder result = new StringBuilder(text);
+
+ // Handle line breaks (\r)
+ result.Replace("\\r", "\n");
+
+ // Handle color codes (^RRGGBB format) - convert to Unity's rich text format
+ string processedText = ProcessColorCodesForLegacy(result);
+
+ return processedText;
+ }
+
+ ///
+ /// Process color codes for TextMeshPro (supports hex colors directly)
+ ///
+ private static string ProcessColorCodes(StringBuilder text)
+ {
+ // Pattern to match color codes: ^ followed by 6 hex characters
+ string pattern = @"\^([0-9A-Fa-f]{6})";
+
+ return Regex.Replace(text.ToString(), pattern, match =>
+ {
+ string hexColor = match.Groups[1].Value;
+ return $"";
+ }, RegexOptions.None);
+ }
+
+ ///
+ /// Process color codes for legacy Text (convert to Unity rich text format)
+ ///
+ private static string ProcessColorCodesForLegacy(StringBuilder text)
+ {
+ // Pattern to match color codes: ^ followed by 6 hex characters
+ string pattern = @"\^([0-9A-Fa-f]{6})";
+
+ return Regex.Replace(text.ToString(), pattern, match =>
+ {
+ string hexColor = match.Groups[1].Value;
+ // Convert hex to Unity color format
+ Color color = HexToColor(hexColor);
+ return $"";
+ }, RegexOptions.None);
+ }
+
+ ///
+ /// Convert hex color string to Unity Color
+ ///
+ /// Hex color string (e.g., "ffcb4a")
+ /// Unity Color object
+ private static Color HexToColor(string hex)
+ {
+ if (hex.Length != 6)
+ return Color.white;
+
+ try
+ {
+ int r = Convert.ToInt32(hex.Substring(0, 2), 16);
+ int g = Convert.ToInt32(hex.Substring(2, 2), 16);
+ int b = Convert.ToInt32(hex.Substring(4, 2), 16);
+
+ return new Color(r / 255f, g / 255f, b / 255f, 1f);
+ }
+ catch
+ {
+ return Color.white;
+ }
+ }
+
// Current selected item for equip/unequip operations
private byte currentSelectedPackage;
private int currentSelectedSlot;
private InventoryItemData currentSelectedItem;
+ private EC_IvtrEquip currentSelectedEquipment;
private const byte PKG_INVENTORY = 0;
private const byte PKG_EQUIPMENT = 1;
@@ -96,6 +210,9 @@ namespace PerfectWorld.Scripts.Managers
currentSelectedSlot = slot;
currentSelectedItem = itemData;
+ // Create equipment object if this is equipment
+ currentSelectedEquipment = CreateEquipmentFromItemData(itemData);
+
FillDetailPanel(package, itemData);
}
else
@@ -104,6 +221,31 @@ namespace PerfectWorld.Scripts.Managers
}
}
+ ///
+ /// Create EC_IvtrEquip object from InventoryItemData
+ ///
+ private EC_IvtrEquip CreateEquipmentFromItemData(InventoryItemData itemData)
+ {
+ if (itemData == null)
+ return null;
+
+ var equipment = new EC_IvtrEquip(itemData.TemplateId, itemData.ExpireDate);
+
+ // Set basic properties (use default values since InventoryItemData doesn't have these)
+ equipment.Price = 0;
+ equipment.Count = itemData.Count;
+ 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, InventoryItemData itemData)
{
if (itemData == null || itemData.Count <= 0)
@@ -129,6 +271,15 @@ namespace PerfectWorld.Scripts.Managers
refreshInterval = Mathf.Max(0.1f, interval);
}
+ public void ToggleEquipmentDetails()
+ {
+ showEquipmentDetails = !showEquipmentDetails;
+ if (currentSelectedItem != null)
+ {
+ FillDetailPanel(currentSelectedPackage, currentSelectedItem);
+ }
+ }
+
public void OnEquipButtonClicked()
{
if (currentSelectedItem == null)
@@ -264,6 +415,107 @@ namespace PerfectWorld.Scripts.Managers
}
return -1;
}
+
+ ///
+ /// Get item description from string table
+ ///
+ /// Item template ID
+ /// Item description text or null if not found
+ 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.GetString(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.GetString(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;
+ }
+
+ ///
+ /// Get extended item description from string table
+ ///
+ /// Item template ID
+ /// Extended item description text or null if not found
+ 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.GetString(messageId);
+ if (!string.IsNullOrEmpty(extendedDesc))
+ {
+ return extendedDesc;
+ }
+ }
+ }
+
+ // Fallback: direct template id
+ {
+ var itemExtDesc = EC_Game.GetItemExtDesc();
+ if (itemExtDesc != null && itemExtDesc.IsInitialized())
+ {
+ string extendedDesc = itemExtDesc.GetString(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;
+ }
+
+ ///
+ /// Get user-friendly text for item state
+ ///
+ /// Item state value
+ /// Human-readable state text
+ 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
@@ -365,11 +617,32 @@ namespace PerfectWorld.Scripts.Managers
{
if (legacy != null)
{
- legacy.text = value ?? string.Empty;
+ legacy.text = FormatForLegacyText(value ?? string.Empty);
}
if (tmp != null)
{
- tmp.text = value ?? string.Empty;
+ tmp.text = FormatForTextMeshPro(value ?? string.Empty);
+ }
+ }
+
+ ///
+ /// Set text with explicit formatting preference
+ ///
+ /// Raw text with formatting codes
+ /// Whether to prefer TextMeshPro formatting
+ public void SetFormatted(string value, bool preferTextMeshPro = true)
+ {
+ string formattedText = preferTextMeshPro ?
+ FormatForTextMeshPro(value ?? string.Empty) :
+ FormatForLegacyText(value ?? string.Empty);
+
+ if (legacy != null)
+ {
+ legacy.text = formattedText;
+ }
+ if (tmp != null)
+ {
+ tmp.text = formattedText;
}
}
}
@@ -390,16 +663,41 @@ namespace PerfectWorld.Scripts.Managers
return;
}
- string name = EC_IvtrItem.ResolveItemName(item.TemplateId);
- nameText?.Set(string.IsNullOrEmpty(name) ? $"Item {item.TemplateId}" : name);
- templateIdText?.Set(item.TemplateId.ToString());
- countText?.Set(item.Count.ToString());
- slotText?.Set(item.Slot.ToString());
- packageText?.Set(GetPackageName(package));
- stateText?.Set(item.State.ToString());
- expireText?.Set(item.ExpireDate.ToString());
- crcText?.Set(item.Crc.ToString());
- contentLenText?.Set((item.Content?.Length ?? 0).ToString());
+ // Get user-friendly text from string tables
+ string itemName = EC_IvtrItem.ResolveItemName(item.TemplateId);
+ string itemDescription = GetItemDescription(item.TemplateId);
+ string itemExtendedDesc = GetItemExtendedDescription(item.TemplateId);
+
+ // Display basic content
+ nameText?.Set(string.IsNullOrEmpty(itemName) ? $"Item {item.TemplateId}" : itemName);
+ descriptionText?.Set(itemDescription ?? "No description available");
+ extendedDescText?.Set(itemExtendedDesc ?? "");
+ countText?.Set($"Count: {item.Count}");
+
+ // Format state properly
+ string stateTextValue = GetItemStateText(item.State);
+ stateText?.Set(stateTextValue);
+
+ // Format expiration date properly
+ if (item.ExpireDate > 0)
+ {
+ DateTime expireDate = DateTimeOffset.FromUnixTimeSeconds(item.ExpireDate).DateTime;
+ expireText?.Set($"Expires: {expireDate:yyyy-MM-dd HH:mm}");
+ }
+ else
+ {
+ expireText?.Set("No expiration");
+ }
+
+ // Display equipment-specific information
+ if (showEquipmentDetails && currentSelectedEquipment != null)
+ {
+ FillEquipmentDetails();
+ }
+ else
+ {
+ ClearEquipmentDetails();
+ }
// Setup equip and drop buttons
SetupEquipButton(package, item);
@@ -408,6 +706,198 @@ namespace PerfectWorld.Scripts.Managers
ShowDetailPanel(true);
}
+ ///
+ /// Fill equipment-specific details
+ ///
+ private void FillEquipmentDetails()
+ {
+ if (currentSelectedEquipment == null)
+ {
+ ClearEquipmentDetails();
+ return;
+ }
+
+ // Level Requirements
+ if (levelReqText != null)
+ {
+ string levelReq = "";
+ if (currentSelectedEquipment.LevelReq > 0)
+ {
+ levelReq = $"Level: {currentSelectedEquipment.LevelReq}";
+ }
+ if (currentSelectedEquipment.ProfReq > 0)
+ {
+ if (!string.IsNullOrEmpty(levelReq)) levelReq += "\\r";
+ levelReq += $"Profession: {currentSelectedEquipment.ProfReq}";
+ }
+ levelReqText.Set(levelReq);
+ }
+
+ // Stats Requirements
+ if (statsReqText != null)
+ {
+ List reqs = new List();
+ if (currentSelectedEquipment.StrengthReq > 0) reqs.Add($"Str: {currentSelectedEquipment.StrengthReq}");
+ if (currentSelectedEquipment.AgilityReq > 0) reqs.Add($"Agi: {currentSelectedEquipment.AgilityReq}");
+ if (currentSelectedEquipment.VitalityReq > 0) reqs.Add($"Vit: {currentSelectedEquipment.VitalityReq}");
+ if (currentSelectedEquipment.EnergyReq > 0) reqs.Add($"Ene: {currentSelectedEquipment.EnergyReq}");
+ if (currentSelectedEquipment.ReputationReq > 0) reqs.Add($"Rep: {currentSelectedEquipment.ReputationReq}");
+
+ statsReqText.Set(string.Join("\\r", reqs));
+ }
+
+ // Endurance
+ if (enduranceText != null)
+ {
+ if (currentSelectedEquipment.MaxEndurance > 0)
+ {
+ int curEndurance = EC_IvtrEquip.VisualizeEndurance(currentSelectedEquipment.CurEndurance);
+ int maxEndurance = EC_IvtrEquip.VisualizeEndurance(currentSelectedEquipment.MaxEndurance);
+ string endurance = $"Endurance: {curEndurance}/{maxEndurance}";
+
+ if (currentSelectedEquipment.IsDestroying())
+ {
+ endurance += " (DESTROYED)";
+ }
+ else if (currentSelectedEquipment.CurEndurance < currentSelectedEquipment.MaxEndurance)
+ {
+ endurance += " (Damaged)";
+ }
+
+ enduranceText.Set(endurance);
+ }
+ else
+ {
+ enduranceText.Set("Endurance: N/A");
+ }
+ }
+
+ // Repair Cost
+ if (repairCostText != null)
+ {
+ if (currentSelectedEquipment.IsRepairable())
+ {
+ int repairCost = currentSelectedEquipment.GetRepairCost();
+ repairCostText.Set($"Repair Cost: {repairCost}");
+ }
+ else
+ {
+ repairCostText.Set("Repair Cost: N/A");
+ }
+ }
+
+ // Properties + Base Stats
+ if (propertiesText != null)
+ {
+ List lines = new List();
+
+ // Base stats decoded from element essence (damage/defense/speed/range/resists)
+ string baseStats = currentSelectedEquipment.GetBaseStatsForDisplay();
+ if (!string.IsNullOrEmpty(baseStats))
+ {
+ lines.Add(baseStats);
+ }
+
+ // Add-on properties from detail payload
+ if (currentSelectedEquipment.Props.Count > 0)
+ {
+ foreach (var prop in currentSelectedEquipment.Props)
+ {
+ if (!prop.Embed && !prop.Suite && !prop.Engraved)
+ {
+ string propDesc = currentSelectedEquipment.FormatPropDesc(prop);
+ if (!string.IsNullOrEmpty(propDesc))
+ {
+ lines.Add(propDesc);
+ }
+ }
+ }
+ }
+
+ string combined = string.Join("\\r", lines);
+ propertiesText.Set(combined);
+ }
+
+ // Refinement
+ if (refineText != null)
+ {
+ if (currentSelectedEquipment.RefineLvl > 0)
+ {
+ refineText.Set($"Refinement Level: +{currentSelectedEquipment.RefineLvl}");
+ }
+ else
+ {
+ refineText.Set("Refinement: None");
+ }
+ }
+
+ // Maker Information
+ if (makerText != null)
+ {
+ if (!string.IsNullOrEmpty(currentSelectedEquipment.Maker))
+ {
+ makerText.Set($"Maker: {currentSelectedEquipment.Maker}");
+ }
+ else
+ {
+ makerText.Set("Maker: Unknown");
+ }
+ }
+
+ // Price
+ if (priceText != null)
+ {
+ int scaledPrice = currentSelectedEquipment.GetScaledPrice();
+ priceText.Set($"Price: {scaledPrice}");
+ }
+
+ // Holes
+ if (holesText != null)
+ {
+ if (currentSelectedEquipment.Holes.Count > 0)
+ {
+ int emptyHoles = currentSelectedEquipment.GetEmptyHoleNum();
+ int totalHoles = currentSelectedEquipment.Holes.Count;
+ holesText.Set($"Holes: {totalHoles - emptyHoles}/{totalHoles} filled");
+ }
+ else
+ {
+ holesText.Set("Holes: None");
+ }
+ }
+
+ // Suite Information
+ if (suiteText != null)
+ {
+ int suiteId = currentSelectedEquipment.GetSuiteID();
+ if (suiteId > 0)
+ {
+ suiteText.Set($"Suite Equipment: {suiteId}");
+ }
+ else
+ {
+ suiteText.Set("Suite: None");
+ }
+ }
+ }
+
+ ///
+ /// Clear equipment details
+ ///
+ private void ClearEquipmentDetails()
+ {
+ levelReqText?.Set("");
+ statsReqText?.Set("");
+ enduranceText?.Set("");
+ repairCostText?.Set("");
+ propertiesText?.Set("");
+ refineText?.Set("");
+ makerText?.Set("");
+ priceText?.Set("");
+ holesText?.Set("");
+ suiteText?.Set("");
+ }
+
private void SetupEquipButton(byte package, InventoryItemData item)
{
if (equipButton == null) return;
@@ -497,16 +987,7 @@ namespace PerfectWorld.Scripts.Managers
}
}
}
+
- private static string GetPackageName(byte pkg)
- {
- switch (pkg)
- {
- case 0: return "PACK_INVENTORY";
- case 1: return "PACK_EQUIPMENT";
- case 3: return "PACK_FASHION";
- default: return $"PACK_{pkg}";
- }
- }
}
}