diff --git a/Assets/Framework/Tool/UITool/UIManager.cs b/Assets/Framework/Tool/UITool/UIManager.cs index c8bcc5c18..efac60991 100644 --- a/Assets/Framework/Tool/UITool/UIManager.cs +++ b/Assets/Framework/Tool/UITool/UIManager.cs @@ -249,24 +249,44 @@ public class UIManager : MonoSingleton } /// /// 显示提示窗口(随时访问)。不自动关闭,需手动点确认/关闭按钮。 - /// 返回 TiShi 实例,调用方可持有引用判断弹窗是否仍存在(已被销毁时 == null 为 true)。 /// /// 提示内容 /// 点击确认按钮时的回调(可选),关闭按钮不触发 /// 是否显示关闭按钮(默认 true,部分提示只能点确认) - public TiShi ShowTiShi(string text, Action onConfirmAction = null, bool showClose = true) + public void ShowTiShi(string text, Action onConfirmAction = null, bool showClose = true) { if (TiShiParefab == null) { Debug.LogWarning("TiShiParefab 未赋值"); - return null; + return; } var obj = Instantiate(TiShiParefab, uiRoot); obj.transform.SetAsLastSibling(); var tishi = obj.GetComponent(); if (tishi != null) tishi.Init(text, onConfirmAction, showClose); - return tishi; + } + + /// + /// 显示提示窗口(随时访问),并注册关闭事件回调。 + /// 关闭事件在点确认或点X时都会触发,常用于恢复外部可弹窗状态。 + /// + /// 提示内容 + /// 点击确认按钮时的回调(可选) + /// 关闭事件回调(点确认或点X都触发) + /// 是否显示关闭按钮(默认 true) + public void ShowTiShi(string text, Action onConfirmAction, Action onClose, bool showClose = true) + { + if (TiShiParefab == null) + { + Debug.LogWarning("TiShiParefab 未赋值"); + return; + } + var obj = Instantiate(TiShiParefab, uiRoot); + obj.transform.SetAsLastSibling(); + var tishi = obj.GetComponent(); + if (tishi != null) + tishi.Init(text, onConfirmAction, onClose, showClose); } IEnumerator IEShowHuoDe(List backPackPropItemDatas, Action onCloseAction) { diff --git a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs index 74adf063e..935c53d9d 100644 --- a/Assets/MyGame/Scripts/Manager/CreateFishManager.cs +++ b/Assets/MyGame/Scripts/Manager/CreateFishManager.cs @@ -1671,6 +1671,14 @@ public class CreateFishManager : MonoSingleton } bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward); + // ⭐ self 流程:设置 bjFishId,供 OnScoreEnd 发送 baoJiangEnd 帧同步给其他客户端 + if (GameManager.Instance.useNotSelfBaoJiangMode + && isSelfKill_Boss + && bossCtrl.currEffect_DieAward != null) + { + var bjEvt = bossCtrl.currEffect_DieAward.GetComponent(); + if (bjEvt != null) bjEvt.bjFishId = killFish.fishId; + } } break; @@ -1865,6 +1873,14 @@ public class CreateFishManager : MonoSingleton } bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward); + // ⭐ self 流程:设置 bjFishId,供 OnScoreEnd 发送 baoJiangEnd 帧同步给其他客户端 + if (GameManager.Instance.useNotSelfBaoJiangMode + && isSelfKill_TBoss + && bossCtrl.currEffect_DieAward != null) + { + var bjEvt = bossCtrl.currEffect_DieAward.GetComponent(); + if (bjEvt != null) bjEvt.bjFishId = killFish.fishId; + } } break; default: diff --git a/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs b/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs index e7d2c965c..6456a198c 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs @@ -535,6 +535,9 @@ namespace Assets.MyGame.Scripts.Net break; // 资产/状态类错误 → 弹窗提示,玩家需明确知晓 case 1002: // 金币不足 + UIManager.Instance.ShowBoardError(ex.message); + + break; case 1003: // 房间不存在 case 1011: // 用户不存在 case 1013: // 道具数量不足 diff --git a/Assets/MyGame/aa/Game/FLZY/BOSS/BJEventBase.cs b/Assets/MyGame/aa/Game/FLZY/BOSS/BJEventBase.cs index c759293bd..7545d118b 100644 --- a/Assets/MyGame/aa/Game/FLZY/BOSS/BJEventBase.cs +++ b/Assets/MyGame/aa/Game/FLZY/BOSS/BJEventBase.cs @@ -36,6 +36,8 @@ public class BJEventBase : MonoBehaviour public int paoBei; // 炮倍 public int fishScore; // 底分 public int bJbeishu; + /// 爆奖关联的鱼ID(self 流程结束时用于发送 baoJiangEnd 帧同步,供其他客户端 Finalize) + public string bjFishId; [Header("移动")] public float showDelay = 3f; // 报完奖后停留时间(让玩家看清爆奖效果) @@ -408,6 +410,18 @@ public class BJEventBase : MonoBehaviour // 回加金币到玩家 JiaFen?.Invoke(); + // ⭐ self 流程结束:本地玩家击杀且开启 not-self 模式时, + // 发送 baoJiangEnd 帧同步给服务端,其他客户端收到后 Finalize not-self BJEndUI。 + // (机器人击杀时由房主延迟代发,不在这里发) + if (isSelf + && GameManager.Instance != null + && GameManager.Instance.useNotSelfBaoJiangMode + && _targetSit != null + && !string.IsNullOrEmpty(bjFishId)) + { + BaoJiangNotSelfHandler.SendBaoJiangEnd(_targetSit.user, bjFishId, bJbeishu, maxScore, posID); + } + // BJEndUI:无论是否开启分数面板都显示 if (bjEndUI != null) { diff --git a/Assets/MyGame/aa/Game/FLZY/BOSS/BaoJiangNotSelfHandler.cs b/Assets/MyGame/aa/Game/FLZY/BOSS/BaoJiangNotSelfHandler.cs index c10ae2eed..7ce9ade94 100644 --- a/Assets/MyGame/aa/Game/FLZY/BOSS/BaoJiangNotSelfHandler.cs +++ b/Assets/MyGame/aa/Game/FLZY/BOSS/BaoJiangNotSelfHandler.cs @@ -185,21 +185,7 @@ public static class BaoJiangNotSelfHandler yield break; } - SyncGameData sgd = new SyncGameData - { - sitPos = 0, - frameType = FrameDataType.baoJiangEnd, - time = BeijingTimeManager.Instance.CurrentTimestampMs, - frameData = new FrameData_BaoJiangEnd - { - userId = userId, - posID = 0, - fishId = fishId, - beishu = beishu, - score = score, - } - }; - WebSocketSendList.SyncGameData(sgd); + SendBaoJiangEnd(userId, fishId, beishu, score); Debug.Log($"[BaoJiangNotSelfHandler] 房主已代发 baoJiangEnd 帧同步:key={key} beishu={beishu} score={score}"); } @@ -233,6 +219,32 @@ public static class BaoJiangNotSelfHandler return effect_DieAwardPrefab.GetComponentInChildren(true); } + /// + /// 发送 baoJiangEnd 帧同步给服务端(服务端广播给所有玩家)。 + /// 用于:① self 流程结束时由击杀者客户端发送(真人玩家击杀); + /// ② 机器人击杀时由房主延迟代发(见 DelayedSendBaoJiangEndForBot)。 + /// + public static void SendBaoJiangEnd(string userId, string fishId, int beishu, int score, int posID = 0) + { + SyncGameData sgd = new SyncGameData + { + sitPos = 0, + frameType = FrameDataType.baoJiangEnd, + time = BeijingTimeManager.Instance.CurrentTimestampMs, + frameData = new FrameData_BaoJiangEnd + { + userId = userId, + posID = posID, + fishId = fishId, + beishu = beishu, + score = score, + } + }; + WebSocketSendList.SyncGameData(sgd); + + Debug.Log($"[BaoJiangNotSelfHandler] 已发送 baoJiangEnd 帧同步:userId={userId} fishId={fishId} beishu={beishu} score={score} posID={posID}"); + } + /// 清空所有 pending(场景切换/退出房间时调用,避免悬空引用) public static void ClearAll() { diff --git a/Assets/MyGame/aa/UI/DefaultPanel/TiShi/TiShi.cs b/Assets/MyGame/aa/UI/DefaultPanel/TiShi/TiShi.cs index 5eb086569..ee84f247a 100644 --- a/Assets/MyGame/aa/UI/DefaultPanel/TiShi/TiShi.cs +++ b/Assets/MyGame/aa/UI/DefaultPanel/TiShi/TiShi.cs @@ -12,6 +12,9 @@ public class TiShi : MonoBehaviour public Action action; + /// 关闭事件回调(点确认或点X都会触发,用于恢复外部状态) + public Action onCloseAction; + private Vector3 _standardSize; private bool _closing; @@ -31,8 +34,22 @@ public class TiShi : MonoBehaviour /// 点击确认按钮时的回调(可选) /// 是否显示关闭按钮(默认 true) public void Init(string text, Action onConfirmAction = null, bool showClose = true) + { + Init(text, onConfirmAction, null, showClose); + } + + /// + /// 初始化提示文字,并注册关闭事件回调。 + /// 关闭事件在点确认或点X时都会触发(先于销毁),用于恢复外部可弹窗状态。 + /// + /// 提示内容 + /// 点击确认按钮时的回调(可选) + /// 关闭事件回调(点确认或点X都触发) + /// 是否显示关闭按钮(默认 true) + public void Init(string text, Action onConfirmAction, Action onClose, bool showClose) { action = onConfirmAction; + onCloseAction = onClose; if (tmpText_TiShiLabel != null) tmpText_TiShiLabel.text = text; if (btn_Close != null) @@ -54,14 +71,16 @@ public class TiShi : MonoBehaviour if (_closing) return; _closing = true; action?.Invoke(); + onCloseAction?.Invoke(); PlayHideThenDestroy(); } - /// 关闭按钮:直接关闭,不触发回调 + /// 关闭按钮:直接关闭,触发关闭事件回调 public void Close() { if (_closing) return; _closing = true; + onCloseAction?.Invoke(); PlayHideThenDestroy(); } diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index e822b86cf..d6d75a76d 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -2126,11 +2126,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } /// - /// 金币不足退出房间弹窗的引用。 - /// 弹窗存在期间不为 null(被 Destroy 后 Unity fake null,== null 为 true), - /// 用于确保屏幕上同一时间最多只显示一个弹窗,散射/自动开火也不会重复弹出。 + /// 金币不足退出房间弹窗是否可弹出。 + /// 弹出后置为 false,仅在金币恢复到能发射最小炮倍时才重置为 true。 + /// 不在弹窗关闭时重置——避免点确认后 RequestLeaveRoom 异步等待期间反复弹窗。 /// - private TiShi _goldInsufficientPanel = null; + private bool _canShowGoldInsufficientPanel = true; /// /// 获取当前房间最小炮倍(paoBeiList 中的最小值) @@ -2146,8 +2146,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base /// 金币不足则不切换攻击状态、不发送开炮请求,避免无效请求与服务端拒绝。 /// ⭐ 当金币不足以发射最小炮倍时,弹窗询问是否退出房间: /// 确认 → 走退出房间逻辑;X号 → 关闭面板。本地判断,不发送开炮请求。 - /// 通过 _goldInsufficientPanel 引用检测保证全局只显示一个弹窗, - /// 弹窗未关闭前绝不重复弹出(散射/自动开火每帧检测也只弹一次)。 + /// 通过 _canShowGoldInsufficientPanel bool 保证唯一性: + /// 弹出后设 false,仅在金币恢复到够最小炮倍时才重置 true, + /// 期间无论弹窗是否关闭、RequestLeaveRoom 是否异步等待,都不会再弹。 /// private bool HasEnoughGoldToFire(bool isSanShe) { @@ -2156,23 +2157,35 @@ public class GameMainPanel_FishBase : GameMainPanel_Base long need = currSitData.consumeGoldNum * count; if (currSitData.goldNum >= need) { - // 金币够当前炮倍:可正常开火 + // 金币够当前炮倍:可正常开火,重置弹窗标志 + _canShowGoldInsufficientPanel = true; return true; } + // ⭐ 金币不够发射(散射按3发/单发按1发)→ 取消锁定,不允许继续锁定攻击 + // toggle_Lock.isOn = false 会触发 onValueChanged → ClearLock + 同步服务器, + // 之后 HandleAutoFire 的 needAutoFire 因 toggle_Lock 关闭而变 false,停止自动锁定开火 + if (toggle_Lock != null && toggle_Lock.isOn) + { + toggle_Lock.isOn = false; + } + // 金币不足当前炮倍:进一步判断是否连最小炮倍都发射不起 long minConsume = GetMinConsumeGold(); if (minConsume <= 0 || currSitData.goldNum >= minConsume) { - // 金币够最小炮倍:玩家可降炮倍继续游戏,不弹窗 + // 金币够最小炮倍:玩家可降炮倍继续游戏,重置弹窗标志 + _canShowGoldInsufficientPanel = true; return false; } // ⭐ 金币不足以发射最小炮倍 → 弹窗询问是否退出房间 - // 关键:只有当前没有弹窗时才弹,避免散射/自动开火反复弹窗 - if (_goldInsufficientPanel == null) + // 关键:只在金币恢复后才重置 bool,不在弹窗关闭时重置, + // 避免 RequestLeaveRoom 异步等待期间反复弹窗 + if (_canShowGoldInsufficientPanel) { - _goldInsufficientPanel = UIManager.Instance.ShowTiShi( + _canShowGoldInsufficientPanel = false; + UIManager.Instance.ShowTiShi( "金币不足,是否退出房间?", () => RequestLeaveRoom(), true);