using System.Net; using System.Net.Sockets; using System.Numerics; using System.Resources; using BattleBitAPI.Common; using BattleBitAPI.Common.Extentions; using BattleBitAPI.Networking; using BattleBitAPI.Pooling; namespace BattleBitAPI.Server { public class ServerListener : IDisposable where TPlayer : Player where TGameServer : GameServer { // --- Public --- public bool IsListening { get; private set; } public bool IsDisposed { get; private set; } public int ListeningPort { get; private set; } // --- Events --- /// /// Fired when an attempt made to connect to the server.
/// Default, any connection attempt will be accepted ///
/// /// /// IPAddress: IP of incoming connection
///
/// /// /// Returns: true if allow connection, false if deny the connection. /// public Func> OnGameServerConnecting { get; set; } /// /// Fired when server needs to validate token from incoming connection.
/// Default, any connection attempt will be accepted ///
/// /// /// IPAddress: IP of incoming connection
/// ushort: Game Port of the connection
/// string: Token of connection
///
/// /// /// Returns: true if allow connection, false if deny the connection. /// public Func> OnValidateGameServerToken { get; set; } /// /// Fired when a game server connects. /// /// /// /// GameServer: Game server that is connecting.
///
public Func, Task> OnGameServerConnected { get; set; } /// /// Fired when a game server disconnects. Check (GameServer.TerminationReason) to see the reason. /// /// /// /// GameServer: Game server that disconnected.
///
public Func, Task> OnGameServerDisconnected { get; set; } /// /// Fired when a new instance of game server created. /// /// /// /// IPAddress: Game server's IP.
/// ushort: Game server's Port.
///
public Func OnCreatingGameServerInstance { get; set; } /// /// Fired when a new instance of player instance created. /// /// /// /// TPlayer: The player instance that was created
/// ulong: The steamID of the player
///
public Func OnCreatingPlayerInstance { get; set; } // --- Private --- private TcpListener mSocket; private Dictionary.Internal resources)> mActiveConnections; private mInstances mInstanceDatabase; private ItemPooling> mGameServerPool; // --- Construction --- public ServerListener() { this.mActiveConnections = new Dictionary.Internal)>(16); this.mInstanceDatabase = new mInstances(); this.mGameServerPool = new ItemPooling>(64); } // --- Starting --- public void Start(IPAddress bindIP, int port) { if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName); if (bindIP == null) throw new ArgumentNullException(nameof(bindIP)); if (IsListening) throw new Exception("Server is already listening."); this.mSocket = new TcpListener(bindIP, port); this.mSocket.Start(); this.ListeningPort = port; this.IsListening = true; mMainLoop(); } public void Start(int port) { Start(IPAddress.Any, port); } // --- Stopping --- public void Stop() { if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName); if (!IsListening) throw new Exception("Already not running."); try { mSocket.Stop(); } catch { } this.mSocket = null; this.ListeningPort = 0; this.IsListening = true; } // --- Main Loop --- private async Task mMainLoop() { while (IsListening) { var client = await mSocket.AcceptTcpClientAsync(); mInternalOnClientConnecting(client); } } private async Task mInternalOnClientConnecting(TcpClient client) { var ip = (client.Client.RemoteEndPoint as IPEndPoint).Address; //Is this IP allowed? bool allow = true; if (OnGameServerConnecting != null) allow = await OnGameServerConnecting(ip); //Close connection if it was not allowed. if (!allow) { //Connection is not allowed from this IP. client.SafeClose(); return; } //Read port,token,version string token; string version; int gamePort; try { using (var source = new CancellationTokenSource(2000)) { using (var readStream = Common.Serialization.Stream.Get()) { var networkStream = client.GetStream(); //Read package type { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the package type"); NetworkCommuncation type = (NetworkCommuncation)readStream.ReadInt8(); if (type != NetworkCommuncation.Hail) throw new Exception("Incoming package wasn't hail."); } //Read the server token { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the Token Size"); int stringSize = readStream.ReadUInt16(); if (stringSize > Const.MaxTokenSize) throw new Exception("Invalid token size"); readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the token"); token = readStream.ReadString(stringSize); } //Read the server version { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the version size"); int stringSize = readStream.ReadUInt16(); if (stringSize > 32) throw new Exception("Invalid version size"); readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the version"); version = readStream.ReadString(stringSize); } //Read port { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the Port"); gamePort = readStream.ReadUInt16(); } } } } catch { client.SafeClose(); return; } var hash = ((ulong)gamePort << 32) | (ulong)ip.ToUInt(); TGameServer server = null; GameServer.Internal resources = null; try { //Does versions match? if (version != Const.Version) throw new Exception("Incoming server's version `" + version + "` does not match with current API version `" + Const.Version + "`"); //Is valid token? if (OnValidateGameServerToken != null) { if (!await OnValidateGameServerToken(ip, (ushort)gamePort, token)) throw new Exception("Token was not valid!"); } //Are there any connections with same IP and port? { bool sessionExist = false; (TGameServer server, GameServer.Internal resources) oldSession; //Any sessions with this IP:Port? lock (this.mActiveConnections) sessionExist = this.mActiveConnections.TryGetValue(hash, out oldSession); if (sessionExist) { //Close old session. oldSession.server.CloseConnection("Reconnecting."); //Wait until session is fully closed. while (oldSession.resources.HasActiveConnectionSession) await Task.Delay(1); } } using (var source = new CancellationTokenSource(Const.HailConnectTimeout)) { using (var readStream = Common.Serialization.Stream.Get()) { var networkStream = client.GetStream(); //Read is server protected bool isPasswordProtected; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the IsPasswordProtected"); isPasswordProtected = readStream.ReadBool(); } //Read the server name string serverName; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the ServerName Size"); int stringSize = readStream.ReadUInt16(); if (stringSize < Const.MinServerNameLength || stringSize > Const.MaxServerNameLength) throw new Exception("Invalid server name size"); readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the ServerName"); serverName = readStream.ReadString(stringSize); } //Read the gamemode string gameMode; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the gamemode Size"); int stringSize = readStream.ReadUInt16(); if (stringSize < Const.MinGamemodeNameLength || stringSize > Const.MaxGamemodeNameLength) throw new Exception("Invalid gamemode size"); readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the gamemode"); gameMode = readStream.ReadString(stringSize); } //Read the gamemap string gamemap; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the map size"); int stringSize = readStream.ReadUInt16(); if (stringSize < Const.MinMapNameLength || stringSize > Const.MaxMapNameLength) throw new Exception("Invalid map size"); readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the map"); gamemap = readStream.ReadString(stringSize); } //Read the mapSize MapSize size; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the MapSize"); size = (MapSize)readStream.ReadInt8(); } //Read the day night MapDayNight dayNight; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the MapDayNight"); dayNight = (MapDayNight)readStream.ReadInt8(); } //Current Players int currentPlayers; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Current Players"); currentPlayers = readStream.ReadInt8(); } //Queue Players int queuePlayers; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Queue Players"); queuePlayers = readStream.ReadInt8(); } //Max Players int maxPlayers; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Max Players"); maxPlayers = readStream.ReadInt8(); } //Read Loading Screen Text string loadingScreenText; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the Loading Screen Text Size"); int stringSize = readStream.ReadUInt16(); if (stringSize < Const.MinLoadingScreenTextLength || stringSize > Const.MaxLoadingScreenTextLength) throw new Exception("Invalid server Loading Screen Text Size"); if (stringSize > 0) { readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the Loading Screen Text"); loadingScreenText = readStream.ReadString(stringSize); } else { loadingScreenText = string.Empty; } } //Read Server Rules Text string serverRulesText; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the Server Rules Text Size"); int stringSize = readStream.ReadUInt16(); if (stringSize < Const.MinServerRulesTextLength || stringSize > Const.MaxServerRulesTextLength) throw new Exception("Invalid server Server Rules Text Size"); if (stringSize > 0) { readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the Server Rules Text"); serverRulesText = readStream.ReadString(stringSize); } else { serverRulesText = string.Empty; } } //Round index uint roundIndex; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the Server Round Index"); roundIndex = readStream.ReadUInt32(); } //Round index long sessionID; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 8, source.Token)) throw new Exception("Unable to read the Server Round ID"); sessionID = readStream.ReadInt64(); } server = this.mInstanceDatabase.GetServerInstance(hash, out resources, this.OnCreatingGameServerInstance, ip, (ushort)gamePort); resources.Set( this.mExecutePackage, this.mGetPlayerInternals, client, ip, gamePort, isPasswordProtected, serverName, gameMode, gamemap, size, dayNight, currentPlayers, queuePlayers, maxPlayers, loadingScreenText, serverRulesText, roundIndex, sessionID ); //Room settings { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the room size"); int roomSize = (int)readStream.ReadUInt32(); readStream.Reset(); if (!await networkStream.TryRead(readStream, roomSize, source.Token)) throw new Exception("Unable to read the room"); resources._RoomSettings.Read(readStream); } //Map&gamemode rotation { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the map&gamemode rotation size"); int rotationSize = (int)readStream.ReadUInt32(); readStream.Reset(); if (!await networkStream.TryRead(readStream, rotationSize, source.Token)) throw new Exception("Unable to read the map&gamemode"); uint count = readStream.ReadUInt32(); while (count > 0) { count--; if (readStream.TryReadString(out var item)) resources._MapRotation.Add(item.ToUpperInvariant()); } count = readStream.ReadUInt32(); while (count > 0) { count--; if (readStream.TryReadString(out var item)) resources._GamemodeRotation.Add(item); } } //Round Settings { readStream.Reset(); if (!await networkStream.TryRead(readStream, RoundSettings.mRoundSettings.Size, source.Token)) throw new Exception("Unable to read the round settings"); resources._RoundSettings.Read(readStream); } //Client Count int clientCount = 0; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Client Count Players"); clientCount = readStream.ReadInt8(); } //Get each client. while (clientCount > 0) { clientCount--; ulong steamid = 0; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 8, source.Token)) throw new Exception("Unable to read the SteamId"); steamid = readStream.ReadUInt64(); } string username; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 2, source.Token)) throw new Exception("Unable to read the Username Size"); int stringSize = readStream.ReadUInt16(); if (stringSize > 0) { readStream.Reset(); if (!await networkStream.TryRead(readStream, stringSize, source.Token)) throw new Exception("Unable to read the Username"); username = readStream.ReadString(stringSize); } else { username = string.Empty; } } uint ipHash = 0; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the ip"); ipHash = readStream.ReadUInt32(); } //Team Team team; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Team"); team = (Team)readStream.ReadInt8(); } //Squad Squads squad; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Squad"); squad = (Squads)readStream.ReadInt8(); } //Role GameRole role; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the Role"); role = (GameRole)readStream.ReadInt8(); } var loadout = new PlayerLoadout(); var wearings = new PlayerWearings(); //IsAlive bool isAlive; { readStream.Reset(); if (!await networkStream.TryRead(readStream, 1, source.Token)) throw new Exception("Unable to read the isAlive"); isAlive = readStream.ReadBool(); } //Loadout + Wearings if (isAlive) { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the LoadoutSize"); int loadoutSize = (int)readStream.ReadUInt32(); readStream.Reset(); if (!await networkStream.TryRead(readStream, loadoutSize, source.Token)) throw new Exception("Unable to read the Loadout + Wearings"); loadout.Read(readStream); wearings.Read(readStream); } TPlayer player = mInstanceDatabase.GetPlayerInstance(steamid, out var playerInternal, this.OnCreatingPlayerInstance); playerInternal.SteamID = steamid; playerInternal.Name = username; playerInternal.IP = new IPAddress(ipHash); playerInternal.Team = team; playerInternal.SquadName = squad; playerInternal.Role = role; playerInternal.IsAlive = isAlive; playerInternal.CurrentLoadout = loadout; playerInternal.CurrentWearings = wearings; //Modifications { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the Modifications Size"); int modificationSize = (int)readStream.ReadUInt32(); readStream.Reset(); if (!await networkStream.TryRead(readStream, modificationSize, source.Token)) throw new Exception("Unable to read the Modifications"); playerInternal._Modifications.Read(readStream); } playerInternal.GameServer = (GameServer)server; playerInternal.SessionID = server.SessionID; resources.AddPlayer(player); } //Squads { readStream.Reset(); if (!await networkStream.TryRead(readStream, 4, source.Token)) throw new Exception("Unable to read the Squad size"); int squadsDataSize = (int)readStream.ReadUInt32(); readStream.Reset(); if (!await networkStream.TryRead(readStream, squadsDataSize, source.Token)) throw new Exception("Unable to read the Squads"); for (int i = 1; i < resources.TeamASquadInternals.Length; i++) { var item = resources.TeamASquadInternals[i]; item.SquadPoints = readStream.ReadInt32(); } for (int i = 1; i < resources.TeamBSquadInternals.Length; i++) { var item = resources.TeamBSquadInternals[i]; item.SquadPoints = readStream.ReadInt32(); } } // --- Finished Reading --- //Assing each player to their squad. foreach (var item in resources.Players.Values) { if (item.InSquad) { var @squad = resources.GetSquadInternal(item.Squad); lock (@squad.Members) @squad.Members.Add((TPlayer)item); } } //Send accepted notification. networkStream.WriteByte((byte)NetworkCommuncation.Accepted); } } } catch (Exception e) { try { var networkStream = client.GetStream(); using (var pck = BattleBitAPI.Common.Serialization.Stream.Get()) { pck.Write((byte)NetworkCommuncation.Denied); pck.Write(e.Message); //Send denied notification. networkStream.Write(pck.Buffer, 0, pck.WritePosition); } await networkStream.FlushAsync(); } catch { } client.SafeClose(); return; } //Set the buffer sizes. client.ReceiveBufferSize = Const.MaxNetworkPackageSize; client.SendBufferSize = Const.MaxNetworkPackageSize; //Join to main server loop. await mHandleGameServer(server, resources); } private async Task mHandleGameServer(TGameServer server, GameServer.Internal @internal) { @internal.HasActiveConnectionSession = true; { // ---- Connected ---- { lock (this.mActiveConnections) this.mActiveConnections.Replace(server.ServerHash, (server, @internal)); server.OnConnected(); if (this.OnGameServerConnected != null) this.OnGameServerConnected(server); } //Update sessions { if (@internal.mPreviousSessionID != @internal.SessionID) { var oldSession = @internal.mPreviousSessionID; @internal.mPreviousSessionID = @internal.SessionID; if (oldSession != 0) server.OnSessionChanged(oldSession, @internal.SessionID); } foreach (var item in @internal.Players) { var @player_internal = mInstanceDatabase.GetPlayerInternals(item.Key); if (@player_internal.PreviousSessionID != @player_internal.SessionID) { var previousID = @player_internal.PreviousSessionID; @player_internal.PreviousSessionID = @player_internal.SessionID; if (previousID != 0) item.Value.OnSessionChanged(previousID, @player_internal.SessionID); } } } // ---- Ticking ---- using (server) { var isTicking = false; async Task mTickAsync() { isTicking = true; await server.OnTick(); isTicking = false; } while (server.IsConnected) { if (!isTicking) mTickAsync(); await server.Tick(); await Task.Delay(10); } } // ---- Disconnected ---- { mCleanup(server, @internal); lock (this.mActiveConnections) this.mActiveConnections.Remove(server.ServerHash); server.OnDisconnected(); if (this.OnGameServerDisconnected != null) this.OnGameServerDisconnected(server); } } @internal.HasActiveConnectionSession = false; } // --- Logic Executing --- private async Task mExecutePackage(GameServer server, GameServer.Internal resources, Common.Serialization.Stream stream) { var communcation = (NetworkCommuncation)stream.ReadInt8(); switch (communcation) { case NetworkCommuncation.PlayerConnected: { if (stream.CanRead(8 + 2 + 4 + (1 + 1 + 1))) { ulong steamID = stream.ReadUInt64(); if (stream.TryReadString(out var username)) { uint ip = stream.ReadUInt32(); Team team = (Team)stream.ReadInt8(); Squads squad = (Squads)stream.ReadInt8(); GameRole role = (GameRole)stream.ReadInt8(); TPlayer player = mInstanceDatabase.GetPlayerInstance(steamID, out var playerInternal, this.OnCreatingPlayerInstance); playerInternal.SteamID = steamID; playerInternal.Name = username; playerInternal.IP = new IPAddress(ip); playerInternal.Team = team; playerInternal.SquadName = squad; playerInternal.Role = role; //Start from default. playerInternal._Modifications.Reset(); playerInternal.GameServer = (GameServer)server; playerInternal.SessionID = server.SessionID; resources.AddPlayer(player); player.OnConnected(); server.OnPlayerConnected(player); if (playerInternal.PreviousSessionID != playerInternal.SessionID) { var previousID = playerInternal.PreviousSessionID; playerInternal.PreviousSessionID = playerInternal.SessionID; if (previousID != 0) player.OnSessionChanged(previousID, playerInternal.SessionID); } } } break; } case NetworkCommuncation.PlayerDisconnected: { if (stream.CanRead(8)) { ulong steamID = stream.ReadUInt64(); bool exist; Player player; lock (resources.Players) exist = resources.Players.Remove(steamID, out player); if (exist) { var @internal = mInstanceDatabase.GetPlayerInternals(steamID); if (@internal.HP > -1f) { @internal.OnDie(); player.OnDied(); server.OnPlayerDied((TPlayer)player); } if (@internal.SquadName != Squads.NoSquad) { var msquad = server.GetSquad(@internal.Team, @internal.SquadName); var rsquad = resources.GetSquadInternal(msquad); @internal.SquadName = Squads.NoSquad; lock (rsquad.Members) rsquad.Members.Remove((TPlayer)player); player.OnLeftSquad(msquad); server.OnPlayerLeftSquad((TPlayer)player, msquad); } @internal.SessionID = 0; @internal.GameServer = null; player.OnDisconnected(); server.OnPlayerDisconnected((TPlayer)player); } } break; } case NetworkCommuncation.OnPlayerTypedMessage: { if (stream.CanRead(2 + 8 + 1 + 2)) { ushort messageID = stream.ReadUInt16(); ulong steamID = stream.ReadUInt64(); if (resources.TryGetPlayer(steamID, out var player)) { ChatChannel chat = (ChatChannel)stream.ReadInt8(); if (stream.TryReadString(out var msg)) { async Task Handle() { var pass = await server.OnPlayerTypedMessage((TPlayer)player, chat, msg); //Respond back. using (var response = Common.Serialization.Stream.Get()) { response.Write((byte)NetworkCommuncation.RespondPlayerMessage); response.Write(messageID); response.Write(pass); server.WriteToSocket(response); } } Handle(); } } } break; } case NetworkCommuncation.OnAPlayerDownedAnotherPlayer: { if (stream.CanRead(8 + 12 + 8 + 12 + 2 + 1 + 1)) { ulong killer = stream.ReadUInt64(); Vector3 killerPos = new Vector3(stream.ReadFloat(), stream.ReadFloat(), stream.ReadFloat()); ulong victim = stream.ReadUInt64(); Vector3 victimPos = new Vector3(stream.ReadFloat(), stream.ReadFloat(), stream.ReadFloat()); if (stream.TryReadString(out var tool)) { PlayerBody body = (PlayerBody)stream.ReadInt8(); ReasonOfDamage source = (ReasonOfDamage)stream.ReadInt8(); if (resources.TryGetPlayer(killer, out var killerClient)) { if (resources.TryGetPlayer(victim, out var victimClient)) { var args = new OnPlayerKillArguments() { Killer = (TPlayer)killerClient, KillerPosition = killerPos, Victim = (TPlayer)victimClient, VictimPosition = victimPos, BodyPart = body, SourceOfDamage = source, KillerTool = tool, }; victimClient.OnDowned(); server.OnAPlayerDownedAnotherPlayer(args); } } } } break; } case NetworkCommuncation.OnPlayerJoining: { if (stream.CanRead(8 + 2)) { ulong steamID = stream.ReadUInt64(); var stats = new PlayerStats(); stats.Read(stream); async Task mHandle() { var args = new PlayerJoiningArguments() { Stats = stats, Squad = Squads.NoSquad, Team = Team.None }; await server.OnPlayerJoiningToServer(steamID, args); using (var response = Common.Serialization.Stream.Get()) { response.Write((byte)NetworkCommuncation.SendPlayerStats); response.Write(steamID); args.Write(response); server.WriteToSocket(response); } } mHandle(); } break; } case NetworkCommuncation.SavePlayerStats: { if (stream.CanRead(8 + 4)) { ulong steamID = stream.ReadUInt64(); PlayerStats stats = new PlayerStats(); stats.Read(stream); server.OnSavePlayerStats(steamID, stats); } break; } case NetworkCommuncation.OnPlayerAskingToChangeRole: { if (stream.CanRead(8 + 1)) { ulong steamID = stream.ReadUInt64(); GameRole role = (GameRole)stream.ReadInt8(); if (resources.TryGetPlayer(steamID, out var client)) { async Task mHandle() { bool accepted = await server.OnPlayerRequestingToChangeRole((TPlayer)client, role); if (accepted) server.SetRoleTo(steamID, role); } mHandle(); } } break; } case NetworkCommuncation.OnPlayerChangedRole: { if (stream.CanRead(8 + 1)) { ulong steamID = stream.ReadUInt64(); GameRole role = (GameRole)stream.ReadInt8(); if (resources.TryGetPlayer(steamID, out var client)) { var @internal = mInstanceDatabase.GetPlayerInternals(steamID); @internal.Role = role; client.OnChangedRole(role); server.OnPlayerChangedRole((TPlayer)client, role); } } break; } case NetworkCommuncation.OnPlayerJoinedASquad: { if (stream.CanRead(8 + 1)) { ulong steamID = stream.ReadUInt64(); Squads squad = (Squads)stream.ReadInt8(); if (resources.TryGetPlayer(steamID, out var client)) { var @internal = mInstanceDatabase.GetPlayerInternals(steamID); @internal.SquadName = squad; var msquad = server.GetSquad(client.Team, squad); var rsquad = resources.GetSquadInternal(msquad); lock (rsquad.Members) rsquad.Members.Add((TPlayer)client); client.OnJoinedSquad(msquad); server.OnPlayerJoinedSquad((TPlayer)client, msquad); } } break; } case NetworkCommuncation.OnPlayerLeftSquad: { if (stream.CanRead(8)) { ulong steamID = stream.ReadUInt64(); if (resources.TryGetPlayer(steamID, out var client)) { var @internal = mInstanceDatabase.GetPlayerInternals(steamID); var oldSquad = client.SquadName; var oldRole = client.Role; @internal.SquadName = Squads.NoSquad; @internal.Role = GameRole.Assault; var msquad = server.GetSquad(client.Team, oldSquad); var rsquad = resources.GetSquadInternal(msquad); @internal.SquadName = Squads.NoSquad; lock (rsquad.Members) rsquad.Members.Remove((TPlayer)client); client.OnLeftSquad(msquad); server.OnPlayerLeftSquad((TPlayer)client, msquad); if (oldRole != GameRole.Assault) { client.OnChangedRole(GameRole.Assault); server.OnPlayerChangedRole((TPlayer)client, GameRole.Assault); } } } break; } case NetworkCommuncation.OnPlayerChangedTeam: { if (stream.CanRead(8 + 1)) { ulong steamID = stream.ReadUInt64(); Team team = (Team)stream.ReadInt8(); if (resources.TryGetPlayer(steamID, out var client)) { var @internal = mInstanceDatabase.GetPlayerInternals(steamID); @internal.Team = team; client.OnChangedTeam(); server.OnPlayerChangeTeam((TPlayer)client, team); } } break; } case NetworkCommuncation.OnPlayerRequestingToSpawn: { if (stream.CanRead(2)) { ulong steamID = stream.ReadUInt64(); OnPlayerSpawnArguments request = new OnPlayerSpawnArguments(); request.Read(stream); ushort vehicleID = stream.ReadUInt16(); if (resources.TryGetPlayer(steamID, out var client)) { async Task mHandle() { var responseSpawn = await server.OnPlayerSpawning((TPlayer)client, request); //Respond back. using (var response = Common.Serialization.Stream.Get()) { response.Write((byte)NetworkCommuncation.SpawnPlayer); response.Write(steamID); if (responseSpawn != null) { response.Write(true); request.Write(response); response.Write(vehicleID); } else { response.Write(false); } server.WriteToSocket(response); } } mHandle(); } } break; } case NetworkCommuncation.OnPlayerReport: { if (stream.CanRead(8 + 8 + 1 + 2)) { ulong reporter = stream.ReadUInt64(); ulong reported = stream.ReadUInt64(); ReportReason reason = (ReportReason)stream.ReadInt8(); stream.TryReadString(out var additionalInfo); if (resources.TryGetPlayer(reporter, out var reporterClient)) { if (resources.TryGetPlayer(reported, out var reportedClient)) { server.OnPlayerReported((TPlayer)reporterClient, (TPlayer)reportedClient, reason, additionalInfo); } } } break; } case NetworkCommuncation.OnPlayerSpawn: { if (stream.CanRead(8 + 2)) { ulong steamID = stream.ReadUInt64(); if (resources.TryGetPlayer(steamID, out var client)) { var @internal = mInstanceDatabase.GetPlayerInternals(steamID); var loadout = new PlayerLoadout(); loadout.Read(stream); @internal.CurrentLoadout = loadout; var wearings = new PlayerWearings(); wearings.Read(stream); @internal.CurrentWearings = wearings; @internal.IsAlive = true; client.OnSpawned(); server.OnPlayerSpawned((TPlayer)client); } } break; } case NetworkCommuncation.OnPlayerDie: { if (stream.CanRead(8)) { ulong steamid = stream.ReadUInt64(); if (resources.TryGetPlayer(steamid, out var client)) { var @internal = mInstanceDatabase.GetPlayerInternals(steamid); @internal.OnDie(); client.OnDied(); server.OnPlayerDied((TPlayer)client); } } break; } case NetworkCommuncation.NotifyNewMapRotation: { if (stream.CanRead(4)) { uint count = stream.ReadUInt32(); lock (resources._MapRotation) { resources._MapRotation.Clear(); while (count > 0) { count--; if (stream.TryReadString(out var map)) resources._MapRotation.Add(map.ToUpperInvariant()); } } } break; } case NetworkCommuncation.NotifyNewGamemodeRotation: { if (stream.CanRead(4)) { uint count = stream.ReadUInt32(); lock (resources._GamemodeRotation) { resources._GamemodeRotation.Clear(); while (count > 0) { count--; if (stream.TryReadString(out var map)) resources._GamemodeRotation.Add(map); } } } break; } case NetworkCommuncation.NotifyNewRoundState: { if (stream.CanRead(RoundSettings.mRoundSettings.Size)) { var oldState = resources._RoundSettings.State; resources._RoundSettings.Read(stream); var newState = resources._RoundSettings.State; if (newState != oldState) { server.OnGameStateChanged(oldState, newState); if (newState == GameState.Playing) server.OnRoundStarted(); else if (newState == GameState.EndingGame) server.OnRoundEnded(); } } break; } case NetworkCommuncation.OnPlayerAskingToChangeTeam: { if (stream.CanRead(8 + 1)) { ulong steamID = stream.ReadUInt64(); Team team = (Team)stream.ReadInt8(); if (resources.TryGetPlayer(steamID, out var client)) { async Task mHandle() { bool accepted = await server.OnPlayerRequestingToChangeTeam((TPlayer)client, team); if (accepted) server.ChangeTeam(steamID, team); } mHandle(); } } break; } case NetworkCommuncation.GameTick: { if (stream.CanRead(4 + 4 + 4)) { float decompressX = stream.ReadFloat(); float decompressY = stream.ReadFloat(); float decompressZ = stream.ReadFloat(); int playerCount = stream.ReadInt8(); while (playerCount > 0) { playerCount--; ulong steamID = stream.ReadUInt64(); //TODO, can compressed further later. ushort com_posX = stream.ReadUInt16(); ushort com_posY = stream.ReadUInt16(); ushort com_posZ = stream.ReadUInt16(); byte com_healt = stream.ReadInt8(); PlayerStand standing = (PlayerStand)stream.ReadInt8(); LeaningSide side = (LeaningSide)stream.ReadInt8(); LoadoutIndex loadoutIndex = (LoadoutIndex)stream.ReadInt8(); bool inSeat = stream.ReadBool(); bool isBleeding = stream.ReadBool(); ushort ping = stream.ReadUInt16(); var @internal = mInstanceDatabase.GetPlayerInternals(steamID); if (@internal.IsAlive) { @internal.Position = new Vector3() { X = com_posX * decompressX, Y = com_posY * decompressY, Z = com_posZ * decompressZ, }; @internal.HP = (com_healt * 0.5f) - 1f; @internal.Standing = standing; @internal.Leaning = side; @internal.CurrentLoadoutIndex = loadoutIndex; @internal.InVehicle = inSeat; @internal.IsBleeding = isBleeding; @internal.PingMs = ping; } } } break; } case NetworkCommuncation.OnPlayerGivenUp: { if (stream.CanRead(8)) { ulong steamID = stream.ReadUInt64(); if (resources.TryGetPlayer(steamID, out var client)) { client.OnGivenUp(); server.OnPlayerGivenUp((TPlayer)client); } } break; } case NetworkCommuncation.OnPlayerRevivedAnother: { if (stream.CanRead(8 + 8)) { ulong from = stream.ReadUInt64(); ulong to = stream.ReadUInt64(); if (resources.TryGetPlayer(to, out var toClient)) { toClient.OnRevivedByAnotherPlayer(); if (resources.TryGetPlayer(from, out var fromClient)) { fromClient.OnRevivedAnotherPlayer(); server.OnAPlayerRevivedAnotherPlayer((TPlayer)fromClient, (TPlayer)toClient); } } } break; } case NetworkCommuncation.OnSquadPointsChanged: { if (stream.CanRead(1 + 1 + 4)) { Team team = (Team)stream.ReadInt8(); Squads squad = (Squads)stream.ReadInt8(); int points = stream.ReadInt32(); var msquad = server.GetSquad(team, squad); var rsquad = resources.GetSquadInternal(msquad); if (rsquad.SquadPoints != points) { rsquad.SquadPoints = points; server.OnSquadPointsChanged(msquad, points); } } break; } case NetworkCommuncation.NotifyNewRoundID: { if (stream.CanRead(4 + 8)) { resources.RoundIndex = stream.ReadUInt32(); resources.SessionID = stream.ReadInt64(); if (resources.mPreviousSessionID != resources.SessionID) { var oldSession = resources.mPreviousSessionID; resources.mPreviousSessionID = resources.SessionID; if (oldSession != 0) server.OnSessionChanged(oldSession, resources.SessionID); } foreach (var item in resources.Players) { var @player_internal = mInstanceDatabase.GetPlayerInternals(item.Key); @player_internal.SessionID = resources.SessionID; if (@player_internal.PreviousSessionID != @player_internal.SessionID) { var previousID = @player_internal.PreviousSessionID; @player_internal.PreviousSessionID = @player_internal.SessionID; if (previousID != 0) item.Value.OnSessionChanged(previousID, @player_internal.SessionID); } } } break; } } } // --- Private --- private void mCleanup(GameServer server, GameServer.Internal @internal) { lock (@internal.Players) { foreach (var item in @internal.Players) { var @player_internal = mInstanceDatabase.GetPlayerInternals(item.Key); @player_internal.SessionID = 0; @player_internal.GameServer = null; } } } private Player.Internal mGetPlayerInternals(ulong steamID) { return mInstanceDatabase.GetPlayerInternals(steamID); } // --- Public --- public IEnumerable ConnectedGameServers { get { using (var list = this.mGameServerPool.Get()) { //Get a copy lock (mActiveConnections) foreach (var item in mActiveConnections.Values) list.ListItems.Add(item.server); //Iterate for (int i = 0; i < list.ListItems.Count; i++) yield return (TGameServer)list.ListItems[i]; } } } public bool TryGetGameServer(IPAddress ip, ushort port, out TGameServer server) { var hash = ((ulong)port << 32) | (ulong)ip.ToUInt(); lock (mActiveConnections) { if (mActiveConnections.TryGetValue(hash, out var _server)) { server = (TGameServer)_server.server; return true; } } server = default; return false; } // --- Disposing --- public void Dispose() { //Already disposed? if (this.IsDisposed) return; this.IsDisposed = true; if (IsListening) Stop(); } // --- Classes --- private class mInstances where TPlayer : Player where TGameServer : GameServer { private Dictionary.Internal)> mGameServerInstances; private Dictionary.Internal)> mPlayerInstances; public mInstances() { this.mGameServerInstances = new Dictionary.Internal)>(64); this.mPlayerInstances = new Dictionary.Internal)>(1024 * 16); } public TGameServer GetServerInstance(ulong hash, out GameServer.Internal @internal, Func createFunc, IPAddress ip, ushort port) { lock (mGameServerInstances) { if (mGameServerInstances.TryGetValue(hash, out var data)) { @internal = data.Item2; return data.Item1; } GameServer server; if (createFunc != null) server = createFunc(ip, port); else server = Activator.CreateInstance(); @internal = new GameServer.Internal(server); GameServer.SetInstance(server, @internal); mGameServerInstances.Add(hash, ((TGameServer)server, @internal)); return (TGameServer)server; } } public TPlayer GetPlayerInstance(ulong steamID, out Player.Internal @internal, Func createFunc) { lock (this.mPlayerInstances) { if (this.mPlayerInstances.TryGetValue(steamID, out var player)) { @internal = player.Item2; return player.Item1; } @internal = new Player.Internal(); Player pplayer; if (createFunc != null) pplayer = createFunc(steamID); else pplayer = Activator.CreateInstance(); Player.SetInstance((TPlayer)pplayer, @internal); mPlayerInstances.Add(steamID, ((TPlayer)pplayer, @internal)); return (TPlayer)pplayer; } } public Player.Internal GetPlayerInternals(ulong steamID) { lock (mPlayerInstances) return mPlayerInstances[steamID].Item2; } } } }