714 lines
28 KiB
C#
714 lines
28 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using asap.core;
|
||
using EventSmash.Manager;
|
||
using game;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
using Activity;
|
||
using GameCore;
|
||
using TimeManager;
|
||
using UniRx;
|
||
using UIExtend.Component;
|
||
using Game;
|
||
|
||
namespace EventSmash.UIPanel
|
||
{
|
||
public class EventSmashPanel : BasePanel
|
||
{
|
||
#region 组件
|
||
private TMP_Text _tokenText;
|
||
private Image _tokenIcon;
|
||
|
||
private TMP_Text _txtTime;
|
||
|
||
private EventSmashProgressCell[] _progressCells;
|
||
private EventSmashCell[] _cellPrefabs;
|
||
|
||
private RewardFlyBatchController _rewardFlyBatch;
|
||
|
||
private Button _btnSmashAll;
|
||
private Button _btnClose;
|
||
private Button _btnInfo;
|
||
|
||
private Button _btnAdd;
|
||
private Button _btnBattlePass;
|
||
|
||
[Header("九格全开通关(节点名 Image_door_mask)")]
|
||
[Tooltip("通关遮罩激活后,经过此时长再 RefreshData")]
|
||
[SerializeField] private float _stageCompleteRefreshDataDelay = 0.3f;
|
||
[Tooltip("已确定领奖队列非空但 ShowUI 异步时,最多再等这么久直到 RewardPopup 显示;仅影响「有弹窗」分支")]
|
||
[SerializeField] private float _rewardPopupAsyncOpenMaxWaitSeconds = 2f;
|
||
[Tooltip("Legacy Animation 的 clip 名,或 Animator 的状态名(哈希);留空则播默认片段 / 第 0 层默认态")]
|
||
[SerializeField]
|
||
private string _doorMaskAnimClipName = "EventSmashScrapStageRefresh";
|
||
// Prefab/场景里序列化的字符串会覆盖上面默认值;通关遮罩应对齐 Animation 里的「关卡刷新」clip(如 EventSmashScrapStageRefresh),勿填 ProgressCell 的 EventSmashScrapPiplineCheckRefresh。
|
||
|
||
private GameObject _imageDoorMask;
|
||
#endregion
|
||
|
||
private EventSmashManager _manager;
|
||
private EventSmashData _data;
|
||
private IDeferredRewardStashService _stashService;
|
||
|
||
/// <summary>与砸罐回调同帧写入,<see cref="RefreshAfterSmashAnimAsync"/> 首帧后读取并清除;不依赖 Manager 跨帧标志。</summary>
|
||
private bool _stageCompletePendingDoorAnim;
|
||
|
||
/// <summary>按收集线加锁:不同 collectionId 可并行领奖/涨条;同一条仍串行(避免同一进度条并发写)。</summary>
|
||
private readonly object _collectionLocksRoot = new object();
|
||
private readonly Dictionary<int, SemaphoreSlim> _collectionClaimLocks = new Dictionary<int, SemaphoreSlim>();
|
||
|
||
/// <summary>引导表 GuidanceGroupDefine 首步 <c>panelName</c> 须与此一致(与预制体名 EventSmashScrapPanel 对齐)。</summary>
|
||
private const string SmashScrapGuidePanelKey = "EventSmashScrapPanel";
|
||
|
||
#region 生命周期
|
||
private void Awake()
|
||
{
|
||
_txtTime = gameObject.FindChildGameObject("text_time").GetComponent<TMP_Text>();
|
||
_tokenText = gameObject.FindChildGameObject("txt_num").GetComponent<TMP_Text>();
|
||
_tokenIcon = gameObject.FindChildGameObject("Img_ticket").GetComponent<Image>();
|
||
_progressCells = gameObject.GetComponentsInChildren<EventSmashProgressCell>(true);
|
||
_cellPrefabs = gameObject.GetComponentsInChildren<EventSmashCell>(true);
|
||
|
||
_rewardFlyBatch = gameObject.GetComponentInChildren<RewardFlyBatchController>(true);
|
||
_rewardFlyBatch.SetActive(true);
|
||
|
||
_btnSmashAll = gameObject.FindChildGameObject("btn_smash_all").GetComponent<Button>();
|
||
_btnSmashAll.onClick.AddListener(OnSmashAllClick);
|
||
|
||
_btnClose = gameObject.FindChildGameObject("btn_close").GetComponent<Button>();
|
||
_btnClose.onClick.AddListener(OnCloseClick);
|
||
|
||
_btnInfo = gameObject.FindChildGameObject("p_btn_questionmark").GetComponent<Button>();
|
||
_btnInfo.onClick.AddListener(OnInfoClick);
|
||
|
||
_btnAdd = gameObject.FindChildGameObject("btn_add").GetComponent<Button>();
|
||
_btnAdd.onClick.AddListener(OnAddClick);
|
||
|
||
var battlePassGo = gameObject.FindChildGameObject("btn_mini_battlepass");
|
||
if (battlePassGo != null)
|
||
{
|
||
_btnBattlePass = battlePassGo.GetComponent<Button>();
|
||
if (_btnBattlePass != null)
|
||
_btnBattlePass.onClick.AddListener(OnBattlePassClick);
|
||
}
|
||
|
||
// 与 EventBossFightMainPanel 一致:礼包/通用领奖面板关闭后同步代币(购买加票等)
|
||
GContext.OnEvent<RewardPanelClose>().Subscribe(OnRewardPanelClose).AddTo(disposables);
|
||
|
||
_imageDoorMask = gameObject.FindChildGameObject("Image_door_mask");
|
||
if (_imageDoorMask != null)
|
||
_imageDoorMask.SetActive(false);
|
||
}
|
||
|
||
protected override void Start()
|
||
{
|
||
base.Start();
|
||
InitManager();
|
||
RefreshData();
|
||
TryTriggerSmashScrapOpenGuide();
|
||
TrySettleAllPendingCollectionClaimsOnOpen();
|
||
UpdateTimer(0L);
|
||
GContext.container.Resolve<ITimeTickService>()?.SecondTick?.Subscribe(UpdateTimer).AddTo(disposables);
|
||
disposables.Add(UniRx.Disposable.Create(() =>
|
||
{
|
||
lock (_collectionLocksRoot)
|
||
{
|
||
foreach (var kv in _collectionClaimLocks)
|
||
{
|
||
try { kv.Value.Dispose(); }
|
||
catch (Exception) { /* ignored */ }
|
||
}
|
||
_collectionClaimLocks.Clear();
|
||
}
|
||
}));
|
||
}
|
||
private void UpdateTimer(long obj)
|
||
{
|
||
_txtTime.text = ConvertTools.ConvertTime2(_data.RemainingTime);
|
||
}
|
||
|
||
private void InitManager()
|
||
{
|
||
_manager = ActivityResolver.Resolve<EventSmashManager>();
|
||
_data = _manager.LoadData();
|
||
_stashService = GContext.container.Resolve<IDeferredRewardStashService>();
|
||
}
|
||
|
||
/// <summary>每期活动在新数据初始化时已 ClearGuide;此处打开面板尝试触发一次(引导组 Smash01)。</summary>
|
||
private void TryTriggerSmashScrapOpenGuide()
|
||
{
|
||
if (_data == null || !_data.IsActive || _data.MainConfig == null)
|
||
return;
|
||
GContext.container.Resolve<GuideDataCenter>()
|
||
.TriggerGuide(GroupName.Smash01.ToString(), SmashScrapGuidePanelKey, false, gameObject.name);
|
||
}
|
||
|
||
private void RefreshData()
|
||
{
|
||
if (_data == null || !_data.IsActive) return;
|
||
|
||
var mainConfig = _data.MainConfig;
|
||
if (mainConfig == null) return;
|
||
|
||
UpdateTokenDisplay();
|
||
UpdateProgressDisplay();
|
||
UpdateGrids();
|
||
UpdateSmashAllButton();
|
||
UpdateBattlePassButton();
|
||
}
|
||
|
||
private void UpdateTokenDisplay()
|
||
{
|
||
if (_tokenText != null && _data != null)
|
||
{
|
||
int tokenCount = _manager.GetCurrentTokenCount(_data);
|
||
_tokenText.text = tokenCount.ToString();
|
||
}
|
||
if (_tokenIcon != null)
|
||
{
|
||
var itemConfig = _manager.Tables.TbItem.GetOrDefault(_data.MainConfig.ItemID);
|
||
GContext.container.Resolve<IUIService>().SetImageSprite(_tokenIcon, itemConfig.Icon);
|
||
}
|
||
}
|
||
|
||
/// <summary>代币数量变化时刷新顶部数量与「一键砸」可用状态(与 BossFight 消耗/领奖后立刻刷新一致)。</summary>
|
||
private void RefreshTokenUiAfterBalanceChange()
|
||
{
|
||
if (_data == null || !_data.IsActive) return;
|
||
UpdateTokenDisplay();
|
||
UpdateSmashAllButton();
|
||
}
|
||
|
||
private void OnRewardPanelClose(RewardPanelClose _)
|
||
{
|
||
RefreshTokenUiAfterBalanceChange();
|
||
}
|
||
|
||
private void UpdateProgressDisplay()
|
||
{
|
||
if (_progressCells == null || _data == null || _manager == null) return;
|
||
if (_data.MainConfig == null) return;
|
||
|
||
var collectionList = _data.Tables.TbEventSmashCollection.DataList;
|
||
for (int i = 0; i < _progressCells.Length && i < collectionList.Count; i++)
|
||
{
|
||
var cell = _progressCells[i];
|
||
if (cell == null) continue;
|
||
|
||
cell.Initialize(collectionList[i].ID, _manager, _data);
|
||
}
|
||
}
|
||
|
||
private void UpdateGrids()
|
||
{
|
||
if (_cellPrefabs == null || _data == null) return;
|
||
|
||
for (int i = 0; i < _cellPrefabs.Length; i++)
|
||
{
|
||
var cell = _cellPrefabs[i];
|
||
if (cell == null) continue;
|
||
cell.Initialize(_data, i, TrySmashSingle, OnSmashAnimComplete,
|
||
(collectionId, fromReward, count) => RunCollectionRewardFlyAsync(collectionId, fromReward, count),
|
||
ProcessCollectionClaimsAfterFlyAsync);
|
||
}
|
||
}
|
||
|
||
private EventSmashProgressCell GetProgressCellByCollectionId(int collectionId)
|
||
{
|
||
if (_progressCells == null || _data?.Tables.TbEventSmashCollection?.DataList == null)
|
||
return null;
|
||
var list = _data.Tables.TbEventSmashCollection.DataList;
|
||
for (int i = 0; i < list.Count && i < _progressCells.Length; i++)
|
||
{
|
||
if (list[i].ID == collectionId)
|
||
return _progressCells[i];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private SemaphoreSlim GetCollectionClaimLock(int collectionId)
|
||
{
|
||
lock (_collectionLocksRoot)
|
||
{
|
||
if (!_collectionClaimLocks.TryGetValue(collectionId, out var sem))
|
||
{
|
||
sem = new SemaphoreSlim(1, 1);
|
||
_collectionClaimLocks[collectionId] = sem;
|
||
}
|
||
return sem;
|
||
}
|
||
}
|
||
|
||
private async Task RunClaimLoopForCollectionLockedAsync(int collectionId, int flyGrantCount = 0,
|
||
bool isSingleSmashOpen = false, bool flushStashAfter = true)
|
||
{
|
||
var sem = GetCollectionClaimLock(collectionId);
|
||
await sem.WaitAsync();
|
||
try
|
||
{
|
||
await RunClaimLoopForCollectionIdAsync(collectionId, flyGrantCount, isSingleSmashOpen, flushStashAfter);
|
||
}
|
||
finally
|
||
{
|
||
sem.Release();
|
||
}
|
||
}
|
||
|
||
/// <summary>收集物飞入后处理达标领奖;不同收集线互不阻塞,可并行涨进度。</summary>
|
||
private Task ProcessCollectionClaimsAfterFlyAsync(int collectionId, int flyGrantCount, bool isSingleSmashOpen)
|
||
{
|
||
if (_manager == null || _data == null)
|
||
return Task.CompletedTask;
|
||
return RunClaimLoopForCollectionLockedAsync(collectionId, flyGrantCount, isSingleSmashOpen);
|
||
}
|
||
|
||
private async Task RunClaimLoopForCollectionIdAsync(int collectionId, int flyGrantCount,
|
||
bool isSingleSmashOpen, bool flushStashAfter = true)
|
||
{
|
||
var progressCell = GetProgressCellByCollectionId(collectionId);
|
||
|
||
if (progressCell != null && !_manager.CanClaimCollectionTier(_data, collectionId))
|
||
{
|
||
await progressCell.PlayCollectionProgressVisualCatchUpAsync(_manager, _data, collectionId);
|
||
}
|
||
|
||
int tierCount = _manager.GetClaimableCollectionTierCount(_data, collectionId);
|
||
if (tierCount <= 0)
|
||
return;
|
||
|
||
var collectionConfig = _manager.Tables.TbEventSmashCollection.GetOrDefault(collectionId);
|
||
if (collectionConfig == null || collectionConfig.CollectingRequirement.Count == 0)
|
||
return;
|
||
|
||
if (progressCell != null)
|
||
{
|
||
progressCell.EnterClaimPresentationMode();
|
||
try
|
||
{
|
||
int amt = _data.GetCollectionData(collectionId);
|
||
int round = _data.CollectionProgressPairs.TryGetValue(collectionId, out var pair) ? pair.round : 0;
|
||
|
||
for (int i = 0; i < tierCount; i++)
|
||
{
|
||
int req = collectionConfig.CollectingRequirement[round % collectionConfig.CollectingRequirement.Count];
|
||
if (i == 0)
|
||
{
|
||
await progressCell.PlayCollectionFillAndCheckFirstTierAsync(
|
||
flyGrantCount,
|
||
isSingleSmashOpen && flyGrantCount > 0);
|
||
}
|
||
else
|
||
{
|
||
await progressCell.PlayCollectionFillAndCheckSubsequentTierAsync(amt, req, collectionConfig);
|
||
}
|
||
|
||
amt -= req;
|
||
round++;
|
||
}
|
||
|
||
for (int i = 0; i < tierCount; i++)
|
||
_manager.ClaimOneCollectionTierToStash(_data, collectionId);
|
||
|
||
await progressCell.PlayCollectionPostGrantVisualAsync();
|
||
}
|
||
finally
|
||
{
|
||
progressCell.ExitClaimPresentationMode();
|
||
}
|
||
|
||
if (flushStashAfter)
|
||
_stashService?.Flush();
|
||
return;
|
||
}
|
||
|
||
while (_manager.CanClaimCollectionTier(_data, collectionId))
|
||
_manager.ClaimOneCollectionTierToStash(_data, collectionId);
|
||
|
||
if (flushStashAfter)
|
||
_stashService?.Flush();
|
||
UpdateProgressDisplay();
|
||
}
|
||
|
||
/// <summary>进面板时补结算(例如上次领奖动效中途关闭);各收集线并行处理。</summary>
|
||
private async void TrySettleAllPendingCollectionClaimsOnOpen()
|
||
{
|
||
if (_manager == null || _data == null)
|
||
return;
|
||
var list = _data.Tables.TbEventSmashCollection?.DataList;
|
||
if (list == null || list.Count == 0)
|
||
return;
|
||
try
|
||
{
|
||
await Task.WhenAll(list.Select(cfg =>
|
||
RunClaimLoopForCollectionLockedAsync(cfg.ID, 0, false, flushStashAfter: false)));
|
||
_stashService?.Flush(RewardType.NormalOne, true);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError(e);
|
||
}
|
||
}
|
||
|
||
private RectTransform GetCollectionFlyTarget(int collectionId)
|
||
{
|
||
if (_progressCells == null || _data?.Tables.TbEventSmashCollection?.DataList == null)
|
||
return null;
|
||
var list = _data.Tables.TbEventSmashCollection.DataList;
|
||
for (int i = 0; i < list.Count && i < _progressCells.Length; i++)
|
||
{
|
||
if (list[i].ID == collectionId)
|
||
return _progressCells[i] != null ? _progressCells[i].GetProgressIconRect() : null;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>收集物:从格子 reward 飞到对应进度图标。</summary>
|
||
private Task RunCollectionRewardFlyAsync(int collectionId, RewardItemNew fromReward, int count)
|
||
{
|
||
if (_rewardFlyBatch == null || fromReward == null || _data?.MainConfig == null)
|
||
return Task.CompletedTask;
|
||
|
||
var targetRt = GetCollectionFlyTarget(collectionId);
|
||
if (targetRt == null)
|
||
return Task.CompletedTask;
|
||
|
||
var iconName = _data.MainConfig.GetCollectionIcon(_data.Tables.TbEventSmashCollection, collectionId);
|
||
if (string.IsNullOrEmpty(iconName))
|
||
return Task.CompletedTask;
|
||
|
||
var request = new BatchedRewardFlyRequest
|
||
{
|
||
Icon = new BatchedRewardFlyRequestIcon { iconName = iconName },
|
||
Quantity = count > 1 ? count : null,
|
||
StartPoint = new BatchedRewardFlyStartPoint(fromReward),
|
||
EndPoint = new BatchedRewardFlyEndPoint(targetRt),
|
||
IsDestinationRewardStash = false,
|
||
AnimationParamIndex = 0,
|
||
};
|
||
return _rewardFlyBatch.OnRewardFlyRequestAsync(request);
|
||
}
|
||
|
||
private void UpdateSmashAllButton()
|
||
{
|
||
if (_data.MainConfig == null) return;
|
||
int unopenedCount = 9 - _data.OpenedGrids.Count;
|
||
_btnSmashAll.enabled = unopenedCount>0;
|
||
}
|
||
|
||
private void UpdateBattlePassButton()
|
||
{
|
||
if (_btnBattlePass == null) return;
|
||
var main = _data?.MainConfig;
|
||
bool show = main != null && main.BattlePassId > 0 && !string.IsNullOrEmpty(main.BattlePassPanel);
|
||
_btnBattlePass.gameObject.SetActive(show);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件处理
|
||
|
||
private bool TrySmashSingle(int gridIndex)
|
||
{
|
||
if (_manager == null || _data == null) return false;
|
||
bool success = _manager.SmashSingle(_data, gridIndex, out bool stageJustCompleted);
|
||
if (success)
|
||
{
|
||
if (stageJustCompleted)
|
||
_stageCompletePendingDoorAnim = true;
|
||
SetInteractable(false);
|
||
RefreshTokenUiAfterBalanceChange();
|
||
}
|
||
return success;
|
||
}
|
||
|
||
private void OnSmashAnimComplete()
|
||
{
|
||
// 推迟一帧再刷新:避免刚结束 Timeline/Animation 的格子与 Initialize 在同一帧写 Transform 竞态(常见表现为最后一格通关后不回到未开启态)。
|
||
_ = RefreshAfterSmashAnimAsync();
|
||
}
|
||
|
||
private async Task RefreshAfterSmashAnimAsync()
|
||
{
|
||
var stageCompletedThisRefresh = false;
|
||
var didRefreshGridsThisRefresh = false;
|
||
try
|
||
{
|
||
await Awaiters.NextFrame;
|
||
|
||
bool stageComplete = _stageCompletePendingDoorAnim;
|
||
_stageCompletePendingDoorAnim = false;
|
||
stageCompletedThisRefresh = stageComplete;
|
||
|
||
if (stageComplete && _imageDoorMask != null)
|
||
{
|
||
try
|
||
{
|
||
// ShowData 后由 WaitUntil… 用 CurRewardQCount 判断是否真有领奖弹窗,再决定等不等关闭
|
||
await WaitUntilSmashRewardPopupReleasedAsync();
|
||
|
||
_imageDoorMask.SetActive(true);
|
||
float clipDuration = await StartDoorMaskVisualAndGetDurationAsync();
|
||
|
||
await Awaiters.Seconds(_stageCompleteRefreshDataDelay);
|
||
// 须含 UpdateGrids:通关时数据已清空 OpenedGrids 并生成新 GridRewardMap。
|
||
RefreshData();
|
||
didRefreshGridsThisRefresh = true;
|
||
|
||
float remaining = Mathf.Max(0f, clipDuration - _stageCompleteRefreshDataDelay);
|
||
if (remaining > 0f)
|
||
await Awaiters.Seconds(remaining);
|
||
else if (clipDuration <= 0f)
|
||
await Awaiters.NextFrame;
|
||
}
|
||
finally
|
||
{
|
||
_imageDoorMask.SetActive(false);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
RefreshData();
|
||
didRefreshGridsThisRefresh = true;
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogException(e);
|
||
}
|
||
finally
|
||
{
|
||
// 通关流程中若等待弹窗/播片异常提前退出,避免九格永远停在上一关表现且按钮永久禁用
|
||
if (stageCompletedThisRefresh && !didRefreshGridsThisRefresh)
|
||
{
|
||
try
|
||
{
|
||
RefreshData();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogException(e);
|
||
}
|
||
}
|
||
|
||
SetInteractable(true);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 砸罐 Cell 已 Publish ShowData(同帧或上一帧)。用 <see cref="CurRewardQCount"/> 同步读 FishingStage 领奖队列深度(与 HomePanel 一致),
|
||
/// 仅当「队列有待展示」或「弹窗已在显示」时才等待关闭;无队列则立即继续,不用时间窗猜弹窗。
|
||
/// </summary>
|
||
private async Task WaitUntilSmashRewardPopupReleasedAsync(float maxWaitSeconds = 15f)
|
||
{
|
||
await Awaiters.NextFrame;
|
||
await Awaiters.NextFrame;
|
||
|
||
var ui = UIManager.Instance;
|
||
if (ui == null)
|
||
return;
|
||
|
||
int queueCount = PublishAndReadRewardQueueCount();
|
||
bool popupVisible = ui.IsShowingUI(UITypes.RewardPopupPanel);
|
||
if (queueCount <= 0 && !popupVisible)
|
||
return;
|
||
|
||
float hardDeadline = Time.realtimeSinceStartup + Mathf.Max(3f, maxWaitSeconds);
|
||
|
||
// 队列非空时 ShowPeek→ShowUI 可能尚未完成,短等到真正显示(串行:有队列才等打开)
|
||
if (!popupVisible)
|
||
{
|
||
float openDeadline = Time.realtimeSinceStartup +
|
||
Mathf.Clamp(_rewardPopupAsyncOpenMaxWaitSeconds, 0.25f, 8f);
|
||
while (Time.realtimeSinceStartup < openDeadline &&
|
||
Time.realtimeSinceStartup < hardDeadline &&
|
||
!ui.IsShowingUI(UITypes.RewardPopupPanel))
|
||
{
|
||
queueCount = PublishAndReadRewardQueueCount();
|
||
if (queueCount <= 0)
|
||
return;
|
||
await Awaiters.NextFrame;
|
||
}
|
||
}
|
||
|
||
if (!ui.IsShowingUI(UITypes.RewardPopupPanel))
|
||
{
|
||
Debug.LogWarning(
|
||
"[EventSmashPanel] 领奖队列曾非空但限时内 RewardPopup 未显示,继续通关遮罩/刷新。");
|
||
return;
|
||
}
|
||
|
||
while (Time.realtimeSinceStartup < hardDeadline &&
|
||
ui.IsShowingUI(UITypes.RewardPopupPanel))
|
||
{
|
||
var gotClose = false;
|
||
var sub = GContext.OnEvent<RewardPanelClose>().Subscribe(_ => gotClose = true);
|
||
try
|
||
{
|
||
while (Time.realtimeSinceStartup < hardDeadline &&
|
||
ui.IsShowingUI(UITypes.RewardPopupPanel) &&
|
||
!gotClose)
|
||
await Awaiters.NextFrame;
|
||
}
|
||
finally
|
||
{
|
||
sub.Dispose();
|
||
}
|
||
|
||
await Awaiters.NextFrame;
|
||
}
|
||
|
||
if (ui.IsShowingUI(UITypes.RewardPopupPanel))
|
||
Debug.LogWarning(
|
||
"[EventSmashPanel] 通关等待 RewardPopup 关闭超时,继续播关门/刷新;请检查 IsShowingUI 是否误报。");
|
||
}
|
||
|
||
/// <summary>FishingStage.GetShowDataCount 在 Publish 时同步写入 count(含 fishCard/fishBox 占位)。</summary>
|
||
private static int PublishAndReadRewardQueueCount()
|
||
{
|
||
var q = new CurRewardQCount();
|
||
GContext.Publish(q);
|
||
return q.count;
|
||
}
|
||
|
||
/// <summary>在已激活的 Image_door_mask 上启动 Legacy Animation 或 Animator,并返回用于等待的总时长(秒)。</summary>
|
||
private async Task<float> StartDoorMaskVisualAndGetDurationAsync()
|
||
{
|
||
if (_imageDoorMask == null) return 0f;
|
||
|
||
var legacy = _imageDoorMask.GetComponentInChildren<Animation>(true);
|
||
if (legacy != null)
|
||
{
|
||
if (!TryPlayLegacyDoorMaskClip(legacy, out var st))
|
||
return 0f;
|
||
|
||
float spd = Mathf.Abs(st.speed) > 0.001f ? Mathf.Abs(st.speed) : 1f;
|
||
return st.clip.length / spd;
|
||
}
|
||
|
||
var animator = _imageDoorMask.GetComponentInChildren<Animator>(true);
|
||
if (animator != null && animator.runtimeAnimatorController != null)
|
||
{
|
||
animator.enabled = true;
|
||
if (!string.IsNullOrEmpty(_doorMaskAnimClipName))
|
||
animator.Play(Animator.StringToHash(_doorMaskAnimClipName), 0, 0f);
|
||
else
|
||
{
|
||
var clips = animator.runtimeAnimatorController.animationClips;
|
||
if (clips != null && clips.Length > 0)
|
||
animator.Play(clips[0].name, 0, 0f);
|
||
else
|
||
return 0f;
|
||
}
|
||
|
||
await Awaiters.NextFrame;
|
||
var info = animator.GetCurrentAnimatorStateInfo(0);
|
||
float spd = Mathf.Abs(info.speed) > 0.001f ? Mathf.Abs(info.speed) : 1f;
|
||
float len = info.length > 0.01f ? info.length / spd : 0f;
|
||
return len;
|
||
}
|
||
|
||
return 0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 1) 配置 clip 名 2) 组件默认 clip 3) 遍历 Animation 列表第一个有 clip 的状态(解决预制体未设默认、名称与配置不一致)。
|
||
/// </summary>
|
||
private bool TryPlayLegacyDoorMaskClip(Animation legacy, out AnimationState st)
|
||
{
|
||
st = null;
|
||
|
||
if (!string.IsNullOrEmpty(_doorMaskAnimClipName) && legacy[_doorMaskAnimClipName] != null)
|
||
{
|
||
st = legacy[_doorMaskAnimClipName];
|
||
legacy.Play(_doorMaskAnimClipName);
|
||
return true;
|
||
}
|
||
|
||
if (legacy.clip != null)
|
||
{
|
||
st = legacy[legacy.clip.name];
|
||
legacy.Play();
|
||
return true;
|
||
}
|
||
|
||
foreach (AnimationState state in legacy)
|
||
{
|
||
if (state == null || state.clip == null) continue;
|
||
st = state;
|
||
legacy.Play(state.name);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void OnSmashAllClick()
|
||
{
|
||
if (_manager == null || _data == null) return;
|
||
if (!_manager.SmashAll(_data, out bool stageJustCompleted)) return;
|
||
|
||
if (stageJustCompleted)
|
||
_stageCompletePendingDoorAnim = true;
|
||
SetInteractable(false);
|
||
RefreshTokenUiAfterBalanceChange();
|
||
EventSmashCell.PlayOpenAll(_cellPrefabs, OnSmashAnimComplete);
|
||
GContext.Publish(new EventUISound("audio_ui_eventsmashyard_all_prop"));
|
||
}
|
||
|
||
private void OnCloseClick()
|
||
{
|
||
if (GContext.container.ResolveAct(EventSmashAct.ActAddress) != null)
|
||
GContext.Publish(new UnloadActToNextAct());
|
||
}
|
||
|
||
private void OnInfoClick()
|
||
{
|
||
// 显示活动说明面板
|
||
_=UIManager.Instance.ShowUI(new UIType(_data.MainConfig.InfoPanel));
|
||
}
|
||
|
||
private void OnAddClick()
|
||
{
|
||
_manager.ShowFestPackPanel(_data);
|
||
}
|
||
|
||
private async void OnBattlePassClick()
|
||
{
|
||
if (_data?.MainConfig == null) return;
|
||
var main = _data.MainConfig;
|
||
if (main.BattlePassId <= 0 || string.IsNullOrEmpty(main.BattlePassPanel)) return;
|
||
await UIManager.Instance.ShowUILoad(new UIType(main.BattlePassPanel));
|
||
}
|
||
|
||
/// <summary>单砸/全砸动效进行中关闭顶部与导航按钮,避免重复操作;结束后再按数据恢复(一键砸仍由 <see cref="UpdateSmashAllButton"/> 决定)。</summary>
|
||
private void SetInteractable(bool interactable)
|
||
{
|
||
if (_btnClose != null)
|
||
_btnClose.enabled = interactable;
|
||
if (_btnInfo != null)
|
||
_btnInfo.enabled = interactable;
|
||
if (_btnAdd != null)
|
||
_btnAdd.enabled = interactable;
|
||
if (_btnBattlePass != null)
|
||
_btnBattlePass.enabled = interactable;
|
||
|
||
if (interactable)
|
||
UpdateSmashAllButton();
|
||
else if (_btnSmashAll != null)
|
||
_btnSmashAll.enabled = false;
|
||
|
||
if (_cellPrefabs != null)
|
||
{
|
||
foreach (var cell in _cellPrefabs)
|
||
cell?.SetClickable(interactable);
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
}
|