Update EC_InventoryUI.cs

This commit is contained in:
HungDK
2025-10-21 16:56:15 +07:00
parent 8a9449330f
commit 9842d9960c
@@ -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 ===
/// <summary>
/// Format text for TextMeshPro components with rich text support
/// </summary>
/// <param name="text">Raw text with formatting codes</param>
/// <returns>Formatted text for TextMeshPro</returns>
private static string FormatForTextMeshPro(string text)
{
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;
}
/// <summary>
/// Format text for legacy Text components (limited rich text support)
/// </summary>
/// <param name="text">Raw text with formatting codes</param>
/// <returns>Formatted text for legacy Text</returns>
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;
}
/// <summary>
/// Process color codes for TextMeshPro (supports hex colors directly)
/// </summary>
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 $"<color=#{hexColor}>";
}, RegexOptions.None);
}
/// <summary>
/// Process color codes for legacy Text (convert to Unity rich text format)
/// </summary>
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 $"<color=#{ColorUtility.ToHtmlStringRGB(color)}>";
}, RegexOptions.None);
}
/// <summary>
/// Convert hex color string to Unity Color
/// </summary>
/// <param name="hex">Hex color string (e.g., "ffcb4a")</param>
/// <returns>Unity Color object</returns>
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
}
}
/// <summary>
/// Create EC_IvtrEquip object from InventoryItemData
/// </summary>
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;
}
/// <summary>
/// Get item description from string table
/// </summary>
/// <param name="templateId">Item template ID</param>
/// <returns>Item description text or null if not found</returns>
private string GetItemDescription(int templateId)
{
try
{
// Prefer mapped message id if available
if (EC_Game.TryGetItemMsg(templateId, out int messageId, out int displayMode))
{
var itemDesc = EC_Game.GetItemDesc();
if (itemDesc != null && itemDesc.IsInitialized())
{
string description = itemDesc.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;
}
/// <summary>
/// Get extended item description from string table
/// </summary>
/// <param name="templateId">Item template ID</param>
/// <returns>Extended item description text or null if not found</returns>
private string GetItemExtendedDescription(int templateId)
{
try
{
// Prefer mapped message id if available
if (EC_Game.TryGetItemMsg(templateId, out int messageId, out int displayMode))
{
var itemExtDesc = EC_Game.GetItemExtDesc();
if (itemExtDesc != null && itemExtDesc.IsInitialized())
{
string extendedDesc = itemExtDesc.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;
}
/// <summary>
/// Get user-friendly text for item state
/// </summary>
/// <param name="state">Item state value</param>
/// <returns>Human-readable state text</returns>
private string GetItemStateText(int state)
{
switch (state)
{
case 0: return "Normal";
case 1: return "Equipped";
case 2: return "Broken";
case 3: return "Locked";
default: return $"State: {state}";
}
}
// Equipment indices and resolution helpers moved to EC_IvtrType
@@ -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);
}
}
/// <summary>
/// Set text with explicit formatting preference
/// </summary>
/// <param name="value">Raw text with formatting codes</param>
/// <param name="preferTextMeshPro">Whether to prefer TextMeshPro formatting</param>
public void SetFormatted(string value, bool preferTextMeshPro = true)
{
string formattedText = preferTextMeshPro ?
FormatForTextMeshPro(value ?? string.Empty) :
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);
}
/// <summary>
/// Fill equipment-specific details
/// </summary>
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<string> reqs = new List<string>();
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<string> lines = new List<string>();
// 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");
}
}
}
/// <summary>
/// Clear equipment details
/// </summary>
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}";
}
}
}
}