341 lines
13 KiB
C#
341 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Cielonos.Core.Interaction;
|
||
using Cielonos.MainGame.Inventory;
|
||
using Cielonos.MainGame.Inventory.Collections;
|
||
using Cielonos.MainGame.UI;
|
||
using Sirenix.OdinInspector;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame.Interactions
|
||
{
|
||
public enum RewardTier
|
||
{
|
||
Minor,
|
||
Major
|
||
}
|
||
|
||
/// <summary>
|
||
/// 奖励台(奖励战斗后出现)。
|
||
/// 生成时隐藏,当 CombatRoomSm 通知清空时出现。
|
||
/// 交互后打开奖励选择面板,在 3 个候选奖励中选择 1 个。
|
||
/// </summary>
|
||
public class RewardTable : InteractableObjectBase
|
||
{
|
||
private const int OFFER_COUNT = 3;
|
||
|
||
[TitleGroup("RewardTier & Prefabs")]
|
||
[SerializeField]
|
||
[Tooltip("不同稀有度对应的 table 模型。")]
|
||
private Dictionary<RewardTier, GameObject> _tablePrefabs = new Dictionary<RewardTier, GameObject>();
|
||
|
||
[SerializeField]
|
||
[Tooltip("被使用后或不可用时显示的模型。")]
|
||
private GameObject _notAvailableTablePrefab;
|
||
private GameObject _currentTable;
|
||
|
||
[TitleGroup("Reward Config")]
|
||
public RewardTier tier;
|
||
|
||
[TitleGroup("Runtime State"), ReadOnly]
|
||
private List<int> _selectedTypes = new List<int>();
|
||
|
||
[TitleGroup("Runtime State"), ReadOnly]
|
||
private List<ItemBase> _currentOffers = new List<ItemBase>();
|
||
|
||
[ShowInInspector, ReadOnly]
|
||
private bool _isAvailable;
|
||
|
||
private void Start()
|
||
{
|
||
// 初始状态:隐藏模型
|
||
if (_currentTable != null)
|
||
{
|
||
_currentTable.SetActive(false);
|
||
}
|
||
|
||
// 监听战斗房间清空事件以显示模型
|
||
CombatManager.CombatRoomSm.OnRoomCleared += OnRoomCleared;
|
||
|
||
// 如果房间已提前清空(例如非战斗阶段),直接显示模型
|
||
if (RunManager.Instance != null && RunManager.Instance.currentPhase == RunPhase.MapSelection)
|
||
{
|
||
OnRoomCleared();
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
CombatManager.CombatRoomSm.OnRoomCleared -= OnRoomCleared;
|
||
|
||
// 清理内存中未被选中的剩余候选物品
|
||
ClearCurrentOffers();
|
||
}
|
||
|
||
private void OnRoomCleared()
|
||
{
|
||
// 激活并显示台子模型
|
||
Setup();
|
||
if (_currentTable != null)
|
||
{
|
||
_currentTable.SetActive(true);
|
||
}
|
||
Debug.Log($"[RewardTable] 战斗房间已清空,奖励台模型已激活 ({tier})。");
|
||
}
|
||
|
||
protected override void InitializeChoices()
|
||
{
|
||
choices.Add(new InteractionChoice("查看", OpenTable));
|
||
}
|
||
|
||
private void Setup()
|
||
{
|
||
if (_currentTable != null) Destroy(_currentTable);
|
||
|
||
if (_tablePrefabs.TryGetValue(tier, out GameObject tablePrefab) && tablePrefab != null)
|
||
{
|
||
_currentTable = Instantiate(tablePrefab, transform);
|
||
}
|
||
|
||
DetermineRewardTypes();
|
||
|
||
_isAvailable = true;
|
||
}
|
||
|
||
private void DetermineRewardTypes()
|
||
{
|
||
_selectedTypes.Clear();
|
||
|
||
// 根据游戏运行种子初始化随机数生成器
|
||
System.Random rng = RunManager.Instance?.currentRun?.randomizer?.Next() ?? new System.Random();
|
||
|
||
// 检查玩家背包中当前是否有任何可升级的装备
|
||
var backpack = MainGameManager.Player.inventorySc.backpackSm;
|
||
bool hasUpgradeable = backpack.mainWeapons.Exists(i => i.IsUpgradable) ||
|
||
backpack.supportEquipments.Exists(i => i.IsUpgradable) ||
|
||
backpack.passiveEquipments.Exists(i => i.IsUpgradable);
|
||
|
||
// 可用的奖励类型池
|
||
// 0: 增加最大生命值, 1: 增加最大能量值, 2: 升级装备值, 3: 稀有材料
|
||
List<int> pool = new List<int> { 0, 1, 3 };
|
||
if (hasUpgradeable)
|
||
{
|
||
pool.Add(2);
|
||
}
|
||
|
||
// 恰好挑选 3 个不重复的奖励选项
|
||
while (_selectedTypes.Count < OFFER_COUNT && pool.Count > 0)
|
||
{
|
||
int index = rng.Next(pool.Count);
|
||
_selectedTypes.Add(pool[index]);
|
||
pool.RemoveAt(index);
|
||
}
|
||
}
|
||
|
||
private void OpenTable()
|
||
{
|
||
if (!_isAvailable)
|
||
{
|
||
Debug.Log("[RewardTable] 奖励台已被使用。");
|
||
return;
|
||
}
|
||
|
||
// 若尚未生成候选奖励,则进行生成
|
||
if (_currentOffers.Count == 0)
|
||
{
|
||
GenerateOffers();
|
||
}
|
||
|
||
if (_currentOffers == null || _currentOffers.Count == 0)
|
||
{
|
||
Debug.LogWarning("[RewardTable] 生成候选奖励失败。");
|
||
return;
|
||
}
|
||
|
||
// 打开奖励选择 UI 页面
|
||
var uiPage = PlayerCanvas.MainGamePages.rewardChoicePage;
|
||
if (uiPage != null)
|
||
{
|
||
uiPage.SetOffers(_currentOffers, HandleRewardSelected);
|
||
uiPage.Open();
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("[RewardTable] PlayerCanvas.MainGamePages.rewardChoicePage 未注册!");
|
||
}
|
||
}
|
||
|
||
private void GenerateOffers()
|
||
{
|
||
ClearCurrentOffers();
|
||
|
||
// 仅在首次打开奖励台的瞬间,动态拉取玩家背包中当前拥有的所有可升级装备
|
||
var backpack = MainGameManager.Player.inventorySc.backpackSm;
|
||
List<ItemBase> upgradeableItems = new List<ItemBase>();
|
||
|
||
foreach (var item in backpack.mainWeapons)
|
||
{
|
||
if (item.IsUpgradable) upgradeableItems.Add(item);
|
||
}
|
||
foreach (var item in backpack.supportEquipments)
|
||
{
|
||
if (item.IsUpgradable) upgradeableItems.Add(item);
|
||
}
|
||
foreach (var item in backpack.passiveEquipments)
|
||
{
|
||
if (item.IsUpgradable) upgradeableItems.Add(item);
|
||
}
|
||
|
||
// 根据之前随机选定的类型,实例化奖励选项的实例物体
|
||
foreach (int typeIndex in _selectedTypes)
|
||
{
|
||
ItemBase itemInstance = null;
|
||
if (typeIndex == 0) // 增加最大生命值
|
||
{
|
||
itemInstance = CreateItemInstance<PermanentCoating>("PermanentCoating");
|
||
if (itemInstance is PermanentCoating coating)
|
||
{
|
||
coating.stackAmount = (tier == RewardTier.Major) ? 2 : 1;
|
||
}
|
||
}
|
||
else if (typeIndex == 1) // 增加最大能量值
|
||
{
|
||
itemInstance = CreateItemInstance<EnergyReservoir>("EnergyReservoir");
|
||
if (itemInstance is EnergyReservoir reservoir)
|
||
{
|
||
reservoir.stackAmount = (tier == RewardTier.Major) ? 2 : 1;
|
||
}
|
||
}
|
||
else if (typeIndex == 2) // 装备升级组件
|
||
{
|
||
itemInstance = CreateItemInstance<PrefabricatedComponent>("PrefabricatedComponent");
|
||
if (itemInstance is PrefabricatedComponent prefabricatedComponent)
|
||
{
|
||
// 预制组件的数量(升级的次数)也与奖励档次挂钩:普通奖为 1 次,Major 奖为 2 次
|
||
prefabricatedComponent.stackAmount = (tier == RewardTier.Major) ? 2 : 1;
|
||
|
||
if (upgradeableItems.Count > 0)
|
||
{
|
||
// 此处使用 Unity 的 Random 随机抽取目标装备,避免在 UI 交互时推进游戏主运行种子的状态
|
||
int randIdx = UnityEngine.Random.Range(0, upgradeableItems.Count);
|
||
prefabricatedComponent.targetEquipment = upgradeableItems[randIdx];
|
||
}
|
||
}
|
||
}
|
||
else if (typeIndex == 3) // 稀有材料
|
||
{
|
||
itemInstance = CreateItemInstance<RareMaterial>("RareMaterial");
|
||
if (itemInstance is RareMaterial rareMaterial)
|
||
{
|
||
// 此处同样使用 Unity 的 Random,避免推进主运行种子,并根据奖励档次生成对应区间的数量
|
||
int amount = (tier == RewardTier.Major) ? UnityEngine.Random.Range(120, 181) : UnityEngine.Random.Range(40, 61);
|
||
rareMaterial.stackAmount = amount;
|
||
}
|
||
}
|
||
|
||
if (itemInstance != null)
|
||
{
|
||
_currentOffers.Add(itemInstance);
|
||
}
|
||
}
|
||
}
|
||
|
||
private ItemBase CreateItemInstance<T>(string prefabName) where T : ItemBase
|
||
{
|
||
string resourcePath = $"Items/Consumable/{prefabName}";
|
||
GameObject prefab = Resources.Load<GameObject>(resourcePath);
|
||
if (prefab == null)
|
||
{
|
||
Debug.LogError($"[RewardTable] Failed to load prefab from Resources/{resourcePath}");
|
||
return null;
|
||
}
|
||
|
||
GameObject inst = Instantiate(prefab, transform);
|
||
inst.name = prefabName;
|
||
T component = inst.GetComponent<T>();
|
||
if (component == null)
|
||
{
|
||
Debug.LogError($"[RewardTable] Prefab {prefabName} is missing script {typeof(T).Name}");
|
||
Destroy(inst);
|
||
return null;
|
||
}
|
||
|
||
return component;
|
||
}
|
||
|
||
private void HandleRewardSelected(int index)
|
||
{
|
||
if (index < 0 || index >= _currentOffers.Count) return;
|
||
|
||
ItemBase selectedItem = _currentOffers[index];
|
||
var backpack = MainGameManager.Player.inventorySc.backpackSm;
|
||
|
||
// 判断该物品是否为可堆叠的消耗品
|
||
ConsumableBase selectedConsumable = selectedItem as ConsumableBase;
|
||
bool wasStackedIntoExisting = false;
|
||
if (selectedConsumable != null)
|
||
{
|
||
// 如果背包中已经存在该消耗品,那么在获取时它会合并堆叠,
|
||
// 此时当前的实例将不需要以单独元素形式加入背包列表。
|
||
ConsumableBase existingItem = backpack.consumables.Find(c => c.GetType() == selectedConsumable.GetType());
|
||
if (existingItem != null)
|
||
{
|
||
wasStackedIntoExisting = true;
|
||
}
|
||
}
|
||
|
||
// 将选中的物品加入到玩家背包
|
||
backpack.ObtainItem(selectedItem);
|
||
|
||
// 处理临时生成的物品实例的生命周期清理(如果它没有在自身的 OnObtained 逻辑中自毁)
|
||
if (selectedItem != null)
|
||
{
|
||
if (wasStackedIntoExisting && selectedConsumable != null)
|
||
{
|
||
// 如果已堆叠合并,销毁临时游戏物体
|
||
Destroy(selectedConsumable.gameObject);
|
||
}
|
||
else if (selectedConsumable != null && backpack.consumables.Contains(selectedConsumable))
|
||
{
|
||
// 如果是作为新的一项添加到背包,将其移动到背包容器节点下并归零坐标
|
||
selectedConsumable.transform.SetParent(MainGameManager.Player.inventorySc.consumableContainer);
|
||
selectedConsumable.transform.localPosition = Vector3.zero;
|
||
}
|
||
}
|
||
|
||
// 销毁其余未被选中的候选物品实例
|
||
for (int i = 0; i < _currentOffers.Count; i++)
|
||
{
|
||
if (i != index && _currentOffers[i] != null)
|
||
{
|
||
Destroy(_currentOffers[i].gameObject);
|
||
}
|
||
}
|
||
_currentOffers.Clear();
|
||
|
||
// 将奖励台标记为已使用,并替换模型为不可用模型
|
||
_isAvailable = false;
|
||
if (_currentTable != null) Destroy(_currentTable);
|
||
if (_notAvailableTablePrefab != null)
|
||
{
|
||
_currentTable = Instantiate(_notAvailableTablePrefab, transform);
|
||
}
|
||
|
||
SetExhausted();
|
||
Debug.Log($"[RewardTable] 已选择索引为 {index} 的奖励,奖励台交互关闭并进入耗尽状态。");
|
||
}
|
||
|
||
private void ClearCurrentOffers()
|
||
{
|
||
foreach (var item in _currentOffers)
|
||
{
|
||
if (item != null)
|
||
{
|
||
Destroy(item.gameObject);
|
||
}
|
||
}
|
||
_currentOffers.Clear();
|
||
}
|
||
}
|
||
}
|