457 lines
13 KiB
C#
457 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Animancer;
|
||
using Logic;
|
||
using Logic.Action;
|
||
using Logic.Audio;
|
||
using Logic.CrashSight;
|
||
using Logic.Multilingual;
|
||
using RuntimeData;
|
||
using TH1_Core.Events;
|
||
using TH1_Core.Managers;
|
||
using TH1_Logic.Core;
|
||
using TH1_Logic.Config;
|
||
using TH1_Logic.Net;
|
||
using TH1_Logic.Steam;
|
||
using TH1_UI.Components;
|
||
using TH1_UI.HintUI;
|
||
using TH1Renderer;
|
||
using TH1Resource;
|
||
using TMPro;
|
||
using UI.HintUI;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
namespace TH1_UI.View.Bottom
|
||
{
|
||
public class UIBottomBottomBarView : Base.View
|
||
{
|
||
//public Button closeButton;
|
||
public ViDelegateAssisstant.Dele OnBtnCloseClick;
|
||
public ViDelegateAssisstant.Dele OnBtnQuitClick;
|
||
|
||
public Button TechButton;
|
||
public Button HeroButton;
|
||
public Button NextButton;
|
||
public Button TabButton;
|
||
public GameObject HeroButtonIconHint;
|
||
public GameObject HeroButtonTextHint;
|
||
public Image HeroButtonAvatar;
|
||
public Button SettingButton;
|
||
public Button QuitButton;
|
||
public Button SLButton;
|
||
public Button RankingButton;
|
||
public Button WikiButton;
|
||
|
||
public UIBottomBottomBarQuitPanelMono QuitPanel;
|
||
public UIBottomBottomBarNextTurnConfirmPanelMono NextTurnConfirmPanel;
|
||
|
||
public int _tabUnitCount = 0;
|
||
private TextMeshProUGUI _nextButtonText;
|
||
private MultilingualTextMono _nextButtonMultilingualText;
|
||
private string _lastNextTurnCountdownParam;
|
||
private int _lastCountdownBeepSecond = -1;
|
||
|
||
protected override void OnInit()
|
||
{
|
||
base.OnInit();
|
||
//closeButton.onClick.RemoveAllListeners();
|
||
//closeButton.onClick.AddListener(() => { OnBtnCloseClick.Invoke(); });
|
||
//CheckPanel.InitStart(RefreshStatus);
|
||
|
||
if (QuitPanel != null)
|
||
{
|
||
QuitPanel.OnInit();
|
||
QuitPanel.OnQuitConfirm += OnQuitConfirmed;
|
||
}
|
||
|
||
if (NextTurnConfirmPanel != null)
|
||
{
|
||
NextTurnConfirmPanel.OnInit();
|
||
NextTurnConfirmPanel.OnNextTurnConfirm += ExecuteNextTurn;
|
||
}
|
||
}
|
||
|
||
public bool CheckHasTabUnit(out GridData grid)
|
||
{
|
||
grid = null;
|
||
if (Main.MapData == null) return false;
|
||
bool ret = false;
|
||
GridData firstGrid = null;
|
||
int tt = 0;
|
||
foreach (var u in Main.MapData.UnitMap.UnitList)
|
||
{
|
||
|
||
if (u.Player(Main.MapData) == Main.MapData.PlayerMap.SelfPlayerData &&
|
||
//u.Renderer(Main.MapData).isGlow())
|
||
(u.Renderer(Main.MapData)?.isGlow() ?? false))
|
||
{
|
||
|
||
grid = u.Grid(Main.MapData);
|
||
if (firstGrid == null) firstGrid = grid;
|
||
ret = true;
|
||
if(tt < _tabUnitCount)
|
||
tt++;
|
||
else
|
||
{
|
||
tt++;
|
||
_tabUnitCount = tt;
|
||
return ret;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
if (ret)
|
||
{
|
||
grid = firstGrid;
|
||
_tabUnitCount = 1;
|
||
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
public void SetTabButtonVisible(bool visible)
|
||
{
|
||
if(visible) ShowButton(TabButton);
|
||
else HideButton(TabButton);
|
||
}
|
||
|
||
public void SetNextTurnButtonVisible(bool visible)
|
||
{
|
||
if(visible) ShowBottomBarNextTurn();
|
||
else HideBottomBarNextTurn();
|
||
}
|
||
|
||
//通常一局只会set一次,每次都是完全清空,然后绑定新的一个对局的按钮
|
||
public void SetContent()
|
||
{
|
||
if (QuitPanel != null)
|
||
QuitPanel.gameObject.SetActive(false);
|
||
if (NextTurnConfirmPanel != null)
|
||
NextTurnConfirmPanel.gameObject.SetActive(false);
|
||
CacheNextTurnButtonText();
|
||
|
||
QuitButton.onClick.RemoveAllListeners();
|
||
QuitButton.onClick.AddListener(OnQuitButtonClicked);
|
||
SettingButton.onClick.RemoveAllListeners();
|
||
SettingButton.onClick.AddListener(OnSettingButtonClicked);
|
||
TechButton.onClick.RemoveAllListeners();
|
||
TechButton.onClick.AddListener(OnTechButtonClicked);
|
||
NextButton.onClick.RemoveAllListeners();
|
||
NextButton.onClick.AddListener(OnNextButtonClicked);
|
||
HeroButton.onClick.RemoveAllListeners();
|
||
HeroButton.onClick.AddListener(OnHeroButtonClicked);
|
||
SLButton.onClick.RemoveAllListeners();
|
||
SLButton.gameObject.SetActive(false);
|
||
if (Main.MapData.Net.Mode == NetMode.Single)
|
||
{
|
||
SLButton.gameObject.SetActive(true);
|
||
SLButton.onClick.AddListener(OnSLButtonClicked);
|
||
}
|
||
|
||
RankingButton.onClick.RemoveAllListeners();
|
||
RankingButton.onClick.AddListener(OnRankingButtonClicked);
|
||
|
||
TabButton.onClick.RemoveAllListeners();
|
||
TabButton.onClick.AddListener(OnTabButtonClicked);
|
||
|
||
if (WikiButton != null)
|
||
{
|
||
WikiButton.onClick.RemoveAllListeners();
|
||
WikiButton.onClick.AddListener(OnWikiButtonClicked);
|
||
}
|
||
|
||
UpdateHeroButtonAvatar();
|
||
|
||
// 初始检查 TabButton 是否显示
|
||
SetTabButtonVisible(CheckHasTabUnit(out var _));
|
||
RefreshNextTurnCountdown();
|
||
}
|
||
|
||
private void OnQuitButtonClicked()
|
||
{
|
||
if (QuitPanel != null)
|
||
{
|
||
QuitPanel.Open();
|
||
}
|
||
else
|
||
{
|
||
OnQuitConfirmed();
|
||
}
|
||
}
|
||
|
||
private void OnQuitConfirmed()
|
||
{
|
||
OnBtnCloseClick.Invoke();
|
||
OnBtnQuitClick.Invoke();
|
||
}
|
||
|
||
private void OnSettingButtonClicked()
|
||
{
|
||
EventManager.Publish(new ShowUITopSetting(){});
|
||
}
|
||
|
||
private void OnTechButtonClicked()
|
||
{
|
||
//if (Main.MapData.CurPlayer != Main.MapData.PlayerMap.SelfPlayerData) return;
|
||
EventManager.Publish(new ShowUIInfoTechTree(){});
|
||
Main.Instance.MapInteractionLogic.CancelAllHighlight();
|
||
}
|
||
|
||
private void OnHeroButtonClicked()
|
||
{
|
||
//if (Main.MapData.CurPlayer != Main.MapData.PlayerMap.SelfPlayerData) return;
|
||
EventManager.Publish(new ShowUIInfoHero(){});
|
||
}
|
||
|
||
private void OnNextButtonClicked()
|
||
{
|
||
if (Main.MapData.CurPlayer != Main.MapData.PlayerMap.SelfPlayerData) return;
|
||
|
||
// 二次确认开关打开 → 弹窗,等用户按 Check(OnNextTurnConfirm → ExecuteNextTurn)
|
||
if (ConfigManager.Instance.Config.ConfirmBeforeNextTurn && NextTurnConfirmPanel != null)
|
||
{
|
||
NextTurnConfirmPanel.Open();
|
||
return;
|
||
}
|
||
|
||
ExecuteNextTurn();
|
||
}
|
||
|
||
// 真正结束本回合的逻辑。CancelAllHighlight / HideUIInfoGridInfo 放在这里,
|
||
// 是为了让"弹窗确认"分支也走同一份清场代码;如果在弹窗里 Cancel,则现场完全保持。
|
||
private void ExecuteNextTurn()
|
||
{
|
||
Main.Instance.MapInteractionLogic.CancelAllHighlight();
|
||
EventManager.Publish(new HideUIInfoGridInfo(){});
|
||
if (Main.MapData.Net.Mode == NetMode.Multi && !LobbyManager.Instance.Lobby.IsLobbyOwner())
|
||
{
|
||
GameNetSender.Instance.TurnEndConfirm();
|
||
}
|
||
// 单机或者是房主直接切
|
||
else Main.PlayerLogic.EndPlayerTurn(Main.MapData, Main.MapData.PlayerMap.SelfPlayerId);
|
||
}
|
||
|
||
private void OnSLButtonClicked()
|
||
{
|
||
if (Main.MapData == null) return;
|
||
if (!Main.MapData.CurPlayer.IsSelfPlayer()) return;
|
||
if (Main.MapData.Net.Mode != NetMode.Single) return;
|
||
|
||
// 切到 Menu 状态前先抓 Empire:MenuState.Enter 会调 Main.Instance.Clear() 清空 MapData
|
||
var loadingEvt = new ShowUIOutsideLoading();
|
||
var selfData = Main.MapData.PlayerMap?.SelfPlayerData;
|
||
if (selfData != null) loadingEvt.Empire = selfData.Empire;
|
||
|
||
Main.Instance.GameLogic.ChangeState(GameState.Menu);
|
||
|
||
//TODO 这里的时间控制要处理
|
||
//播放loading
|
||
EventManager.Publish(loadingEvt);
|
||
//重置游戏
|
||
Timer.Instance.TimerRegister(this, () => { Main.Instance.ResumeMatch(); }, 0.3f,"MainUI_ShowLoadingAndRusume");
|
||
//关闭loading
|
||
Timer.Instance.TimerRegister(this,()=> { EventManager.Publish(new HideUIOutsideLoading()); },1f,"MainUI_ShowLoadingAndRusume2");
|
||
}
|
||
|
||
|
||
private void OnRankingButtonClicked()
|
||
{
|
||
EventManager.Publish(new ShowUIBottomRanking());
|
||
}
|
||
|
||
private void OnWikiButtonClicked()
|
||
{
|
||
// Inside 界面通过全局 EventManager 调起 Outside 的 Wiki 浮窗:
|
||
// UIEventManagerBinder 订阅 ShowUIOutsideWiki 后会调 UIOutsideManager.OpenWiki,
|
||
// Wiki 不占用 Outside 的 _task 槽位,可叠加在战内 UI 之上
|
||
EventManager.Publish(new ShowUIOutsideWiki());
|
||
}
|
||
|
||
private void OnTabButtonClicked()
|
||
{
|
||
SetTabButtonVisible(CheckHasTabUnit(out var grid));
|
||
if (grid == null) return;
|
||
// Tab 切换单位前先清掉所有 attack/ally 高亮,避免目标 unit 被当作技能目标释放技能
|
||
Main.Instance.MapInteractionLogic.CancelAllHighlight();
|
||
Main.Instance.MapInteractionLogic.OnTileClicked(Main.MapData,grid);
|
||
}
|
||
public void CloseView()
|
||
{
|
||
AudioManager.Instance.StopMusic();
|
||
}
|
||
|
||
public void UpdateHeroButtonAvatar()
|
||
{
|
||
if (Main.MapData?.PlayerMap?.SelfPlayerData?.PlayerHeroData == null) return;
|
||
bool newHero = Main.MapData.PlayerMap.SelfPlayerData.PlayerHeroData.GetHeroButtonHint();
|
||
HeroButtonTextHint.SetActive(newHero);
|
||
HeroButtonIconHint.SetActive(newHero);
|
||
|
||
HeroButtonAvatar.sprite = Main.MapData.PlayerMap.SelfPlayerData.PlayerHeroData.GetHeroButtonSprite();
|
||
}
|
||
|
||
|
||
public void ExecuteButtonClick(Button button)
|
||
{
|
||
var pointer = new PointerEventData(EventSystem.current);
|
||
var go = button.gameObject;
|
||
|
||
ExecuteEvents.Execute(go, pointer, ExecuteEvents.pointerDownHandler);
|
||
ExecuteEvents.Execute(go, pointer, ExecuteEvents.pointerUpHandler);
|
||
button.onClick.Invoke();
|
||
}
|
||
|
||
|
||
public void HideBottomBarNextTurn()
|
||
{
|
||
//HideButton(TechButton);
|
||
//HideButton(HeroButton);
|
||
HideButton(NextButton);
|
||
SetNextTurnCountdownParam(string.Empty);
|
||
}
|
||
|
||
public void HideBottomBarTabUnit()
|
||
{
|
||
//HideButton(TechButton);
|
||
//HideButton(HeroButton);
|
||
HideButton(TabButton);
|
||
}
|
||
|
||
public void ShowBottomBarTabUnit()
|
||
{
|
||
//HideButton(TechButton);
|
||
//HideButton(HeroButton);
|
||
ShowButton(TabButton);
|
||
}
|
||
public void ShowBottomBarNextTurn()
|
||
{
|
||
//ShowButton(TechButton);
|
||
ShowButton(NextButton);
|
||
RefreshNextTurnCountdown();
|
||
//ShowButton(HeroButton);
|
||
}
|
||
|
||
public void RefreshNextTurnCountdown()
|
||
{
|
||
SetNextTurnCountdownParam(GetNextTurnCountdownParam());
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
RefreshNextTurnCountdown();
|
||
}
|
||
|
||
private string GetNextTurnCountdownParam()
|
||
{
|
||
var map = Main.MapData;
|
||
if (map?.MapConfig == null || map.Net == null || map.PlayerMap == null) return string.Empty;
|
||
if (map.Net.Mode != NetMode.Multi) return string.Empty;
|
||
if (!map.MapConfig.IsLimitTime || map.MapConfig.TimeLimitSeconds <= 0) return string.Empty;
|
||
if (map.CurPlayer == null || map.PlayerMap.SelfPlayerData == null) return string.Empty;
|
||
if (map.CurPlayer != map.PlayerMap.SelfPlayerData) return string.Empty;
|
||
if (NextButton == null || !NextButton.gameObject.activeInHierarchy) return string.Empty;
|
||
|
||
var remainingSeconds = map.Net.PlayerStartTime > 0f
|
||
? Mathf.CeilToInt(map.MapConfig.TimeLimitSeconds - (Time.time - map.Net.PlayerStartTime))
|
||
: map.MapConfig.TimeLimitSeconds;
|
||
if (remainingSeconds < 0) remainingSeconds = 0;
|
||
PlayCountdownBeepIfNeeded(remainingSeconds);
|
||
return $" {remainingSeconds}s";
|
||
}
|
||
|
||
private void SetNextTurnCountdownParam(string param)
|
||
{
|
||
CacheNextTurnButtonText();
|
||
if (_nextButtonMultilingualText == null) return;
|
||
param ??= string.Empty;
|
||
if (string.IsNullOrEmpty(param)) _lastCountdownBeepSecond = -1;
|
||
if (_lastNextTurnCountdownParam == param) return;
|
||
|
||
_lastNextTurnCountdownParam = param;
|
||
_nextButtonMultilingualText.ParamList = new List<string> { param };
|
||
_nextButtonMultilingualText.OnMultilingualChanged();
|
||
}
|
||
|
||
private void PlayCountdownBeepIfNeeded(int secondsLeft)
|
||
{
|
||
if (secondsLeft > 10 || secondsLeft <= 0)
|
||
{
|
||
_lastCountdownBeepSecond = -1;
|
||
return;
|
||
}
|
||
if (_lastCountdownBeepSecond == secondsLeft) return;
|
||
_lastCountdownBeepSecond = secondsLeft;
|
||
AudioManager.Instance.PlayAudio("SFX/UI_beep");
|
||
}
|
||
|
||
private void CacheNextTurnButtonText()
|
||
{
|
||
if (_nextButtonMultilingualText != null) return;
|
||
if (NextButton == null) return;
|
||
_nextButtonText = NextButton.GetComponentInChildren<TextMeshProUGUI>(true);
|
||
_nextButtonMultilingualText = _nextButtonText != null
|
||
? _nextButtonText.GetComponent<MultilingualTextMono>()
|
||
: null;
|
||
}
|
||
|
||
|
||
public void SetTechButtonVisible(bool visible)
|
||
{
|
||
if(visible)ShowButton(TechButton);
|
||
else HideButton(TechButton);
|
||
}
|
||
|
||
public void SetHeroButtonVisible(bool visible)
|
||
{
|
||
if(visible)ShowButton(HeroButton);
|
||
else HideButton(HeroButton);
|
||
}
|
||
|
||
// 每个 Button 的最新可见意图 token。每次 Show/Hide 调用递增;
|
||
// HideButton 在 OnEnd 里比对 token,若期间已经被新的 Show/Hide 覆盖则不再 SetActive(false),
|
||
// 避免 FadeOut 动画的旧 OnEnd 把已经 ShowButton 显示出来的按钮又关掉
|
||
// (NextTurn 按钮联机模式不显示的根因之一)。
|
||
private readonly Dictionary<Button, int> _buttonVisibleToken = new Dictionary<Button, int>();
|
||
|
||
private void HideButton(Button t)
|
||
{
|
||
_buttonVisibleToken.TryGetValue(t, out var token);
|
||
token++;
|
||
_buttonVisibleToken[t] = token;
|
||
int snapshot = token;
|
||
|
||
var cpn = t.GetComponent<AnimancerComponent>();
|
||
if (cpn == null)
|
||
{
|
||
t.gameObject.SetActive(false);
|
||
return;
|
||
}
|
||
|
||
var state = cpn.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeOut);
|
||
state.Events.OnEnd = () =>
|
||
{
|
||
// 仅在期间没有新的 Show/Hide 覆盖时才真正隐藏
|
||
if (_buttonVisibleToken.TryGetValue(t, out var cur) && cur == snapshot)
|
||
t.gameObject.SetActive(false);
|
||
state.Events.OnEnd = null;
|
||
};
|
||
}
|
||
|
||
private void ShowButton(Button t)
|
||
{
|
||
_buttonVisibleToken.TryGetValue(t, out var token);
|
||
_buttonVisibleToken[t] = token + 1;
|
||
|
||
t.gameObject.SetActive(true);
|
||
var cpn = t.GetComponent<AnimancerComponent>();
|
||
if (cpn == null) return;
|
||
cpn.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeIn);
|
||
}
|
||
}
|
||
|
||
}
|