Files
5.24BuYu/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs
T
2026-09-22 03:59:20 +08:00

765 lines
37 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Assets.MyGame.Scripts.Net.WebResponses;
using Best.WebSockets;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using static Assets.MyGame.Scripts.Net.WebResponses.ChineseChess.ChineseChessGameClassList;
namespace Assets.MyGame.Scripts.Net
{
public partial class WebSocketManager : MonoSingleton<WebSocketManager>
{
public void OnMessage(WebSocket web, string msg, SocketType type)
{
NetLog.Log($"[WebSocket] {type} 收到消息: {msg}");
// ⭐ 优化:用 char 分割避免 Split 产生 string[] 数组 GC
// 单条消息通常只有 1-2 个分隔段,直接手动解析
int start = 0;
int sep;
while ((sep = msg.IndexOf('\u001e', start)) >= 0)
{
ProcessSingleMessage(msg, start, sep - start, type);
start = sep + 1;
}
// 处理最后一段(或没有分隔符的整条消息)
if (start < msg.Length)
ProcessSingleMessage(msg, start, msg.Length - start, type);
}
private void ProcessSingleMessage(string msg, int start, int length, SocketType type)
{
// 跳过空白段
if (length <= 0) return;
// 快速检查 "{}"(握手包),避免 Substring + Trim
if (length == 2 && msg[start] == '{' && msg[start + 1] == '}')
{
OnHandshakeCompleted(type);
return;
}
// 提取子串(此处必须分配,后续反序列化需要)
string item = msg.Substring(start, length);
// ⭐ 优化:用 JsonTextReader 只读 type/target 做路由,跳过 arguments 反序列化
// 原代码:JsonConvert.DeserializeObject<WebSocketResponseBase<object>>(item)
// 会反序列化整个 arguments 数组(包含鱼数据等),但这些数据在路由阶段完全不需要,
// 具体处理方法内部会重新反序列化。这能消除预解析阶段 90%+ 的 GC。
var v = ParseHeader(item);
if (v == null) return;
#region 消息处理
switch (type)
{
case SocketType.login:
LoginMSG(item, v);
break;
case SocketType.room:
case SocketType.game:
FishMSG(item, v);
break;
case SocketType.chess:
ChineseChess(item, v);
break;
case SocketType.customer:
CustomerMSG(item, v);
break;
default:
break;
}
#endregion
}
private static void ChineseChess(string msg, WebSocketResponseBase<object> v)
{
var roomMainPanel = UIManager.Instance.Get<ChineseChessMainPanel>(UIPanel.MainGamePanelZGXQ);
switch ((MessageType)v.type)
{
case MessageType.Invocation:
switch (v.target)
{
case "GetRoomList":
UIManager.Instance.ShowAsync(UIPanel.MainGamePanelZGXQ, msg);
Screen.orientation = ScreenOrientation.Portrait;
break;
case "BeginGame":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg.ToString()).arguments[0];
roomMainPanel.subUIManager.CloseAll();
roomMainPanel.StartGame(msg);
roomMainPanel.UpdateUserData(va);
}
break;
case "CreateRoomNotice":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg.ToString()).arguments[0];
var panel = roomMainPanel.subUIManager.Get<UIChineseChessDistrict>(UIPanel.ZGXQDistrict);
if (panel != null)
{
panel.UpdataRoom(va);
}
}
break;
case "JoinRoomNotice":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg.ToString()).arguments[0];
var panel = roomMainPanel.subUIManager.Get<UIChineseChessDistrict>(UIPanel.ZGXQDistrict);
if (panel != null)
{
panel.UpdataRoom(va);
}
}
break;
//所有人
case "GoldConfigRoomNotice":
roomMainPanel.subUIManager.CloseAll();
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQGameExplain, msg);
break;
// 房主开始游戏 → 通知红方配金币
case "GoldConfigRequired":
roomMainPanel.OnGoldConfigRequired(msg);
break;
//单发给触发人
case "GoldConfigSuccess":
break;
case "ReadyNotice":
{
// ReadyNotice arguments[0] 是用户ID字符串,不是房间对象
var userId = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
// 标记该玩家已准备
if (roomMainPanel.roomData != null && roomMainPanel.roomData.users != null)
{
var u = roomMainPanel.roomData.users.Find(x => x.userId == userId);
if (u != null) u.isReady = true;
// 只有双方都准备好才进入游戏
if (roomMainPanel.roomData.users.Count >= 2
&& roomMainPanel.roomData.users.TrueForAll(x => x.isReady))
{
roomMainPanel.InitRoomData();
}
}
}
break;
case "GameOperation":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
roomMainPanel.IsShowHint(va["data"].ToString(), Convert.ToInt32(va["type"]));
}
break;
case "EndGameSuccess":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
if (va == null || va == "")
{
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQHe);
}
else if (va == GameManager.Instance.user.id)
{
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin);
}
else
{
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQDefeated);
}
}
break;
case "ExitRoomSuccess":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
if (GameManager.Instance.user.id == va)
{
WebSocketSendList.GetRoomListChineseChess(1);
}
else
{
for (int i = 0; i < roomMainPanel.roomData.users.Count; i++)
{
var item = roomMainPanel.roomData.users[i];
if (item.userId != va)
{
roomMainPanel.roomData.users.Remove(item);
roomMainPanel.UpdateUserData(roomMainPanel.roomData);
i--;
}
}
}
}
break;
case "ExitRoomNotice":
{
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg.ToString()).arguments[0];
var panel = roomMainPanel.subUIManager.Get<UIChineseChessDistrict>(UIPanel.ZGXQDistrict);
if (panel != null)
{
panel.UpdataRoom(va);
}
}
}
break;
case "Disconnect":
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin);
break;
case "GameError":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
if (va == "只有代理才能配置密码")
{
UIManager.Instance.Get<ChineseChessMainPanel>(UIPanel.MainGamePanelZGXQ).subUIManager.Get<UIChineseChessCreateRoom>(UIPanel.ZGXQCreateRoom).ShowTiShi();
}
else
UIManager.Instance.ShowBoardError(va);
}
break;
// ======== 新增缺失事件 ========
case "JoinRoom":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg).arguments[0];
// 调用者自身加入成功
if (roomMainPanel != null) roomMainPanel.UpdateUserData(va);
}
break;
case "Reconnect":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
Debug.Log($"[Chess] 重连: userId={va["userId"]}, userName={va["userName"]}");
}
break;
case "AllBeginTime":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg).arguments[0];
Debug.Log("[Chess] 双方已准备,开始计时");
}
break;
case "GameEnd":
{
if (roomMainPanel != null) roomMainPanel.StopWaitCountdown(); // 游戏结束停走棋倒计时
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
string winUserId = va["winUserId"]?.ToString();
if (string.IsNullOrEmpty(winUserId))
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQHe);
else if (winUserId == GameManager.Instance.user.BaseInfo.number)
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQWin);
else
roomMainPanel.subUIManager.ShowAsync(UIPanel.ZGXQDefeated);
}
break;
case "EndGameRequestSent":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
Debug.Log("[Chess] 结束请求已发送: " + va);
}
break;
case "OpponentRequestEnd":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
Debug.Log("[Chess] 对手请求结束: " + va);
}
break;
case "RestartGameSuccess":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg).arguments[0];
if (roomMainPanel != null) roomMainPanel.StartGame(msg);
}
break;
case "RestartRoomNotice":
{
var va = JsonConvert.DeserializeObject<WebSocketResponseBase<ChessGameRoom>>(msg).arguments[0];
var panel = roomMainPanel?.subUIManager.Get<UIChineseChessDistrict>(UIPanel.ZGXQDistrict);
if (panel != null) panel.UpdataRoom(va);
}
break;
case "SyncGameData":
{
// 象棋走棋帧同步数据:后端只透传,交给主面板解析处理
if (roomMainPanel != null) roomMainPanel.OnChessNetData(msg);
}
break;
default:
break;
}
break;
case MessageType.StreamItem:
break;
case MessageType.Completion:
break;
case MessageType.StreamInvocation:
break;
case MessageType.CancelInvocation:
break;
case MessageType.Ping:
break;
case MessageType.Close:
break;
default:
break;
}
}
private static void CustomerMSG(string msg, WebSocketResponseBase<object> v)
{
switch ((MessageType)v.type)
{
case MessageType.Invocation:
switch (v.target)
{
// 发起会话回调(用户端):服务端返回 {isOk, msg, conversation}
case "OnConversationStarted":
{
var result = JsonConvert.DeserializeObject<WebSocketResponseBase<CustomerConversationStartedResult>>(msg).arguments[0];
WebSocketReponseList.Customer_OnConversationStarted(result);
}
break;
// 消息接收回调(用户端 & 客服端)
case "OnMessageReceived":
{
var result = JsonConvert.DeserializeObject<WebSocketResponseBase<CustomerMessageReceived>>(msg).arguments[0];
WebSocketReponseList.Customer_OnMessageReceived(result);
}
break;
// 新会话通知(客服端)
case "OnNewConversation":
{
var result = JsonConvert.DeserializeObject<WebSocketResponseBase<CustomerNewConversationNotice>>(msg).arguments[0];
WebSocketReponseList.Customer_OnNewConversation(result);
}
break;
default:
break;
}
break;
case MessageType.StreamItem:
break;
case MessageType.Completion:
break;
case MessageType.StreamInvocation:
break;
case MessageType.CancelInvocation:
break;
case MessageType.Ping:
break;
case MessageType.Close:
break;
default:
break;
}
}
/// <summary>
/// LoginHub 消息处理(Broadcast / AssetUpdated / BackpackUpdated / NewEmail / KillAnnouncement)
/// </summary>
private static void LoginMSG(string msg, WebSocketResponseBase<object> v)
{
switch ((MessageType)v.type)
{
case MessageType.Invocation:
switch (v.target)
{
case "Broadcast":
WebSocketReponseList.LoginBroadcast(msg);
break;
case "AssetUpdated":
WebSocketReponseList.AssetUpdated(msg);
break;
case "BackpackUpdated":
WebSocketReponseList.BackpackUpdated(msg);
break;
case "NewEmail":
WebSocketReponseList.NewEmail(msg);
break;
case "KillAnnouncement":
WebSocketReponseList.KillAnnouncement(msg);
break;
case "LevelChanged":
WebSocketReponseList.LevelChanged(msg);
break;
case "PendingRoomNotice":
WebSocketReponseList.PendingRoomNotice(msg);
break;
case "AccountKicked":
WebSocketReponseList.AccountKicked(msg);
break;
case "ChangeConfig":
WebSocketReponseList.ChangeConfig(msg);
break;
case "KickToMainPage":
WebSocketReponseList.KickToMainPage(msg);
break;
default:
break;
}
break;
}
}
private static void FishMSG(string msg, WebSocketResponseBase<object> v)
{
switch ((MessageType)v.type)
{
case MessageType.Invocation:
switch (v.target)
{
case "GetRoomListResult":
//UIManager.Instance.Get<UIHome_BY_SelectRoomLevelPanel>(UIPanel.SelectRoomPenel);
WebSocketReponseList.GetRoomListResult(msg);
break;
case "RoomJoinNotice":
WebSocketReponseList.RoomJoinNotice(msg);
break;
case "RoomInfo":
WebSocketReponseList.RoomInfo(msg);
break;
case "RoomFireNotice":
WebSocketReponseList.RoomFireNotice(msg);
break;
case "RoomKillNotice":
WebSocketReponseList.RoomKillNotice(msg);
break;
case "SyncPlayerGold":
WebSocketReponseList.SyncPlayerGold(msg);
break;
case "ChangeGunMultipleNotice":
WebSocketReponseList.ChangeGunMultipleNotice(msg);
break;
case "GetRoomInfoResult":
WebSocketReponseList.GetRoomInfoResult(msg);
break;
case "SyncGameData":
WebSocketReponseList.SyncGameDataResult(msg);
break;
case "RoomLeaveNotice":
WebSocketReponseList.LeaveRoomResult(msg);
//WebSocketManager.Instance.Close(SocketType.game);
break;
case "ChangeControllerUser":
{
// ⭐ 房主控制权移交:仅维护 roomOwnerSit,不再设置 fangzhu
// (鱼创建已由服务端 OnFishSpawn 统一下发,客户端无需房主出鱼)
var userId = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(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();
}
}
break;
case "Exception":
{
var ex = JsonConvert.DeserializeObject<WebSocketResponseBase<WebSocketReponseList.FishGameExceptionData>>(msg).arguments[0];
if (ex == null) break;
// 根据错误码分类处理
switch (ex.code)
{
case 1001: // 房间已满 → 弹窗提示并跳转房间列表
UIManager.Instance.ShowTiShi(ex.message, () =>
{
switch (GameManager.Instance.gameData.currGameType)
{
case GameType.JQQD:
break;
case GameType.defaultPanel:
UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelDefault, null, (panel) =>
{
WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1);
});
break;
case GameType.FLZY:
UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelFLZY, null, (panel) =>
{
WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1);
});
break;
case GameType.YYSC:
UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelYYSC, null, (panel) =>
{
WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1);
});
break;
case GameType.ZGXQ:
UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelZGXQ, null, (panel) =>
{
WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1);
});
break;
case GameType.XJMH:
UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelXJMH, null, (panel) =>
{
WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1);
});
break;
case GameType.HHWK:
UIManager.Instance.ShowAsync(UIPanel.HomeMainPanel, UIPanel.MainGamePanelHHWK, null, (panel) =>
{
WebSocketSendList.GetRoomListFish(GameManager.Instance.gameData.currRoomData.roomLevel, 1);
});
break;
default:
break;
}
});
break;
// 资产/状态类错误 → 弹窗提示,玩家需明确知晓
case 1002: // 金币不足
UIManager.Instance.ShowBoardError(ex.message);
break;
case 1003: // 房间不存在
case 1011: // 用户不存在
case 1013: // 道具数量不足
case 1014: // 钻石不足
UIManager.Instance.ShowTiShi(ex.message);
break;
// 参数/配置/技术性错误 → Toast 轻提示
case 1004: // 玩家不在房间
case 1005: // 签名错误
case 1006: // 请求已过期
case 1007: // 鱼配置不存在
case 1008: // 子弹参数无效
case 1009: // 金币参数超出范围
case 1012: // 道具配置不存在
case 1015: // 炮倍参数无效
default: // 0=未知错误
UIManager.Instance.ShowBoardError(ex.message);
break;
}
}
break;
// ======== 新增 FishGameHub 缺失事件 ========
case "BindSuccess":
{
var msgText = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
Debug.Log("[FishGameHub] BindSuccess: " + msgText);
}
break;
case "SendPlayerInfoResult":
{
var gold = JsonConvert.DeserializeObject<WebSocketResponseBase<long?>>(msg).arguments[0];
// 发送玩家信息结果,gold 为当前金币数(可为 null)
if (gold.HasValue)
WebSocketReponseList.SyncPlayerGold(JsonConvert.DeserializeObject<WebSocketResponseBase<long>>(msg).arguments[0].ToString());
}
break;
case "RoomDisconnectNotice":
{
var userIdDisc = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
Debug.Log("[FishGameHub] 玩家断线: " + userIdDisc);
}
break;
//case "ChangeGunMultipleNotice":
// WebSocketReponseList.ChangeGunMultipleNotice(msg);
// break;
case "UsePropNotice":
{
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
var userId = data["userId"]?.ToString();
string propNumber = data["propNumber"]?.ToString();
Debug.Log($"[FishGameHub] 使用道具: userId={userId}, prop={propNumber}");
var panel = CreateFishManager.Instance.currGameType;
if (panel != null)
panel.OnUsePropNotice(userId, propNumber);
}
break;
case "TorpedoResultNotice":
// ⭐ 鱼雷使用结果(房间广播):所有客户端都播爆奖
// (随机炸死场上小鱼 + 红包分包爆金币,isSelf 决定金/银币与加分)
WebSocketReponseList.TorpedoResultNotice(msg);
break;
case "ErrorMessage":
// ⭐ 错误通知兜底(鱼雷等新接口失败时推送;老接口走 Exception 错误码)
WebSocketReponseList.ErrorMessage(msg);
break;
case "KillNotice":
// 全局击杀公告(真人击杀/机器人模拟)
WebSocketReponseList.KillNotice(msg);
break;
case "OnFishExpired":
{
var fishIds = JsonConvert.DeserializeObject<WebSocketResponseBase<int[]>>(msg).arguments[0];
Debug.Log($"[FishGameHub] 鱼过期: {string.Join(",", fishIds)}");
}
break;
case "OnFishList":
// 鱼列表刷新
WebSocketReponseList.OnFishList(msg);
break;
// ======== FishGameHub / FishRoomHub 补充事件 ========
case "BotLeaveRoomNotice":
// 机器人退出房间,数据 { userId }
WebSocketReponseList.BotLeaveRoomNotice(msg);
break;
case "HostDisconnect":
// 房主断线,其他玩家可接受机器人控制权
WebSocketReponseList.HostDisconnectNotice(msg);
break;
case "OnSeatChange":
// 座位变更 { type, userId, position, showWaiting? }
WebSocketReponseList.OnSeatChange(msg);
break;
// ======== 新协议事件 ========
case "OnFishSpawn":
// ⭐ 服务端出鱼事件(破坏性替代 createFish 帧同步)
// type: "init"=首次出鱼 / "spawn"=定时出鱼 / "sync"=重连或取消挂机同步
WebSocketReponseList.OnFishSpawn(msg);
break;
case "FishFrozen":
// ⭐ 冰冻道具(40003)生效:鱼存活时间延长 15 秒,客户端写死 15s 倒计时
WebSocketReponseList.FishFrozen(msg);
break;
case "PlayerAfkNotice":
// 玩家挂机通知 { userId }
WebSocketReponseList.PlayerAfkNotice(msg);
break;
case "PlayerCancelAfkNotice":
// 玩家取消挂机通知 { userId }
WebSocketReponseList.PlayerCancelAfkNotice(msg);
break;
case "SummonCardFishNotice":
// ⭐ 召唤卡召唤通知:某玩家使用召唤卡,服务端已创建召唤鱼(实际鱼由 OnFishSpawn 下发,这里只播提示)
WebSocketReponseList.SummonCardFishNotice(msg);
break;
case "RoomChange":
{
// FishRoomHub 房间变化通知(人数/状态变更),更新房间列表
Debug.Log("[FishRoomHub] 房间变化通知");
WebSocketReponseList.RoomChangeNotice(msg);
}
break;
default:
break;
}
break;
case MessageType.StreamItem:
break;
case MessageType.Completion:
break;
case MessageType.StreamInvocation:
break;
case MessageType.CancelInvocation:
break;
case MessageType.Ping:
break;
case MessageType.Close:
break;
default:
break;
}
}
/// <summary>
/// 轻量解析:用 JsonTextReader 只读 type 和 target 字段做路由,
/// 跳过 arguments 数组反序列化(节省 90%+ 预解析 GC)。
/// arguments 在具体处理方法内部按需反序列化。
/// </summary>
private static WebSocketResponseBase<object> ParseHeader(string item)
{
if (string.IsNullOrEmpty(item)) return null;
try
{
using (var sr = new StringReader(item))
using (var reader = new JsonTextReader(sr))
{
var result = new WebSocketResponseBase<object>();
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
string propName = (string)reader.Value;
// 读到 arguments 就停止,不再解析后续(节省大量 GC)
if (propName == "arguments")
break;
reader.Read(); // 读值
if (propName == "type")
{
result.type = Convert.ToInt32(reader.Value);
}
else if (propName == "target")
{
result.target = reader.Value as string;
}
// invocationId 等字段跳过
}
}
return result;
}
}
catch
{
return null;
}
}
}
}