using MessagePack; using Microsoft.AspNetCore.DataProtection.KeyManagement; using NanoidDotNet; using System.Numerics; using System.Threading.Tasks; using XNet.Business.Dto; using XNet.Business.Entity; using XNet.Business.Net; using XNet.Business.PathNavigation; namespace XNet.Business.Tank.Manager { public class PlayerManager { private readonly WsConnectionManager _wsManager; private readonly NavMeshManager _navMeshManager; private readonly SceneAgent _sceneAgent; private readonly List RoleColor = new List(); public PlayerManager(WsConnectionManager wsManager, NavMeshManager navMeshManager, SceneAgent sceneAgent) { _wsManager = wsManager; _navMeshManager = navMeshManager; _sceneAgent = sceneAgent; RoleColor.AddRange(["#8CE929", "#FFBF5F", "#FFF45F", "#FF5F5F", "#5FECFF", "#EF5FFF", "#497352","#736049","#6B7349","#734949","#495F73","#734972"]); } public async Task SubcribeRoom(string connId, byte[] data) { // 处理订阅实例请求 var subscribeReq = MessagePackSerializer.Deserialize(data); if (subscribeReq != null) { //roomId为空时,新创建一个唯一房间ID if (string.IsNullOrWhiteSpace(subscribeReq.RoomId)) { subscribeReq.RoomId = Nanoid.Generate(); } await _wsManager.SendSerializeMessageToPointWsSocket(connId, new BaseRoomMsg { Type = WsMsgType.SUBSCRIBE_ROOM, RoomId = subscribeReq.RoomId, SenderId = connId, Data = string.Empty } ); } } public async Task ChangeRoomKey(string connId, byte[] data) { // 处理订阅实例请求 var changeRoomReq = MessagePackSerializer.Deserialize(data); if (changeRoomReq != null && !string.IsNullOrWhiteSpace(changeRoomReq.RoomId)) { string createRoomId = changeRoomReq.RoomId; await _wsManager.SubscribeInstance(connId, changeRoomReq.MapKey, false, createRoomId, true);//Map_Forest Test } } public async Task LockTarget(string connId, byte[] data) { var playerInfo = _wsManager.GetConnectionInfo(connId); if (playerInfo == null) return; // 处理锁定实例请求 var lockReq = MessagePackSerializer.Deserialize(data); await _wsManager.SendMessageToRoomBatchAsync(playerInfo.FirstRoomId, playerInfo.PlayerId, WsMsgType.ROOM_MSG, lockReq); } public async Task CreateOrJoinRoom(string connId, byte[] data) { var playerInfo = _wsManager.GetConnectionInfo(connId); if (playerInfo == null) return; // 处理订阅实例请求 var roomReq = MessagePackSerializer.Deserialize(data); if (roomReq != null) { //roomId为空时,新创建一个唯一房间ID if (string.IsNullOrWhiteSpace(roomReq.RoomId)) { roomReq.RoomId = Nanoid.Generate(); } string createRoomId = roomReq.RoomId; createRoomId = (await _wsManager.SubscribeInstance(connId, roomReq.MapKey, true, createRoomId, false))!;//Map_Forest Test 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) { room.RoomName = roomReq.RoomName; InitSets(room, connId, roomReq.AICount); await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new EnterRoomReply { Type = WsMsgType.CHANGE_ROOM_KEY_NAME, MapKey = room.MapKey } ); ControlPlayer? 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.SendBaseSingleMessage(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 room, string connId, int aiCount) { ControlPlayer self = _wsManager.GetConnectionInfo(connId)!; // 初始化座位数组,长度为房间最大人数,默认填充null List sets = new List(); 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)) ControlPlayer? 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 = null; } // 给当前玩家赋值座位索引 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 SendMessageToRoomOthers(string connId, byte[] data) { var playerState = _wsManager.GetConnectionInfo(connId); if (playerState != null) { await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data, [connId]); } } public async Task SendMessageToRoom(string connId, byte[] data) { var playerState = _wsManager.GetConnectionInfo(connId); if (playerState != null) { await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data); } } public async Task SendMessageToSelf(string connId, byte[] data) { var playerState = _wsManager.GetConnectionInfo(connId); if (playerState != null) { await _wsManager.SendBaseSingleMessage(connId, WsMsgType.TO_SELF, data); } } public async Task RequestPath(string connId, byte[] data) { var posReq = MessagePackSerializer.Deserialize(data); if (posReq != null) { var playerState = _wsManager.GetConnectionInfo(connId); //if (playerState != null) //{ // await _wsManager.SendMessageToRoomBatchAsync(playerState.FirstRoomId, data, [connId]); //} if (playerState != null && playerState.IsCanControl && posReq.EndPos != null) { var room = _wsManager.GetPlayerRoomInfo(playerState.FirstRoomId, connId); if (room != null) { //客户端传上来的位置,角度等为了省流量,存成整形,这里处理成单精度浮点型要除以 LocationMultiply,默认除以1000 var toPt = new Vector3(posReq.EndPos.X, posReq.EndPos.Y, posReq.EndPos.Z) / Global.LocationMultiply; var closetPt = _navMeshManager.GetClosetPoint(room.RoomId!, toPt,out long startRef); if (closetPt != null) { room.Crowd?.RequestMoveTarget(playerState.Agent, startRef, closetPt.Value); } } } } await Task.CompletedTask; } public async Task RequestInit(string connId, byte[] data) { // 处理订阅实例请求 var message = MessagePackSerializer.Deserialize(data); if (message != null) { var player = _wsManager.GetConnectionInfo(connId); if (player == null) return; var room = _wsManager.GetPlayerRoomInfo(player.FirstRoomId, connId); if (room != null) { if (room.EndTime == null) { room.EndTime = DateTime.Now.AddMilliseconds(PlayerRoomInfo.GameMillisecond); } if (room.WillEndTime == null) { room.WillEndTime = DateTime.Now.AddMilliseconds(PlayerRoomInfo.GameMillisecond - PlayerRoomInfo.GameWillEndMillisecond); } if (room.StartGameTime == null) { room.StartGameTime = DateTime.Now; } if (message.IsAI) { message.PlayerId = Nanoid.Generate(); } else { message.PlayerId = player.PlayerId; } await RequestInitPlayer(room!, room?.MapKey!, player, message); //可以移动了 await SetPlayerIsCanControl(room?.RoomId!, player, true, null); //开始无敌 SetPlayerIsCanHit(connId, false); _ = Task.Delay(5000).ContinueWith((t) => { //5秒后会收到伤害 SetPlayerIsCanHit(connId, true); }); var pos = player.CurrentPos * Global.LocationMultiply; var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); var euler = player.CurrentEuler * Global.LocationMultiply; var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new RequestInitMsg { Type = WsMsgType.REQUEST_INIT, PlayerId = player.PlayerId, Pos = convertPos, Euler = convertEuler, IsAI = message.IsAI, GameTime = (long)(PlayerRoomInfo.GameMillisecond - (DateTime.Now - room!.StartGameTime.Value).TotalMilliseconds) }); } } } public async Task Login(string connId, byte[] data) { var loginReq = MessagePackSerializer.Deserialize(data); if (loginReq != null) { var player = _wsManager.GetConnectionInfo(connId); if (player != null) { //await _wsManager.RemoveConnection(connId); player.NickName = loginReq.NickName; player.HeadImageUrl = loginReq.HeadImageUrl; player.DeviceColor = loginReq.DeviceColor; player.IsAI = false; var room = _wsManager.GetPlayerRoomInfo(player.FirstRoomId, connId); if (room != null) { player.PlayerId = connId; player.IsAI = false; room.Players[connId] = player; if (room.Players.Count == 1) { room.Host = null; } float blood = await CheckLoginAndCreateHostAI(connId, room, loginReq.MapKey, loginReq); await _wsManager.SendBaseSingleMessage(connId, WsMsgType.PRIVATGE, new LoginResultMsg { Type = WsMsgType.LOGIN, PlayerId = player.PlayerId, Result = LoginResult.Success, Blood = player.Blood, DeviceColor = player.DeviceColor }); } } } } public void SetPlayerIsCanHit(string connId, bool isCanHit) { var player = _wsManager.GetConnectionInfo(connId); if (player != null) { player.IsCanHit = isCanHit; } } public bool GetPlayerIsCanHit(string connId) { var player = _wsManager.GetConnectionInfo(connId); if (player != null) { return player.IsCanHit; } return false; } private async Task CheckLoginAndCreateHostAI(string connId, PlayerRoomInfo room, string mapKey, PlayerLoginReq loginReq) { bool isFirstPlayer = room.Host == null; bool hasAI = room.Players.Any(p => p.Value.IsAI); if (isFirstPlayer) { if (room.Players.TryGetValue(connId, out var loginClient)) { room.Host = loginClient; loginClient.BirthPositions.Clear(); if (loginReq.BirthPositions != null) { for (int i = 0; i < loginReq.BirthPositions.Length; i++) { loginClient.BirthPositions.Add(loginReq.BirthPositions[i]); } } if (!hasAI && loginReq.HasAI) { var deviceInfo = DeviceManager.Instance.DeviceInfo; for (int i = 0; i < Global.AICount; i++) { int bodyIdx = new Random().Next(0, Global.MaxPartKindCount); int weaponIdx = new Random().Next(0, Global.MaxPartKindCount); int armorIdx = new Random().Next(0, Global.MaxPartKindCount); int chassisIdx = new Random().Next(0, Global.MaxPartKindCount); var startPos = loginClient.BirthPositions[new Random().Next(0, loginClient.BirthPositions.Count)]; var message = new PlayerInitReq { IsAI = true, PlayerId = Nanoid.Generate(), BodyIdx = bodyIdx, WeaponIdx = weaponIdx, ArmorIdx = armorIdx, ChassisIdx = chassisIdx, DeviceColor = RoleColor[new Random().Next(0, RoleColor.Count)], FindFaceDistance = deviceInfo.Weapon[weaponIdx].FindFaceDistance, AttackPower = deviceInfo.Weapon[weaponIdx].AttackPower, Armor = deviceInfo.Armor[armorIdx].Armor, Speed = deviceInfo.Chassis[chassisIdx].Speed, Radius = loginReq.Radius, StartPos = startPos, Euler = new Vec3(0, 0, 0) //CurrentEuler = new Dto.Vec3(), //NickName = loginReq.NickName, }; var aiPlayer = new ControlPlayer { PlayerId = message.PlayerId, IsAI = true, IsEnterMap = false }; await this.RequestInitPlayer(room, mapKey, aiPlayer, message); //可以移动了 await SetPlayerIsCanControl(room.RoomId, aiPlayer, true, null); //开始无敌 SetPlayerIsCanHit(connId, false); _ = Task.Delay(5000).ContinueWith((t) => { //5秒后会收到伤害 SetPlayerIsCanHit(connId, true); }); } } return loginClient.MaxBlood; } } return 0f; } private async Task RequestInitPlayer(PlayerRoomInfo room, string mapKey, ControlPlayer loginPlayer, PlayerInitReq message) { if (loginPlayer == null || loginPlayer.IsEnterMap) { return; } loginPlayer.IsAI = message.IsAI; loginPlayer.PlayerId = message.PlayerId; loginPlayer.NickName = message.NickName; var deviceInfo = DeviceManager.Instance.DeviceInfo; var armorInfo = deviceInfo.Armor[message.ArmorIdx]; var weaponInfo = deviceInfo.Weapon[message.WeaponIdx]; var chassisInfo = deviceInfo.Chassis[message.ChassisIdx]; loginPlayer.MaxBlood = armorInfo.Blood; loginPlayer.Blood = armorInfo.Blood; loginPlayer.BodyIdx = message.BodyIdx; loginPlayer.WeaponIdx = message.WeaponIdx; loginPlayer.ArmorIdx = message.ArmorIdx; loginPlayer.ChassisIdx = message.ChassisIdx; loginPlayer.FindFaceDistance = weaponInfo.FindFaceDistance; loginPlayer.AttackPower = weaponInfo.AttackPower; loginPlayer.Armor = armorInfo.Armor; loginPlayer.Speed = chassisInfo.Speed; loginPlayer.Radius = message.Radius; loginPlayer.DeviceColor = message.DeviceColor; loginPlayer.CurrentPos = new Vector3(message.StartPos!.X, message.StartPos.Y, message.StartPos.Z)!; loginPlayer.CurrentEuler = new Vector3(message.Euler!.X, message.Euler.Y, message.Euler.Z)!; float speedScale = 1f; if (loginPlayer.IsAI) { speedScale = 0.5f;//AI速度减为一半 loginPlayer.Skill1Level = (byte)new Random().Next(1, 4); loginPlayer.Skill2Level = (byte)new Random().Next(1, 4); loginPlayer.Skill3Level = (byte)new Random().Next(1, 4); loginPlayer.Skill4Level = (byte)new Random().Next(1, 4); } var startPos = _navMeshManager.GetClosetPoint(room.RoomId, loginPlayer.CurrentPos / 1000, out long startRef); if (startPos != null) { loginPlayer.CurrentPos = startPos.Value; loginPlayer.Agent = _sceneAgent.AddAgent(room.RoomId, loginPlayer.CurrentPos, loginPlayer.Radius, loginPlayer.Radius, loginPlayer.PlayerId, loginPlayer.Speed * speedScale * 2, loginPlayer.Speed * speedScale); } loginPlayer.TmpPos = loginPlayer.CurrentPos; _sceneAgent.AgentTeleport(room.RoomId, loginPlayer.Agent.idx, loginPlayer.CurrentPos); loginPlayer.IsEnterMap = true; if (!loginPlayer.IsAI) { //同步其他玩家的信息给自己 await SyncOtherPlayerInfoToSelf(room.RoomId, loginPlayer); //向其他玩家发送自己的信息 await SyncSelfInfoToOtherPlayer(room.RoomId, loginPlayer); //同步AI信息给登录玩家 await SyncAIToOthersPlayer(room.RoomId, loginPlayer); } } /// /// 同步其他玩家的信息给自己 /// /// /// private async Task SyncOtherPlayerInfoToSelf(string roomId, ControlPlayer client) { var room = _wsManager.GetPlayerRoomInfo(roomId, client.PlayerId); if (room != null) { foreach (var playerKv in room.Players) { var player = playerKv.Value; if (!player.IsAI && player.IsEnterMap && player.PlayerId != client.PlayerId) { var pos = player.CurrentPos * Global.LocationMultiply; var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); var euler = player.CurrentEuler * Global.LocationMultiply; var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); await _wsManager.SendBaseSingleMessage(client.PlayerId, WsMsgType.PRIVATGE, new PlayerInitSync { Type = WsMsgType.ADD_OR_UPDATE_PLAYER, PlayerId = player.PlayerId, IsAI = player.IsAI, NickName = player.NickName, Blood = player.MaxBlood, BodyIdx = player.BodyIdx, ArmorIdx = player.ArmorIdx, ChassisIdx = player.ChassisIdx, DeviceColor = player.DeviceColor, HeadImageUrl = player.HeadImageUrl, Position = convertPos, EulerAngles = convertEuler, }); } } } } /// /// //向其他玩家发送自己的信息 /// /// /// private async Task SyncSelfInfoToOtherPlayer(string roomId, ControlPlayer client) { var room = _wsManager.GetPlayerRoomInfo(roomId, client.PlayerId); if (room != null) { foreach (var playerKv in room.Players) { var player = playerKv.Value; if (!player.IsAI && player.IsEnterMap && player.PlayerId != client.PlayerId) { var pos = client.CurrentPos * Global.LocationMultiply; var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); var euler = client.CurrentEuler * Global.LocationMultiply; var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); await _wsManager.SendBaseSingleMessage(player.PlayerId, WsMsgType.PRIVATGE, new PlayerInitSync { Type = WsMsgType.ADD_OR_UPDATE_PLAYER, PlayerId = client.PlayerId, IsAI = false, NickName = client.NickName, Blood = client.MaxBlood, BodyIdx = client.BodyIdx, ArmorIdx = client.ArmorIdx, ChassisIdx = client.ChassisIdx, DeviceColor = client.DeviceColor, HeadImageUrl = client.HeadImageUrl, Position = convertPos, EulerAngles = convertEuler, }); } } } } /// /// 同步AI给登录玩家 /// /// /// private async Task SyncAIToOthersPlayer(string roomId, ControlPlayer client) { var room = _wsManager.GetPlayerRoomInfo(roomId, client.PlayerId); if (room != null) { foreach (var playerKv in room.Players) { var player = playerKv.Value; if (player.IsAI && player.IsEnterMap) { var pos = client.CurrentPos * Global.LocationMultiply; var convertPos = new Vec3((int)pos.X, (int)pos.Y, (int)pos.Z); var euler = client.CurrentEuler * Global.LocationMultiply; var convertEuler = new Vec3((int)euler.X, (int)euler.Y, (int)euler.Z); await _wsManager.SendBaseSingleMessage(player.PlayerId, WsMsgType.PRIVATGE, new PlayerInitSync { Type = WsMsgType.ADD_OR_UPDATE_PLAYER, PlayerId = client.PlayerId, IsAI = false, NickName = client.NickName, Blood = client.MaxBlood, BodyIdx = client.BodyIdx, ArmorIdx = client.ArmorIdx, ChassisIdx = client.ChassisIdx, DeviceColor = client.DeviceColor, HeadImageUrl = client.HeadImageUrl, Position = convertPos, EulerAngles = convertEuler, }); } } } } public async Task SetPlayerIsCanControl(string roomId, ControlPlayer client, bool isCanControl, PlayerLocationSyncReq? locationSyncReq) { client.IsCanControl = true; if (locationSyncReq != null) { var pos = locationSyncReq.Pos; var convertPos = new Vector3((int)(pos!.X / Global.LocationMultiply), (int)(pos.Y / Global.LocationMultiply), (int)(pos.Z / Global.LocationMultiply)); var euler = client.CurrentEuler * Global.LocationMultiply; var convertEuler = new Vector3((int)(euler!.X / Global.LocationMultiply), (int)(euler.Y / Global.LocationMultiply), (int)(euler.Z / Global.LocationMultiply)); var closetPos = _navMeshManager.GetClosetPoint(roomId, convertPos, out long startRef); if (closetPos != null) { _sceneAgent.AgentTeleport(roomId, client.Agent.idx, closetPos.Value); } client.CurrentEuler = convertEuler; } } } }