This commit is contained in:
wuyanchen 2025-12-30 16:50:07 +08:00
parent 511b8f022e
commit 4824e84575
11 changed files with 569 additions and 73 deletions

View File

@ -104,9 +104,9 @@ namespace XNet.Business
AGENT_ROTATION_SYNC = 0x0019,
/// <summary>
/// 更改房间MapKey 和 Name
/// 更改房间地图信息
/// </summary>
CHANGE_ROOM_KEY_NAME = 0x1016
CHANGE_ROOM_KEY_NAME = 0x1016,
}
@ -117,6 +117,71 @@ namespace XNet.Business
public string RoomId { get; set; } = string.Empty;
}
// 客户端订阅实例的请求消息
[MessagePackObject]
public class CreateOrJoinRoomReq
{
[Key("roomId")]
public string RoomId { get; set; } = string.Empty;
[Key("headImageUrl")]
public string HeadImageUrl { get; set; } = string.Empty;
[Key("mapKey")]
public string MapKey { get; set; } = string.Empty;
[Key("aiCount")]
public int AICount { get; set; } = 0;
[Key("nickName")]
public string NickName { get; set; } = string.Empty;
[Key("playerId")]
public string PlayerId { get; set; } = string.Empty;
}
[MessagePackObject]
public class PlayerInitReq
{
[Key("roomId")]
public string RoomId { get; set; } = string.Empty;
[Key("isAI")]
public bool IsAI { get; set; } = false;
[Key("hostPlayerId")]
public string HostPlayerId { get; set; } = string.Empty;
[Key("playerId")]
public string PlayerId { get; set; } = string.Empty;
[Key("nickName")]
public string NickName { get; set; } = string.Empty;
[Key("findFaceDistance")]
public float FindFaceDistance { get; set; } = 0;
[Key("radius")]
public float Radius { get; set; } = 0;
[Key("attackPower")]
public float AttackPower { get; set; } = 0;
[Key("armor")]
public float Armor { get; set; } = 0;
[Key("speed")]
public float Speed { get; set; } = 0;
[Key("blood")]
public float Blood { get; set; } = 0;
[Key("bodyIdx")]
public int BodyIdx { get; set; } = 0;
[Key("weaponIdx")]
public int WeaponIdx { get; set; } = 0;
[Key("armorIdx")]
public int ArmorIdx { get; set; } = 0;
[Key("chassisIdx")]
public int ChassisIdx { get; set; } = 0;
[Key("deviceColor")]
public string DeviceColor { get; set; } = "#FFFFFF";
[Key("startPos")]
public Vec3? StartPos { get; set; }
[Key("euler")]
public Vec3? Euler { get; set; }
}
// 客户端订阅实例的请求消息
[MessagePackObject]
public class SubscribeInstanceReq
@ -128,6 +193,15 @@ namespace XNet.Business
public string MapKey { get; set; } = string.Empty;
}
[MessagePackObject]
public class EnterRoomReply
{
[Key("type")]
public WsMsgType Type { get; set; }
[Key("mapKey")]
public string MapKey { get; set; } = string.Empty;
}
// Agent位置同步消息服务端推送
[MessagePackObject]
public class AgentLocationSyncMsg
@ -144,7 +218,7 @@ namespace XNet.Business
/// 房间信息
/// </summary>
[MessagePackObject]
public class RoomMsg<T>
public class BaseRoomMsg<T>
{
[Key("roomId")]
public string RoomId { get; set; } = string.Empty;
@ -157,6 +231,47 @@ namespace XNet.Business
}
/// <summary>
/// 房间用户同步信息
/// </summary>
[MessagePackObject]
public class RoomUserSync
{
[Key("type")]
public WsMsgType Type { get; set; }
[Key("roomId")]
public string RoomId { get; set; } = string.Empty;
[Key("playerId")]
public string PlayerId { get; set; } = string.Empty;
[Key("nickName")]
public string NickName { get; set; } = string.Empty;
[Key("headImageUrl")]
public string? HeadImageUrl { get; set; } = null;
[Key("setIdx")]
public int SetIdx { get; set; } = 0;
[Key("isPublic")]
public bool IsPublic { get; set; } = true;
}
/// <summary>
/// 消息基类
/// </summary>
[MessagePackObject]
public class TypeBaseMsg<T>
{
[Key("type")]
public WsMsgType Type { get; set; }
[Key("senderId")]
public string SenderId { get; set; } = string.Empty;
[Key("data")]
public T? Data { get; set; } = default;
}
/// <summary>
/// 消息基类
/// </summary>

View File

@ -5,9 +5,9 @@ namespace XNet.Business.Entity
public class ControlPlayer
{
/// <summary>
/// 用户ID
/// 用户连接ID
/// </summary>
public string Id { get; set; } = string.Empty;
public string PlayerId { get; set; } = string.Empty;
/// <summary>
/// 是否碰到非地面障碍物或者碰到其他对象,需要转向

View File

@ -8,12 +8,20 @@ namespace XNet.Business.Entity
/// 玩家房间实体
/// </summary>
/// <typeparam name="T">玩家实体包括AI</typeparam>
public class PlayerRoom<T>
public class PlayerRoomInfo<T>
{
/// <summary>
/// AI 玩家数量
/// </summary>
public int AIPlayerCount { get; set; } = 0;
/// <summary>
/// 3V3对战最多6个玩家
/// </summary>
public int MaxPlayerCount { get; set; } = 6;
/// <summary>
/// 寻路导航对象
/// </summary>
public DtCrowd Crowd { get; set; } = null!;
public DtCrowd? Crowd { get; set; } = null;
/// <summary>
/// 导航实体集合
@ -31,9 +39,20 @@ namespace XNet.Business.Entity
public Vec3 WorldMax { get; set; } = new Vec3();
public PlayerRoom() { }
public string MapKey { get; set; } = string.Empty;
public string RoomId { get; set; } = string.Empty;
public PlayerRoom(DtCrowd crowd, Vec3 worldMin, Vec3 worldMax)
/// <summary>
/// 房间席位集合
/// </summary>
public Dictionary<string, RoomSet> Sets { get; set; } = new Dictionary<string, RoomSet>();
public string Host { get; set; } = string.Empty;
public bool IsPublic { get; set; } = true;
public PlayerRoomInfo() { }
public PlayerRoomInfo(DtCrowd crowd, Vec3 worldMin, Vec3 worldMax)
{
Crowd = crowd;
WorldMin = worldMin;

View File

@ -0,0 +1,18 @@
using System.Collections.Concurrent;
using System.Net.WebSockets;
namespace XNet.Business.Entity
{
public class PlayerStateInfo
{
public WebSocket WebSocket { get; set; } = null!;
public ConcurrentDictionary<string, bool> RoomIds { get; set; } = new();
public string FirstRoomId { get => RoomIds.Keys.FirstOrDefault()!; set => RoomIds.TryAdd(value, true); }
public string HeadImageUrl { get; set; } = string.Empty;
public string NickName { get; set; } = string.Empty;
public string PlayerId { get; set; } = string.Empty;
public bool IsAI { get; set; } = false;
public byte SetIdx { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace XNet.Business.Entity
{
public class RoomSet
{
/// <summary>
/// 玩家ID
/// </summary>
public string PlayerId { get; set; } = string.Empty;
/// <summary>
/// 玩家座位索引
/// </summary>
public int SetIdx { get; set; } = 0;
}
}

View File

@ -1,11 +0,0 @@
using System.Collections.Concurrent;
using System.Net.WebSockets;
namespace XNet.Business.Entity
{
public class WebSocketInfo
{
public WebSocket WebSocket { get; set; } = null!;
public ConcurrentDictionary<string, bool> RoomIds { get; set; } = new();
}
}

View File

@ -24,7 +24,7 @@ namespace XNet.Business
private async Task SendSyncLocationMessage()
{
List<RoomMsg<AgentLocationSyncMsg>> allSyncMsgs = [];
List<BaseRoomMsg<AgentLocationSyncMsg>> allSyncMsgs = [];
foreach (var instanceIdKv in _sceneAgent.InstanceIds)
{
var syncMsgs = _sceneAgent.GetAgentsNeedSync(instanceIdKv.Key);
@ -54,21 +54,21 @@ namespace XNet.Business
// 1. 加载所有静态地图资源 (Templates)
// 这里假设路径固定,实际可从配置表读取
_navMeshManager.LoadTemplate("Map_Forest", @"D:\NavMeshExport.obj");
_navMeshManager.LoadTemplate("SD_V2", @"D:\NavMeshExport.obj");
//_navMeshManager.LoadTemplate("Map_Dungeon", @"D:\NavMeshExport.obj");
// 2. 模拟创建副本逻辑 (实际应由 WebAPI 或 MatchService 触发)
// 假设现在有两个队伍分别开启了森林副本
string instanceGuid_A = "Instance_TeamA_" + Nanoid.Generate();
string instanceGuid_B = "Instance_TeamB_" + Nanoid.Generate();
//// 2. 模拟创建副本逻辑 (实际应由 WebAPI 或 MatchService 触发)
//// 假设现在有两个队伍分别开启了森林副本
//string instanceGuid_A = "Instance_TeamA_" + Nanoid.Generate();
//string instanceGuid_B = "Instance_TeamB_" + Nanoid.Generate();
// 注册 NavMesh 映射
_sceneAgent.CreateInstance(instanceGuid_A, "Map_Forest");
_sceneAgent.CreateInstance(instanceGuid_B, "Map_Forest"); // 复用同一份 NavMesh 内存
//// 注册 NavMesh 映射
//_sceneAgent.CreateInstance(instanceGuid_A, "Map_Forest");
//_sceneAgent.CreateInstance(instanceGuid_B, "Map_Forest"); // 复用同一份 NavMesh 内存
// 为这两个副本创建独立的物理/避障模拟器
_sceneAgent.CreateCrowdForInstance(instanceGuid_A);
_sceneAgent.CreateCrowdForInstance(instanceGuid_B);
//// 为这两个副本创建独立的物理/避障模拟器
//_sceneAgent.CreateCrowdForInstance(instanceGuid_A);
//_sceneAgent.CreateCrowdForInstance(instanceGuid_B);
//// 添加一些测试怪物
//for(int i = 0; i < 1000; i++)

View File

@ -14,8 +14,11 @@ namespace XNet.Business.Net
public class WsConnectionManager
{
// ========== 原有核心字段 ==========
private readonly ConcurrentDictionary<string, WebSocketInfo> _connections = new();
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, bool>> _instanceSubscribers = new();
private readonly ConcurrentDictionary<string, PlayerStateInfo> _connections = new();
/// <summary>
/// 第一个string键代表实例房间ID第二个string键代表用户连接ID值为玩家房间实体
/// </summary>
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, PlayerRoomInfo<ControlPlayer>>> _instanceSubscribers = new();
//private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
// ========== 新增:对象池配置 ==========
@ -26,6 +29,10 @@ namespace XNet.Business.Net
private readonly SceneAgent _sceneAgent = null!;
public ConcurrentDictionary<string, ConcurrentDictionary<string, PlayerRoomInfo<ControlPlayer>>> InstanceSubscribers => _instanceSubscribers;
public ConcurrentDictionary<string, PlayerStateInfo> Connections => _connections;
public WsConnectionManager(SceneAgent sceneAgent)
{
_sceneAgent = sceneAgent;
@ -42,35 +49,35 @@ namespace XNet.Business.Net
public string AddConnection(WebSocket socket)
{
string connId = $"Conn_{Nanoid.Generate()}";
_connections.TryAdd(connId, new WebSocketInfo
Connections.TryAdd(connId, new PlayerStateInfo
{
WebSocket = socket
});
Console.WriteLine($"[WS .NET 10] 新连接:{connId},当前连接数:{_connections.Count}");
Console.WriteLine($"[WS .NET 10] 新连接:{connId},当前连接数:{Connections.Count}");
return connId;
}
public void RemoveConnection(string connId)
{
if (_connections.TryRemove(connId, out var socketInfo))
if (Connections.TryRemove(connId, out var socketInfo))
{
foreach (var instanceId in socketInfo.RoomIds.Keys)
foreach (var roomId in socketInfo.RoomIds.Keys)
{
_instanceSubscribers[instanceId].TryRemove(connId, out _);
InstanceSubscribers[roomId].TryRemove(connId, out _);
if (_instanceSubscribers[instanceId].IsEmpty)
if (InstanceSubscribers[roomId].IsEmpty)
{
_sceneAgent.RemoveInstance(instanceId);
_instanceSubscribers.TryRemove(instanceId, out _);
_sceneAgent.RemoveInstance(roomId);
InstanceSubscribers.TryRemove(roomId, out _);
}
}
Console.WriteLine($"[WS .NET 10] 连接断开:{connId},当前连接数:{_connections.Count}");
Console.WriteLine($"[WS .NET 10] 连接断开:{connId},当前连接数:{Connections.Count}");
}
}
public bool SubscribeInstance(string connId, string mapKey, ref string roomId)
public bool SubscribeInstance(string connId, string mapKey, bool isCreateAgent, ref string roomId, float maxAgentRadius = 0.1f)
{
if (!_connections.TryGetValue(connId, out WebSocketInfo? socketInfo))
if (!Connections.TryGetValue(connId, out PlayerStateInfo? socketInfo))
{
Console.WriteLine($"[WS .NET 10] 订阅失败:连接 {connId} 不存在");
return false;
@ -84,13 +91,40 @@ namespace XNet.Business.Net
bool isNewInstance = false;
_instanceSubscribers.GetOrAdd(roomId, (key) =>
var playerRooms = InstanceSubscribers.GetOrAdd(roomId, (key) =>
{
isNewInstance = true;
return new ConcurrentDictionary<string, bool>();
return new ConcurrentDictionary<string, PlayerRoomInfo<ControlPlayer>>();
});
_instanceSubscribers[roomId].TryAdd(connId, true);
// 如果已经加入其他房间,先移除旧房间订阅(对战游戏只能加入一个房间)
if (socketInfo.RoomIds.Any())
{
var oldRoomId = socketInfo.RoomIds.Keys.First();
if (oldRoomId != roomId)
{
socketInfo.RoomIds.Clear();
InstanceSubscribers[oldRoomId].TryRemove(connId, out _);
if (InstanceSubscribers[oldRoomId].IsEmpty)
{
_sceneAgent.RemoveInstance(oldRoomId);
InstanceSubscribers.TryRemove(oldRoomId, out _);
}
}
}
if (playerRooms != null)
{
var playerRoom = playerRooms.GetOrAdd(connId, new PlayerRoomInfo<ControlPlayer> { MapKey = mapKey, RoomId = roomId });
if (!playerRoom.Players.Any())
{
playerRoom.Players.TryAdd(connId, new ControlPlayer { PlayerId = connId });
}
if (playerRoom.Players.Count == 1)
{
playerRoom.MapKey = mapKey;
}
}
socketInfo.RoomIds[roomId] = true;
@ -99,22 +133,83 @@ namespace XNet.Business.Net
//新建房间,创建地图副本
_sceneAgent.CreateInstance(roomId, mapKey);
}
if (isCreateAgent)
{
CreateCrowdForInstance(connId, roomId, maxAgentRadius);
}
return true;
}
public bool HasSubscribeInstance(string instanceId)
public bool CreateCrowdForInstance(string connId, string roomId, float maxAgentRadius = 0.1f)
{
if (_instanceSubscribers.ContainsKey(instanceId))
if (InstanceSubscribers[roomId].TryGetValue(connId, out var room))
{
return true;
if (room.Crowd == null)
{
room.Crowd = _sceneAgent.CreateCrowdForInstance(roomId, maxAgentRadius);
if (room.Crowd != null)
{
return true;
}
}
}
return false;
}
public bool HasSubscribeInstance(string instanceId)
{
return InstanceSubscribers.ContainsKey(instanceId);
}
public PlayerStateInfo? GetConnectionInfo(string connId)
{
if (Connections.TryGetValue(connId, out var socketInfo))
{
return socketInfo;
}
return null;
}
public PlayerRoomInfo<ControlPlayer>? GetPlayerRoomInfo(string roomId, string connId)
{
if (InstanceSubscribers.TryGetValue(roomId, out var rooms))
{
if (rooms.TryGetValue(connId, out var roomInfo))
{
return roomInfo;
}
}
return null;
}
public async Task SendBaseSingleMessage<T>(string connId, WsMsgType type, T msg)
{
var baseMessage = new BaseMsg
{
SenderId = connId,
Type = type,
Data = MessagePackSerializer.Serialize(msg)
};
await SendSerializeMessageToPointWsSocket(connId, baseMessage);
}
public async Task SendTypeBaseSingleMessage<T>(string connId, WsMsgType type, T msg)
{
var baseMessage = new TypeBaseMsg<T>
{
SenderId = connId,
Type = type,
Data = msg
};
await SendSerializeMessageToPointWsSocket(connId, baseMessage);
}
public async Task SendSerializeMessageToPointWsSocket<T>(string connId, T syncMsg)
{
if (_connections.TryGetValue(connId, out var socketInfo))
if (Connections.TryGetValue(connId, out var socketInfo))
{
// 3. 复用:从池获取失效连接列表
var deadConnIds = _deadConnListPool.Get();
@ -146,18 +241,62 @@ namespace XNet.Business.Net
}
public async Task SendSerializeBatchMessageToPointWsSocket<T>(RoomMsg<T> syncMsg)
public async Task SendMessageToRoomBatchAsync<T>(string roomId, string connId, WsMsgType type, T msg)
{
await SendMessageToRoomBatchAsync([syncMsg]);
await SendMessageToRoomBatchAsync([new BaseRoomMsg<T> {
Type = type,
RoomId = roomId,
SenderId = connId,
Data = msg
}]);
}
public async Task SendMessageToRoomBatchAsync<T>(List<RoomMsg<T>> syncMsgs)
public async Task SendMessageToRoomBatchAsync(string roomId, byte[] msgBytes, List<string>? exceptIds = null)
{
if (!InstanceSubscribers.TryGetValue(roomId, out var room)) return;
// 3. 复用:从池获取失效连接列表
var deadConnIds = _deadConnListPool.Get();
try
{
foreach (var connKv in room)
{
if (exceptIds != null && exceptIds.Contains(connKv.Key))
{
continue;
}
if (Connections.TryGetValue(connKv.Key, out var socketInfo))
{
// 3. 发送整串字节数组
_ = SendToSingleConnAsync(socketInfo.WebSocket, msgBytes, msgBytes.Length, connKv.Key, deadConnIds);
}
}
// 清理失效连接
foreach (var deadConnId in deadConnIds)
{
RemoveConnection(deadConnId);
}
}
finally
{
_deadConnListPool.Return(deadConnIds); // 归还,自动清空
}
//}
//finally
//{
// _syncMsgListPool.Return(msgList); // 归还,自动清空
//}
}
public async Task SendMessageToRoomBatchAsync<T>(List<BaseRoomMsg<T>> syncMsgs, List<string>? exceptIds = null)
{
if (syncMsgs.Count == 0) return;
foreach (var group in syncMsgs.GroupBy(m => m.RoomId))
{
if (!_instanceSubscribers.TryGetValue(group.Key, out var subscriberConnIds)) continue;
if (!InstanceSubscribers.TryGetValue(group.Key, out var room)) continue;
// 1. 复用:从池获取消息列表
//var msgList = _syncMsgListPool.Get();
@ -175,9 +314,13 @@ namespace XNet.Business.Net
var deadConnIds = _deadConnListPool.Get();
try
{
foreach (var connKv in subscriberConnIds)
foreach (var connKv in room)
{
if (_connections.TryGetValue(connKv.Key, out var socketInfo))
if (exceptIds != null && exceptIds.Contains(connKv.Key))
{
continue;
}
if (Connections.TryGetValue(connKv.Key, out var socketInfo))
{
if (msgBytesLength != 0)
{
@ -286,7 +429,7 @@ namespace XNet.Business.Net
public async ValueTask DisposeAsync()
{
foreach (var (key, socketInfo) in _connections)
foreach (var (key, socketInfo) in Connections)
{
if (socketInfo.WebSocket.State == WebSocketState.Open)
{
@ -295,8 +438,8 @@ namespace XNet.Business.Net
socketInfo.WebSocket.Dispose();
RemoveConnection(key);
}
_connections.Clear();
_instanceSubscribers.Clear();
Connections.Clear();
InstanceSubscribers.Clear();
}
}
}

View File

@ -102,9 +102,9 @@ namespace XNet.Business.PathNavigation
// ==========================================
// 修复4GetAgentsNeedSync 遍历 AgentIndices
// ==========================================
public List<RoomMsg<AgentLocationSyncMsg>> GetAgentsNeedSync(string instanceId)
public List<BaseRoomMsg<AgentLocationSyncMsg>> GetAgentsNeedSync(string instanceId)
{
List<RoomMsg<AgentLocationSyncMsg>> syncList = new();
List<BaseRoomMsg<AgentLocationSyncMsg>> syncList = new();
if (!_crowdInstances.TryGetValue(instanceId, out var ci) || !_agentLastState.TryGetValue(instanceId, out var lastStates))
{
return syncList;
@ -136,7 +136,7 @@ namespace XNet.Business.PathNavigation
if (posChanged || rotChanged)
{
// 加入同步列表
syncList.Add(new RoomMsg<AgentLocationSyncMsg>
syncList.Add(new BaseRoomMsg<AgentLocationSyncMsg>
{
RoomId = instanceId,
Data = new AgentLocationSyncMsg
@ -153,7 +153,7 @@ namespace XNet.Business.PathNavigation
else
{
// 首次同步,直接加入并缓存
syncList.Add(new RoomMsg<AgentLocationSyncMsg>
syncList.Add(new BaseRoomMsg<AgentLocationSyncMsg>
{
RoomId = instanceId,
Data = new AgentLocationSyncMsg
@ -219,12 +219,12 @@ namespace XNet.Business.PathNavigation
// 初始化实例的Agent状态缓存
public bool CreateCrowdForInstance(string instanceId, float maxAgentRadius = 0.1f)
public DtCrowd? CreateCrowdForInstance(string instanceId, float maxAgentRadius = 0.1f)
{
// 使用 NavMeshManager 新增的 Helper 方法
if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out var navMesh, out _))
{
return false;
return null;
}
var config = new DtCrowdConfig(maxAgentRadius);
@ -243,7 +243,7 @@ namespace XNet.Business.PathNavigation
// 初始化状态缓存
bool isAddStateOK = _agentLastState.TryAdd(instanceId, new ConcurrentDictionary<int, AgentState>());
return isAddCrowdOK && isAddStateOK;
return crowd;
}
public void RemoveCrowdInstance(string instanceId)

View File

@ -22,6 +22,8 @@ namespace XNet.Business.Tank.Manager
{
Actions[WsMsgType.SUBSCRIBE_ROOM] = _playerManager.SubcribeRoom;
Actions[WsMsgType.CHANGE_ROOM_KEY_NAME] = _playerManager.ChangeRoomKey;
Actions[WsMsgType.CREATE_OR_JOIN_ROOM] = _playerManager.CreateOrJoinRoom;
Actions[WsMsgType.ROOM_MSG_OTHER] = _playerManager.SendMessageToRoomOthers;
}

View File

@ -1,5 +1,8 @@
using MessagePack;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using NanoidDotNet;
using System.Numerics;
using XNet.Business.Entity;
using XNet.Business.Net;
namespace XNet.Business.Tank.Manager
@ -25,7 +28,7 @@ namespace XNet.Business.Tank.Manager
subscribeReq.RoomId = Nanoid.Generate();
}
await _wsManager.SendSerializeMessageToPointWsSocket(connId,
new RoomMsg<string>
new BaseRoomMsg<string>
{
Type = WsMsgType.SUBSCRIBE_ROOM,
RoomId = subscribeReq.RoomId,
@ -39,16 +42,208 @@ namespace XNet.Business.Tank.Manager
public async Task ChangeRoomKey(string connId, byte[] data)
{
// 处理订阅实例请求
var subscribeReq = MessagePackSerializer.Deserialize<SubscribeInstanceReq>(data);
if (subscribeReq != null && !string.IsNullOrWhiteSpace(subscribeReq.RoomId))
var changeRoomReq = MessagePackSerializer.Deserialize<SubscribeInstanceReq>(data);
if (changeRoomReq != null && !string.IsNullOrWhiteSpace(changeRoomReq.RoomId))
{
string createRoomId = subscribeReq.RoomId;
_wsManager.SubscribeInstance(connId, subscribeReq.MapKey, ref createRoomId);//Map_Forest Test
string createRoomId = changeRoomReq.RoomId;
_wsManager.SubscribeInstance(connId, changeRoomReq.MapKey, false, ref createRoomId);//Map_Forest Test
}
}
public async Task CreateOrJoinRoom(string connId, byte[] data)
{
var socketInfo = _wsManager.GetConnectionInfo(connId);
if (socketInfo == null) return;
// 处理订阅实例请求
var roomReq = MessagePackSerializer.Deserialize<CreateOrJoinRoomReq>(data);
if (roomReq != null)
{
string createRoomId = roomReq.RoomId;
//roomId为空时新创建一个唯一房间ID
if (string.IsNullOrWhiteSpace(roomReq.RoomId))
{
roomReq.RoomId = Nanoid.Generate();
}
_wsManager.SubscribeInstance(connId, roomReq.MapKey, false, ref createRoomId);//Map_Forest Test
var playerInfo = _wsManager.GetConnectionInfo(connId)!;
if (playerInfo != null)
{
if (string.IsNullOrWhiteSpace(playerInfo.PlayerId))
{
playerInfo.PlayerId = connId;// string.IsNullOrWhiteSpace(roomReq.PlayerId) ? connId : roomReq.PlayerId;
playerInfo.IsAI = false;
}
playerInfo.HeadImageUrl = roomReq.HeadImageUrl;
playerInfo.NickName = roomReq.NickName;
}
var room = _wsManager.GetPlayerRoomInfo(roomReq.RoomId, connId);
if (room != null)
{
InitSets(room, connId, roomReq.AICount);
await _wsManager.SendTypeBaseSingleMessage(connId, WsMsgType.PRIVATGE,
new EnterRoomReply
{
Type = WsMsgType.CHANGE_ROOM_KEY_NAME,
MapKey = room.MapKey
}
);
PlayerStateInfo? self = _wsManager.GetConnectionInfo(connId);
await _wsManager.SendMessageToRoomBatchAsync(room.RoomId, connId, WsMsgType.PRIVATGE, new RoomUserSync
{
Type = WsMsgType.CREATE_OR_JOIN_ROOM,
RoomId = room.RoomId,
PlayerId = connId,
HeadImageUrl = self?.HeadImageUrl,
SetIdx = self?.SetIdx ?? 0,
IsPublic = room.IsPublic
});
if (_wsManager.InstanceSubscribers.TryGetValue(room.RoomId, out var roomUsers))
{
foreach (var userKv in roomUsers)
{
var other = _wsManager.GetConnectionInfo(userKv.Key);
if (other != null && other.PlayerId != connId)
{
await _wsManager.SendTypeBaseSingleMessage(connId, WsMsgType.PRIVATGE,
new RoomUserSync
{
Type = WsMsgType.CREATE_OR_JOIN_ROOM,
RoomId = room.RoomId,
PlayerId = connId,
NickName = other.NickName,
HeadImageUrl = other.HeadImageUrl,
SetIdx = other.SetIdx,
IsPublic = room.IsPublic
}
);
}
}
}
}
}
}
private void InitSets(PlayerRoomInfo<ControlPlayer> room, string connId, int aiCount)
{
PlayerStateInfo self = _wsManager.GetConnectionInfo(connId)!;
// 初始化座位数组长度为房间最大人数默认填充null
List<RoomSet?> sets = new List<RoomSet?>();
for (int i = 0; i < room.MaxPlayerCount; i++)
{
sets.Add(null);
}
// 初始化座位索引为-1无可用座位
int setIdx = -1;
// 如果已有座位数据,遍历填充座位数组并重新分配索引
if (room.Sets.Count > 0)
{
int tmpIndex = 0;
// 遍历所有已存在的座位键值对 (对应TS: state.sets.forEach)
foreach (var kvp in room.Sets)
{
string key = kvp.Key;
RoomSet set = kvp.Value;
// 从玩家集合中获取当前玩家 (对应TS: state.players.get(key))
PlayerStateInfo? playerItem = _wsManager.GetConnectionInfo(key);
if (playerItem == null)
{
sets[tmpIndex] = null;
}
else
{
sets[tmpIndex] = set;
playerItem.SetIdx = (byte)tmpIndex; // uint8对应byte强转
}
tmpIndex++;
}
// 查找第一个空座位 或 当前客户端sessionId对应的座位 (对应TS: sets.findIndex)
setIdx = sets.FindIndex(s => s == null || s.PlayerId == connId);
}
else
{
// 没有任何座位数据默认分配第0个座位
setIdx = 0;
}
// 如果获取到有效座位索引
if (setIdx != -1)
{
// 第一个进房间的玩家,将房主标识置空 (对应TS逻辑)
if (setIdx == 0)
{
room.Host = string.Empty;
}
// 给当前玩家赋值座位索引
self.SetIdx = (byte)setIdx;
// 更新/新增座位数据到状态集合中
if (room.Sets.ContainsKey(connId))
{
// 已存在该玩家的座位,更新信息
RoomSet roomSet = room.Sets[connId];
roomSet.PlayerId = connId;
roomSet.SetIdx = (byte)setIdx;
}
else
{
// 不存在则新建座位对象,添加到集合
RoomSet roomSet = new RoomSet();
roomSet.PlayerId = connId;
roomSet.SetIdx = (byte)setIdx;
room.Sets.Add(connId, roomSet);
}
}
// AI数量初始化逻辑消息中存在aiCount 且 未初始化过AI数量赋值
if (aiCount > 0 && room.AIPlayerCount == 0)
{
room.AIPlayerCount = aiCount;
}
}
public async Task RequestInit(string connId, byte[] data)
{
var socketInfo = _wsManager.GetConnectionInfo(connId);
if (socketInfo == null) return;
// 处理订阅实例请求
var initReq = MessagePackSerializer.Deserialize<PlayerInitReq>(data);
if (initReq != null)
{
}
}
public async Task SendMessageToRoomOthers(string connId, byte[] data)
{
var playerState = _wsManager.GetConnectionInfo(connId);
if (playerState != null)
{
await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data, [connId]);
}
}
public async Task RequestPath(string connId, byte[] data)
{
//var playerState = _wsManager.GetConnectionInfo(connId);
//if (playerState != null)
//{
// await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data, [connId]);
//}
await Task.CompletedTask;
}
}