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