Files
ft/Client/Assets/Scripts/Event1v1Battle/Panel/MainPanel/Event1v1BattleSlapPanel.cs
2026-06-29 21:18:33 +08:00

497 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using asap.core;
using cfg;
using Event1v1Battle.Data;
using game;
using GameCore;
using TimeManager;
using TMPro;
using UIExtend.Component;
using UniRx;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UI;
namespace Event1v1Battle.Panel.MainPanel
{
/// <summary>
/// 1v1扇耳光大赛主战斗面板 Event1v1BattleSlapPanel
/// </summary>
public class Event1v1BattleSlapPanel : BasePanel
{
#region UI Elements
// Top
private TMP_Text _textEventTime;
private TMP_Text _textStageHP;
private Image _imgStageHPBar;
private TMP_Text _textPlayerHP;
private Image _imgPlayerHPBar;
private Image[] _lifeImages;
/// <summary> fx_hploss 下与子节点,与 p_lives_icon 中心形顺序一一对应(本次扣第几颗心就开哪一个) </summary>
private GameObject[] _fxHpLossSlots;
private int? _overrideLifeDisplayCount;
private Head _myself;
private Head _enemy;
private GameObject _iconEmpty;
private Button _btnInfo;
private Button _btnPack; // 礼包按钮
//buff
private GameObject _saleObj;
private TMP_Text _textSaleTime;
private GameObject fx_ui_event1v1battle_slap_buff;
// Bottom
private Button _btnBattle;
private Button _btnStart;
private Button _btnReward;
private Button _btnClose;
private Button _btnRevive;
private TMP_Text _textRound;
private TMP_Text _textReviveBtn;
private StageItemCell[] _stageItemCells;
//chest
private PlayableDirector _chestDirector;
#endregion
#region Manager
private Event1v1BattleSlapManager _manager;
private Event1v1BattleSlapData Data => _manager?.LoadData();
#endregion
[Header("chest reward")]
[SerializeField] private float chestRewardTime = 1f;
[Header("fx_hploss")]
[Tooltip("子节点上无 Animation 默认 clip 时的等待时长(秒)")]
[SerializeField] private float _fxHpLossWaitSeconds = 0.5f;
private void Awake()
{
// Top
_textEventTime = gameObject.FindChildGameObject("p_text_event_time")?.GetComponent<TMP_Text>();
_lifeImages = gameObject.FindChildGameObject("p_lives_icon")?.GetComponentsInChildren<Image>(true);
var fxHpLossRoot = gameObject.FindChildGameObject("fx_hploss");
if (fxHpLossRoot != null)
{
var t = fxHpLossRoot.transform;
int childCount = t.childCount;
int slotCount = Mathf.Min(3, childCount);
_fxHpLossSlots = new GameObject[slotCount];
for (int i = 0; i < slotCount; i++)
{
_fxHpLossSlots[i] = t.GetChild(i).gameObject;
_fxHpLossSlots[i].SetActive(false);
}
}
_textStageHP = gameObject.FindChildGameObject("p_text_blood")?.GetComponent<TMP_Text>(); // NPC血量文本
_imgStageHPBar = gameObject.FindChildGameObject("p_bar_enermy")?.GetComponent<Image>(); // NPC血条
_enemy = gameObject.FindChildGameObject("head_enermy")?.GetComponent<Head>();
_iconEmpty = gameObject.FindChildGameObject("p_icon_empty");
_textPlayerHP = gameObject.FindChildGameObject("p_text_blood_myself")?.GetComponent<TMP_Text>(); // 玩家血量文本在myself下
_imgPlayerHPBar = gameObject.FindChildGameObject("p_bar_myself")?.GetComponent<Image>(); // 玩家血条
_myself = gameObject.FindChildGameObject("head_myself")?.GetComponent<Head>();
_btnPack = gameObject.FindChildGameObject("btn_pack")?.GetComponent<Button>();
_btnInfo = gameObject.FindChildGameObject("p_btn_questionmark")?.GetComponentInChildren<Button>();
// Bottom
_btnBattle = gameObject.FindChildGameObject("btn_battle")?.GetComponentInChildren<Button>();
_btnStart = gameObject.FindChildGameObject("btn_start")?.GetComponentInChildren<Button>();
_btnRevive = gameObject.FindChildGameObject("btn_revive")?.GetComponentInChildren<Button>();
_textReviveBtn = _btnRevive != null ? _btnRevive.GetComponentInChildren<TMP_Text>(true) : null;
_btnReward = gameObject.FindChildGameObject("btn_reward")?.GetComponent<Button>();
_btnClose = gameObject.FindChildGameObject("p_btn_close")?.GetComponent<Button>();
_textRound = gameObject.FindChildGameObject("p_text_rond")?.GetComponent<TMP_Text>(); // 修正prefab中是p_text_rond
_stageItemCells = gameObject.FindChildGameObject("p_bar_root")?.GetComponentsInChildren<StageItemCell>();
//buff
_saleObj = gameObject.FindChildGameObject("text_sale");
_textSaleTime = gameObject.FindChildGameObject("text_time").GetComponent<TMP_Text>();
fx_ui_event1v1battle_slap_buff = gameObject.FindChildGameObject("fx_ui_event1v1battle_slap_buff");
_chestDirector = gameObject.FindChildGameObject("spine_root").GetComponentInChildren<PlayableDirector>();
// Button listeners
if (_btnInfo)
_btnInfo.onClick.AddListener(OnClickInfo);
if (_btnPack)
_btnPack.onClick.AddListener(OnClickPack);
if (_btnBattle)
_btnBattle.onClick.AddListener(OnClickBattle);
if (_btnStart)
_btnStart.onClick.AddListener(OnClickStart);
if (_btnRevive)
_btnRevive.onClick.AddListener(OnClickRevive);
if (_btnReward)
_btnReward.onClick.AddListener(OnClickReward);
if (_btnClose)
_btnClose.onClick.AddListener(OnClickClose);
}
protected override void OnDestroy()
{
base.OnDestroy();
if (_btnInfo)
_btnInfo.onClick.RemoveAllListeners();
if (_btnBattle)
_btnBattle.onClick.RemoveAllListeners();
if (_btnPack)
_btnPack.onClick.RemoveAllListeners();
if (_btnClose)
_btnClose.onClick.RemoveAllListeners();
if (_btnReward)
_btnReward.onClick.RemoveAllListeners();
if (_btnStart)
_btnStart.onClick.RemoveAllListeners();
if (_btnRevive)
_btnRevive.onClick.RemoveAllListeners();
}
protected override void Start()
{
StartTimers();
GContext.OnEvent<AddGlobalBuffEvent>().Subscribe(OnAddGlobalBuffEvent).AddTo(disposables);
}
private void OnAddGlobalBuffEvent(AddGlobalBuffEvent e)
{
UpdateSaleBuff();
}
#region UI Refresh
private void RefreshUI()
{
if (Data == null) return;
UpdateButtons();
UpdateEventTime();
UpdateRoundInfo();
UpdateHP();
UpdateRemainingLives();
UpdateStageItem();
UpdateAvatar();
UpdateSaleBuff();
}
private void UpdateAvatar()
{
IUserService userService = GContext.container.Resolve<IUserService>();
_myself.SetData(userService.AvatarUrl);
if (Data is not { IsOnGoing: true })
{
_iconEmpty.SetActive(true);
}
else
{
_iconEmpty.SetActive(false);
_enemy.SetData(Data.NpcIcon);
}
}
private void UpdateStageItem()
{
if (Data == null || _stageItemCells == null) return;
var stageDatas = Data.StageHistory?.Select(history => (false, history.Value)).ToList() ?? new List<(bool, bool)>();
stageDatas.Add((true, false));
_stageItemCells.SetData(stageDatas);
}
private void UpdateButtons()
{
if (Data == null) return;
bool awaitingRevive = _manager != null && _manager.IsAwaitingPaidRevive(Data);
if (_btnBattle != null)
{
_btnBattle.transform.parent.SetActive(Data.IsOnGoing);
_btnBattle.SetActive(Data.IsOnGoing);
}
if (_btnStart != null)
{
_btnStart.transform.parent.SetActive(!Data.IsOnGoing && !awaitingRevive);
_btnStart.SetActive(!Data.IsOnGoing && !awaitingRevive);
}
if (_btnRevive != null)
{
_btnRevive.transform.parent.SetActive(!Data.IsOnGoing && awaitingRevive);
_btnRevive.SetActive(!Data.IsOnGoing && awaitingRevive);
}
if (_textReviveBtn != null && _manager != null && awaitingRevive)
{
int iapId = _manager.GetPaidReviveIapTableId(Data);
GContext.container.Resolve<PlayerItemData>().ResolveIapId(iapId, out _, out var price);
_textReviveBtn.text = LocalizationMgr.GetFormatTextValue("UI_Event1v1BattleSlapPanel_15", price);
}
}
private void UpdateEventTime()
{
if (_textEventTime == null || Data == null) return;
_textEventTime.text = ConvertTools.ConvertTime2(Data.RemainingTime);
}
private void UpdateRoundInfo()
{
if (_textRound == null || Data == null) return;
_textRound.text = LocalizationMgr.GetFormatTextValue("UI_Event1v1BattleSlapPanel_5", Data.StageHistory?.Count + 1 ?? 1,
Data.GetAllStageCount());
}
private void UpdateHP()
{
if (Data == null) return;
// NPC HP - 使用DOTween动画
if (_textStageHP)
_textStageHP.text = $"{Data.NpcHp}";
if (_imgStageHPBar)
_imgStageHPBar.fillAmount = Data.MaxHp > 0 ? (float)Data.NpcHp / Data.MaxHp : 0; ;
// Player HP - 使用DOTween动画
int playerMaxHP = _manager.GetPlayerMaxHp(Data);
if (_textPlayerHP)
_textPlayerHP.text = $"{Data.PlayerHp}";
if (_imgPlayerHPBar)
_imgPlayerHPBar.fillAmount = playerMaxHP > 0 ? (float)Data.PlayerHp / playerMaxHP : 0;
}
private void UpdateRemainingLives()
{
if (_lifeImages == null || Data == null) return;
int count = _overrideLifeDisplayCount.HasValue
? Mathf.Clamp(_overrideLifeDisplayCount.Value, 0, _lifeImages.Length)
: Mathf.Clamp(Data.PlayerLifeCount, 0, _lifeImages.Length);
for (int i = 0; i < _lifeImages.Length; i++)
{
_lifeImages[i].gameObject.SetActive(i >= _lifeImages.Length - count);
}
}
private void UpdateSaleBuff()
{
if (_saleObj == null || _textSaleTime == null) return;
var buffDataCenter = GContext.container.Resolve<BuffDataCenter>();
var targetBuffData = buffDataCenter.GetBuffTimeDataByType<EventSlapDamageBuff>();
if (targetBuffData == null) return;
if (targetBuffData is { isEnd: false } && (targetBuffData.buffEndTime - ZZTimeHelper.UtcNow().UtcNowOffset()).TotalSeconds > 0)
{
_saleObj.SetActive(false);
_textSaleTime.SetActive(true);
fx_ui_event1v1battle_slap_buff.SetActive(true);
var remaining = targetBuffData.buffEndTime - ZZTimeHelper.UtcNow().UtcNowOffset();
_textSaleTime.text = ConvertTools.ConvertTime2(remaining);
}
else
{
_saleObj.SetActive(true);
_textSaleTime.SetActive(false);
fx_ui_event1v1battle_slap_buff.SetActive(false);
}
}
#endregion
#region Timers
private void StartTimers()
{
GContext.container.Resolve<ITimeTickService>()?.SecondTick?.Subscribe(UpdateTimer).AddTo(disposables);
}
private void UpdateTimer(long obj)
{
UpdateEventTime();
UpdateSaleBuff();
}
#endregion
#region Player Actions
private void OnClickStart()
{
if (Data == null || _manager == null) return;
_manager.ShowStagePanel(Data);
}
private async void OnClickRevive()
{
if (Data == null || _manager == null) return;
if (!_manager.IsAwaitingPaidRevive(Data))
return;
await _manager.TryPurchasePaidReviveAsync(Data);
RefreshUI();
}
private void OnClickBattle()
{
GContext.container.Resolve<GuideDataCenter>().TriggerGuide(GroupName.Slap01.ToString(), "HomePanel", false);
UIManager.Instance.DestroyUI(UITypes.Event1v1BattleSlapPanel);
}
#endregion
#region Panel Navigation
private void OnClickInfo()
{
if (Data?.InitConfig == null) return;
UITypes.Event1v1BattleSlapInfoPopupPanel.SetType(Data.InitConfig.InfoPanel);
_ = UIManager.Instance.ShowUI(UITypes.Event1v1BattleSlapInfoPopupPanel);
}
private void OnClickPack()
{
_manager?.ShowFestPackPanel();
}
private void OnClickReward()
{
_manager?.ShowAllStageRewardPanel(Data);
}
private void OnClickClose()
{
UIManager.Instance.DestroyUI(UITypes.Event1v1BattleSlapPanel);
}
public async Task PlayStageRewardSendAni()
{
await PlayChestOpenAni();
_manager.SendStageReward(Data);
await Awaiters.Seconds(0.2f);
// 停表并回到与首次 Play 前一致的起点Evaluate 才会把绑定对象刷回该时刻姿态(仅 Stop/time 往往画面仍停在结尾)
_chestDirector.Stop();
_chestDirector.time = _chestDirector.initialTime;
_chestDirector.Evaluate();
}
#endregion
#region Public Methods
/// <summary>
/// 面板开始时调用
/// </summary>
public async void OnSetData()
{
try
{
int pendingHeartBefore = _manager?.PresentationData?.PendingMainPanelHeartVisualBefore ?? 0;
if (pendingHeartBefore > 0)
{
_overrideLifeDisplayCount = pendingHeartBefore;
RefreshUI();
await PlayReturnFromTvDefeatLifeLossIntroAsync(pendingHeartBefore);
_manager?.PresentationData.ClearPendingMainPanelHeartLossVisual();
_overrideLifeDisplayCount = null;
}
RefreshUI();
if (Data.IsNeedSendReward)
{
await PlayStageRewardSendAni();
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
/// <summary>
/// TV 战败回到主界面:先保持「扣心前」心形显示,在 fx_hploss 下打开对应槽位特效,再与存档对齐。
/// 槽位与主界面心形 Image 顺序一致:本次失去的是下标 (n - 扣前命数),与 UpdateRemainingLives 规则相同。
/// </summary>
async Task PlayReturnFromTvDefeatLifeLossIntroAsync(int heartCountBeforeLoss)
{
if (_lifeImages == null || _lifeImages.Length == 0 || heartCountBeforeLoss <= 0)
{
await Awaiters.Seconds(0.35f);
return;
}
int n = _lifeImages.Length;
int slotIndex = Mathf.Clamp(n - heartCountBeforeLoss, 0, n - 1);
if (_fxHpLossSlots == null || slotIndex >= _fxHpLossSlots.Length || _fxHpLossSlots[slotIndex] == null)
{
await Awaiters.Seconds(0.35f);
return;
}
var fxSlot = _fxHpLossSlots[slotIndex];
fxSlot.SetActive(true);
await WaitFxHpLossSlotAsync(fxSlot);
fxSlot.SetActive(false);
}
async Task WaitFxHpLossSlotAsync(GameObject fxSlot)
{
var anim = fxSlot.GetComponentInChildren<Animation>(true);
if (anim != null && anim.clip != null)
{
await anim.PlayAndWaitAsync(anim.clip.name);
return;
}
await Awaiters.Seconds(Mathf.Max(0.05f, _fxHpLossWaitSeconds));
}
public void SetData(Event1v1BattleSlapManager manager)
{
_manager = manager;
OnSetData();
}
public async Task PlayChestOpenAni()
{
try
{
if (_chestDirector == null || _chestDirector.playableAsset == null) return;
_chestDirector.Play();
await Awaiters.Seconds((float)_chestDirector.playableAsset.duration);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
#endregion
#if UNITY_EDITOR
#region debug
[Header("debug")]
[SerializeField] private int debug_stageID = 1001;
[ContextMenu("Debug_SetStage")]
public void Debug_SetStage()
{
_manager.Debug_SetStage(debug_stageID);
RefreshUI();
}
[SerializeField] private int debug_lifeCount;
[ContextMenu("Debug_SetLife")]
public void Debug_SetLife()
{
_manager.Debug_SetLife(debug_lifeCount);
}
#endregion
#endif
}
}