using Assets.MyGame.Scripts.Net.API; using Assets.MyGame.Scripts.Net.WebResponses; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using UnityEngine.SceneManagement; namespace Assets.MyGame.Scripts.Net { public class WebSocketReponseList { /// 已加载(或正在加载)的游戏场景名集合,防止人机 RoomJoinNotice 触发重复 LoadSceneAsync private static readonly HashSet _loadedGameScenes = new HashSet(); /// 清除指定场景的加载标记(退出游戏时调用,下次进入可重新加载) public static void ClearLoadedGameScene(string sceneName) { if (!string.IsNullOrEmpty(sceneName)) _loadedGameScenes.Remove(sceneName); } /// /// 获取房间列表 room /// /// public static void GetRoomListChineseChessResult(string msg) { var var1 = UIManager.Instance.Show(UIPanel.MainGamePanelZGXQ, msg); } public static void GetGoldConfigSuccessChineseChessResult(string msg) { UIManager.Instance.Get(UIPanel.MainGamePanelZGXQ).subUIManager.ShowAsync(UIPanel.ZGXQGameExplain); } /// /// 获取房间列表 room /// /// public static void GetRoomListResult(string msg) { var var1 = UIManager.Instance.Get(UIPanel.HomeMainPanel); var selectRoomPanel = var1.subUIManager.Get(UIPanel.SelectRoomLeavePenel); if (selectRoomPanel != null && selectRoomPanel.isMySelectRoom) { // 手动选座:显示房间列表给玩家选 selectRoomPanel.panel_UIHome_BY_MySelectRoomPenel.Show(msg); } // 快速加入分支已迁移到 OnSelectRoom → /api/Room/QuickJoin,不再走 GetRoomList } /// /// 加入房间 room /// /// public static void RoomJoinNotice(string msg) { // ⭐ 只反序列化一次复用(金币流水 / currRoomData.players 补齐 / 座位激活) RoomJoinNotice notice = null; try { notice = JsonConvert.DeserializeObject>(msg).arguments[0]; } catch { } // ⭐ 记录金币流水:收到加钱信息(RoomJoinNotice 推送,尚未执行)—— 只记录自己 try { string myId = GameManager.Instance.user?.id; if (notice != null && !string.IsNullOrEmpty(myId) && notice.UserId == myId) { GameManager.LogGoldFlowReceiveGain("RoomJoinNotice", $"推送金币={notice.Gold} 钻石={notice.Diamond} 炮台消耗={notice.CannonMultiple} 座位={notice.Position}"); } } catch { } // ⭐ 修复:把加入玩家 upsert 进 currRoomData.players。 // JoinGame 里 SetUserInfo 按 userId 从 currRoomData.players 查显示信息 // (昵称/头像/VIP/短编号),而 currRoomData 只在本地进房收到 RoomInfo 时写一次, // 后加入的玩家不在快照里 → 老客户端把他显示成匿名(无昵称/默认头像/原始32位长ID)。 // RoomJoinNotice 自带完整玩家数据,这里补进快照后再走 JoinGame。 UpsertPlayerToCurrRoom(notice); string sceneName = ""; UIPanel uIPanel = UIPanel.None; switch (GameManager.Instance.gameData.currGameType) { case GameType.JQQD: break; case GameType.defaultPanel: break; case GameType.FLZY: sceneName = "FLZY"; uIPanel = UIPanel.MainGamePanelFLZY; break; case GameType.YYSC: sceneName = "YYSC"; uIPanel = UIPanel.MainGamePanelYYSC; break; case GameType.ZGXQ: break; case GameType.XJMH: sceneName = "XJMH"; uIPanel = UIPanel.MainGamePanelXJMH; break; case GameType.HHWK: sceneName = "HHWK"; uIPanel = UIPanel.MainGamePanelHHWK; break; default: break; } if (UIManager.Instance.Get(uIPanel)==null) { UIManager.Instance.ShowAsync(uIPanel, UIPanel.HomeMainPanel, msg); // 卸载所有已加载的游戏地图场景(保证场景唯一性) string[] gameScenes = { "FLZY", "YYSC", "XJMH", "HHWK" }; foreach (var sn in gameScenes) { // 不卸载即将加载的目标场景 if (sn == sceneName) continue; var s = SceneManager.GetSceneByName(sn); if (s.IsValid() && s.isLoaded) { SceneManager.UnloadSceneAsync(sn); _loadedGameScenes.Remove(sn); } } // 目标场景仅加载一次:标记已加载后不再重复 LoadSceneAsync(防止人机通知触发重复加载) if (!string.IsNullOrEmpty(sceneName) && !_loadedGameScenes.Contains(sceneName)) { _loadedGameScenes.Add(sceneName); var targetScene = SceneManager.GetSceneByName(sceneName); if (!targetScene.IsValid() || !targetScene.isLoaded) SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); } UIManager.Instance.Close(UIPanel.HomeMainPanel); WebSocketManager.Instance.isGame = true; } else { // 面板已存在 → 新玩家加入,转发给现有面板激活座位 var panel = UIManager.Instance.Get(uIPanel); if (panel is GameMainPanel_FishBase fishPanel && notice != null) { // 如果座位被bot占用则替换 var targetSit = fishPanel.list_Sits.Find(s => s.posID == notice.Position); if (targetSit != null && targetSit.isBot) { targetSit.isBot = false; targetSit.ShowWaiting(); } fishPanel.JoinGame(notice.Position, notice.Gold, notice.Diamond, notice.CannonMultiple, 0, notice.UserId, notice.CannonId, notice.WingsId, notice.WarGodId); // 后端动态加入的机器人也要标记 if (targetSit != null && !string.IsNullOrEmpty(notice.UserId) && notice.UserId.StartsWith("bot_")) targetSit.isBot = true; } } } /// /// 把 RoomJoinNotice 玩家数据写入 currRoomData.players(存在则更新,不存在则添加)。 /// JoinGame 里 SetUserInfo 按 userId 从 currRoomData.players 查显示信息 /// (昵称/头像/VIP/短编号),后加入的玩家不在快照里会显示成匿名玩家。 /// private static void UpsertPlayerToCurrRoom(RoomJoinNotice notice) { if (notice == null || string.IsNullOrEmpty(notice.UserId)) return; var roomData = GameManager.Instance.gameData?.currRoomData; if (roomData == null) return; if (roomData.players == null) roomData.players = new List(); var p = roomData.players.Find(x => x.userId == notice.UserId); if (p == null) { p = new RoomPlayerInfo(); roomData.players.Add(p); } p.userId = notice.UserId; p.userName = notice.UserName; p.userNumber = notice.UserNumber; p.avatar = notice.Avatar; p.isRedName = notice.IsRedName; p.level = notice.Level; p.exp = notice.Exp; p.maxExp = notice.MaxExp; p.vipLevel = notice.VipLevel; p.cId = notice.CId; p.gold = notice.Gold; p.diamond = notice.Diamond; p.position = notice.Position; p.cannonId = notice.CannonId; p.wingsId = notice.WingsId; p.warGodId = notice.WarGodId; p.cannonMultiple = notice.CannonMultiple; p.beginCoin = notice.BeginCoin; p.beginTime = notice.BeginTime; p.state = notice.State; p.playerFactor = notice.PlayerFactor; p.totalRechargeGold = notice.TotalRechargeGold; p.safeBoxGold = notice.SafeBoxGold; p.isBot = notice.IsBot; p.isAfk = notice.IsAfk; } /// /// 房间列表 room /// /// public static void RoomInfo(string msg) { WebSocketManager.Instance.isGame = false; var roomData = JsonConvert.DeserializeObject>(msg).arguments[0]; if (roomData != null) { GameManager.Instance.gameData.currRoomData = roomData; } else { Debug.LogError("roomdata是空:" + msg); } } public static void RoomKillNotice(string msg) { // 离开倒计时期间不触发击杀/爆奖特效 var panel = UIManager.Instance.Get(UIPanel.MainGamePanelDefault) ?? UIManager.Instance.Get(UIPanel.MainGamePanelFLZY) ?? UIManager.Instance.Get(UIPanel.MainGamePanelYYSC) ?? UIManager.Instance.Get(UIPanel.MainGamePanelXJMH) ?? UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); if (panel != null && panel.IsLeavingCountdown()) return; CreateFishManager.Instance.Ws_DieFish(msg); } public static void RoomFireNotice(string msg) { FireCannonSendData fireCannonSendData = JsonConvert.DeserializeObject>(msg).arguments[0]; var syncGameData = fireCannonSendData.extra; EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_FireBullet, fireCannonSendData); } public static void SyncPlayerGold(string msg) { var gold = JsonConvert.DeserializeObject>(msg).arguments[0]; switch (GameManager.Instance.gameData.currGameType) { case GameType.JQQD: break; case GameType.defaultPanel: UpdateLocalPlayerGold(UIPanel.MainGamePanelDefault, gold); break; case GameType.FLZY: UpdateLocalPlayerGold(UIPanel.MainGamePanelFLZY, gold); break; case GameType.YYSC: UpdateLocalPlayerGold(UIPanel.MainGamePanelYYSC, gold); break; case GameType.ZGXQ: break; case GameType.XJMH: UpdateLocalPlayerGold(UIPanel.MainGamePanelXJMH, gold); break; case GameType.HHWK: UpdateLocalPlayerGold(UIPanel.MainGamePanelHHWK, gold); break; default: break; } } /// /// 炮倍变更通知:服务端确认玩家炮倍切换后广播给房间内所有玩家。 /// 客户端收到后才应用新炮倍到 consumeGoldNum 和 UI,避免开火扣费不一致。 /// public static void ChangeGunMultipleNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; var panel = UIManager.Instance.Get(UIPanel.MainGamePanelDefault) ?? UIManager.Instance.Get(UIPanel.MainGamePanelFLZY) ?? UIManager.Instance.Get(UIPanel.MainGamePanelYYSC) ?? UIManager.Instance.Get(UIPanel.MainGamePanelXJMH) ?? UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); if (panel == null) return; panel.ApplyCannonMultiple(data.userId, data.multiple); } /// /// 后端推送 SyncPlayerGold 时同步本地玩家金币(底层值 + UI 显示)。 /// 之前只更新 goldNum 不刷新 tMP_GoldNum.text,导致 UI 显示和后端 authoritative 值不同步, /// 后续杀鱼累加基于修正值但 UI 显示旧值,差距越拉越大。 /// private static void UpdateLocalPlayerGold(UIPanel panelType, long gold) { var panel = UIManager.Instance.Get(panelType); if (panel == null || panel.currSitData == null) return; long oldGold = panel.currSitData.goldNum; long diff = gold - oldGold; // ⭐ 记录金币流水:收到后端推送 SyncPlayerGold(尚未执行) if (diff > 0) GameManager.LogGoldFlowReceiveGain("SyncPlayerGold", $"推送金币={gold} 修正前金币={oldGold} 差值=+{diff} 座位={panel.currSitData.posID}"); else if (diff < 0) GameManager.LogGoldFlowReceiveCost("SyncPlayerGold", $"推送金币={gold} 修正前金币={oldGold} 差值={diff} 座位={panel.currSitData.posID}"); panel.currSitData.goldNum = gold; if (panel.currSitData.tMP_GoldNum != null) panel.currSitData.tMP_GoldNum.text = gold.ToString(); // ⭐ 记录金币流水:已执行修正(本地已写入) if (diff > 0) GameManager.LogGoldFlowAppliedGain("SyncPlayerGold", $"已修正金币={gold} 修正前金币={oldGold} 增加金币=+{diff} 座位={panel.currSitData.posID}"); else if (diff < 0) GameManager.LogGoldFlowAppliedCost("SyncPlayerGold", $"已修正金币={gold} 修正前金币={oldGold} 扣除金币={diff} 座位={panel.currSitData.posID}"); } public static void GetRoomInfoResult(string msg) { //GameManager.Instance.StartGame();RoomChangeResponse var roomData = JsonConvert.DeserializeObject>(msg).arguments[0]; // ⭐ 记录金币流水:收到加钱信息(RoomInfo 推送玩家金币,尚未执行)—— 只记录自己 if (roomData?.players != null) { string myId = GameManager.Instance.user?.id; foreach (var p in roomData.players) { if (string.IsNullOrEmpty(myId) || p.userId != myId) continue; GameManager.LogGoldFlowReceiveGain("RoomInfo", $"推送金币={p.gold} 钻石={p.diamond} 炮台消耗={p.cannonMultiple} 座位={p.position} 房间={roomData.roomId}"); } } UIHome_BY_SelectRoomLevelPanel.StartGame(roomData); } public static void SyncGameDataResult(string msg) { var syncGameData = JsonConvert.DeserializeObject>(msg).arguments[0]; switch (syncGameData.frameType) { case FrameDataType.none: break; case FrameDataType.rotateTurrect: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_Rotate, syncGameData); break; case FrameDataType.fireBullet: // 机器人角度同步:SyncFire_ForBot 发出的 SyncGameData(fireBullet) // 与 RoomFireNotice(FireCannonSendData) 分开处理,仅用于存储角度到 botLastAngle EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_BotFireAngle, syncGameData); break; // createFish=3 已废弃:鱼创建改由服务端 OnFishSpawn 事件下发 case FrameDataType.changeAutoTarget: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_ChangeTargetMonster, syncGameData); break; case FrameDataType.randomDieFish: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_RendromDieFish, syncGameData); break; case FrameDataType.freezeFish: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_FreezeFish, syncGameData); break; case FrameDataType.changeTurretState: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_TurretState, syncGameData); break; case FrameDataType.changeTurret: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_ChangeTurret, syncGameData); break; case FrameDataType.changeCannonMultiple: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_ChangeCannonMultiple, syncGameData); break; case FrameDataType.changeWing: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_ChangeWing, syncGameData); break; case FrameDataType.changeZhanShen: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_ChangeZhanShen, syncGameData); break; case FrameDataType.biaoQing: EventCenter.Instance.EventTrigger(ConstEventString.SyncGameData_BiaoQing, syncGameData); break; // requestSyncFish=11 已废弃:鱼同步改由服务端 OnFishSpawn(type=sync) 下发 case FrameDataType.baoJiangEnd: EventCenter.Instance.EventTrigger(ConstEventString.BaoJiangEnd, syncGameData); break; default: break; } } // ================================================================ // 新协议事件分发 (OnFishSpawn / PlayerAfk / FishFrozen / KillNotice 等) // ================================================================ /// /// 服务端出鱼事件 OnFishSpawn 处理。 /// 破坏性替代旧 createFish 帧同步:服务端统一出鱼,客户端只接收并生成。 /// public static void OnFishSpawn(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; EventCenter.Instance.EventTrigger(ConstEventString.OnFishSpawn, data); NetLog.Log("出鱼:", msg); } /// 鱼列表刷新 public static void OnFishList(string msg) { var fishList = JsonConvert.DeserializeObject>(msg).arguments[0]; EventCenter.Instance.EventTrigger(ConstEventString.OnFishList, fishList); } /// /// 冰冻道具(40003)生效事件 FishFrozen。 /// 鱼存活时间延长 15 秒,客户端写死 15s 倒计时。 /// public static void FishFrozen(string msg) { EventCenter.Instance.EventTrigger(ConstEventString.FishFrozen); } /// /// FishGameHub TorpedoResultNotice — 鱼雷使用结果(房间广播)。 /// 所有客户端都播爆奖流程(与 Boss 爆奖 RoomKillNotice 全端实例化同惯例): /// 随机炸死场上小鱼 + 红包分包爆金币 + 分数面板 + 飞回使用者座位结算。 /// public static void TorpedoResultNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; Debug.Log($"[FishGameHub] 鱼雷结果: userId={data?.userId} prop={data?.propNumber} gold={data?.gold} color={data?.color}"); var panel = CreateFishManager.Instance.currGameType; if (panel != null) panel.OnTorpedoResult(data); } /// /// FishGameHub ErrorMessage — 错误通知兜底 /// (鱼雷等新接口失败时推送:房间不存在/道具配置不存在/用户不存在;老接口走 Exception 错误码)。 /// public static void ErrorMessage(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (!string.IsNullOrEmpty(data?.message)) UIManager.Instance.ShowBoardError(data.message); } /// 玩家挂机通知 public static void PlayerAfkNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; EventCenter.Instance.EventTrigger(ConstEventString.PlayerAfkNotice, data); } /// 玩家取消挂机通知 public static void PlayerCancelAfkNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; EventCenter.Instance.EventTrigger(ConstEventString.PlayerCancelAfkNotice, data); } /// /// 召唤卡召唤通知 SummonCardFishNotice。 /// 某玩家使用召唤卡(40006),服务端已创建 Boss鱼(飞机场)/黄金鱼(其他场次)。 /// 仅用于播放召唤提示/特效/显示召唤玩家,实际鱼对象仍由 OnFishSpawn 创建。 /// public static void SummonCardFishNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; EventCenter.Instance.EventTrigger(ConstEventString.SummonCardFishNotice, data); NetLog.Log("召唤通知:", msg); } /// /// 全局击杀公告 KillNotice。 /// 真人击杀/机器人模拟,机器人模拟时额外带 fishName/multiplier/sceneName。 /// public static void KillNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; EventCenter.Instance.EventTrigger(ConstEventString.KillNotice, data); } /// 机器人退出房间通知 public static void BotLeaveRoomNotice(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; WebSocketReponseList.BotLeaveRoom(data.userId); } /// 房主断线 public static void HostDisconnectNotice(string msg) { EventCenter.Instance.EventTrigger(ConstEventString.HostDisconnect); WebSocketReponseList.HostDisconnect(); } /// /// 座位变更 OnSeatChange。 /// 数据: { type, userId, position, showWaiting? } /// public static void OnSeatChange(string msg) { var data = JsonConvert.DeserializeObject>>(msg).arguments[0]; if (data == null) return; // JSON 字段可能是 long/double/string/bool 等任意类型,统一 ToString()+TryParse 容错 int seatType = data.TryGetValue("type", out var t) && int.TryParse(t?.ToString(), out var st) ? st : 0; string userId = data.TryGetValue("userId", out var u) ? u?.ToString() : ""; int position = data.TryGetValue("position", out var p) && int.TryParse(p?.ToString(), out var pos) ? pos : -1; bool showWaiting = data.TryGetValue("showWaiting", out var sw) && (sw is bool b ? b : (sw != null && bool.TryParse(sw.ToString(), out var bv) && bv)); WebSocketReponseList.SeatChange(seatType, userId, position, showWaiting); } /// /// 房主控制权移交 ChangeControllerUser。 /// 仅维护 SitPosData.roomOwnerSit,不再设置 fangzhu(鱼创建已由服务端统一出鱼)。 /// public static void ChangeControllerUser(string msg) { var userId = JsonConvert.DeserializeObject>(msg).arguments[0]; if (userId == GameManager.Instance.user.id) { // 本地玩家成为房主:同步本地房主引用 if (SitPosData.localPlayerSit != null) SitPosData.roomOwnerSit = SitPosData.localPlayerSit; } else if (userId != null && userId.StartsWith("bot_")) { // 房主是人机:人机无法真正发包,由本地首个非人机玩家代行房主显示职责 SitPosData.TransferRoomOwner(); } // 其他真人玩家成为房主时无需本地操作(roomOwnerSit 由 TransferRoomOwner/ShowPlayer 维护) } public static void LeaveRoomResult(string msg) { //WebSocketManager.Instance.isGame = false; //GameManager.Instance.StartGame();RoomChangeResponse var response = JsonConvert.DeserializeObject>(msg); // ⭐ 数据保护:arguments 为空直接返回,避免抛异常 if (response?.arguments == null || response.arguments.Count == 0) { #if UNITY_EDITOR Debug.LogWarning("[LeaveRoomResult] arguments 为空,忽略"); #endif return; } var userid = response.arguments[0]; switch (GameManager.Instance.gameData.currGameType) { case GameType.JQQD: break; case GameType.defaultPanel: break; case GameType.FLZY: { var panel = UIManager.Instance.Get(UIPanel.MainGamePanelFLZY); if (panel == null || panel.currSitData == null) break; if (panel.currSitData.user == userid) { ExitFishingGame(UIPanel.MainGamePanelFLZY); break; } foreach (var item in panel.list_Sits) { if (item.user == userid) { item.ShowWaiting(); } } } break; case GameType.YYSC: { var panel = UIManager.Instance.Get(UIPanel.MainGamePanelYYSC); if (panel == null || panel.currSitData == null) break; if (panel.currSitData.user == userid) { ExitFishingGame(UIPanel.MainGamePanelYYSC); break; } foreach (var item in panel.list_Sits) { if (item.user == userid) { item.ShowWaiting(); } } } break; case GameType.ZGXQ: break; case GameType.XJMH: { var panel = UIManager.Instance.Get(UIPanel.MainGamePanelXJMH); if (panel == null || panel.currSitData == null) break; if (panel.currSitData.user == userid) { ExitFishingGame(UIPanel.MainGamePanelXJMH); break; } foreach (var item in panel.list_Sits) { Debug.Log(item.user + "---" + userid); if (item.user == userid) { Debug.Log("推出星际迷航"); item.ShowWaiting(); } } } break; case GameType.HHWK: { var panel = UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); if (panel == null || panel.currSitData == null) break; if (panel.currSitData.user == userid) { ExitFishingGame(UIPanel.MainGamePanelHHWK); break; } foreach (var item in panel.list_Sits) { if (item.user == userid) { item.ShowWaiting(); } } } break; default: break; } } /// /// 统一退出捕鱼游戏流程: /// 1. 关闭 game socket(自己的离房通知已收到,可以正常关闭) /// 2. 拉取最新用户数据并触发 OnUserDataChanged 事件,同步大厅金币/钻石/等级等 UI /// 3. 通知游戏面板 socket 已返回 → 解除进度条 0.9 的等待 → 走完剩余进度后自动切回 home /// private static void ExitFishingGame(UIPanel gamePanel) { // 标记游戏已结束 WebSocketManager.Instance.isGame = false; // ⭐ 记录金币流水:退出房间 GameManager.LogGoldFlow("ExitRoom", $"退出房间 房间={GameManager.Instance.gameData?.currRoomData?.roomId}"); // 关闭游戏连接 WebSocketManager.Instance.Close(SocketType.game); // 拉取最新用户数据,触发 UI 同步更新(金币/钻石/等级/VIP等) APIRequestList.GetUser((user) => { GameManager.Instance.UpdataUserData(user); }); // ⭐ 通知面板 socket 已返回 → 进度条从 0.9 走到 1.0 → 切回 home var panel = UIManager.Instance.Get(gamePanel); if (panel != null) { panel.OnLeaveRoomResultReceived(gamePanel); } else { // 兜底:拿不到面板直接切换 UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, gamePanel); } } // ---------------- LoginHub ---------------- /// /// LoginHub Broadcast — 大厅公告广播 /// public static void LoginBroadcast(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; UIManager.Instance.ShowBoardGongGao(data.message); } /// /// LoginHub AssetUpdated — 资产变动通知 /// public static void AssetUpdated(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; // 触发用户数据刷新 var userId = GameManager.Instance?.user?.id; if (!string.IsNullOrEmpty(userId)) APIRequestList.GetUser(userId, a => GameManager.Instance.UpdataUserData(a)); } /// /// LoginHub BackpackUpdated — 背包变动通知 /// public static void BackpackUpdated(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; // 刷新用户数据(道具列表在 GetUser 返回中) var userId = GameManager.Instance?.user?.id; if (!string.IsNullOrEmpty(userId)) APIRequestList.GetUser(userId, a => GameManager.Instance.UpdataUserData(a)); } /// /// LoginHub NewEmail — 新邮件通知 /// public static void NewEmail(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; UIManager.Instance.ShowBoardError($"收到新邮件:{data.title}"); } /// /// LoginHub KillAnnouncement — 全局击杀公告(特殊 Boss 击杀全服推送) /// public static void KillAnnouncement(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; // 服务端已拼好 text(格式:恭喜玩家*****在风雷之渊 高级场捕获 人鱼公主*2000倍 150000000大奖) if (data != null && !string.IsNullOrEmpty(data.text)) UIManager.Instance.ShowBoardGongGao(data.text); } /// /// LoginHub LevelChanged — 后台修改玩家等级 /// 直接调用 GetUser 刷新用户数据,触发 OnUserDataChanged 同步 UI /// public static void LevelChanged(string msg) { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; var userId = GameManager.Instance?.user?.id; if (!string.IsNullOrEmpty(userId)) APIRequestList.GetUser(userId, a => GameManager.Instance.UpdataUserData(a)); } // ---------------- LoginHub 断线房间 ---------------- /// 当前是否有待处理的断线房间弹窗(防止重复弹) private static bool _pendingRoomActive; /// /// LoginHub PendingRoomNotice — 登录后检测到有未完成的断线游戏 /// 弹窗提示用户选择"返回房间"(调 Reconnect)或"离开"(调 DismissPendingRoom) /// 60 秒无操作自动离开 /// public static void PendingRoomNotice(string msg) { if (_pendingRoomActive) return; // 防重复 var data = JsonConvert.DeserializeObject>(msg).arguments[0]; if (data == null) return; _pendingRoomActive = true; var userId = GameManager.Instance?.user?.id; if (string.IsNullOrEmpty(userId)) return; // 创建 TiShi 弹窗(直接 Instantiate 拿引用,方便 60 秒后销毁) var prefab = UIManager.Instance.TiShiParefab; if (prefab == null) { Debug.LogWarning("[PendingRoomNotice] TiShiParefab 未赋值,自动离开"); WebSocketSendList.DismissPendingRoom(userId); _pendingRoomActive = false; return; } var obj = UnityEngine.Object.Instantiate(prefab, UIManager.Instance.uiRoot); obj.transform.SetAsLastSibling(); var tishi = obj.GetComponent(); if (tishi == null) { UnityEngine.Object.Destroy(obj); _pendingRoomActive = false; return; } // 标记用户是否已操作 bool acted = false; // 确认 = 返回房间 tishi.Init($"检测到您在 {data.gameType} {data.roomName} 有未完成的游戏,是否返回房间?", () => { acted = true; WebSocketSendList.Reconnect(userId); }); // 关闭 = 离开(额外给 btn_Close 加监听;TiShi.Close 本身只销毁不触发 action) if (tishi.btn_Close != null) { tishi.btn_Close.onClick.AddListener(() => { if (acted) return; acted = true; WebSocketSendList.DismissPendingRoom(userId); }); } // 60 秒无操作自动离开 WebSocketManager.Instance.StartCoroutine(PendingRoomCountdown( 60f, () => acted, () => { acted = true; WebSocketSendList.DismissPendingRoom(userId); if (obj != null) UnityEngine.Object.Destroy(obj); _pendingRoomActive = false; })); } /// 断线房间弹窗倒计时协程(到时检查是否已操作,未操作则触发回调) private static System.Collections.IEnumerator PendingRoomCountdown( float seconds, System.Func hasActed, System.Action onTimeout) { yield return new UnityEngine.WaitForSeconds(seconds); if (!hasActed()) onTimeout?.Invoke(); } /// /// LoginHub PendingRoomNotice 数据 /// [Serializable] public class PendingRoomData { public string roomId; public string gameType; public string roomName; } /// /// LoginHub AccountKicked — 账号被顶通知 /// 该账号在其他设备登录时,旧连接收到此事件。 /// 前端应提示"账号已在其他地方登录"并清空房间 UI、断开游戏连接。 /// public static void AccountKicked(string msg) { try { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; var userId = GameManager.Instance?.user?.id; // 只处理当前登录用户被顶的情况 if (data != null && !string.IsNullOrEmpty(data.userId) && !string.IsNullOrEmpty(userId) && data.userId == userId) { UIManager.Instance.ShowTiShi("账号已在其他地方登录", () => { GameManager.Instance.Logout(); return; }, showClose: false); } } catch (Exception e) { Debug.LogWarning("[AccountKicked] 解析失败: " + e.Message); } } /// /// LoginHub AccountKicked 数据 /// [Serializable] public class AccountKickedData { public string userId; } /// /// LoginHub ChangeConfig — 后台活动配置或游戏配置保存发布后广播 /// changeType: 1=活动配置更新 2=游戏配置更新 /// 前端按 changeType 重新请求对应配置 /// public static void ChangeConfig(string msg) { try { var changeType = JsonConvert.DeserializeObject>(msg).arguments[0]; switch (changeType) { case 1: // 活动配置更新 APIRequestList.GetActivityConfig(res => { if (res != null) GameManager.Instance.activityConfig = res; }); break; case 2: // 游戏配置更新 APIManager.Response(APIType.GameConfig_Get, res => { if (res != null) { GameManager.Instance.gameConfig = res; var v = GameManager.Instance.dic_fishTypeConfigs; if (v != null) { v.Clear(); foreach (var item in res.fishTypeConfigs) v[item.fishNumber] = item; } } }); break; } } catch (Exception e) { Debug.LogWarning("[ChangeConfig] 解析失败: " + e.Message); } } /// /// LoginHub KickToMainPage — 强制退出到主页面 /// 当前账号连接已失效(被顶号/用户不匹配),前端应立即清除所有游戏 UI 并导航到主页面/登录页 /// public static void KickToMainPage(string msg) { try { var data = JsonConvert.DeserializeObject>(msg).arguments[0]; var userId = GameManager.Instance?.user?.id; // 只处理当前登录用户被踢的情况 if (data != null && !string.IsNullOrEmpty(data.userId) && !string.IsNullOrEmpty(userId) && data.userId != userId) return; } catch (Exception e) { Debug.LogWarning("[KickToMainPage] 解析失败: " + e.Message); } UIManager.Instance.ShowTiShi("连接已失效,请重新登录", () => { GameManager.Instance.Logout(); }, showClose: false); } /// /// LoginHub KickToMainPage 数据 /// [Serializable] public class KickToMainPageData { public string userId; } // ---------------- FishGameHub 补充 ---------------- /// /// FishGameHub BotLeaveRoomNotice — 机器人退出房间 /// 仅触发对应座位显示等待,不影响本地玩家(自己退出走 LeaveRoomResult) /// public static void BotLeaveRoom(string botUserId) { if (string.IsNullOrEmpty(botUserId)) return; switch (GameManager.Instance.gameData.currGameType) { case GameType.FLZY: ReleaseBotSit(UIPanel.MainGamePanelFLZY, botUserId); break; case GameType.YYSC: ReleaseBotSit(UIPanel.MainGamePanelYYSC, botUserId); break; case GameType.XJMH: ReleaseBotSit(UIPanel.MainGamePanelXJMH, botUserId); break; case GameType.HHWK: ReleaseBotSit(UIPanel.MainGamePanelHHWK, botUserId); break; default: break; } } /// 释放机器人座位(显示等待界面,标记为 bot) private static void ReleaseBotSit(UIPanel panelName, string botUserId) { var panel = UIManager.Instance.Get(panelName); if (panel == null) return; foreach (var sit in panel.list_Sits) { if (sit.user == botUserId) { sit.isBot = true; sit.ShowWaiting(); break; } } } /// /// FishGameHub HostDisconnect — 房主断线 /// 其他玩家可接受机器人控制权。本地房主引用若已断线则转移给本地首个非人机玩家。 /// public static void HostDisconnect() { // 房主断线 → 触发房主转移(SitPosData 已封装好转移逻辑) SitPosData.TransferRoomOwner(); } /// /// FishGameHub OnSeatChange — 座位变更 /// { type, userId, position, showWaiting? } /// type 字段后端未明确定义,先按 showWaiting 决定显示状态 /// public static void SeatChange(int seatType, string userId, int position, bool showWaiting) { if (string.IsNullOrEmpty(userId) || position < 0) return; GameMainPanel_FishBase panel = null; switch (GameManager.Instance.gameData.currGameType) { case GameType.FLZY: panel = UIManager.Instance.Get(UIPanel.MainGamePanelFLZY); break; case GameType.YYSC: panel = UIManager.Instance.Get(UIPanel.MainGamePanelYYSC); break; case GameType.XJMH: panel = UIManager.Instance.Get(UIPanel.MainGamePanelXJMH); break; case GameType.HHWK: panel = UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); break; default: break; } if (panel == null) return; var targetSit = panel.list_Sits.Find(s => s.posID == position); if (targetSit == null) return; if (showWaiting) { // 显示等待界面(玩家离开该座位) targetSit.ShowWaiting(); } else { // 玩家进入该座位 targetSit.isBot = !string.IsNullOrEmpty(userId) && userId.StartsWith("bot_"); } } /// /// FishRoomHub RoomChange — 房间变化通知 /// 房间列表界面收到时刷新显示。当前仅在房间列表 UI 开启时更新数据, /// 具体刷新逻辑由 UIHome_BY_SelectRoomLevelPanel / 手动选座面板按需实现。 /// public static void RoomChangeNotice(string msg) { try { var roomData = JsonConvert.DeserializeObject>(msg).arguments[0]; if (roomData == null) return; // ⭐ 修复:游戏中收到本房间的 RoomChange(players 是全量最新数据)→ 同步座位。 // room socket 进游戏后仍保持连接,玩家加入/离开时都会推 RoomChange; // 若 game socket 侧没收到(或没处理)RoomJoinNotice/RoomLeaveNotice, // 老客户端就识别不到新玩家。这里按 players 与 currRoomData.players 全量比对: // 新加入的入座(JoinGame),离开的释放座位(ShowWaiting),幂等可重复。 SyncSeatsFromRoomChange(roomData); // 通知手动选座面板刷新(若开启) var homeMain = UIManager.Instance.Get(UIPanel.HomeMainPanel); if (homeMain == null) return; var selectRoomPanel = homeMain.subUIManager.Get(UIPanel.SelectRoomLeavePenel); if (selectRoomPanel != null && selectRoomPanel.isMySelectRoom) { selectRoomPanel.panel_UIHome_BY_MySelectRoomPenel?.OnRoomChange(roomData); } } catch (Exception e) { Debug.LogWarning("[RoomChangeNotice] 解析失败: " + e.Message); } } /// /// 游戏中收到本房间 RoomChange 时同步座位:players 与 currRoomData.players 全量比对。 /// 新加入(列表有、快照无)→ JoinGame 入座;离开(快照有、列表无)→ ShowWaiting 释放座位。 /// 与 RoomJoinNotice/RoomLeaveNotice 处理幂等(快照里已有的玩家不会重复入座)。 /// private static void SyncSeatsFromRoomChange(RoomChangeResponse roomData) { var currRoom = GameManager.Instance.gameData?.currRoomData; // 只处理当前所在房间(房间列表里其他房间的 RoomChange 不关心) if (currRoom == null || roomData.roomId != currRoom.roomId) return; if (roomData.players == null) return; if (currRoom.players == null) currRoom.players = new List(); var panel = UIManager.Instance.Get(UIPanel.MainGamePanelDefault) ?? UIManager.Instance.Get(UIPanel.MainGamePanelFLZY) ?? UIManager.Instance.Get(UIPanel.MainGamePanelYYSC) ?? UIManager.Instance.Get(UIPanel.MainGamePanelXJMH) ?? UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); // 1. 新加入:列表有、快照无 → 写入快照并入座 foreach (var p in roomData.players) { if (string.IsNullOrEmpty(p.userId)) continue; if (currRoom.players.Exists(x => x.userId == p.userId)) continue; currRoom.players.Add(p); if (panel == null) continue; // 不在游戏中只补数据,进房流程自己会建座位 bool isBot = p.isBot || p.userId.StartsWith("bot_"); var targetSit = panel.list_Sits.Find(s => s.posID == p.position); // 真人顶掉 bot 座位时先替换(与 RoomJoinNotice 分支同逻辑) if (targetSit != null && targetSit.isBot && !isBot) { targetSit.isBot = false; targetSit.ShowWaiting(); } panel.JoinGame(p.position, p.gold, p.diamond, p.cannonMultiple, 0, p.userId, p.cannonId, p.wingsId, p.warGodId); } // 2. 离开:快照有、列表无 → 释放座位并移出快照 for (int i = currRoom.players.Count - 1; i >= 0; i--) { var p = currRoom.players[i]; if (string.IsNullOrEmpty(p.userId)) continue; if (roomData.players.Exists(x => x.userId == p.userId)) continue; currRoom.players.RemoveAt(i); if (panel == null) continue; foreach (var sit in panel.list_Sits) { if (sit.user == p.userId) sit.ShowWaiting(); } } } /// /// LoginHub LevelChanged 数据(字段名对齐后端大写首字母) /// [Serializable] public class LevelChangedData { public int NewLevel; public long NewExp; public long NewGold; public int NewDiamond; public string Source; public string Remark; } // ---------------- Customer ---------------- /// /// 发起会话回调(用户端) /// isOk=false 时弹错误提示;isOk=true 时自动加入会话房间 /// public static void Customer_OnConversationStarted(CustomerConversationStartedResult result) { if (!result.isOk) { UIManager.Instance.ShowBoardError(result.msg); return; } // 加入会话房间,之后才能收发消息 //WebSocketSendList.Customer_JoinConversation(result.conversation.id); GetActiveServicePanel()?.OnConversationStarted(result); } /// /// 消息接收回调(用户端 & 客服端) /// public static void Customer_OnMessageReceived(CustomerMessageReceived msg) { GetActiveServicePanel()?.OnMessageReceived(msg); Debug.Log($"[Customer] 收到消息 from {msg.senderName}({msg.senderType}): {msg.content}"); } /// /// 获取当前活动的客服面板(主界面或登录界面打开的) /// private static UIHome_BY_ServicePanel GetActiveServicePanel() { // 先尝试主界面 var home = UIManager.Instance.Get(UIPanel.HomeMainPanel); if (home != null && home.subUIManager != null) { var panel = home.subUIManager.Get(UIPanel.ServicePanel); if (panel != null) return panel; } // 再尝试登录界面(游客模式) var login = UIManager.Instance.Get(UIPanel.LoginPanel); if (login != null && login.uIManager != null) { return login.uIManager.Get(UIPanel.ServicePanel); } return null; } /// /// 新会话通知(客服端) /// public static void Customer_OnNewConversation(CustomerNewConversationNotice notice) { // 客服端自动加入会话房间 WebSocketSendList.Customer_JoinConversation(notice.conversationId); // TODO: 通知客服端 UI 显示新会话(替换为你的实际 Panel 调用) // UIManager.Instance.Get(UIPanel.CustomerServicePanel)?.OnNewConversation(notice); Debug.Log($"[Customer] 新会话来了,userId={notice.userId},userName={notice.userName}"); } /// /// LoginHub Broadcast 数据 /// [Serializable] public class LoginBroadcastData { public string userName; public string avatarUrl; public int level; public int vipLevel; public string message; public long time; } /// /// LoginHub KillAnnouncement 数据(全局击杀公告) /// [Serializable] public class KillAnnouncementData { /// 公告文本(服务端已拼接:恭喜玩家*****在风雷之渊 高级场捕获 人鱼公主*2000倍 150000000大奖) public string text; /// 玩家名(已脱敏,全部替换为*) public string userName; /// 游戏类型名(如:风雷之渊) public string gameTypeName; /// 房间名(如:高级场) public string roomName; /// 鱼名称(如:人鱼公主) public string fishName; /// 炮台倍数 public int multiple; /// 获得金币 public long gold; /// 时间戳(Unix秒) public long time; } /// /// LoginHub AssetUpdated 数据 /// [Serializable] public class LoginAssetData { public int currencyType; public int changeType; public long amount; public long newGold; public int newDiamond; public string source; public string remark; } /// /// LoginHub BackpackUpdated 数据 /// [Serializable] public class LoginBackpackData { public string action; public string propNumber; public int count; public List props; } /// /// LoginHub NewEmail 数据 /// [Serializable] public class LoginNewEmailData { public string title; public string emailId; } /// /// FishGameHub Exception 数据({code, message}) /// code: 0=未知错误 /// 1001=房间已满 1002=金币不足 1003=房间不存在 1004=玩家不在房间 /// 1005=签名错误 1006=请求已过期 1007=鱼配置不存在 1008=子弹参数无效 /// 1009=金币参数超出范围 1011=用户不存在 1012=道具配置不存在 /// 1013=道具数量不足 1014=钻石不足 1015=炮倍参数无效 /// [Serializable] public class FishGameExceptionData { public int code; public string message; } } } // end namespace