`
This commit is contained in:
parent
511b8f022e
commit
4824e84575
@ -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>
|
||||
|
||||
@ -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>
|
||||
/// 是否碰到非地面障碍物或者碰到其他对象,需要转向
|
||||
|
||||
@ -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;
|
||||
18
XNet.Business/Entity/PlayerStateInfo.cs
Normal file
18
XNet.Business/Entity/PlayerStateInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
15
XNet.Business/Entity/RoomSet.cs
Normal file
15
XNet.Business/Entity/RoomSet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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++)
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,9 +102,9 @@ namespace XNet.Business.PathNavigation
|
||||
// ==========================================
|
||||
// 修复4:GetAgentsNeedSync 遍历 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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user