2026-05-10 11:47:55 -04:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
using Cielonos.MainGame.Inventory;
|
2026-05-10 11:47:55 -04:00
|
|
|
|
using Cielonos.MainGame.Interactions;
|
|
|
|
|
|
using Cielonos.MainGame.Items;
|
|
|
|
|
|
using Sirenix.OdinInspector;
|
|
|
|
|
|
using SLSUtilities.UI;
|
|
|
|
|
|
using TMPro;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Cielonos.MainGame.UI
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 物流中心(商店)UI 页面。
|
|
|
|
|
|
/// 左侧为可购买商品列表(Selector),右侧为物品详情面板(ItemDetailPanel)。
|
|
|
|
|
|
/// 玩家点击列表条目选中商品,点击 Buy 确认购买(消耗 RareMaterial 并获得装备)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class LogisticsCenterUIPage : UIPageBase
|
|
|
|
|
|
{
|
|
|
|
|
|
[Title("Selectors")]
|
|
|
|
|
|
public GameObject selectorPrefab;
|
|
|
|
|
|
public RectTransform selectorContainer;
|
|
|
|
|
|
public List<LogisticsCenterSelector> selectors = new List<LogisticsCenterSelector>();
|
|
|
|
|
|
|
|
|
|
|
|
[Title("Detail Panel")]
|
|
|
|
|
|
public ItemDetailPanel itemDetailPanel;
|
|
|
|
|
|
|
|
|
|
|
|
[Title("Rare Material Display")]
|
|
|
|
|
|
public TMP_Text currentRareMaterialText;
|
|
|
|
|
|
public TMP_Text predictedRareMaterialText;
|
|
|
|
|
|
|
|
|
|
|
|
[Title("Buttons")]
|
|
|
|
|
|
public List<Button> categoryButtons;
|
|
|
|
|
|
private static readonly ItemFilter[] CategoryFilters =
|
|
|
|
|
|
{
|
|
|
|
|
|
ItemFilter.None,
|
|
|
|
|
|
ItemFilter.ByType(ItemType.MainWeapon),
|
|
|
|
|
|
ItemFilter.ByType(ItemType.Support),
|
|
|
|
|
|
ItemFilter.ByType(ItemType.Passive),
|
|
|
|
|
|
ItemFilter.ByType(ItemType.Consumable)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public Button buyButton;
|
|
|
|
|
|
public Button closeButton;
|
|
|
|
|
|
|
|
|
|
|
|
// - 运行时数据 -
|
|
|
|
|
|
private LogisticsCenterSelector currentSelected;
|
|
|
|
|
|
private Action<int> onPurchaseCallback;
|
|
|
|
|
|
private List<ShopOffer> currentOffers;
|
|
|
|
|
|
private ItemFilter activeFilter;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前选中的 Selector 索引,-1 表示未选中。</summary>
|
|
|
|
|
|
public int SelectedIndex => currentSelected != null ? selectors.IndexOf(currentSelected) : -1;
|
|
|
|
|
|
|
|
|
|
|
|
protected override void Start()
|
|
|
|
|
|
{
|
|
|
|
|
|
base.Start();
|
|
|
|
|
|
|
|
|
|
|
|
if (buyButton != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
buyButton.onClick.AddListener(OnBuyClicked);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (closeButton != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
closeButton.onClick.AddListener(OnCloseClicked);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定分类按钮
|
|
|
|
|
|
if (categoryButtons != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < categoryButtons.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (categoryButtons[i] == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
int index = i;
|
|
|
|
|
|
categoryButtons[i].onClick.AddListener(() => OnCategoryClicked(index));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用商品列表填充选择器,并注册购买回调。
|
|
|
|
|
|
/// 每次调用会清除旧选择器并重新生成。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="offers">商品列表,每个商品包含预制体和价格。</param>
|
|
|
|
|
|
/// <param name="onPurchase">玩家点击 Buy 后的回调,参数为选中的商品在原始列表中的索引。</param>
|
|
|
|
|
|
public void SetOffers(List<ShopOffer> offers, Action<int> onPurchase)
|
|
|
|
|
|
{
|
|
|
|
|
|
onPurchaseCallback = onPurchase;
|
|
|
|
|
|
currentOffers = offers;
|
|
|
|
|
|
|
|
|
|
|
|
ClearSelectors();
|
|
|
|
|
|
|
|
|
|
|
|
if (offers == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < offers.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ShopOffer offer = offers[i];
|
|
|
|
|
|
if (offer == null || offer.prefab == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
ItemBase itemComponent = offer.prefab.GetComponent<ItemBase>();
|
|
|
|
|
|
if (itemComponent == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"[LogisticCenterUIPage] 预制体 '{offer.prefab.name}' 缺少 ItemBase 组件,已跳过。");
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogisticsCenterSelector selector = CreateSelector();
|
|
|
|
|
|
selector.Setup(itemComponent, offer.price);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置选中状态与过滤
|
|
|
|
|
|
currentSelected = null;
|
|
|
|
|
|
activeFilter = ItemFilter.None;
|
|
|
|
|
|
RefreshBuyButton();
|
|
|
|
|
|
RefreshCurrencyDisplay();
|
|
|
|
|
|
|
|
|
|
|
|
// 详情面板默认隐藏
|
|
|
|
|
|
if (itemDetailPanel != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
itemDetailPanel.ClearPanel();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 由 LogisticCenterSelector 调用,选中指定的选择器。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void SelectItem(LogisticsCenterSelector selector)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (selector == null || !selectors.Contains(selector)) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 取消上一个选中
|
|
|
|
|
|
if (currentSelected != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentSelected.SetSelected(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置新选中
|
|
|
|
|
|
currentSelected = selector;
|
|
|
|
|
|
currentSelected.SetSelected(true);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新详情面板
|
|
|
|
|
|
if (itemDetailPanel != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
itemDetailPanel.SetItem(currentSelected.item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新购买按钮状态与货币预测显示
|
|
|
|
|
|
RefreshBuyButton();
|
|
|
|
|
|
RefreshCurrencyDisplay();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void OnPageOpened()
|
|
|
|
|
|
{
|
|
|
|
|
|
RefreshBuyButton();
|
|
|
|
|
|
RefreshCurrencyDisplay();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void OnPageClosed()
|
|
|
|
|
|
{
|
|
|
|
|
|
currentSelected = null;
|
|
|
|
|
|
onPurchaseCallback = null;
|
|
|
|
|
|
currentOffers = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnBuyClicked()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentSelected == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
int selectorIndex = SelectedIndex;
|
|
|
|
|
|
if (selectorIndex < 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 RareMaterial 是否足够
|
|
|
|
|
|
int playerCurrency = MainGameManager.Player.inventorySc.GetRareMaterialAmount();
|
|
|
|
|
|
if (playerCurrency < currentSelected.price)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Log("[LogisticCenterUIPage] RareMaterial 不足,无法购买。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 先缓存回调
|
|
|
|
|
|
Action<int> callback = onPurchaseCallback;
|
|
|
|
|
|
|
|
|
|
|
|
// 调用购买逻辑(会扣除 RareMaterial 并修改 currentOffers 列表)
|
|
|
|
|
|
callback?.Invoke(selectorIndex);
|
|
|
|
|
|
|
|
|
|
|
|
// 移除已购买的选择器
|
|
|
|
|
|
LogisticsCenterSelector purchasedSelector = currentSelected;
|
|
|
|
|
|
currentSelected = null;
|
|
|
|
|
|
selectors.Remove(purchasedSelector);
|
|
|
|
|
|
Destroy(purchasedSelector.gameObject);
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新所有选择器的价格颜色和货币显示
|
|
|
|
|
|
RefreshAllSelectorPrices();
|
|
|
|
|
|
RefreshBuyButton();
|
|
|
|
|
|
RefreshCurrencyDisplay();
|
|
|
|
|
|
|
|
|
|
|
|
// 清空详情面板
|
|
|
|
|
|
if (itemDetailPanel != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
itemDetailPanel.ClearPanel();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnCloseClicked()
|
|
|
|
|
|
{
|
|
|
|
|
|
Close();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分类按钮点击回调,根据索引切换当前过滤类型并刷新可见列表。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnCategoryClicked(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (index < 0 || index >= CategoryFilters.Length) return;
|
|
|
|
|
|
|
|
|
|
|
|
activeFilter = CategoryFilters[index];
|
|
|
|
|
|
ApplyFilter();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按当前 activeFilter 显示/隐藏选择器。
|
|
|
|
|
|
/// 若当前选中的商品被隐藏,则取消选中。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ApplyFilter()
|
|
|
|
|
|
{
|
|
|
|
|
|
bool selectedHidden = false;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (LogisticsCenterSelector selector in selectors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (selector == null || selector.item == null || selector.item.contentData == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
bool visible = activeFilter.Match(selector.item);
|
|
|
|
|
|
selector.gameObject.SetActive(visible);
|
|
|
|
|
|
|
|
|
|
|
|
if (!visible && selector == currentSelected)
|
|
|
|
|
|
{
|
|
|
|
|
|
selectedHidden = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 被过滤掉的选中项需要清除
|
|
|
|
|
|
if (selectedHidden)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentSelected.SetSelected(false);
|
|
|
|
|
|
currentSelected = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (itemDetailPanel != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
itemDetailPanel.ClearPanel();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RefreshBuyButton();
|
|
|
|
|
|
RefreshCurrencyDisplay();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private LogisticsCenterSelector CreateSelector()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (selectorPrefab == null || selectorContainer == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[LogisticCenterUIPage] selectorPrefab 或 selectorContainer 未配置。");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GameObject obj = Instantiate(selectorPrefab, selectorContainer);
|
|
|
|
|
|
LogisticsCenterSelector selector = obj.GetComponent<LogisticsCenterSelector>();
|
|
|
|
|
|
if (selector == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[LogisticCenterUIPage] selectorPrefab 缺少 LogisticCenterSelector 组件。");
|
|
|
|
|
|
Destroy(obj);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
selectors.Add(selector);
|
|
|
|
|
|
return selector;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearSelectors()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (LogisticsCenterSelector selector in selectors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (selector != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Destroy(selector.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
selectors.Clear();
|
|
|
|
|
|
currentSelected = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RefreshBuyButton()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (buyButton == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (currentSelected == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
buyButton.interactable = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int playerCurrency = MainGameManager.Player.inventorySc.GetRareMaterialAmount();
|
|
|
|
|
|
bool canAfford = playerCurrency >= currentSelected.price;
|
|
|
|
|
|
buyButton.interactable = canAfford;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RefreshCurrencyDisplay()
|
|
|
|
|
|
{
|
|
|
|
|
|
int rmAmount = MainGameManager.Player.inventorySc.GetRareMaterialAmount();
|
|
|
|
|
|
currentRareMaterialText.text = "Rare Materials: " + rmAmount.ToString();
|
|
|
|
|
|
|
|
|
|
|
|
if (currentSelected == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
predictedRareMaterialText.text = string.Empty;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int predicted = rmAmount - currentSelected.price;
|
|
|
|
|
|
predictedRareMaterialText.color = predicted >= 0 ? new Color(0.3f, 1f, 0.3f, 1f) : new Color(1f, 0.3f, 0.3f, 1f);
|
|
|
|
|
|
predictedRareMaterialText.text = "(" + predicted.ToString() + ")";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RefreshAllSelectorPrices()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (LogisticsCenterSelector selector in selectors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (selector != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
selector.RefreshPriceDisplay();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|