mirror of
https://github.com/EggLinks/DanhengServer-OpenSource.git
synced 2026-01-02 20:26:03 +08:00
feat: marble full game
This commit is contained in:
@@ -8,5 +8,6 @@ public enum MarblePlayerPhaseEnum
|
||||
PerformanceFinish = 3,
|
||||
Gaming = 4,
|
||||
Launching = 5,
|
||||
UseTech = 6
|
||||
SimulateFinish = 6,
|
||||
UseTech = 7
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class DanhengConnection
|
||||
public static readonly ConcurrentBag<int> 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;
|
||||
|
||||
@@ -26,7 +26,7 @@ public class LobbyRoomInstance(PlayerInstance owner, long roomId, FightGameMode
|
||||
|
||||
public async ValueTask AddPlayer(PlayerInstance player, List<int> 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<Retcode> 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);
|
||||
|
||||
@@ -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<int> 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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<MarbleGameBaseSyncData> CheckPlayerWin()
|
||||
{
|
||||
var winPlayer = Players.OfType<MarbleGamePlayerInstance>().FirstOrDefault(x => x.Score >= 6);
|
||||
if (winPlayer == null) return [];
|
||||
|
||||
List<MarbleGameBaseSyncData> syncData = [];
|
||||
// win
|
||||
foreach (var player in Players.OfType<MarbleGamePlayerInstance>())
|
||||
{
|
||||
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<MarbleGamePlayerInstance>())
|
||||
{
|
||||
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<MarbleGamePlayerInstance>().ToList().All(x => x.Phase == MarblePlayerPhaseEnum.LoadFinish))
|
||||
if (Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().ToList().All(x => x.Phase == MarblePlayerPhaseEnum.PerformanceFinish))
|
||||
if (Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>())
|
||||
{
|
||||
player.ChangeRound();
|
||||
@@ -123,6 +196,59 @@ public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance
|
||||
Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>()
|
||||
.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<MarbleGamePlayerInstance>())
|
||||
{
|
||||
player.Phase = MarblePlayerPhaseEnum.Gaming;
|
||||
}
|
||||
|
||||
await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch,
|
||||
[
|
||||
new MarbleGameInfoSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.SwitchRound, this,
|
||||
Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>())
|
||||
{
|
||||
player.Phase = MarblePlayerPhaseEnum.Gaming;
|
||||
}
|
||||
|
||||
await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch,
|
||||
[
|
||||
new MarbleGameInfoSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.RoundEnd, this,
|
||||
Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGameSealSyncData> syncData = [];
|
||||
List<BaseMarbleGameSyncData> syncData = [];
|
||||
|
||||
foreach (var sealInst in Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().SelectMany(x => x.SealList.Values)
|
||||
.FirstOrDefault(x => x.Id == id);
|
||||
if (sealInst == null) continue;
|
||||
case CollisionRecord record:
|
||||
{
|
||||
var sealInst = Players.OfType<MarbleGamePlayerInstance>().SelectMany(x => x.SealList.Values)
|
||||
.FirstOrDefault(x => x.Id == record.BallA.Id);
|
||||
if (sealInst == null) continue;
|
||||
var sealInstB = Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>())
|
||||
{
|
||||
p.Phase = MarblePlayerPhaseEnum.Launching;
|
||||
}
|
||||
|
||||
if (winData.Count > 0)
|
||||
{
|
||||
await EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
public List<BaseMarbleGameSyncData> DoDamage(MarbleGameSealInstance attacker, MarbleGameSealInstance target, float time)
|
||||
{
|
||||
List<BaseMarbleGameSyncData> syncData = [];
|
||||
var attackerPlayer = Players.OfType<MarbleGamePlayerInstance>().FirstOrDefault(x =>
|
||||
x.SealList.ContainsKey(attacker.Id));
|
||||
var targetPlayer = Players.OfType<MarbleGamePlayerInstance>().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<MarbleGamePlayerInstance>().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<BaseMarbleGameSyncData> ReviveAllDeadSeals(float time)
|
||||
{
|
||||
List<BaseMarbleGameSyncData> syncData = [];
|
||||
var seals = Players.OfType<MarbleGamePlayerInstance>()
|
||||
.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<MarbleGamePlayerInstance>()
|
||||
.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,
|
||||
|
||||
26
GameServer/Game/MultiPlayer/MarbleGame/Physics/Ball.cs
Normal file
26
GameServer/Game/MultiPlayer/MarbleGame/Physics/Ball.cs
Normal file
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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<Ball> Balls { get; } = [];
|
||||
public int LaunchTeam { get; set; } = 0;
|
||||
public List<object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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<CollisionRecord> 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<string> 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; }
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,24 @@ public class MarbleGameInfoSyncData(
|
||||
MarbleGameRoomInstance room,
|
||||
List<MarbleGameSealSyncData> 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<MarbleGameSealSyncData> syncDatas) : MarbleGameBaseSyncData(type)
|
||||
List<BaseMarbleGameSyncData> 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()) }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\DanhengKcpSharp\DanhengKcpSharp.csproj" />
|
||||
<ProjectReference Include="..\Proto\Proto.csproj" />
|
||||
<PackageReference Include="Genbox.VelcroPhysics" Version="0.1.0-alpha.2" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.1" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.29.1" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user