mirror of
https://github.com/EggLinks/DanhengServer-OpenSource.git
synced 2026-01-02 20:26:03 +08:00
feat: marble game entry
This commit is contained in:
17
Common/Data/Excel/MarbleMatchInfoExcel.cs
Normal file
17
Common/Data/Excel/MarbleMatchInfoExcel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace EggLink.DanhengServer.Data.Excel;
|
||||
|
||||
[ResourceEntity("MarbleMatchInfo.json")]
|
||||
public class MarbleMatchInfoExcel : ExcelResource
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
|
||||
public override void Loaded()
|
||||
{
|
||||
GameData.MarbleMatchInfoData.Add(ID, this);
|
||||
}
|
||||
}
|
||||
23
Common/Data/Excel/MarbleSealExcel.cs
Normal file
23
Common/Data/Excel/MarbleSealExcel.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace EggLink.DanhengServer.Data.Excel;
|
||||
|
||||
[ResourceEntity("MarbleSeal.json")]
|
||||
public class MarbleSealExcel : ExcelResource
|
||||
{
|
||||
public int ID { get; set; }
|
||||
public int Attack { get; set; }
|
||||
public float MaxSpeed { get; set; }
|
||||
public float Mass { get; set; }
|
||||
public int Hp { get; set; }
|
||||
public int ActionPriority { get; set; }
|
||||
public float Size { get; set; }
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
|
||||
public override void Loaded()
|
||||
{
|
||||
GameData.MarbleSealData.Add(ID, this);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,13 @@ public static class GameData
|
||||
|
||||
public static ActivityConfig ActivityConfig { get; set; } = new();
|
||||
|
||||
#region Marble
|
||||
|
||||
public static Dictionary<int, MarbleMatchInfoExcel> MarbleMatchInfoData { get; private set; } = [];
|
||||
public static Dictionary<int, MarbleSealExcel> MarbleSealData { get; private set; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Banners
|
||||
|
||||
21
Common/Enums/Fight/MarbleNetWorkMsgEnum.cs
Normal file
21
Common/Enums/Fight/MarbleNetWorkMsgEnum.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace EggLink.DanhengServer.Enums.Fight;
|
||||
|
||||
public enum MarbleNetWorkMsgEnum
|
||||
{
|
||||
None = 0,
|
||||
PlayerEnter = 1,
|
||||
PerformanceFinish = 2,
|
||||
LoadFinish = 3,
|
||||
SimulateFinish = 4,
|
||||
Launch = 5,
|
||||
UseTech = 6,
|
||||
TechFinish = 7,
|
||||
Emoji = 8,
|
||||
SnapShot = 9,
|
||||
SyncBatch = 10,
|
||||
GameStart = 11,
|
||||
SyncNotify = 12,
|
||||
GameFinish = 13,
|
||||
SyncSnapShot = 14,
|
||||
Operation = 15
|
||||
}
|
||||
12
Common/Enums/Fight/MarblePlayerPhaseEnum.cs
Normal file
12
Common/Enums/Fight/MarblePlayerPhaseEnum.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace EggLink.DanhengServer.Enums.Fight;
|
||||
|
||||
public enum MarblePlayerPhaseEnum
|
||||
{
|
||||
NotEnter = 0,
|
||||
EnterGame = 1,
|
||||
LoadFinish = 2,
|
||||
PerformanceFinish = 3,
|
||||
Gaming = 4,
|
||||
Launching = 5,
|
||||
UseTech = 6
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class FriendManager(PlayerInstance player) : BasePlayerManager(player)
|
||||
}
|
||||
|
||||
// receive message
|
||||
var recvPlayer = Listener.GetActiveConnection(recvUid)?.Player!;
|
||||
var recvPlayer = Listener.GetActiveConnection(recvUid)?.Player;
|
||||
if (recvPlayer != null)
|
||||
{
|
||||
await recvPlayer.FriendManager!.ReceiveMessage(sendUid, recvUid, message, extraId);
|
||||
@@ -148,6 +148,19 @@ public class FriendManager(PlayerInstance player) : BasePlayerManager(player)
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask SendInviteMessage(int sendUid, int recvUid, LobbyInviteInfo info)
|
||||
{
|
||||
var proto = new PacketRevcMsgScNotify((uint)recvUid, (uint)sendUid, info);
|
||||
await Player.SendPacket(proto);
|
||||
|
||||
// receive message
|
||||
var recvPlayer = Listener.GetActiveConnection(recvUid)?.Player;
|
||||
if (recvPlayer != null)
|
||||
{
|
||||
await recvPlayer.FriendManager!.ReceiveInviteMessage(sendUid, recvUid, info);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ReceiveMessage(int sendUid, int recvUid, string? message = null, int? extraId = null)
|
||||
{
|
||||
var data = new FriendChatData
|
||||
@@ -176,6 +189,13 @@ public class FriendManager(PlayerInstance player) : BasePlayerManager(player)
|
||||
await Player.SendPacket(proto);
|
||||
}
|
||||
|
||||
public async ValueTask ReceiveInviteMessage(int sendUid, int recvUid, LobbyInviteInfo info)
|
||||
{
|
||||
var proto = new PacketRevcMsgScNotify((uint)recvUid, (uint)sendUid, info);
|
||||
|
||||
await Player.SendPacket(proto);
|
||||
}
|
||||
|
||||
public List<ChatMessageData> GetHistoryInfo(int uid)
|
||||
{
|
||||
if (!FriendData.ChatHistory.TryGetValue(uid, out var history)) return [];
|
||||
|
||||
113
GameServer/Game/Lobby/LobbyRoomInstance.cs
Normal file
113
GameServer/Game/Lobby/LobbyRoomInstance.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby.Player;
|
||||
using EggLink.DanhengServer.GameServer.Game.Player;
|
||||
using EggLink.DanhengServer.GameServer.Server;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Multiplayer;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
|
||||
public class LobbyRoomInstance(PlayerInstance owner, long roomId, FightGameMode gameMode, int lobbyMode)
|
||||
{
|
||||
public long RoomId { get; set; } = roomId;
|
||||
public FightGameMode GameMode { get; set; } = gameMode;
|
||||
public List<LobbyPlayerInstance> Players { get; set; } = [];
|
||||
public int LobbyMode { get; set; } = lobbyMode;
|
||||
public bool IsInGame { get; set; }
|
||||
|
||||
public async ValueTask BroadCastToRoom(BasePacket packet)
|
||||
{
|
||||
foreach (var player in Players)
|
||||
{
|
||||
await player.Player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask AddPlayer(PlayerInstance player, List<int> sealList, LobbyCharacterType characterType)
|
||||
{
|
||||
await AddPlayer(new LobbyPlayerInstance(player, characterType)
|
||||
{
|
||||
EquippedSealList = sealList
|
||||
});
|
||||
}
|
||||
|
||||
public async ValueTask AddPlayer(LobbyPlayerInstance player)
|
||||
{
|
||||
if (Players.Any(x => x.Player.Uid == player.Player.Uid)) return;
|
||||
await BroadCastToRoom(new PacketLobbySyncInfoScNotify(player.Player.Uid, this, LobbyModifyType.JoinLobby));
|
||||
Players.Add(player);
|
||||
}
|
||||
|
||||
public async ValueTask RemovePlayer(int uid)
|
||||
{
|
||||
var remove = Players.RemoveAll(x => x.Player.Uid == uid);
|
||||
if (remove == 0) return;
|
||||
|
||||
await BroadCastToRoom(new PacketLobbySyncInfoScNotify(uid, this, LobbyModifyType.QuitLobby));
|
||||
if (Players.Count == 0)
|
||||
{
|
||||
// remove from manager
|
||||
ServerUtils.LobbyServerManager.RemoveLobbyRoom(RoomId);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Retcode> LobbyStartFight()
|
||||
{
|
||||
// check status
|
||||
if (IsInGame)
|
||||
return Retcode.RetLobbyRoomPalyerFighting;
|
||||
|
||||
if (Players.Count(x => x.CharacterType != LobbyCharacterType.LobbyCharacterWatcher) != 2)
|
||||
return Retcode.RetLobbyRoomPalyerNotReady;
|
||||
|
||||
if (Players.Any(x =>
|
||||
x.CharacterType == LobbyCharacterType.LobbyCharacterMember &&
|
||||
x.CharacterStatus != LobbyCharacterStatus.Ready)) return Retcode.RetLobbyRoomPalyerNotReady;
|
||||
|
||||
if (Players.Any(x =>
|
||||
x.CharacterType != LobbyCharacterType.LobbyCharacterWatcher &&
|
||||
x.EquippedSealList.Count != 3)) return Retcode.RetLobbyRoomPalyerNotReady;
|
||||
|
||||
var leader = Players.Find(x => x.CharacterType == LobbyCharacterType.LobbyCharacterLeader);
|
||||
if (leader == null) return Retcode.RetLobbyRoomPalyerFighting;
|
||||
|
||||
// start fight
|
||||
foreach (var instance in Players)
|
||||
{
|
||||
instance.CharacterStatus = LobbyCharacterStatus.LobbyStartFight;
|
||||
}
|
||||
|
||||
await BroadCastToRoom(new PacketLobbySyncInfoScNotify(leader.Player.Uid, this, LobbyModifyType.LobbyStartFight));
|
||||
return Retcode.RetSucc;
|
||||
}
|
||||
|
||||
public async ValueTask<Retcode> StartFight()
|
||||
{
|
||||
// alrdy check status in lobby start fight
|
||||
if (IsInGame)
|
||||
return Retcode.RetLobbyRoomPalyerFighting;
|
||||
|
||||
// create fight room
|
||||
var fightRoom = ServerUtils.MultiPlayerGameServerManager.CreateRoom(this);
|
||||
if (fightRoom == null) return Retcode.RetLobbyRoomNotExist;
|
||||
|
||||
IsInGame = true;
|
||||
await BroadCastToRoom(new PacketMultiplayerFightGameStartScNotify(fightRoom));
|
||||
|
||||
// start fight
|
||||
foreach (var instance in Players)
|
||||
{
|
||||
instance.CharacterStatus = LobbyCharacterStatus.Fighting;
|
||||
}
|
||||
|
||||
await BroadCastToRoom(new PacketLobbySyncInfoScNotify(0, this, LobbyModifyType.FightStart));
|
||||
return Retcode.RetSucc;
|
||||
}
|
||||
|
||||
public LobbyPlayerInstance? GetPlayerByUid(int uid)
|
||||
{
|
||||
var player = Players.FirstOrDefault(x => x.Player.Uid == uid);
|
||||
return player;
|
||||
}
|
||||
}
|
||||
42
GameServer/Game/Lobby/LobbyServerManager.cs
Normal file
42
GameServer/Game/Lobby/LobbyServerManager.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Player;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
|
||||
/// <summary>
|
||||
/// Server Manager:
|
||||
/// a manager would be only initialized when the server is started
|
||||
/// </summary>
|
||||
public class LobbyServerManager
|
||||
{
|
||||
public long CurLobbyRoomId { get; set; }
|
||||
public Dictionary<long, LobbyRoomInstance> LobbyRoomInstances { get; set; } = [];
|
||||
|
||||
public async ValueTask<LobbyRoomInstance> CreateLobbyRoom(PlayerInstance ownerPlayer, int lobbyMode, List<int> sealList)
|
||||
{
|
||||
var roomId = ++CurLobbyRoomId;
|
||||
var room = new LobbyRoomInstance(ownerPlayer, roomId, FightGameMode.Marble, lobbyMode);
|
||||
LobbyRoomInstances.Add(roomId, room);
|
||||
await room.AddPlayer(ownerPlayer, sealList, LobbyCharacterType.LobbyCharacterLeader);
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
public void RemoveLobbyRoom(long roomId)
|
||||
{
|
||||
LobbyRoomInstances.Remove(roomId, out _);
|
||||
}
|
||||
|
||||
public LobbyRoomInstance? GetPlayerJoinedRoom(int uid)
|
||||
{
|
||||
foreach (var room in LobbyRoomInstances.Values)
|
||||
{
|
||||
if (room.Players.Any(x => x.Player.Uid == uid))
|
||||
{
|
||||
return room;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
33
GameServer/Game/Lobby/Player/LobbyPlayerInstance.cs
Normal file
33
GameServer/Game/Lobby/Player/LobbyPlayerInstance.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Player;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.Lobby.Player;
|
||||
|
||||
public class LobbyPlayerInstance(PlayerInstance player, LobbyCharacterType characterType)
|
||||
{
|
||||
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 LobbyBasicInfo ToProto()
|
||||
{
|
||||
return new LobbyBasicInfo
|
||||
{
|
||||
PlayerLobbyInfo = new LobbyPlayerInfo
|
||||
{
|
||||
CharacterType = CharacterType,
|
||||
Status = CharacterStatus
|
||||
},
|
||||
StageInfo = new LobbyGameInfo
|
||||
{
|
||||
LobbyMarbleInfo = new LobbyMarbleInfo
|
||||
{
|
||||
LobbySealList = { EquippedSealList.Select(x => (uint)x) },
|
||||
Rank = 1
|
||||
}
|
||||
},
|
||||
BasicInfo = Player.Data.ToLobbyProto()
|
||||
};
|
||||
}
|
||||
}
|
||||
20
GameServer/Game/MultiPlayer/BaseGamePlayerInstance.cs
Normal file
20
GameServer/Game/MultiPlayer/BaseGamePlayerInstance.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby.Player;
|
||||
using EggLink.DanhengServer.GameServer.Server;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
|
||||
public abstract class BaseGamePlayerInstance(LobbyPlayerInstance lobby)
|
||||
{
|
||||
public LobbyPlayerInstance LobbyPlayer { get; } = lobby;
|
||||
public bool EnterGame { get; set; }
|
||||
public Connection? Connection { get; set; }
|
||||
|
||||
public async ValueTask SendPacket(BasePacket packet)
|
||||
{
|
||||
if (Connection == null) return;
|
||||
if (!Connection.IsOnline) return;
|
||||
|
||||
await Connection.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
|
||||
public abstract class BaseMultiPlayerGameRoomInstance(long roomId, LobbyRoomInstance parentLobby)
|
||||
{
|
||||
public FightGameMode GameMode { get; } = parentLobby.GameMode;
|
||||
public long RoomId { get; } = roomId;
|
||||
public LobbyRoomInstance ParentLobby { get; } = parentLobby;
|
||||
public List<BaseGamePlayerInstance> Players { get; } = [];
|
||||
|
||||
public BaseGamePlayerInstance? GetPlayerById(int uid)
|
||||
{
|
||||
return Players.FirstOrDefault(player => player.LobbyPlayer.Player.Uid == uid);
|
||||
}
|
||||
|
||||
public FightSessionInfo ToSessionInfo()
|
||||
{
|
||||
return new FightSessionInfo
|
||||
{
|
||||
SessionGameMode = GameMode,
|
||||
SessionRoomId = (ulong)RoomId
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using EggLink.DanhengServer.Data;
|
||||
using EggLink.DanhengServer.Enums.Fight;
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby.Player;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame;
|
||||
|
||||
public class MarbleGamePlayerInstance : BaseGamePlayerInstance
|
||||
{
|
||||
public Dictionary<int, MarbleGameSealInstance> SealList { get; set; } = [];
|
||||
public MarbleTeamType TeamType { get; set; }
|
||||
public MarblePlayerPhaseEnum Phase { get; set; } = MarblePlayerPhaseEnum.NotEnter;
|
||||
public int CurItemId { get; set; }
|
||||
public int Score { get; set; }
|
||||
public HashSet<int> AllowMoveSealList { get; set; } = [];
|
||||
|
||||
public MarbleGamePlayerInstance(LobbyPlayerInstance lobby, MarbleTeamType type) : base(lobby)
|
||||
{
|
||||
TeamType = type;
|
||||
CurItemId = TeamType == MarbleTeamType.TeamA ? 200 : 300;
|
||||
|
||||
var posXBaseValue = TeamType == MarbleTeamType.TeamA ? -1 : 1;
|
||||
var index = 0;
|
||||
foreach (var seal in lobby.EquippedSealList)
|
||||
{
|
||||
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 rotX = posXBaseValue * -1f;
|
||||
AllowMoveSealList.Add(CurItemId);
|
||||
|
||||
SealList.Add(CurItemId, new MarbleGameSealInstance(CurItemId++, seal)
|
||||
{
|
||||
Position = new MarbleSealVector
|
||||
{
|
||||
X = posX,
|
||||
Y = posY
|
||||
},
|
||||
Rotation = new MarbleSealVector
|
||||
{
|
||||
X = rotX
|
||||
},
|
||||
Attack = marbleSeal.Attack,
|
||||
CurHp = marbleSeal.Hp,
|
||||
MaxHp = marbleSeal.Hp,
|
||||
Size = marbleSeal.Size,
|
||||
Mass = marbleSeal.Mass,
|
||||
MaxSpeed = marbleSeal.MaxSpeed
|
||||
});
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeRound()
|
||||
{
|
||||
foreach (var instance in SealList.Values)
|
||||
{
|
||||
AllowMoveSealList.Add(instance.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
135
GameServer/Game/MultiPlayer/MarbleGame/MarbleGameRoomInstance.cs
Normal file
135
GameServer/Game/MultiPlayer/MarbleGame/MarbleGameRoomInstance.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using EggLink.DanhengServer.Enums.Fight;
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame;
|
||||
|
||||
public class MarbleGameRoomInstance : BaseMultiPlayerGameRoomInstance
|
||||
{
|
||||
public MarbleTeamType CurMoveTeamType { get; set; }
|
||||
public int CurRound { get; set; }
|
||||
|
||||
public MarbleGameRoomInstance(long roomId, LobbyRoomInstance parentLobby) : base(roomId, parentLobby)
|
||||
{
|
||||
// random move team type
|
||||
CurMoveTeamType = (MarbleTeamType)Random.Shared.Next(1, 3);
|
||||
// set player
|
||||
foreach (var player in parentLobby.Players)
|
||||
{
|
||||
Players.Add(new MarbleGamePlayerInstance(player, (MarbleTeamType)(parentLobby.Players.IndexOf(
|
||||
player) + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask BroadCastToRoom(BasePacket packet)
|
||||
{
|
||||
foreach (var player in Players)
|
||||
{
|
||||
await player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask EnterGame(int uid)
|
||||
{
|
||||
var player = GetPlayerById(uid);
|
||||
if (player == null) return;
|
||||
player.EnterGame = true;
|
||||
|
||||
if (player is MarbleGamePlayerInstance marblePlayer)
|
||||
{
|
||||
marblePlayer.Phase = MarblePlayerPhaseEnum.EnterGame;
|
||||
}
|
||||
|
||||
if (Players.All(x => x.EnterGame))
|
||||
{
|
||||
// send basic info
|
||||
await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch,
|
||||
MarbleNetWorkMsgEnum.GameStart, this));
|
||||
}
|
||||
}
|
||||
|
||||
#region Handler
|
||||
|
||||
public async ValueTask HandleGeneralRequest(MarbleGamePlayerInstance player, uint msgType, byte[] reqData)
|
||||
{
|
||||
var messageType = (MarbleNetWorkMsgEnum)msgType;
|
||||
|
||||
switch (messageType)
|
||||
{
|
||||
case MarbleNetWorkMsgEnum.LoadFinish:
|
||||
await LoadFinish(player);
|
||||
break;
|
||||
case MarbleNetWorkMsgEnum.PerformanceFinish:
|
||||
await PerformanceFinish(player);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask LoadFinish(MarbleGamePlayerInstance player)
|
||||
{
|
||||
player.Phase = MarblePlayerPhaseEnum.LoadFinish;
|
||||
if (Players.OfType<MarbleGamePlayerInstance>().ToList().All(x => x.Phase == MarblePlayerPhaseEnum.LoadFinish))
|
||||
{
|
||||
// next phase (performance)
|
||||
await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch,
|
||||
[new MarblePerformanceSyncData(MarbleNetWorkMsgEnum.SyncNotify)]));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask PerformanceFinish(MarbleGamePlayerInstance player)
|
||||
{
|
||||
player.Phase = MarblePlayerPhaseEnum.PerformanceFinish;
|
||||
if (Players.OfType<MarbleGamePlayerInstance>().ToList().All(x => x.Phase == MarblePlayerPhaseEnum.PerformanceFinish))
|
||||
{
|
||||
// next phase (round start)
|
||||
await RoundStart();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Round
|
||||
|
||||
public async ValueTask RoundStart()
|
||||
{
|
||||
CurRound++;
|
||||
foreach (var player in Players.OfType<MarbleGamePlayerInstance>())
|
||||
{
|
||||
player.ChangeRound();
|
||||
}
|
||||
|
||||
await BroadCastToRoom(new PacketFightGeneralScNotify(MarbleNetWorkMsgEnum.SyncBatch,
|
||||
[
|
||||
new MarbleGameInfoSyncData(MarbleNetWorkMsgEnum.SyncNotify, MarbleSyncType.RoundStart, this,
|
||||
Players.OfType<MarbleGamePlayerInstance>().SelectMany(x => x.SealList.Values)
|
||||
.Select(x => new MarbleGameSealSyncData(x, MarbleFrameType.RoundStart)).ToList())
|
||||
]));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public MarbleGameInfo ToProto()
|
||||
{
|
||||
return new MarbleGameInfo
|
||||
{
|
||||
LobbyBasicInfo = { ParentLobby.Players.Select(x => x.ToProto()) },
|
||||
CurActionTeamType = CurMoveTeamType,
|
||||
LevelId = (uint)Random.Shared.Next(100, 103),
|
||||
TeamAPlayer = (uint)Players[0].LobbyPlayer.Player.Uid,
|
||||
TeamBPlayer = (uint)Players[1].LobbyPlayer.Player.Uid,
|
||||
TeamARank = 1,
|
||||
TeamBRank = 1,
|
||||
TeamASealList = { (Players[0] as MarbleGamePlayerInstance)!.SealList.Select(x => (uint)x.Value.SealId) },
|
||||
TeamBSealList = { (Players[1] as MarbleGamePlayerInstance)!.SealList.Select(x => (uint)x.Value.SealId) },
|
||||
PlayerAScore = (uint)(Players[0] as MarbleGamePlayerInstance)!.Score,
|
||||
PlayerBScore = (uint)(Players[1] as MarbleGamePlayerInstance)!.Score,
|
||||
ControlByServer = true
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal;
|
||||
|
||||
public class MarbleGameSealInstance(int itemId, int sealId)
|
||||
{
|
||||
public int Id { get; set; } = itemId;
|
||||
public int SealId { get; set; } = sealId;
|
||||
public int CurHp { get; set; }
|
||||
public int MaxHp { get; set; }
|
||||
public int Attack { get; set; }
|
||||
public float Mass { get; set; }
|
||||
public float MaxSpeed { get; set; }
|
||||
public float Size { get; set; }
|
||||
public bool OnStage { get; set; }
|
||||
public MarbleSealVector Position { get; set; } = new();
|
||||
public MarbleSealVector Rotation { get; set; } = new();
|
||||
|
||||
public MarbleGameSealInstance Clone()
|
||||
{
|
||||
return new MarbleGameSealInstance(Id, SealId)
|
||||
{
|
||||
CurHp = CurHp,
|
||||
MaxHp = MaxHp,
|
||||
Attack = Attack,
|
||||
Size = Size,
|
||||
Mass = Mass,
|
||||
MaxSpeed = MaxSpeed,
|
||||
OnStage = OnStage,
|
||||
Position = Position.Clone(),
|
||||
Rotation = Rotation.Clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal;
|
||||
|
||||
public class MarbleGameSealSyncData(MarbleGameSealInstance inst, MarbleFrameType frameType)
|
||||
{
|
||||
public MarbleGameSealInstance Instance { get; set; } = inst.Clone();
|
||||
|
||||
public MarbleGameSyncData ToProto()
|
||||
{
|
||||
return new MarbleGameSyncData
|
||||
{
|
||||
Attack = Instance.Attack,
|
||||
Id = (uint)Instance.Id,
|
||||
Hp = Instance.CurHp,
|
||||
MaxHp = Instance.MaxHp,
|
||||
SealPosition = Instance.Position,
|
||||
SealRotation = Instance.Rotation,
|
||||
SealOnStage = Instance.OnStage,
|
||||
SealSize = Instance.Size,
|
||||
FrameType = frameType
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using EggLink.DanhengServer.Enums.Fight;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync;
|
||||
|
||||
public abstract class MarbleGameBaseSyncData(MarbleNetWorkMsgEnum type)
|
||||
{
|
||||
public MarbleNetWorkMsgEnum MessageType { get; set; } = type;
|
||||
public abstract MarbleGameSyncInfo ToProto();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using EggLink.DanhengServer.Enums.Fight;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Seal;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync;
|
||||
|
||||
public class MarbleGameInfoSyncData(MarbleNetWorkMsgEnum type, MarbleSyncType syncType, MarbleGameRoomInstance room, List<MarbleGameSealSyncData> syncDatas) : MarbleGameBaseSyncData(type)
|
||||
{public override MarbleGameSyncInfo ToProto()
|
||||
{
|
||||
return new MarbleGameSyncInfo
|
||||
{
|
||||
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()) }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using EggLink.DanhengServer.Enums.Fight;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync;
|
||||
|
||||
public class MarblePerformanceSyncData(MarbleNetWorkMsgEnum type) : MarbleGameBaseSyncData(type)
|
||||
{
|
||||
public override MarbleGameSyncInfo ToProto()
|
||||
{
|
||||
return new MarbleGameSyncInfo
|
||||
{
|
||||
MarbleSyncType = MarbleSyncType.Performance
|
||||
};
|
||||
}
|
||||
}
|
||||
39
GameServer/Game/MultiPlayer/MultiPlayerGameServerManager.cs
Normal file
39
GameServer/Game/MultiPlayer/MultiPlayerGameServerManager.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
|
||||
public class MultiPlayerGameServerManager
|
||||
{
|
||||
public Dictionary<long, BaseMultiPlayerGameRoomInstance> Rooms { get; } = [];
|
||||
public long CurRoomId { get; set; }
|
||||
|
||||
public BaseMultiPlayerGameRoomInstance? CreateRoom(LobbyRoomInstance lobbyRoom)
|
||||
{
|
||||
if (lobbyRoom.GameMode != FightGameMode.Marble) return null;
|
||||
var roomId = ++CurRoomId;
|
||||
var room = new MarbleGameRoomInstance(roomId, lobbyRoom);
|
||||
Rooms.Add(roomId, room);
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
public void RemoveRoom(long roomId)
|
||||
{
|
||||
Rooms.Remove(roomId, out _);
|
||||
}
|
||||
|
||||
public BaseMultiPlayerGameRoomInstance? GetPlayerJoinedRoom(int uid)
|
||||
{
|
||||
foreach (var room in Rooms.Values)
|
||||
{
|
||||
if (room.Players.Any(x => x.LobbyPlayer.Player.Uid == uid))
|
||||
{
|
||||
return room;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame;
|
||||
using EggLink.DanhengServer.GameServer.Game.Player;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
@@ -17,6 +18,8 @@ public class Connection(KcpConversation conversation, IPEndPoint remote) : Danhe
|
||||
private static readonly Logger Logger = new("GameServer");
|
||||
|
||||
public PlayerInstance? Player { get; set; }
|
||||
public MarbleGameRoomInstance? MarbleRoom { get; set; }
|
||||
public MarbleGamePlayerInstance? MarblePlayer { get; set; }
|
||||
|
||||
public override async void Start()
|
||||
{
|
||||
|
||||
@@ -18,5 +18,7 @@ public class HandlerSendMsgCsReq : Handler
|
||||
else if (req.MessageType == MsgType.Emoji)
|
||||
await connection.Player!.FriendManager!.SendMessage(connection.Player!.Uid, (int)req.TargetList[0], null,
|
||||
(int)req.ExtraId);
|
||||
else if (req.MessageType == MsgType.Invite)
|
||||
await connection.Player!.FriendManager!.SendInviteMessage(connection.Player!.Uid, (int)req.TargetList[0], req.InviteInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
using EggLink.DanhengServer.Util.Security;
|
||||
using EggLink.DanhengServer.Util;
|
||||
using System.Security.Cryptography;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Fight;
|
||||
|
||||
[Opcode(CmdIds.FightEnterCsReq)]
|
||||
public class HandlerFightEnterCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = FightEnterCsReq.Parser.ParseFrom(data);
|
||||
if (!ServerUtils.MultiPlayerGameServerManager.Rooms.TryGetValue((long)req.EnterRoomId, out var room))
|
||||
{
|
||||
await connection.SendPacket(new PacketFightEnterScRsp(Retcode.RetFightRoomNotExist));
|
||||
connection.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (room is not MarbleGameRoomInstance marbleGame)
|
||||
{
|
||||
await connection.SendPacket(new PacketFightEnterScRsp(Retcode.RetFightRoomNotExist));
|
||||
connection.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
var player = room.GetPlayerById((int)req.Uid);
|
||||
if (player is not MarbleGamePlayerInstance marble)
|
||||
{
|
||||
await connection.SendPacket(new PacketFightEnterScRsp(Retcode.RetFightRoomNotExist));
|
||||
connection.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
connection.MarblePlayer = marble;
|
||||
marble.Connection = connection;
|
||||
connection.MarbleRoom = marbleGame;
|
||||
|
||||
if (ConfigManager.Config.GameServer.UsePacketEncryption)
|
||||
{
|
||||
var tempRandom = new MT19937((ulong)DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
connection.ClientSecretKeySeed = tempRandom.NextUInt64();
|
||||
}
|
||||
|
||||
connection.State = SessionStateEnum.ACTIVE;
|
||||
connection.DebugFile = Path.Combine(ConfigManager.Config.Path.LogPath, "Debug/", $"{req.Uid}/",
|
||||
$"Debug-{DateTime.Now:yyyy-MM-dd HH-mm-ss}-FightGame.log");
|
||||
|
||||
await connection.SendPacket(new PacketFightEnterScRsp(connection.ClientSecretKeySeed));
|
||||
|
||||
if (ConfigManager.Config.GameServer.UsePacketEncryption)
|
||||
{
|
||||
connection.XorKey = Crypto.GenerateXorKey(connection.ClientSecretKeySeed);
|
||||
}
|
||||
|
||||
await marbleGame.EnterGame(player.LobbyPlayer.Player.Uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Fight;
|
||||
|
||||
[Opcode(CmdIds.FightGeneralCsReq)]
|
||||
public class HandlerFightGeneralCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = FightGeneralCsReq.Parser.ParseFrom(data);
|
||||
|
||||
if (connection.MarbleRoom == null || connection.MarblePlayer == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketFightGeneralScRsp(Retcode.RetFightRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
await connection.MarbleRoom.HandleGeneralRequest(connection.MarblePlayer, req.NetworkMsgType,
|
||||
req.FightGeneralInfo?.ToByteArray() ?? []);
|
||||
|
||||
await connection.SendPacket(new PacketFightGeneralScRsp(req.NetworkMsgType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Fight;
|
||||
|
||||
[Opcode(CmdIds.FightHeartBeatCsReq)]
|
||||
public class HandlerFightHeartBeatCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = FightHeartBeatCsReq.Parser.ParseFrom(data);
|
||||
|
||||
await connection.SendPacket(new PacketFightHeartBeatScRsp(req.ClientTimeMs));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Lobby;
|
||||
|
||||
[Opcode(CmdIds.LobbyCreateCsReq)]
|
||||
public class HandlerLobbyCreateCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = LobbyCreateCsReq.Parser.ParseFrom(data);
|
||||
|
||||
if (req.FightGameMode != FightGameMode.Marble)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyCreateScRsp(Retcode.RetTimeout));
|
||||
return;
|
||||
}
|
||||
|
||||
var lobbyMode = req.LobbyMode;
|
||||
var marbleList = req.LobbyGameInfo.LobbyMarbleInfo.LobbySealList.Select(x => (int)x).ToList();
|
||||
|
||||
var room = await ServerUtils.LobbyServerManager.CreateLobbyRoom(connection.Player!, (int)lobbyMode, marbleList);
|
||||
await connection.SendPacket(new PacketLobbyCreateScRsp(room));
|
||||
}
|
||||
}
|
||||
40
GameServer/Server/Packet/Recv/Lobby/HandlerLobbyJoinCsReq.cs
Normal file
40
GameServer/Server/Packet/Recv/Lobby/HandlerLobbyJoinCsReq.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Lobby;
|
||||
|
||||
[Opcode(CmdIds.LobbyJoinCsReq)]
|
||||
public class HandlerLobbyJoinCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = LobbyJoinCsReq.Parser.ParseFrom(data);
|
||||
|
||||
var room = ServerUtils.LobbyServerManager.LobbyRoomInstances.GetValueOrDefault((long)req.RoomId);
|
||||
|
||||
if (room == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyJoinScRsp(Retcode.RetLobbyRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
if (room.Players.Count >= 2)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyJoinScRsp(Retcode.RetLobbyRoomPalyerFull));
|
||||
return;
|
||||
}
|
||||
|
||||
var player = room.GetPlayerByUid(connection.Player!.Uid);
|
||||
if (player != null)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyJoinScRsp(Retcode.RetLobbyRoomPalyerFull));
|
||||
return;
|
||||
}
|
||||
|
||||
await room.AddPlayer(connection.Player!, req.LobbyGameInfo.LobbyMarbleInfo.LobbySealList.Select(x => (int)x).ToList(),
|
||||
LobbyCharacterType.LobbyCharacterMember);
|
||||
|
||||
await connection.SendPacket(new PacketLobbyJoinScRsp(room));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Lobby;
|
||||
|
||||
[Opcode(CmdIds.LobbyModifyPlayerInfoCsReq)]
|
||||
public class HandlerLobbyModifyPlayerInfoCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = LobbyModifyPlayerInfoCsReq.Parser.ParseFrom(data);
|
||||
|
||||
var room = ServerUtils.LobbyServerManager.GetPlayerJoinedRoom(connection.Player!.Uid);
|
||||
if (room == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyModifyPlayerInfoScRsp(Retcode.RetLobbyRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
var player = room.GetPlayerByUid(connection.Player.Uid);
|
||||
if (player == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyModifyPlayerInfoScRsp(Retcode.RetLobbyRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
player.EquippedSealList = req.LobbyGameInfo.LobbyMarbleInfo.LobbySealList.Select(x => (int)x).ToList();
|
||||
player.CharacterStatus = req.Type switch
|
||||
{
|
||||
LobbyModifyType.Ready => LobbyCharacterStatus.Ready,
|
||||
LobbyModifyType.Operating => LobbyCharacterStatus.Operating,
|
||||
_ => player.CharacterStatus
|
||||
};
|
||||
|
||||
await room.BroadCastToRoom(new PacketLobbySyncInfoScNotify(player.Player.Uid, room, req.Type));
|
||||
await connection.SendPacket(new PacketLobbyModifyPlayerInfoScRsp(Retcode.RetSucc));
|
||||
}
|
||||
}
|
||||
22
GameServer/Server/Packet/Recv/Lobby/HandlerLobbyQuitCsReq.cs
Normal file
22
GameServer/Server/Packet/Recv/Lobby/HandlerLobbyQuitCsReq.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Lobby;
|
||||
|
||||
[Opcode(CmdIds.LobbyQuitCsReq)]
|
||||
public class HandlerLobbyQuitCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var room = ServerUtils.LobbyServerManager.GetPlayerJoinedRoom(connection.Player!.Uid);
|
||||
if (room == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyQuitScRsp(Retcode.RetLobbyRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
await room.RemovePlayer(connection.Player.Uid);
|
||||
await connection.SendPacket(new PacketLobbyQuitScRsp(Retcode.RetSucc));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Lobby;
|
||||
|
||||
[Opcode(CmdIds.LobbyStartFightCsReq)]
|
||||
public class HandlerLobbyStartFightCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var room = ServerUtils.LobbyServerManager.GetPlayerJoinedRoom(connection.Player!.Uid);
|
||||
if (room == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketLobbyStartFightScRsp(Retcode.RetLobbyRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
var code = await room.LobbyStartFight();
|
||||
await connection.SendPacket(new PacketLobbyStartFightScRsp(code));
|
||||
|
||||
await room.StartFight();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Marble;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Marble;
|
||||
|
||||
[Opcode(CmdIds.MarbleGetDataCsReq)]
|
||||
public class HandlerMarbleGetDataCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
await connection.SendPacket(new PacketMarbleGetDataScRsp());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Marble;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Marble;
|
||||
|
||||
[Opcode(CmdIds.MarbleLevelFinishCsReq)]
|
||||
public class HandlerMarbleLevelFinishCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = MarbleLevelFinishCsReq.Parser.ParseFrom(data);
|
||||
|
||||
await connection.SendPacket(new PacketMarbleLevelFinishScRsp(req.MarbleLevelId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Marble;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Marble;
|
||||
|
||||
[Opcode(CmdIds.MarbleUpdateShownSealCsReq)]
|
||||
public class HandlerMarbleUpdateShownSealCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var req = MarbleUpdateShownSealCsReq.Parser.ParseFrom(data);
|
||||
|
||||
await connection.SendPacket(new PacketMarbleUpdateShownSealScRsp(req.UpdateSealList));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Match;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Match;
|
||||
|
||||
[Opcode(CmdIds.GetCrossInfoCsReq)]
|
||||
public class HandlerGetCrossInfoCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
await connection.SendPacket(new PacketGetCrossInfoScRsp());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Multiplayer;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Multiplayer;
|
||||
|
||||
[Opcode(CmdIds.MultiplayerGetFightGateCsReq)]
|
||||
public class HandlerMultiplayerGetFightGateCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var room = ServerUtils.MultiPlayerGameServerManager.GetPlayerJoinedRoom(connection.Player!.Uid);
|
||||
if (room == null)
|
||||
{
|
||||
await connection.SendPacket(new PacketMultiplayerGetFightGateScRsp(Retcode.RetFightRoomNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
await connection.SendPacket(new PacketMultiplayerGetFightGateScRsp(room));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,12 @@ public class HandlerPlayerLogoutCsReq : Handler
|
||||
{
|
||||
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
|
||||
{
|
||||
var room = ServerUtils.LobbyServerManager.GetPlayerJoinedRoom(connection.Player!.Uid);
|
||||
if (room != null)
|
||||
{
|
||||
await room.RemovePlayer(connection.Player.Uid);
|
||||
}
|
||||
|
||||
await connection.SendPacket(CmdIds.PlayerLogoutScRsp);
|
||||
connection.Stop();
|
||||
}
|
||||
|
||||
@@ -32,4 +32,18 @@ public class PacketRevcMsgScNotify : BasePacket
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketRevcMsgScNotify(uint toUid, uint fromUid, LobbyInviteInfo info) : base(CmdIds.RevcMsgScNotify)
|
||||
{
|
||||
var proto = new RevcMsgScNotify
|
||||
{
|
||||
ChatType = ChatType.Private,
|
||||
SourceUid = fromUid,
|
||||
TargetUid = toUid,
|
||||
InviteInfo = info,
|
||||
MessageType = MsgType.Invite
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
29
GameServer/Server/Packet/Send/Fight/PacketFightEnterScRsp.cs
Normal file
29
GameServer/Server/Packet/Send/Fight/PacketFightEnterScRsp.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
using EggLink.DanhengServer.Util;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
|
||||
public class PacketFightEnterScRsp : BasePacket
|
||||
{
|
||||
public PacketFightEnterScRsp(Retcode code) : base(CmdIds.FightEnterScRsp)
|
||||
{
|
||||
var proto = new FightEnterScRsp
|
||||
{
|
||||
Retcode = (uint)code
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketFightEnterScRsp(ulong keySeed) : base(CmdIds.FightEnterScRsp)
|
||||
{
|
||||
var proto = new FightEnterScRsp
|
||||
{
|
||||
SecretKeySeed = keySeed,
|
||||
ServerTimestampMs = (ulong)Extensions.GetUnixMs()
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using EggLink.DanhengServer.Enums.Fight;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer.MarbleGame.Sync;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
|
||||
public class PacketFightGeneralScNotify : BasePacket
|
||||
{
|
||||
public PacketFightGeneralScNotify(MarbleNetWorkMsgEnum msgType, MarbleNetWorkMsgEnum syncType, MarbleGameRoomInstance game) : base(CmdIds.FightGeneralScNotify)
|
||||
{
|
||||
var proto = new FightGeneralScNotify
|
||||
{
|
||||
NetworkMsgType = (uint)msgType,
|
||||
FightGeneralInfo = new FightGeneralServerInfo
|
||||
{
|
||||
FightGameInfo = { new FightGameInfo
|
||||
{
|
||||
MarbleGameInfo = game.ToProto(),
|
||||
GameMessageType = (uint)syncType
|
||||
} }
|
||||
}
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketFightGeneralScNotify(MarbleNetWorkMsgEnum msgType, List<MarbleGameBaseSyncData> sync) : base(CmdIds.FightGeneralScNotify)
|
||||
{
|
||||
var proto = new FightGeneralScNotify
|
||||
{
|
||||
NetworkMsgType = (uint)msgType,
|
||||
FightGeneralInfo = new FightGeneralServerInfo
|
||||
{
|
||||
FightGameInfo = { sync.Select(x => new FightGameInfo
|
||||
{
|
||||
GameMessageType = (uint)x.MessageType,
|
||||
MarbleGameSyncInfo = x.ToProto()
|
||||
}) }
|
||||
}
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
|
||||
public class PacketFightGeneralScRsp : BasePacket
|
||||
{
|
||||
public PacketFightGeneralScRsp(uint networkType) : base(CmdIds.FightGeneralScRsp)
|
||||
{
|
||||
var proto = new FightGeneralScRsp
|
||||
{
|
||||
NetworkMsgType = networkType
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketFightGeneralScRsp(Retcode code) : base(CmdIds.FightGeneralScRsp)
|
||||
{
|
||||
var proto = new FightGeneralScRsp
|
||||
{
|
||||
Retcode = (uint)code
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
using EggLink.DanhengServer.Util;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Fight;
|
||||
|
||||
public class PacketFightHeartBeatScRsp : BasePacket
|
||||
{
|
||||
public PacketFightHeartBeatScRsp(ulong clientTime) : base(CmdIds.FightHeartBeatScRsp)
|
||||
{
|
||||
var proto = new FightHeartBeatScRsp
|
||||
{
|
||||
ServerTimeMs = (ulong)Extensions.GetUnixMs(),
|
||||
ClientTimeMs = clientTime
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
|
||||
public class PacketLobbyCreateScRsp : BasePacket
|
||||
{
|
||||
public PacketLobbyCreateScRsp(LobbyRoomInstance room) : base(CmdIds.LobbyCreateScRsp)
|
||||
{
|
||||
var proto = new LobbyCreateScRsp
|
||||
{
|
||||
RoomId = (ulong)room.RoomId,
|
||||
FightGameMode = room.GameMode,
|
||||
LobbyBasicInfo = { room.Players.Select(x => x.ToProto()) },
|
||||
LobbyMode = (uint)room.LobbyMode
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketLobbyCreateScRsp(Retcode retCode) : base(CmdIds.LobbyCreateScRsp)
|
||||
{
|
||||
var proto = new LobbyCreateScRsp
|
||||
{
|
||||
Retcode = (uint)retCode
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
31
GameServer/Server/Packet/Send/Lobby/PacketLobbyJoinScRsp.cs
Normal file
31
GameServer/Server/Packet/Send/Lobby/PacketLobbyJoinScRsp.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
|
||||
public class PacketLobbyJoinScRsp : BasePacket
|
||||
{
|
||||
public PacketLobbyJoinScRsp(Retcode retcode) : base(CmdIds.LobbyJoinScRsp)
|
||||
{
|
||||
var proto = new LobbyJoinScRsp
|
||||
{
|
||||
Retcode = (uint)retcode
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketLobbyJoinScRsp(LobbyRoomInstance room) : base(CmdIds.LobbyJoinScRsp)
|
||||
{
|
||||
var proto = new LobbyJoinScRsp
|
||||
{
|
||||
RoomId = (ulong)room.RoomId,
|
||||
FightGameMode = room.GameMode,
|
||||
LobbyBasicInfo = { room.Players.Select(x => x.ToProto()) },
|
||||
LobbyMode = (uint)room.LobbyMode
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
|
||||
public class PacketLobbyModifyPlayerInfoScRsp : BasePacket
|
||||
{
|
||||
public PacketLobbyModifyPlayerInfoScRsp(Retcode code) : base(CmdIds.LobbyModifyPlayerInfoScRsp)
|
||||
{
|
||||
var proto = new LobbyModifyPlayerInfoScRsp
|
||||
{
|
||||
Retcode = (uint)code
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
17
GameServer/Server/Packet/Send/Lobby/PacketLobbyQuitScRsp.cs
Normal file
17
GameServer/Server/Packet/Send/Lobby/PacketLobbyQuitScRsp.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
|
||||
public class PacketLobbyQuitScRsp : BasePacket
|
||||
{
|
||||
public PacketLobbyQuitScRsp(Retcode code) : base(CmdIds.LobbyQuitScRsp)
|
||||
{
|
||||
var proto = new LobbyModifyPlayerInfoScRsp
|
||||
{
|
||||
Retcode = (uint)code
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
|
||||
public class PacketLobbyStartFightScRsp : BasePacket
|
||||
{
|
||||
public PacketLobbyStartFightScRsp(Retcode code) : base(CmdIds.LobbyStartFightScRsp)
|
||||
{
|
||||
var proto = new LobbyStartFightScRsp
|
||||
{
|
||||
Retcode = (uint)code
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Lobby;
|
||||
|
||||
public class PacketLobbySyncInfoScNotify : BasePacket
|
||||
{
|
||||
public PacketLobbySyncInfoScNotify(int uid, LobbyRoomInstance room, LobbyModifyType modifyType) : base(
|
||||
CmdIds.LobbySyncInfoScNotify)
|
||||
{
|
||||
var proto = new LobbySyncInfoScNotify
|
||||
{
|
||||
LobbyBasicInfo = { room.Players.Select(x => x.ToProto()) },
|
||||
Uid = (uint)uid,
|
||||
Type = modifyType
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using EggLink.DanhengServer.Data;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Marble;
|
||||
|
||||
public class PacketMarbleGetDataScRsp : BasePacket
|
||||
{
|
||||
public PacketMarbleGetDataScRsp() : base(CmdIds.MarbleGetDataScRsp)
|
||||
{
|
||||
var proto = new MarbleGetDataScRsp
|
||||
{
|
||||
OwnedSealList = { GameData.MarbleSealData.Keys.Select(x => (uint)x) },
|
||||
MarbleFinishLevelIdList = { GameData.MarbleMatchInfoData.Keys.Select(x => (uint)x) }
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Marble;
|
||||
|
||||
public class PacketMarbleLevelFinishScRsp : BasePacket
|
||||
{
|
||||
public PacketMarbleLevelFinishScRsp(uint levelId) : base(CmdIds.MarbleLevelFinishScRsp)
|
||||
{
|
||||
var proto = new MarbleLevelFinishScRsp
|
||||
{
|
||||
MarbleLevelId = levelId
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Marble;
|
||||
|
||||
public class PacketMarbleUpdateShownSealScRsp : BasePacket
|
||||
{
|
||||
public PacketMarbleUpdateShownSealScRsp(ICollection<uint> sealList) : base(CmdIds.MarbleUpdateShownSealScRsp)
|
||||
{
|
||||
var proto = new MarbleUpdateShownSealScRsp
|
||||
{
|
||||
UpdateSealList = { sealList }
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Match;
|
||||
|
||||
public class PacketGetCrossInfoScRsp : BasePacket
|
||||
{
|
||||
public PacketGetCrossInfoScRsp() : base(CmdIds.GetCrossInfoScRsp)
|
||||
{
|
||||
var proto = new GetCrossInfoScRsp
|
||||
{
|
||||
FightGameMode = FightGameMode.Marble
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Multiplayer;
|
||||
|
||||
public class PacketMultiplayerFightGameStartScNotify : BasePacket
|
||||
{
|
||||
public PacketMultiplayerFightGameStartScNotify(BaseMultiPlayerGameRoomInstance room) : base(CmdIds.MultiplayerFightGameStartScNotify)
|
||||
{
|
||||
var proto = new MultiplayerFightGameStartScNotify
|
||||
{
|
||||
SessionInfo = room.ToSessionInfo(),
|
||||
LobbyBasicInfo = { room.ParentLobby.Players.Select(x => x.ToProto()) }
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
using EggLink.DanhengServer.Kcp;
|
||||
using EggLink.DanhengServer.Proto;
|
||||
using EggLink.DanhengServer.Util;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Multiplayer;
|
||||
|
||||
public class PacketMultiplayerGetFightGateScRsp : BasePacket
|
||||
{
|
||||
public PacketMultiplayerGetFightGateScRsp(Retcode code) : base(CmdIds.MultiplayerGetFightGateScRsp)
|
||||
{
|
||||
var proto = new MultiplayerGetFightGateScRsp
|
||||
{
|
||||
Retcode = (uint)code
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
|
||||
public PacketMultiplayerGetFightGateScRsp(BaseMultiPlayerGameRoomInstance room) : base(CmdIds.MultiplayerGetFightGateScRsp)
|
||||
{
|
||||
var proto = new MultiplayerGetFightGateScRsp
|
||||
{
|
||||
GateRoomId = (ulong)room.RoomId,
|
||||
Ip = ConfigManager.Config.GameServer.PublicAddress,
|
||||
Port = ConfigManager.Config.GameServer.Port
|
||||
};
|
||||
|
||||
SetData(proto);
|
||||
}
|
||||
}
|
||||
10
GameServer/Server/ServerUtils.cs
Normal file
10
GameServer/Server/ServerUtils.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using EggLink.DanhengServer.GameServer.Game.Lobby;
|
||||
using EggLink.DanhengServer.GameServer.Game.MultiPlayer;
|
||||
|
||||
namespace EggLink.DanhengServer.GameServer.Server;
|
||||
|
||||
public static class ServerUtils
|
||||
{
|
||||
public static LobbyServerManager LobbyServerManager { get; set; } = new();
|
||||
public static MultiPlayerGameServerManager MultiPlayerGameServerManager { get; set; } = new();
|
||||
}
|
||||
Reference in New Issue
Block a user