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();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|