diff --git a/Common/Enums/Fight/MarblePlayerPhaseEnum.cs b/Common/Enums/Fight/MarblePlayerPhaseEnum.cs index 92de9d2d..61c583b7 100644 --- a/Common/Enums/Fight/MarblePlayerPhaseEnum.cs +++ b/Common/Enums/Fight/MarblePlayerPhaseEnum.cs @@ -8,5 +8,6 @@ public enum MarblePlayerPhaseEnum PerformanceFinish = 3, Gaming = 4, Launching = 5, - UseTech = 6 + SimulateFinish = 6, + UseTech = 7 } \ No newline at end of file diff --git a/DanhengKcpSharp/DanhengConnection.cs b/DanhengKcpSharp/DanhengConnection.cs index 1e9556ac..87b0b0e3 100644 --- a/DanhengKcpSharp/DanhengConnection.cs +++ b/DanhengKcpSharp/DanhengConnection.cs @@ -18,7 +18,7 @@ public class DanhengConnection public static readonly ConcurrentBag IgnoreLog = [ CmdIds.PlayerHeartBeatCsReq, CmdIds.PlayerHeartBeatScRsp, CmdIds.SceneEntityMoveCsReq, - CmdIds.SceneEntityMoveScRsp, CmdIds.GetShopListCsReq, CmdIds.GetShopListScRsp + CmdIds.SceneEntityMoveScRsp, CmdIds.GetShopListCsReq, CmdIds.GetShopListScRsp, CmdIds.FightHeartBeatCsReq, CmdIds.FightHeartBeatScRsp ]; protected readonly CancellationTokenSource CancelToken; diff --git a/GameServer/Game/Lobby/LobbyRoomInstance.cs b/GameServer/Game/Lobby/LobbyRoomInstance.cs index 21fd029e..dedfc512 100644 --- a/GameServer/Game/Lobby/LobbyRoomInstance.cs +++ b/GameServer/Game/Lobby/LobbyRoomInstance.cs @@ -26,7 +26,7 @@ public class LobbyRoomInstance(PlayerInstance owner, long roomId, FightGameMode public async ValueTask AddPlayer(PlayerInstance player, List sealList, LobbyCharacterType characterType) { - await AddPlayer(new LobbyPlayerInstance(player, characterType) + await AddPlayer(new LobbyPlayerInstance(player, characterType, this) { EquippedSealList = sealList }); @@ -105,6 +105,15 @@ public class LobbyRoomInstance(PlayerInstance owner, long roomId, FightGameMode return Retcode.RetSucc; } + public async ValueTask EndFight(LobbyPlayerInstance player) + { + // alrdy check status in lobby start fight + IsInGame = false; + player.CharacterStatus = LobbyCharacterStatus.Idle; + await BroadCastToRoom(new PacketLobbySyncInfoScNotify(player.Player.Uid, this, LobbyModifyType.FightEnd)); + return Retcode.RetSucc; + } + public LobbyPlayerInstance? GetPlayerByUid(int uid) { var player = Players.FirstOrDefault(x => x.Player.Uid == uid); diff --git a/GameServer/Game/Lobby/Player/LobbyPlayerInstance.cs b/GameServer/Game/Lobby/Player/LobbyPlayerInstance.cs index 01843005..4c77bf90 100644 --- a/GameServer/Game/Lobby/Player/LobbyPlayerInstance.cs +++ b/GameServer/Game/Lobby/Player/LobbyPlayerInstance.cs @@ -3,12 +3,13 @@ using EggLink.DanhengServer.Proto; namespace EggLink.DanhengServer.GameServer.Game.Lobby.Player; -public class LobbyPlayerInstance(PlayerInstance player, LobbyCharacterType characterType) +public class LobbyPlayerInstance(PlayerInstance player, LobbyCharacterType characterType, LobbyRoomInstance lobby) { public PlayerInstance Player { get; } = player; public List EquippedSealList { get; set; } = []; public LobbyCharacterType CharacterType { get; set; } = characterType; public LobbyCharacterStatus CharacterStatus { get; set; } = LobbyCharacterStatus.Idle; + public LobbyRoomInstance LobbyRoom { get; set; } = lobby; public LobbyBasicInfo ToProto() { diff --git a/GameServer/Game/MultiPlayer/BaseGamePlayerInstance.cs b/GameServer/Game/MultiPlayer/BaseGamePlayerInstance.cs index 13b12e80..c07e6854 100644 --- a/GameServer/Game/MultiPlayer/BaseGamePlayerInstance.cs +++ b/GameServer/Game/MultiPlayer/BaseGamePlayerInstance.cs @@ -8,6 +8,7 @@ public abstract class BaseGamePlayerInstance(LobbyPlayerInstance lobby) { public LobbyPlayerInstance LobbyPlayer { get; } = lobby; public bool EnterGame { get; set; } + public bool LeaveGame { get; set; } public Connection? Connection { get; set; } public async ValueTask SendPacket(BasePacket packet) diff --git a/GameServer/Game/MultiPlayer/MarbleGame/MarbleGamePlayerInstance.cs b/GameServer/Game/MultiPlayer/MarbleGame/MarbleGamePlayerInstance.cs index e48c3f18..5c390f1b 100644 --- a/GameServer/Game/MultiPlayer/MarbleGame/MarbleGamePlayerInstance.cs +++ b/GameServer/Game/MultiPlayer/MarbleGame/MarbleGamePlayerInstance.cs @@ -27,7 +27,7 @@ public class MarbleGamePlayerInstance : BaseGamePlayerInstance if (!GameData.MarbleSealData.TryGetValue(seal, out var marbleSeal)) continue; var posY = (index - 1) * 1.5f; - var posX = posXBaseValue * (Math.Abs(index - 1) * 0.5f + 2); + var posX = posXBaseValue * (Math.Abs(index - 1) * 1 + 3); var rotX = posXBaseValue * -1f; AllowMoveSealList.Add(CurItemId); diff --git a/GameServer/Game/MultiPlayer/MarbleGame/MarbleGameRoomInstance.cs b/GameServer/Game/MultiPlayer/MarbleGame/MarbleGameRoomInstance.cs index ccbc8c7e..aac63e3b 100644 --- a/GameServer/Game/MultiPlayer/MarbleGame/MarbleGameRoomInstance.cs +++ b/GameServer/Game/MultiPlayer/MarbleGame/MarbleGameRoomInstance.cs @@ -1,25 +1,32 @@ -using EggLink.DanhengServer.Data; +using System.Numerics; +using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Enums.Fight; using EggLink.DanhengServer.GameServer.Game.Lobby; using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Physics; using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal; using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync; +using EggLink.DanhengServer.GameServer.Server; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Multiplayer; using EggLink.DanhengServer.Kcp; using EggLink.DanhengServer.Proto; -using Microsoft.Xna.Framework; +using EggLink.DanhengServer.Util; namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame; public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance { public MarbleTeamType CurMoveTeamType { get; set; } + public MarbleTeamType FirstMoveTeamType { get; set; } public int CurRound { get; set; } + public int TurnCount { get; set; } + public long WaitingOperationEndTime { get; set; } public MarbleGameRoomInstance(long roomId, LobbyRoomInstance parentLobby) : base(roomId, parentLobby) { // random move team type CurMoveTeamType = (MarbleTeamType)Random.Shared.Next(1, 3); + FirstMoveTeamType = CurMoveTeamType; // set player foreach (var player in parentLobby.Players) { @@ -28,6 +35,9 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance } } + + #region Player + public async ValueTask BroadCastToRoom(BasePacket packet) { foreach (var player in Players) @@ -36,6 +46,14 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance } } + public async ValueTask BroadCastToRoomPlayer(BasePacket packet) + { + foreach (var player in Players.Where(x => !x.LeaveGame)) + { + await player.LobbyPlayer.Player.SendPacket(packet); + } + } + public async ValueTask EnterGame(int uid) { var player = GetPlayerById(uid); @@ -55,6 +73,48 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance } } + public async ValueTask OnPlayerHeartBeat() + { + var curTime = Extensions.GetUnixMs(); + if (WaitingOperationEndTime > 0 && curTime >= WaitingOperationEndTime) + { + // timeout + await SwitchTurn(); + } + } + + public List CheckPlayerWin() + { + var winPlayer = Players.OfType().FirstOrDefault(x => x.Score >= 6); + if (winPlayer == null) return []; + + List syncData = []; + // win + foreach (var player in Players.OfType()) + { + syncData.Add(new MarbleGameFinishSyncData(player, player == winPlayer)); + } + + return syncData; + } + + public async ValueTask EndGame() + { + // end game + await BroadCastToRoom(new PacketFightSessionStopScNotify(this)); + await BroadCastToRoom(new PacketMultiplayerFightGameFinishScNotify(this)); + foreach (var marblePlayer in Players.OfType()) + { + await marblePlayer.LobbyPlayer.LobbyRoom.EndFight(marblePlayer.LobbyPlayer); + marblePlayer.Connection?.Stop(); + } + + // remove room + ServerUtils.MultiPlayerGameServerManager.RemoveRoom(RoomId); + } + + #endregion + #region Handler public async ValueTask HandleGeneralRequest(MarbleGamePlayerInstance player, uint msgType, byte[] reqData) @@ -79,7 +139,8 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch, MarbleNetWorkMsgEnum.Operation, operationReq)); break; - default: + case MarbleNetWorkMsgEnum.SimulateFinish: + await HandleSimulateFinish(player); break; } } @@ -87,7 +148,7 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance public async ValueTask LoadFinish(MarbleGamePlayerInstance player) { player.Phase = MarblePlayerPhaseEnum.LoadFinish; - if (Players.OfType().ToList().All(x => x.Phase == MarblePlayerPhaseEnum.LoadFinish)) + if (Players.OfType().ToList().Where(x => !x.LeaveGame).All(x => x.Phase == MarblePlayerPhaseEnum.LoadFinish)) { // next phase (performance) await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch, @@ -98,13 +159,23 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance public async ValueTask PerformanceFinish(MarbleGamePlayerInstance player) { player.Phase = MarblePlayerPhaseEnum.PerformanceFinish; - if (Players.OfType().ToList().All(x => x.Phase == MarblePlayerPhaseEnum.PerformanceFinish)) + if (Players.OfType().ToList().Where(x => !x.LeaveGame).All(x => x.Phase == MarblePlayerPhaseEnum.PerformanceFinish)) { // next phase (round start) await RoundStart(); } } + public async ValueTask HandleSimulateFinish(MarbleGamePlayerInstance player) + { + player.Phase = MarblePlayerPhaseEnum.SimulateFinish; + if (Players.OfType().ToList().Where(x => !x.LeaveGame).All(x => x.Phase == MarblePlayerPhaseEnum.SimulateFinish)) + { + // switch turn + await SwitchTurn(); + } + } + #endregion #region Round @@ -112,6 +183,8 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance public async ValueTask RoundStart() { CurRound++; + TurnCount = 0; + CurMoveTeamType = FirstMoveTeamType; foreach (var player in Players.OfType()) { player.ChangeRound(); @@ -123,6 +196,59 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance Players.OfType().SelectMany(x => x.SealList.Values) .Select(x => new MarbleGameSealSyncData(x, MarbleFrameType.RoundStart)).ToList()) ])); + + WaitingOperationEndTime = Extensions.GetUnixMs() + 1000 * 30; + TurnCount++; + } + + public async ValueTask SwitchTurn() + { + var moveCount = Players.OfType() + .SelectMany(x => x.SealList.Values.Where(j => j.OnStage)).Count(); + + if (moveCount == TurnCount) + { + await RoundEnd(); + return; + } + + CurMoveTeamType = CurMoveTeamType == MarbleTeamType.TeamA ? MarbleTeamType.TeamB : MarbleTeamType.TeamA; + + foreach (var player in Players.OfType()) + { + player.Phase = MarblePlayerPhaseEnum.Gaming; + } + + await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch, + [ + new MarbleGameInfoSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.SwitchRound, this, + Players.OfType().SelectMany(x => x.SealList.Values) + .Select(x => new MarbleGameSealSyncData(x, MarbleFrameType.RoundStart)).ToList()) + ])); + + WaitingOperationEndTime = Extensions.GetUnixMs() + 1000 * 30; + TurnCount++; + } + + public async ValueTask RoundEnd() + { + FirstMoveTeamType = FirstMoveTeamType == MarbleTeamType.TeamA ? MarbleTeamType.TeamB : MarbleTeamType.TeamA; + + foreach (var player in Players.OfType()) + { + player.Phase = MarblePlayerPhaseEnum.Gaming; + } + + await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch, + [ + new MarbleGameInfoSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.RoundEnd, this, + Players.OfType().SelectMany(x => x.SealList.Values) + .Select(x => new MarbleGameSealSyncData(x, MarbleFrameType.RoundEnd)).ToList()) + ])); + + WaitingOperationEndTime = 0; + + await RoundStart(); } #endregion @@ -131,19 +257,25 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance public async ValueTask HandleLaunch(int itemId, Vector2 rotation) { + WaitingOperationEndTime = 0; + var player = Players.OfType().FirstOrDefault(x => x.SealList.ContainsKey(itemId)); if (player == null) return; var seal = player.SealList[itemId]; if (!GameData.MarbleSealData.TryGetValue(seal.SealId, out var sealExcel)) return; + player.AllowMoveSealList.Remove(seal.Id); + var speed = sealExcel.MaxSpeed * rotation; - var simulator = new PhysicsSimulator( - gravity: new Vector2(0, 0), + var simulator = new CollisionSimulator( leftBound: -5.25f, rightBound: 5.25f, topBound: 3f, bottomBound: -3f - ); + ) + { + LaunchTeam = itemId / 100 + }; seal.Velocity = new MarbleSealVector { @@ -151,55 +283,297 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance Y = speed.Y }; - List syncData = []; + List syncData = []; foreach (var sealInst in Players.OfType().SelectMany(x => x.SealList.Values) .Where(x => x.OnStage)) { simulator.AddBall(sealInst.Id, new Vector2(sealInst.Position.X, sealInst.Position.Y), sealInst.Mass, - sealInst.Size, new Vector2(sealInst.Velocity.X, sealInst.Velocity.Y)); + sealInst.Size, new Vector2(sealInst.Velocity.X, sealInst.Velocity.Y), hp:sealInst.CurHp, atk:sealInst.Attack); } syncData.AddRange(Players.OfType().SelectMany(x => x.SealList.Values) .Select(sealInst => new MarbleGameSealActionSyncData(sealInst, MarbleFrameType.ActionStart))); - simulator.Simulate(maxDuration: 20f); + syncData.Add(new MarbleGameSealLaunchStopSyncData(seal, MarbleFrameType.Launch)); + simulator.Simulate(); - foreach (var record in simulator.CollisionRecords) + foreach (var recordRaw in simulator.Records) // process record { - syncData.Add(new MarbleGameSealCollisionSyncData(seal, record.ObjectAId, record.ObjectBId, record.Time, record.Position)); - } - - foreach (var body in simulator.World.BodyList) - { - if (body?.UserData is int id) + switch (recordRaw) { - var sealInst = Players.OfType().SelectMany(x => x.SealList.Values) - .FirstOrDefault(x => x.Id == id); - if (sealInst == null) continue; + case CollisionRecord record: + { + var sealInst = Players.OfType().SelectMany(x => x.SealList.Values) + .FirstOrDefault(x => x.Id == record.BallA.Id); + if (sealInst == null) continue; + var sealInstB = Players.OfType().SelectMany(x => x.SealList.Values) + .FirstOrDefault(x => x.Id == (record.BallB?.Id ?? 1)); - sealInst.Position = new MarbleSealVector + sealInst.Velocity = new MarbleSealVector + { + X = record.BallA.Velocity.X, + Y = record.BallA.Velocity.Y + }; + + sealInst.Position = new MarbleSealVector + { + X = record.BallA.Position.X, + Y = record.BallA.Position.Y + }; + + if (record.BallA.Velocity != Vector2.Zero) // avoid zero vector + { + var velocityANormal = Vector2.Normalize(record.BallA.Velocity); + sealInst.Rotation = new MarbleSealVector + { + X = velocityANormal.X, + Y = velocityANormal.Y + }; + } + + if (sealInstB != null && record.BallB != null) + { + var velocityBNormal = Vector2.Normalize(record.BallB.Velocity); + if (record.BallB.Velocity != Vector2.Zero) + { + sealInstB.Rotation = new MarbleSealVector + { + X = velocityBNormal.X, + Y = velocityBNormal.Y + }; + } + + sealInstB.Velocity = new MarbleSealVector + { + X = record.BallB.Velocity.X, + Y = record.BallB.Velocity.Y + }; + + sealInstB.Position = new MarbleSealVector + { + X = record.BallB.Position.X, + Y = record.BallB.Position.Y + }; + + if (sealInstB.Id / 100 != sealInst.Id / 100) + { + // different teams + // do damage to b + syncData.AddRange(sealInst.Id / 100 == itemId / 100 + ? DoDamage(sealInst, sealInstB, record.Time) + // do damage to a + : DoDamage(sealInstB, sealInst, record.Time)); + } + } + + syncData.Add(new MarbleGameSealCollisionSyncData(sealInst, record.BallA.Id, record.BallB?.Id ?? 1, + record.Time, record.CollisionPos, sealInstB?.Velocity)); + if (sealInstB != null) + { + syncData.Add(new MarbleGameSealCollisionSyncData(sealInstB, record.BallA.Id, + record.BallB?.Id ?? 1, + record.Time, record.CollisionPos, sealInst.Velocity)); + } + + break; + } + case StopRecord stopRecord: { - X = body.Position.X, - Y = body.Position.Y - }; - sealInst.Rotation = new MarbleSealVector + var sealInst = Players.OfType().SelectMany(x => x.SealList.Values) + .FirstOrDefault(x => x.Id == stopRecord.Ball.Id); + if (sealInst == null) continue; + sealInst.Velocity = new MarbleSealVector + { + X = 0, + Y = 0 + }; + + sealInst.Position = new MarbleSealVector + { + X = stopRecord.Ball.Position.X, + Y = stopRecord.Ball.Position.Y + }; + + syncData.Add(new MarbleGameSealLaunchStopSyncData(sealInst, MarbleFrameType.Stop, stopRecord.Time)); + break; + } + case ChangeSpeedRecord changeSpeedRecord: { - X = body.Rotation - }; - sealInst.Velocity = new MarbleSealVector - { - X = body.LinearVelocity.X, - Y = body.LinearVelocity.Y - }; + var sealInst = Players.OfType().SelectMany(x => x.SealList.Values) + .FirstOrDefault(x => x.Id == changeSpeedRecord.Ball.Id); + if (sealInst == null) continue; + sealInst.Velocity = new MarbleSealVector + { + X = changeSpeedRecord.Ball.Velocity.X, + Y = changeSpeedRecord.Ball.Velocity.Y + }; + + sealInst.Position = new MarbleSealVector + { + X = changeSpeedRecord.Ball.Position.X, + Y = changeSpeedRecord.Ball.Position.Y + }; + + if (changeSpeedRecord.Ball.Velocity != Vector2.Zero) + { + var normal = Vector2.Normalize(changeSpeedRecord.Ball.Velocity); + sealInst.Rotation = new MarbleSealVector + { + X = normal.X, + Y = normal.Y + }; + } + + syncData.Add(new MarbleGameSealLaunchStopSyncData(sealInst, MarbleFrameType.ChangeSpeed, + changeSpeedRecord.Time)); + break; + } } } + foreach (var ball in simulator.Balls) + { + var sealInst = Players.OfType().SelectMany(x => x.SealList.Values) + .FirstOrDefault(x => x.Id == ball.Id); + if (sealInst == null) continue; + + sealInst.Position = new MarbleSealVector + { + X = ball.Position.X, + Y = ball.Position.Y + }; + + sealInst.Velocity = new MarbleSealVector + { + X = ball.Velocity.X, + Y = ball.Velocity.Y + }; + } + syncData.AddRange(Players.OfType().SelectMany(x => x.SealList.Values) - .Select(sealInst => new MarbleGameSealActionSyncData(sealInst, MarbleFrameType.ActionEnd, simulator.CurrentTime))); + .Select(sealInst => + new MarbleGameSealActionSyncData(sealInst, MarbleFrameType.ActionEnd, simulator.CurTime))); + + syncData.AddRange(ReviveAllDeadSeals(simulator.CurTime)); + var winData = CheckPlayerWin(); await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch, - [new MarbleGameInfoLaunchingSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.SimulateStart, simulator.CurrentTime, itemId, syncData)])); + [ + new MarbleGameInfoLaunchingSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.SimulateStart, + simulator.CurTime, itemId, syncData), ..winData + ])); + + foreach (var p in Players.OfType()) + { + p.Phase = MarblePlayerPhaseEnum.Launching; + } + + if (winData.Count > 0) + { + await EndGame(); + } + } + + public List DoDamage(MarbleGameSealInstance attacker, MarbleGameSealInstance target, float time) + { + List syncData = []; + var attackerPlayer = Players.OfType().FirstOrDefault(x => + x.SealList.ContainsKey(attacker.Id)); + var targetPlayer = Players.OfType().FirstOrDefault(x => + x.SealList.ContainsKey(target.Id)); + + if (attackerPlayer == null || targetPlayer == null) return syncData; + var damage = attacker.Attack; + target.CurHp -= damage; + syncData.Add(new MarbleGameHpChangeSyncData(target, MarbleFrameType.HpChange, -damage, time)); + if (target.CurHp <= 0) + { + // die + // score + if (Players.OfType().All(x => x.Score < 6)) + { + attackerPlayer.Score++; + syncData.Add(new MarbleGameScoreSyncData((Players[0] as MarbleGamePlayerInstance)!.Score, + (Players[1] as MarbleGamePlayerInstance)!.Score, MarbleFrameType.TeamScore)); + } + + target.CurHp = 0; + } + + return syncData; + } + + public List ReviveAllDeadSeals(float time) + { + List syncData = []; + var seals = Players.OfType() + .SelectMany(x => x.SealList.Values).ToList(); + + var deadSeals = seals.Where(j => j.CurHp <= 0).ToList(); + if (deadSeals.Count == 0) return syncData; + + foreach (var seal in deadSeals) + { + var player = Players.OfType() + .FirstOrDefault(x => x.SealList.ContainsKey(seal.Id)); + if (player == null) continue; + + var posXBaseValue = player.TeamType == MarbleTeamType.TeamA ? -1 : 1; + var index = seal.Id % 100; + var posY = (index - 1) * 1.5f; + var posX = posXBaseValue * (Math.Abs(index - 1) * 1 + 3); + seal.CurHp = seal.MaxHp; + seal.OnStage = true; + seal.Velocity = new MarbleSealVector(); + + seal.Position = new MarbleSealVector + { + X = posX, + Y = posY + }; + + seal.Rotation = new MarbleSealVector + { + X = posXBaseValue * -1f, + }; + + syncData.Add(new MarbleGameSealActionSyncData(seal, MarbleFrameType.Revive, time)); + } + + bool anyMove; + do + { + // detect collision with seals + anyMove = false; + for (var i = 0; i < seals.Count; i++) + { + for (var j = i + 1; j < seals.Count; j++) + { + var sealA = seals[i]; + var sealB = seals[j]; + + var sealAPos = new Vector2(sealA.Position.X, sealA.Position.Y); + var sealBPos = new Vector2(sealB.Position.X, sealB.Position.Y); + if (!(Vector2.Distance(sealBPos, sealAPos) <= sealA.Size + sealB.Size)) continue; + + anyMove = true; + // move sealB away + var normalVec = Vector2.Normalize(sealBPos - sealAPos); + var moveDistance = sealA.Size + sealB.Size - Vector2.Distance(sealBPos, sealAPos) + 0.1f; + var moveVec = normalVec * moveDistance; + sealB.Position = new MarbleSealVector + { + X = sealB.Position.X + moveVec.X, + Y = sealB.Position.Y + moveVec.Y + }; + + syncData.Add(new MarbleGameSealActionSyncData(sealB, MarbleFrameType.Revive, time)); + } + } + } while (anyMove); + + return syncData; } #endregion @@ -210,7 +584,7 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance { LobbyBasicInfo = { ParentLobby.Players.Select(x => x.ToProto()) }, CurActionTeamType = CurMoveTeamType, - LevelId = (uint)Random.Shared.Next(100, 103), + LevelId = 101, TeamAPlayer = (uint)Players[0].LobbyPlayer.Player.Uid, TeamBPlayer = (uint)Players[1].LobbyPlayer.Player.Uid, TeamARank = 1, diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Physics/Ball.cs b/GameServer/Game/MultiPlayer/MarbleGame/Physics/Ball.cs new file mode 100644 index 00000000..ca540201 --- /dev/null +++ b/GameServer/Game/MultiPlayer/MarbleGame/Physics/Ball.cs @@ -0,0 +1,26 @@ +using System.Numerics; + +namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Physics; + +public class Ball(int id, Vector2 position, float mass, float radius, Vector2? velocity = null, bool isStatic = false, int hp = 100, int atk = 0) +{ + public int Id { get; } = id; + public Vector2 Position { get; set; } = position; + public Vector2 Velocity { get; set; } = velocity ?? Vector2.Zero; + public Vector2 StageInitialVelocity { get; set; } = velocity ?? Vector2.Zero; + public float Radius { get; } = radius; + public float Mass { get; } = mass; + public bool IsStatic { get; set; } = isStatic; + public int Hp { get; set; } = hp; + public int Atk { get; set; } = atk; + + public BallSnapshot GetSnapshot() + { + return new BallSnapshot + { + Id = Id, + Position = Position, + Velocity = Velocity + }; + } +} \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Physics/BallSnapshot.cs b/GameServer/Game/MultiPlayer/MarbleGame/Physics/BallSnapshot.cs new file mode 100644 index 00000000..8942ee5d --- /dev/null +++ b/GameServer/Game/MultiPlayer/MarbleGame/Physics/BallSnapshot.cs @@ -0,0 +1,10 @@ +using System.Numerics; + +namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Physics; + +public class BallSnapshot +{ + public int Id { get; set; } + public Vector2 Position { get; set; } + public Vector2 Velocity { get; set; } +} \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Physics/CollisionSimulator.cs b/GameServer/Game/MultiPlayer/MarbleGame/Physics/CollisionSimulator.cs new file mode 100644 index 00000000..eaa17c90 --- /dev/null +++ b/GameServer/Game/MultiPlayer/MarbleGame/Physics/CollisionSimulator.cs @@ -0,0 +1,189 @@ +using System.Numerics; + +namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Physics; + +public class CollisionSimulator(float leftBound, float rightBound, float topBound, float bottomBound, float deceleration = 15f) +{ + public float Deceleration = deceleration; + public const float StepTime = 0.001f; + public List Balls { get; } = []; + public int LaunchTeam { get; set; } = 0; + public List Records { get; } = []; + public float CurTime { get; set; } + private float LeftBound { get; } = leftBound; + private float RightBound { get; } = rightBound; + private float TopBound { get; } = topBound; + private float BottomBound { get; } = bottomBound; + + public void AddBall(int id, Vector2 position, float mass, float radius, Vector2? velocity = null, bool isStatic = false, int hp = 100, int atk = 0) + { + Balls.Add(new Ball(id, position, mass, radius, velocity, isStatic, hp, atk)); + } + + public void AdvanceTime(float time) + { + CurTime += time; + + foreach (var ball in Balls.Where(ball => !ball.IsStatic)) + { + if (ball.Velocity.Length() < 0.01f) + { + if (ball.Velocity != Vector2.Zero) + { + ball.Velocity = Vector2.Zero; + Records.Add(new StopRecord(CurTime, ball.GetSnapshot())); + } + continue; + } + + ball.Position += ball.Velocity * time; + ball.Velocity -= Vector2.Normalize(ball.Velocity) * Deceleration * time; + } + + CheckWallCollision(); + CheckBallCollision(); + } + + public void CheckWallCollision() + { + foreach (var ball in Balls.Where(ball => !ball.IsStatic)) + { + if (ball.Velocity.Length() < 0.001f) continue; + + var collide = false; + var collisionPosition = Vector2.Zero; + if (ball.Position.X + ball.Radius >= RightBound || ball.Position.X - ball.Radius <= LeftBound) + { + collisionPosition = ball.Position with { X = MathF.Sign(ball.Velocity.X) * RightBound }; + ball.Velocity = ball.Velocity with { X = -ball.Velocity.X }; + + collide = true; + } + + if (ball.Position.Y + ball.Radius >= TopBound || ball.Position.Y - ball.Radius <= BottomBound) + { + collisionPosition = ball.Position with { Y = MathF.Sign(ball.Velocity.Y) * TopBound }; + ball.Velocity = ball.Velocity with { Y = -ball.Velocity.Y }; + + collide = true; + } + + if (collide) + { + ball.StageInitialVelocity = ball.Velocity; + Records.Add(new CollisionRecord(CurTime, ball.GetSnapshot(), null, collisionPosition)); + } + } + } + + public void CheckBallCollision() + { + for (var i = 0; i < Balls.Count; i++) + { + for (var j = i + 1; j < Balls.Count; j++) + { + var ballA = Balls[i]; + var ballB = Balls[j]; + if (ballA.IsStatic || ballB.IsStatic) continue; // skip static balls + + var distance = Vector2.Distance(ballA.Position, ballB.Position); + if (distance <= ballA.Radius + ballB.Radius) + { + HandleBallCollision(ballA, ballB); + } + } + } + } + + public void HandleBallCollision(Ball ballA, Ball ballB) + { + var deltaPos = ballB.Position - ballA.Position; + if (deltaPos == Vector2.Zero) return; + + // get normal and tangent vectors + var normal = Vector2.Normalize(deltaPos); + var tangent = new Vector2(-normal.Y, normal.X); + + // split velocity into normal and tangent components + var v1n = Vector2.Dot(ballA.Velocity, normal); + var v1t = Vector2.Dot(ballA.Velocity, tangent); + var v2n = Vector2.Dot(ballB.Velocity, normal); + var v2t = Vector2.Dot(ballB.Velocity, tangent); + + var massA = ballA.Mass; + var massB = ballB.Mass; + var totalMass = massA + massB; + + // switch to normal velocity + var newV1n = (v1n * (massA - massB) + 2 * massB * v2n) / totalMass; + var newV2n = (v2n * (massB - massA) + 2 * massA * v1n) / totalMass; + + // combine velocity + ballA.Velocity = newV1n * normal + v1t * tangent; + ballB.Velocity = newV2n * normal + v2t * tangent; + + // fix pos + var overlap = ballA.Radius + ballB.Radius - deltaPos.Length(); + if (overlap > 0) + { + var correction = normal * overlap; + if (!ballA.IsStatic) ballA.Position -= correction * massB / totalMass; + if (!ballB.IsStatic) ballB.Position += correction * massA / totalMass; + } + + ballA.StageInitialVelocity = ballA.Velocity; + ballB.StageInitialVelocity = ballB.Velocity; + + // record + var collisionPos = ballA.Position + normal * ballA.Radius; + Records.Add(new CollisionRecord(CurTime, ballA.GetSnapshot(), ballB.GetSnapshot(), collisionPos)); + + // check if ball is dead + if (ballA.Id / 100 != ballB.Id / 100) + { + // different teams + if (ballA.Id / 100 == LaunchTeam) + { + ballB.Hp -= ballA.Atk; + if (ballB.Hp <= 0) + { + ballB.Velocity = Vector2.Zero; + ballB.StageInitialVelocity = Vector2.Zero; + ballB.IsStatic = true; + } + } + else + { + ballA.Hp -= ballB.Atk; + if (ballA.Hp <= 0) + { + ballA.Velocity = Vector2.Zero; + ballA.StageInitialVelocity = Vector2.Zero; + ballA.IsStatic = true; + } + } + } + } + + public void Simulate() + { + while (!AllObjectStopped()) + { + AdvanceTime(StepTime); + foreach (var ball in Balls.Where(x => x.StageInitialVelocity != Vector2.Zero && x.Velocity.Length() > 0.01f)) + { + var speed = ball.Velocity.Length(); + if (ball.StageInitialVelocity.Length() / 2 > speed) + { + ball.StageInitialVelocity = Vector2.Zero; // avoid infinite loop + Records.Add(new ChangeSpeedRecord(CurTime, ball.GetSnapshot())); + } + } + } + } + + public bool AllObjectStopped() + { + return Balls.All(ball => ball.Velocity.Length() == 0); + } +} \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Physics/PhysicsRecord.cs b/GameServer/Game/MultiPlayer/MarbleGame/Physics/PhysicsRecord.cs new file mode 100644 index 00000000..2b03b1f3 --- /dev/null +++ b/GameServer/Game/MultiPlayer/MarbleGame/Physics/PhysicsRecord.cs @@ -0,0 +1,7 @@ +using System.Numerics; + +namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Physics; + +public record CollisionRecord(float Time, BallSnapshot BallA, BallSnapshot? BallB, Vector2 CollisionPos); +public record StopRecord(float Time, BallSnapshot Ball); +public record ChangeSpeedRecord(float Time, BallSnapshot Ball); \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Physics/PhysicsSimulator.cs b/GameServer/Game/MultiPlayer/MarbleGame/Physics/PhysicsSimulator.cs deleted file mode 100644 index e6942b49..00000000 --- a/GameServer/Game/MultiPlayer/MarbleGame/Physics/PhysicsSimulator.cs +++ /dev/null @@ -1,102 +0,0 @@ - -using Genbox.VelcroPhysics.Dynamics; -using Genbox.VelcroPhysics.Factories; -using Microsoft.Xna.Framework; - -namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Physics; - -public class PhysicsSimulator -{ - public World World { get; } - public List CollisionRecords { get; } = []; - - public float CurrentTime; - - public PhysicsSimulator(Vector2 gravity, float leftBound, float rightBound, float topBound, float bottomBound) - { - World = new World(gravity); - CreateWalls(leftBound, rightBound, topBound, bottomBound); - - World.ContactManager.OnBroadphaseCollision += OnCollisionDetected; - } - - private void CreateWalls(float left, float right, float top, float bottom) - { - // 创建四个静态墙体 - BodyFactory.CreateEdge(World, new Vector2(left, bottom), new Vector2(left, top)); // 左墙 - BodyFactory.CreateEdge(World, new Vector2(right, bottom), new Vector2(right, top)); // 右墙 - BodyFactory.CreateEdge(World, new Vector2(left, top), new Vector2(right, top)); // 上墙 - BodyFactory.CreateEdge(World, new Vector2(left, bottom), new Vector2(right, bottom));// 下墙 - } - - private void OnCollisionDetected(ref FixtureProxy proxyA, ref FixtureProxy proxyB) - { - Fixture fixtureA = proxyA.Fixture; - Fixture fixtureB = proxyB.Fixture; - - // 过滤墙体碰撞记录(可选) - if (fixtureA.Body.BodyType == BodyType.Static || - fixtureB.Body.BodyType == BodyType.Static) - return; - - if (fixtureA.Body.UserData is int idA && fixtureB.Body.UserData is int idB) - { - CollisionRecords.Add(new CollisionRecord - { - Time = CurrentTime, - ObjectAId = idA, - ObjectBId = idB, - Position = (fixtureA.Body.Position + fixtureB.Body.Position) / 2, // 使用实际接触点 - VelocityA = fixtureA.Body.LinearVelocity, - VelocityB = fixtureB.Body.LinearVelocity - }); - } - } - - public void AddBall(int id, Vector2 position, float mass, float radius, Vector2? velocity = null) - { - float density = mass / (MathF.PI * radius * radius); - - Body body = BodyFactory.CreateCircle(World, radius, density, position); - body.BodyType = BodyType.Dynamic; - body.Restitution = 0.8f; // 统一恢复系数 - body.Friction = 0.3f; - body.LinearDamping = 1.5f; // 使用阻尼代替手动衰减 - body.UserData = id; - body.IsBullet = true; // 启用CCD - - if (velocity.HasValue) - body.LinearVelocity = velocity.Value; - } - - - - public void Simulate(float maxDuration, float timeStep = 0.001f) - { - World.Step(0); - while (CurrentTime < maxDuration && !AllObjectsStopped()) - { - World.Step(timeStep); - CurrentTime += timeStep; - } - } - - public IEnumerable GetFinalPositions() - { - return World.BodyList.Select(body => - $"ID {(int)(body?.UserData ?? -1)}: ({body.Position.X:F2}, {body.Position.Y:F2})"); - } - - private bool AllObjectsStopped() => - World.BodyList.All(b => b.LinearVelocity.Length() < 0.1f); -} - -public class CollisionRecord -{ - public float Time { get; set; } - public int ObjectAId { get; set; } - public int ObjectBId { get; set; } - public Vector2 Position { get; set; } - public Vector2 VelocityA { get; set; } - public Vector2 VelocityB { get; set; } -} \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Seal/MarbleGameSealSyncData.cs b/GameServer/Game/MultiPlayer/MarbleGame/Seal/MarbleGameSealSyncData.cs index 21917b39..63b4b03c 100644 --- a/GameServer/Game/MultiPlayer/MarbleGame/Seal/MarbleGameSealSyncData.cs +++ b/GameServer/Game/MultiPlayer/MarbleGame/Seal/MarbleGameSealSyncData.cs @@ -1,13 +1,49 @@ -using EggLink.DanhengServer.Proto; -using Microsoft.Xna.Framework; +using System.Numerics; +using EggLink.DanhengServer.Proto; namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal; -public class MarbleGameSealSyncData(MarbleGameSealInstance inst, MarbleFrameType frameType) +public abstract class BaseMarbleGameSyncData +{ + public abstract MarbleGameSyncData ToProto(); +} + +public class MarbleGameScoreSyncData(int playerAScore, int playerBScore, MarbleFrameType frameType) : BaseMarbleGameSyncData +{ + public override MarbleGameSyncData ToProto() + { + return new MarbleGameSyncData + { + FrameType = frameType, + PlayerAScore = (uint)playerAScore, + PlayerBScore = (uint)playerBScore + }; + } +} + +public class MarbleGameHpChangeSyncData(MarbleGameSealInstance inst, MarbleFrameType frameType, int changeValue, float time = 0f) : MarbleGameSealSyncData(inst, frameType) +{ + public override MarbleGameSyncData ToProto() + { + return new MarbleGameSyncData + { + FrameType = FrameType, + Id = (uint)Instance.Id, + Time = time, + Hp = Instance.CurHp, + MaxHp = Instance.MaxHp, + HpChangeValue = changeValue, + Attack = Instance.Attack + }; + } +} + +public class MarbleGameSealSyncData(MarbleGameSealInstance inst, MarbleFrameType frameType) : BaseMarbleGameSyncData { public MarbleGameSealInstance Instance { get; set; } = inst.Clone(); + public MarbleFrameType FrameType { get; set; } = frameType; - public virtual MarbleGameSyncData ToProto() + public override MarbleGameSyncData ToProto() { return new MarbleGameSyncData { @@ -19,7 +55,7 @@ public class MarbleGameSealSyncData(MarbleGameSealInstance inst, MarbleFrameType SealRotation = Instance.Rotation, SealOnStage = Instance.OnStage, SealSize = Instance.Size, - FrameType = frameType + FrameType = FrameType }; } } @@ -38,14 +74,33 @@ public class MarbleGameSealActionSyncData(MarbleGameSealInstance inst, MarbleFra SealRotation = Instance.Rotation, SealOnStage = Instance.OnStage, SealSize = Instance.Size, - SealVelocity = Instance.Velocity, - FrameType = frameType, + FrameType = FrameType, Time = time }; } } -public class MarbleGameSealCollisionSyncData(MarbleGameSealInstance inst, int collideOwnerId, int collideTargetId, float time, Vector2 collidePos) : MarbleGameSealSyncData(inst, MarbleFrameType.Collide) + +public class MarbleGameSealLaunchStopSyncData(MarbleGameSealInstance inst, MarbleFrameType frameType, float time = 0) : MarbleGameSealSyncData(inst, frameType) +{ + public override MarbleGameSyncData ToProto() + { + return new MarbleGameSyncData + { + Attack = Instance.Attack, + Id = (uint)Instance.Id, + Hp = Instance.CurHp, + MaxHp = Instance.MaxHp, + SealPosition = Instance.Position, + SealRotation = Instance.Rotation, + FrameType = FrameType, + Time = time, + SealVelocity = Instance.Velocity + }; + } +} + +public class MarbleGameSealCollisionSyncData(MarbleGameSealInstance inst, int collideOwnerId, int collideTargetId, float time, Vector2 collidePos, MarbleSealVector? targetVelocity) : MarbleGameSealSyncData(inst, MarbleFrameType.Collide) { public override MarbleGameSyncData ToProto() { @@ -57,8 +112,6 @@ public class MarbleGameSealCollisionSyncData(MarbleGameSealInstance inst, int co MaxHp = Instance.MaxHp, SealPosition = Instance.Position, SealRotation = Instance.Rotation, - SealOnStage = Instance.OnStage, - SealSize = Instance.Size, FrameType = MarbleFrameType.Collide, CollideType = collideTargetId == 1 ? MarbleFactionType.Field : collideTargetId / 100 == collideOwnerId / 100 ? MarbleFactionType.Ally : MarbleFactionType.Enemy, CollideOwnerId = (uint)collideOwnerId, @@ -68,7 +121,7 @@ public class MarbleGameSealCollisionSyncData(MarbleGameSealInstance inst, int co X = collidePos.X, Y = collidePos.Y }, - CollisionTargetVelocity = new MarbleSealVector(), + CollisionTargetVelocity = targetVelocity ?? new MarbleSealVector(), SealVelocity = Instance.Velocity, Time = time }; diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameBaseSyncData.cs b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameBaseSyncData.cs index ea36632e..25106593 100644 --- a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameBaseSyncData.cs +++ b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameBaseSyncData.cs @@ -6,5 +6,5 @@ namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync; public abstract class MarbleGameBaseSyncData(MarbleNetWorkMsgEnum type) { public MarbleNetWorkMsgEnum MessageType { get; set; } = type; - public abstract MarbleGameSyncInfo ToProto(); + public abstract FightGameInfo ToProto(); } \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameFinishSyncData.cs b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameFinishSyncData.cs new file mode 100644 index 00000000..c097e012 --- /dev/null +++ b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameFinishSyncData.cs @@ -0,0 +1,28 @@ +using EggLink.DanhengServer.Enums.Fight; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync; + +public class MarbleGameFinishSyncData(MarbleGamePlayerInstance player, bool isWin) : MarbleGameBaseSyncData(MarbleNetWorkMsgEnum.GameFinish) +{ + public override FightGameInfo ToProto() + { + return new FightGameInfo + { + GameMessageType = (uint)MessageType, + RogueFinishInfo = new MarbleGameFinishInfo + { + IsWin = isWin, + SealOwnerUid = (uint)player.LobbyPlayer.Player.Uid, + SealFinishInfoList = + { + player.SealList.Keys.Select(x => new MarbleSealFinishInfo + { + ItemId = (uint)x, + MatchTitleId = 2 + }) + } + } + }; + } +} \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameInfoSyncData.cs b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameInfoSyncData.cs index 0afe29a9..e3ad37c9 100644 --- a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameInfoSyncData.cs +++ b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarbleGameInfoSyncData.cs @@ -10,19 +10,24 @@ public class MarbleGameInfoSyncData( MarbleGameRoomInstance room, List syncDatas) : MarbleGameBaseSyncData(type) { - public override MarbleGameSyncInfo ToProto() + public override FightGameInfo ToProto() { - return new MarbleGameSyncInfo + return new FightGameInfo { - MarbleSyncType = syncType, - CurRound = (uint)room.CurRound, - AllowedMoveSealList = + GameMessageType = (uint)MessageType, + MarbleGameSyncInfo = new MarbleGameSyncInfo { - (room.Players[(int)room.CurMoveTeamType - 1] as MarbleGamePlayerInstance)!.AllowMoveSealList.Select(x => - (uint)x) - }, - MarbleGameSyncData = { syncDatas.Select(x => x.ToProto()) }, - FirstPlayerActionEnd = room.CurMoveTeamType == MarbleTeamType.TeamA + MarbleSyncType = syncType, + CurRound = (uint)room.CurRound, + AllowedMoveSealList = + { + (room.Players[(int)room.CurMoveTeamType - 1] as MarbleGamePlayerInstance)!.AllowMoveSealList.Select( + x => + (uint)x) + }, + MarbleGameSyncData = { syncDatas.Select(x => x.ToProto()) }, + FirstPlayerActionEnd = room.CurMoveTeamType == MarbleTeamType.TeamA + } }; } } @@ -32,17 +37,21 @@ public class MarbleGameInfoLaunchingSyncData( MarbleSyncType syncType, float time, int itemId, - List syncDatas) : MarbleGameBaseSyncData(type) + List syncDatas) : MarbleGameBaseSyncData(type) { - public override MarbleGameSyncInfo ToProto() + public override FightGameInfo ToProto() { - return new MarbleGameSyncInfo + return new FightGameInfo { - Launching = true, - MarbleSyncType = syncType, - MoveTotalTime = time, - QueuePosition = (uint)itemId, - MarbleGameSyncData = { syncDatas.Select(x => x.ToProto()) } + GameMessageType = (uint)MessageType, + MarbleGameSyncInfo = new MarbleGameSyncInfo + { + Launching = true, + MarbleSyncType = syncType, + MoveTotalTime = time, + QueuePosition = (uint)itemId, + MarbleGameSyncData = { syncDatas.Select(x => x.ToProto()) } + } }; } } \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarblePerformanceSyncData.cs b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarblePerformanceSyncData.cs index 1cd2d602..bb4cab27 100644 --- a/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarblePerformanceSyncData.cs +++ b/GameServer/Game/MultiPlayer/MarbleGame/Sync/MarblePerformanceSyncData.cs @@ -5,11 +5,15 @@ namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync; public class MarblePerformanceSyncData(MarbleNetWorkMsgEnum type) : MarbleGameBaseSyncData(type) { - public override MarbleGameSyncInfo ToProto() + public override FightGameInfo ToProto() { - return new MarbleGameSyncInfo + return new FightGameInfo { - MarbleSyncType = MarbleSyncType.Performance + GameMessageType = (uint)MessageType, + MarbleGameSyncInfo = new MarbleGameSyncInfo + { + MarbleSyncType = MarbleSyncType.Performance + } }; } } \ No newline at end of file diff --git a/GameServer/Game/MultiPlayer/MultiPlayerGameServerManager.cs b/GameServer/Game/MultiPlayer/MultiPlayerGameServerManager.cs index 3cd56584..39b7f2c0 100644 --- a/GameServer/Game/MultiPlayer/MultiPlayerGameServerManager.cs +++ b/GameServer/Game/MultiPlayer/MultiPlayerGameServerManager.cs @@ -28,7 +28,7 @@ public class MultiPlayerGameServerManager { foreach (var room in Rooms.Values) { - if (room.Players.Any(x => x.LobbyPlayer.Player.Uid == uid)) + if (room.Players.Any(x => !x.LeaveGame && x.LobbyPlayer.Player.Uid == uid)) { return room; } diff --git a/GameServer/GameServer.csproj b/GameServer/GameServer.csproj index 2b30e87b..d59e6a63 100644 --- a/GameServer/GameServer.csproj +++ b/GameServer/GameServer.csproj @@ -20,7 +20,6 @@ - diff --git a/GameServer/Server/Packet/Recv/Fight/HandlerFightHeartBeatCsReq.cs b/GameServer/Server/Packet/Recv/Fight/HandlerFightHeartBeatCsReq.cs index 3db0b294..09ede0ba 100644 --- a/GameServer/Server/Packet/Recv/Fight/HandlerFightHeartBeatCsReq.cs +++ b/GameServer/Server/Packet/Recv/Fight/HandlerFightHeartBeatCsReq.cs @@ -11,6 +11,11 @@ public class HandlerFightHeartBeatCsReq : Handler { var req = FightHeartBeatCsReq.Parser.ParseFrom(data); + if (connection.MarbleRoom != null) + { + await connection.MarbleRoom.OnPlayerHeartBeat(); + } + await connection.SendPacket(new PacketFightHeartBeatScRsp(req.ClientTimeMs)); } } \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Multiplayer/HandlerMultiplayerFightGiveUpCsReq.cs b/GameServer/Server/Packet/Recv/Multiplayer/HandlerMultiplayerFightGiveUpCsReq.cs new file mode 100644 index 00000000..8de43844 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Multiplayer/HandlerMultiplayerFightGiveUpCsReq.cs @@ -0,0 +1,21 @@ +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Multiplayer; + +[Opcode(CmdIds.MultiplayerFightGiveUpCsReq)] +public class HandlerMultiplayerFightGiveUpCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = MultiplayerFightGiveUpCsReq.Parser.ParseFrom(data); + + var roomId = req.GateRoomId; + ServerUtils.MultiPlayerGameServerManager.Rooms.TryGetValue((long)roomId, out var room); + var player = room?.GetPlayerById(connection.MarblePlayer?.LobbyPlayer.Player.Uid ?? connection.Player!.Uid); + if (player != null) + player.LeaveGame = true; + + await connection.SendPacket(CmdIds.MultiplayerFightGiveUpScRsp); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Fight/PacketFightGeneralScNotify.cs b/GameServer/Server/Packet/Send/Fight/PacketFightGeneralScNotify.cs index aedbbecc..9051a1c9 100644 --- a/GameServer/Server/Packet/Send/Fight/PacketFightGeneralScNotify.cs +++ b/GameServer/Server/Packet/Send/Fight/PacketFightGeneralScNotify.cs @@ -51,11 +51,7 @@ public class PacketFightGeneralScNotify : BasePacket NetworkMsgType = (uint)msgType, FightGeneralInfo = new FightGeneralServerInfo { - FightGameInfo = { sync.Select(x => new FightGameInfo - { - GameMessageType = (uint)x.MessageType, - MarbleGameSyncInfo = x.ToProto() - }) } + FightGameInfo = { sync.Select(x => x.ToProto()) } } }; diff --git a/GameServer/Server/Packet/Send/Fight/PacketFightSessionStopScNotify.cs b/GameServer/Server/Packet/Send/Fight/PacketFightSessionStopScNotify.cs new file mode 100644 index 00000000..23f483e1 --- /dev/null +++ b/GameServer/Server/Packet/Send/Fight/PacketFightSessionStopScNotify.cs @@ -0,0 +1,22 @@ +using EggLink.DanhengServer.GameServer.Game.MultiPlayer; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight; + +public class PacketFightSessionStopScNotify : BasePacket +{ + public PacketFightSessionStopScNotify(BaseMultiPlayerGameRoomInstance room) : base(CmdIds.FightSessionStopScNotify) + { + var proto = new FightSessionStopScNotify + { + SessionInfo = new FightSessionInfo + { + SessionGameMode = room.GameMode, + SessionRoomId = (ulong)room.RoomId + } + }; + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Multiplayer/PacketMultiplayerFightGameFinishScNotify.cs b/GameServer/Server/Packet/Send/Multiplayer/PacketMultiplayerFightGameFinishScNotify.cs new file mode 100644 index 00000000..57263417 --- /dev/null +++ b/GameServer/Server/Packet/Send/Multiplayer/PacketMultiplayerFightGameFinishScNotify.cs @@ -0,0 +1,22 @@ +using EggLink.DanhengServer.GameServer.Game.MultiPlayer; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Multiplayer; + +public class PacketMultiplayerFightGameFinishScNotify : BasePacket +{ + public PacketMultiplayerFightGameFinishScNotify(BaseMultiPlayerGameRoomInstance room) : base(CmdIds.MultiplayerFightGameFinishScNotify) + { + var proto = new MultiplayerFightGameFinishScNotify + { + SessionInfo = new FightSessionInfo + { + SessionGameMode = room.GameMode, + SessionRoomId = (ulong)room.RoomId + } + }; + + SetData(proto); + } +} \ No newline at end of file