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.GameArchive; 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 RectTransform ChatAreaRoot; 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 == null || !Main.MapData.CurPlayer.IsSelfPlayer()) return; if (Main.MapData.Net == null || Main.MapData.Net.Mode != NetMode.Single) return; var resumeRecord = GameArchiveManager.Instance?.GetQuickResumeRecord(NetMode.Single); if (resumeRecord == null) 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; if (Main.Instance?.GameLogic == null) return; Main.Instance.GameLogic.ChangeState(GameState.Menu); //TODO 这里的时间控制要处理 //播放loading EventManager.Publish(loadingEvt); //重置游戏 Timer.Instance.TimerRegister(this, () => { Main.Instance.ResumeMatch(resumeRecord); }, 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, forceCameraCenter: true); } 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 turnTimeLimitSeconds = map.GetTurnTimeLimitSeconds(map.CurPlayer.Id); if (turnTimeLimitSeconds <= 0) return string.Empty; var remainingSeconds = map.Net.PlayerStartTime > 0f ? Mathf.CeilToInt(turnTimeLimitSeconds - (Time.time - map.Net.PlayerStartTime)) : turnTimeLimitSeconds; 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 { 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(true); _nextButtonMultilingualText = _nextButtonText != null ? _nextButtonText.GetComponent() : 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 _buttonVisibleToken = new Dictionary(); private void HideButton(Button t) { _buttonVisibleToken.TryGetValue(t, out var token); token++; _buttonVisibleToken[t] = token; int snapshot = token; var cpn = t.GetComponent(); 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(); if (cpn == null) return; cpn.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeIn); } } }