319 lines
9.9 KiB
C#
319 lines
9.9 KiB
C#
// Filename : NPCShopDetailPanel.cs
|
||
// Creator : Converted from C++ EC_Shop
|
||
// Date : 2024
|
||
|
||
using BrewMonster.Network;
|
||
using BrewMonster.Scripts;
|
||
using CSNetwork.C2SCommand;
|
||
using System.Text.RegularExpressions;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
public class NPCShopDetailPanel : MonoBehaviour
|
||
{
|
||
[Header("UI Components")]
|
||
public TextOutlet itemDescriptionText;
|
||
public Image itemIconImage;
|
||
//public TextMeshProUGUI itemPriceText;
|
||
|
||
[Header("Action Buttons")]
|
||
public Button closeButton;
|
||
public Button buyButton;
|
||
|
||
private GShopItem currentItem;
|
||
private NPCShopUIManager shopManager;
|
||
private int shopItemIndex;
|
||
|
||
void Start()
|
||
{
|
||
SetupEventListeners();
|
||
}
|
||
|
||
void SetupEventListeners()
|
||
{
|
||
if (closeButton != null)
|
||
closeButton.onClick.AddListener(OnCloseClicked);
|
||
|
||
if (buyButton != null)
|
||
buyButton.onClick.AddListener(OnBuyButtonClicked);
|
||
}
|
||
|
||
public void SetupDetailPanel(GShopItem item, NPCShopUIManager manager, int index)
|
||
{
|
||
currentItem = item;
|
||
shopManager = manager;
|
||
shopItemIndex = index;
|
||
|
||
UpdateDisplay();
|
||
}
|
||
|
||
public void UpdateDisplay()
|
||
{
|
||
if (currentItem.id == 0)
|
||
{
|
||
Debug.LogWarning("[NPCShopDetailPanel] Current item ID is 0, skipping display update");
|
||
return;
|
||
}
|
||
|
||
// Set item description
|
||
if (itemDescriptionText != null)
|
||
{
|
||
string description = GetItemDescription((int)currentItem.id);
|
||
description = SanitizeDescription(description);
|
||
itemDescriptionText.Set(description);
|
||
}
|
||
|
||
// Set price (use first available price)
|
||
uint price = 0;
|
||
if (currentItem.buy != null && currentItem.buy.Length > 0)
|
||
{
|
||
for (int i = 0; i < currentItem.buy.Length; i++)
|
||
{
|
||
if (currentItem.buy[i].price > 0)
|
||
{
|
||
price = currentItem.buy[i].price;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// if (itemPriceText != null)
|
||
// itemPriceText.text = price > 0 ? $"Price: {price}" : "Price: N/A";
|
||
|
||
// Load icon
|
||
if (itemIconImage != null)
|
||
{
|
||
LoadItemIcon(itemIconImage, (int)currentItem.id);
|
||
}
|
||
}
|
||
|
||
private string SanitizeDescription(string desc)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(desc))
|
||
return string.Empty;
|
||
|
||
// Normalize escaped + real line breaks (including unicode line separators)
|
||
desc = Regex.Replace(
|
||
desc,
|
||
@"\\r\\n|\\n|\\r|\r\n|\r|\u2028|\u2029",
|
||
"\n",
|
||
RegexOptions.CultureInvariant);
|
||
|
||
var colorRegex = new Regex(@"\^([0-9A-Fa-f]{6})", RegexOptions.CultureInvariant);
|
||
if (colorRegex.IsMatch(desc))
|
||
{
|
||
var sbColors = new System.Text.StringBuilder();
|
||
int lastIndex = 0;
|
||
bool opened = false;
|
||
foreach (Match m in colorRegex.Matches(desc))
|
||
{
|
||
sbColors.Append(desc, lastIndex, m.Index - lastIndex);
|
||
if (opened)
|
||
sbColors.Append("</color>");
|
||
sbColors.Append("<color=#").Append(m.Groups[1].Value).Append(">");
|
||
opened = true;
|
||
lastIndex = m.Index + m.Length;
|
||
}
|
||
sbColors.Append(desc, lastIndex, desc.Length - lastIndex);
|
||
if (opened)
|
||
sbColors.Append("</color>");
|
||
desc = sbColors.ToString();
|
||
}
|
||
|
||
string[] lines = desc.Split('\n');
|
||
var sb = new System.Text.StringBuilder();
|
||
|
||
for (int i = 0; i < lines.Length; i++)
|
||
{
|
||
string line = lines[i];
|
||
if (string.IsNullOrWhiteSpace(line))
|
||
continue;
|
||
line = Regex.Replace(line, @"[\u200B-\u200D\uFEFF]", string.Empty, RegexOptions.CultureInvariant);
|
||
string plain = Regex.Replace(line, @"<[^>]*>", string.Empty, RegexOptions.CultureInvariant)
|
||
.Replace(':', ':')
|
||
.Trim();
|
||
if (Regex.IsMatch(plain, @"^(?:ID|Price)\s*:", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||
continue;
|
||
line = Regex.Replace(
|
||
line,
|
||
@"(?i)\b(?:ID|Price)\b\s*[::]\s*\d+\b",
|
||
string.Empty,
|
||
RegexOptions.CultureInvariant).Trim();
|
||
|
||
if (string.IsNullOrWhiteSpace(line))
|
||
continue;
|
||
|
||
if (sb.Length > 0)
|
||
sb.AppendLine();
|
||
|
||
sb.Append(line);
|
||
}
|
||
|
||
return sb.ToString().Trim();
|
||
}
|
||
|
||
|
||
void LoadItemIcon(Image iconImage, int itemId)
|
||
{
|
||
if (itemId <= 0 || iconImage == null)
|
||
return;
|
||
|
||
// Ensure the Image component is enabled and properly configured
|
||
iconImage.enabled = true;
|
||
iconImage.preserveAspect = true;
|
||
iconImage.type = Image.Type.Simple;
|
||
|
||
// Use the existing icon loading system from EC_IvtrItemUtils
|
||
Sprite iconSprite = EC_IvtrItemUtils.Instance.ResolveItemIconSprite(itemId);
|
||
|
||
if (iconSprite != null)
|
||
{
|
||
iconImage.sprite = iconSprite;
|
||
iconImage.color = Color.white;
|
||
}
|
||
else
|
||
{
|
||
iconImage.sprite = null;
|
||
}
|
||
}
|
||
|
||
string GetItemDescription(int itemId)
|
||
{
|
||
// First check if description is already in the item data
|
||
if (!string.IsNullOrEmpty(currentItem.desc))
|
||
{
|
||
return currentItem.desc;
|
||
}
|
||
|
||
// 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 = (EC_IvtrEquip)EC_IvtrItem.CreateItem(itemId, 0, 1);
|
||
|
||
// For NPC shop items, we typically have static data only
|
||
// Try to get description using GetNormalDesc() which works with base stats
|
||
equipment.SetPriceScale((int)EC_IvtrItem.ScaleType.SCALE_BUY, 1f);
|
||
string equipDesc = equipment.GetDesc();
|
||
if (!string.IsNullOrEmpty(equipDesc))
|
||
{
|
||
// 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();
|
||
item.SetPriceScale((int)EC_IvtrItem.ScaleType.SCALE_BUY, 1f);
|
||
string description = item.GetDesc();
|
||
if (!string.IsNullOrEmpty(description))
|
||
{
|
||
return description.Replace("\\r", "\n");
|
||
}
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogWarning($"[NPCShopDetailPanel] Failed to get description for item {itemId}: {ex.Message}");
|
||
}
|
||
|
||
return "No description available.";
|
||
}
|
||
|
||
public void ClearText()
|
||
{
|
||
if(itemDescriptionText != null)
|
||
itemDescriptionText.Set(string.Empty);
|
||
}
|
||
|
||
void OnCloseClicked()
|
||
{
|
||
gameObject.SetActive(false);
|
||
}
|
||
|
||
void OnBuyButtonClicked()
|
||
{
|
||
if (currentItem.id == 0)
|
||
{
|
||
Debug.LogWarning("[NPCShopDetailPanel] Cannot buy item with ID 0");
|
||
return;
|
||
}
|
||
|
||
// Get price from item
|
||
uint price = 0;
|
||
if (currentItem.buy != null && currentItem.buy.Length > 0)
|
||
{
|
||
for (int i = 0; i < currentItem.buy.Length; i++)
|
||
{
|
||
if (currentItem.buy[i].price > 0)
|
||
{
|
||
price = currentItem.buy[i].price;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Server requires SEVNPC_HELLO with NPC id before buy, and the correct shop slot index
|
||
if (shopManager != null && shopManager.CurrentNPCID != 0)
|
||
UnityGameSession.c2s_CmdNPCSevHello((int)shopManager.CurrentNPCID);
|
||
|
||
// Create npc_trade_item: tid = template ID, index = shop slot (server validates this), count = quantity
|
||
npc_trade_item[] items = new npc_trade_item[1];
|
||
items[0] = new npc_trade_item
|
||
{
|
||
tid = (int)currentItem.id,
|
||
index = (uint)shopItemIndex,
|
||
count = 1
|
||
};
|
||
|
||
UnityGameSession.c2s_CmdNPCSevBuy(1, items);
|
||
|
||
Debug.Log($"[NPCShopDetailPanel] Sent buy command for item {currentItem.id}, price {price}");
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
// Clean up event listeners
|
||
if (closeButton != null)
|
||
closeButton.onClick.RemoveListener(OnCloseClicked);
|
||
|
||
if (buyButton != null)
|
||
buyButton.onClick.RemoveListener(OnBuyButtonClicked);
|
||
}
|
||
|
||
// === Text Outlet Helper ===
|
||
[System.Serializable]
|
||
public class TextOutlet
|
||
{
|
||
[SerializeField] public Text legacy;
|
||
[SerializeField] public TMPro.TextMeshProUGUI tmp;
|
||
Color myColorDecimal = new Color(1f, 0.816f, 0.365f, 1f);
|
||
|
||
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);
|
||
Debug.Log($"[TextOutlet] Set text: {tmp.text}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|