diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 57c0b2f38..caff3a527 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.IO Server = server; } - public void PollForChanges() + public async Task PollForChanges() { while (!Server.Manager.ShutdownRequested()) { @@ -45,7 +45,7 @@ namespace IW4MAdmin.Application.IO { try { - UpdateLogEvents(); + await UpdateLogEvents(); } catch (Exception e) @@ -59,7 +59,7 @@ namespace IW4MAdmin.Application.IO } } - private void UpdateLogEvents() + private async Task UpdateLogEvents() { long fileSize = Reader.Length; @@ -74,7 +74,7 @@ namespace IW4MAdmin.Application.IO PreviousFileSize = fileSize; - var events = Reader.ReadEventsFromLog(Server, fileDiff, 0); + var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0); foreach (var ev in events) { diff --git a/Application/IO/GameLogReader.cs b/Application/IO/GameLogReader.cs index db35950a8..64d499020 100644 --- a/Application/IO/GameLogReader.cs +++ b/Application/IO/GameLogReader.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading.Tasks; namespace IW4MAdmin.Application.IO { @@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO Parser = parser; } - public ICollection ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) + public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { // allocate the bytes for the new log lines List logLines = new List(); @@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO // open the file as a stream using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType)) { + // todo: max async // take the old start position and go back the number of new characters rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End); // the difference should be in the range of a int :P diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index 3669ef0e3..9ddf6cc9a 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; using System.Net.Http; +using System.Threading.Tasks; using static SharedLibraryCore.Utilities; namespace IW4MAdmin.Application.IO @@ -29,18 +30,19 @@ namespace IW4MAdmin.Application.IO public int UpdateInterval => 1000; - public ICollection ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) + public async Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) { #if DEBUG == true server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log"); #endif var events = new List(); string b64Path = server.LogPath.ToBase64UrlSafeString(); - var response = Api.Log(b64Path).Result; + var response = await Api.Log(b64Path); if (!response.Success) { server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})"); + return events; } // parse each line diff --git a/Application/Manager.cs b/Application/Manager.cs index dd5e98ce9..be9aeb84c 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -151,7 +151,9 @@ namespace IW4MAdmin.Application { Logger.WriteWarning($"Delayed event for {e.Origin} was ignored because the target has left"); // hack: don't do anything with the event because the target is invalid + e.Origin = null; e.Type = GameEvent.EventType.Unknown; + } } Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing"); @@ -265,11 +267,7 @@ namespace IW4MAdmin.Application ThreadPool.GetAvailableThreads(out int availableThreads, out int m); Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks"); #endif -#if DEBUG - await Task.Delay(10000); -#else await Task.Delay(ConfigHandler.Configuration().RConPollRate); -#endif } // trigger the event processing loop to end diff --git a/Application/Server.cs b/Application/Server.cs index 02e76f6e0..3d8fa41f6 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -26,6 +26,7 @@ namespace IW4MAdmin { private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex; private GameLogEventDetection LogEvent; + public int Id { get; private set; } public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) { @@ -33,30 +34,28 @@ namespace IW4MAdmin public override int GetHashCode() { - if (GameName == Game.IW4) + if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28965") { - // todo: make this better with collisions - int id = Math.Abs($"{IP}:{Port.ToString()}".Select(a => (int)a).Sum()); - - // hack: this is a nasty fix for get hashcode being changed - switch (id) - { - case 765: - return 886229536; - case 760: - return 1645744423; - case 761: - return 1645809959; - } - - return id; + return 886229536; } - else + if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28960") { - int id = HashCode.Combine(IP, Port); - return id < 0 ? Math.Abs(id) : id; + return 1645744423; } + + if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28970") + { + return 1645809959; + } + + if (Id == 0) + { + Id = HashCode.Combine(IP, Port); + Id = Id < 0 ? Math.Abs(Id) : Id; + } + + return Id; } public async Task OnPlayerJoined(Player logClient) @@ -560,11 +559,13 @@ namespace IW4MAdmin if (E.Type == GameEvent.EventType.Broadcast) { +#if DEBUG == false // this is a little ugly but I don't want to change the abstract class if (E.Data != null) { await E.Owner.ExecuteCommandAsync(E.Data); } +#endif } while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2)) @@ -661,7 +662,7 @@ namespace IW4MAdmin waiterList.Add(e); } // wait for all the disconnect tasks to finish - await Task.WhenAll(waiterList.Select(e => e.WaitAsync())); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); waiterList.Clear(); // this are our new connecting clients @@ -686,7 +687,7 @@ namespace IW4MAdmin } // wait for all the connect tasks to finish - await Task.WhenAll(waiterList.Select(e => e.WaitAsync())); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); if (ConnectionErrors > 0) { @@ -700,7 +701,7 @@ namespace IW4MAdmin catch (NetworkException e) { ConnectionErrors++; - if (ConnectionErrors == 1) + if (ConnectionErrors == 3) { Logger.WriteError($"{e.Message} {IP}:{Port}, {loc["SERVER_ERROR_POLLING"]}"); Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}"); diff --git a/Application/SharedGUIDKick.js b/Application/SharedGUIDKick.js new file mode 100644 index 000000000..8de2311d9 --- /dev/null +++ b/Application/SharedGUIDKick.js @@ -0,0 +1,31 @@ +var plugin = { + author: 'RaidMax', + version: 1.1, + name: 'Shared GUID Kicker Plugin', + + onEventAsync: function (gameEvent, server) { + // make sure we only check for IW4(x) + if (server.GameName !== 2) { + return false; + + + } + + // connect or join event + if (gameEvent.Type === 3) { + // this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID + if (gameEvent.Origin.NetworkId === -805366929435212061) { + gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient); + } + } + }, + + onLoadAsync: function (manager) { + }, + + onUnloadAsync: function () { + }, + + onTickAsync: function (server) { + } +}; \ No newline at end of file diff --git a/Application/VPNDetection.js b/Application/VPNDetection.js new file mode 100644 index 000000000..32dc4e6c8 --- /dev/null +++ b/Application/VPNDetection.js @@ -0,0 +1,62 @@ +var plugin = { + author: 'RaidMax', + version: 1.0, + name: 'VPN Detection Plugin', + + manager: null, + logger: null, + vpnExceptionIds: [], + + checkForVpn: function (origin) { + var exempt = false; + // prevent players that are exempt from being kicked + this.vpnExceptionIds.forEach(function (id) { + if (id === origin.ClientId) { + exempt = true; + return false; + } + }); + + if (exempt) { + return; + } + + var usingVPN = false; + + try { + var cl = new System.Net.Http.HttpClient(); + var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result; + var co = re.Content; + var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result); + co.Dispose(); + re.Dispose(); + cl.Dispose(); + usingVPN = parsedJSON.success && parsedJSON.proxy; + } catch (e) { + this.logger.WriteError(e.message); + } + + if (usingVPN) { + this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')'); + origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient); + } + }, + + onEventAsync: function (gameEvent, server) { + // connect event + if (gameEvent.Type === 3) { + this.checkForVpn(gameEvent.Origin); + } + }, + + onLoadAsync: function (manager) { + this.manager = manager; + this.logger = manager.GetLogger(0); + }, + + onUnloadAsync: function () { + }, + + onTickAsync: function (server) { + } +}; \ No newline at end of file diff --git a/Plugins/IW4ScriptCommands/Commands/Balance.cs b/Plugins/IW4ScriptCommands/Commands/Balance.cs index 3547a5ff9..1cdf59703 100644 --- a/Plugins/IW4ScriptCommands/Commands/Balance.cs +++ b/Plugins/IW4ScriptCommands/Commands/Balance.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace IW4ScriptCommands.Commands { class Balance - { + { private class TeamAssignment { public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; } @@ -17,33 +17,39 @@ namespace IW4ScriptCommands.Commands public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; } } - public static string GetTeamAssignments(Player client, string teamsString = "") + public static string GetTeamAssignments(Player client, bool isDisconnect, Server server, string teamsString = "") { var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries) .Select(c => c.Split(',')) .Select(c => new TeamAssignment() { CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]), - Num = client.CurrentServer.GetPlayersAsList().FirstOrDefault(p => p.ClientNumber== Int32.Parse(c[0]))?.ClientNumber ?? -1, - Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(client.CurrentServer.Players.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, client.CurrentServer.GetHashCode()) + Num = server.GetPlayersAsList().FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0]))?.ClientNumber ?? -1, + Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(server.Players.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, server.GetHashCode()) }) .ToList(); // at least one team is full so we can't balance - if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(client.CurrentServer.MaxClients / 2.0) - || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(client.CurrentServer.MaxClients / 2.0)) + if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(server.MaxClients / 2.0) + || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(server.MaxClients / 2.0)) { - // E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]); + // E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]); return string.Empty; } List teamAssignments = new List(); - var activeClients = client.CurrentServer.GetPlayersAsList().Select(c => new TeamAssignment() + var _c = server.GetPlayersAsList(); + if (isDisconnect && client != null) + { + _c = _c.Where(c => c.ClientNumber != client.ClientNumber).ToList(); + } + + var activeClients = _c.Select(c => new TeamAssignment() { Num = c.ClientNumber, - Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, client.CurrentServer.GetHashCode()), - CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, client.CurrentServer.GetHashCode()).Team + Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.GetHashCode()), + CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.GetHashCode()).Team }) .Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator) .Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam) diff --git a/Plugins/IW4ScriptCommands/GscApiController.cs b/Plugins/IW4ScriptCommands/GscApiController.cs index d4121ad13..0b02deadc 100644 --- a/Plugins/IW4ScriptCommands/GscApiController.cs +++ b/Plugins/IW4ScriptCommands/GscApiController.cs @@ -35,14 +35,18 @@ namespace WebfrontCore.Controllers.API } [HttpGet("{networkId}")] - public IActionResult GetTeamAssignments(string networkId, string teams = "") + public IActionResult GetTeamAssignments(string networkId, int serverId, string teams = "", bool isDisconnect = false) { + return Unauthorized(); + var client = Manager.GetActiveClients() - .First(c => c.NetworkId == networkId.ConvertLong()); + .FirstOrDefault(c => c.NetworkId == networkId.ConvertLong()); + + var server = Manager.GetServers().First(c => c.GetHashCode() == serverId); teams = teams ?? string.Empty; - string assignments = Balance.GetTeamAssignments(client, teams); + string assignments = Balance.GetTeamAssignments(client, isDisconnect, server, teams); return Content(assignments); } diff --git a/Plugins/IW4ScriptCommands/Plugin.cs b/Plugins/IW4ScriptCommands/Plugin.cs index 37c54fbec..82ee95a2f 100644 --- a/Plugins/IW4ScriptCommands/Plugin.cs +++ b/Plugins/IW4ScriptCommands/Plugin.cs @@ -17,15 +17,10 @@ namespace IW4ScriptCommands public Task OnEventAsync(GameEvent E, Server S) { - //if (E.Type == GameEvent.EventType.JoinTeam || E.Type == GameEvent.EventType.Disconnect) - //{ - // E.Origin = new SharedLibraryCore.Objects.Player() - // { - // ClientId = 1, - // CurrentServer = E.Owner - // }; - // return new Commands.Balance().ExecuteAsync(E); - //} + if (E.Type == GameEvent.EventType.Start) + { + return S.SetDvarAsync("sv_iw4madmin_serverid", S.GetHashCode()); + } if (E.Type == GameEvent.EventType.Warn) { diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index e2ef5745c..280fb79b6 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -265,7 +265,7 @@ namespace SharedLibraryCore.Commands public override Task ExecuteAsync(GameEvent E) { - String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString); + String You = String.Format("{0} [^3#{1}^7] {2} ^7[^3@{3}^7] ^7[{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString); E.Origin.Tell(You); return Task.CompletedTask; diff --git a/SharedLibraryCore/Interfaces/IGameLogReader.cs b/SharedLibraryCore/Interfaces/IGameLogReader.cs index 56ad2d3ea..7dcbf76d2 100644 --- a/SharedLibraryCore/Interfaces/IGameLogReader.cs +++ b/SharedLibraryCore/Interfaces/IGameLogReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace SharedLibraryCore.Interfaces { @@ -16,7 +17,7 @@ namespace SharedLibraryCore.Interfaces /// /// /// - ICollection ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); + Task> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); /// /// how long the log file is /// diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index ab311fa89..0263e3821 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -89,41 +89,43 @@ namespace SharedLibraryCore.RCon byte[] response = null; retrySend: - connectionState.SendEventArgs.UserToken = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) { - DontFragment = true, - Ttl = 42, + //DontFragment = true, + Ttl = 100, ExclusiveAddressUse = true, - }; - - connectionState.OnSentData.Reset(); - connectionState.OnReceivedData.Reset(); - connectionState.ConnectionAttempts++; + }) + { + connectionState.SendEventArgs.UserToken = socket; + connectionState.OnSentData.Reset(); + connectionState.OnReceivedData.Reset(); + connectionState.ConnectionAttempts++; #if DEBUG == true - Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); + Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); #endif - try - { - response = await SendPayloadAsync(payload, waitForResponse); - connectionState.OnComplete.Release(1); - connectionState.ConnectionAttempts = 0; - } - - catch/* (Exception ex)*/ - { - if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) + try { - // Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); - await Task.Delay(StaticHelpers.FloodProtectionInterval); - goto retrySend; + response = await SendPayloadAsync(payload, waitForResponse); + connectionState.OnComplete.Release(1); + connectionState.ConnectionAttempts = 0; } - connectionState.OnComplete.Release(1); - //Log.WriteDebug(ex.GetExceptionInfo()); - throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]"); + catch/* (Exception ex)*/ + { + if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) + { + // Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); + await Task.Delay(StaticHelpers.FloodProtectionInterval); + goto retrySend; + } + + connectionState.OnComplete.Release(1); + //Log.WriteDebug(ex.GetExceptionInfo()); + throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]"); + } } - string responseString = Utilities.EncodingType.GetString(response, 0, response.Length).TrimEnd('\0') + '\n'; + string responseString = Utilities.EncodingType.GetString(response, 0, response.Length) + '\n'; if (responseString.Contains("Invalid password")) { @@ -135,9 +137,12 @@ namespace SharedLibraryCore.RCon throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_NOTSET"]); } + Log.WriteInfo(responseString); + string[] splitResponse = responseString.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(line => line.Trim()).ToArray(); return splitResponse; + } private async Task SendPayloadAsync(byte[] payload, bool waitForResponse) diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index e7924c8f5..f229866f8 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -117,19 +117,14 @@ namespace SharedLibraryCore /// Message to be sent to all players public GameEvent Broadcast(string message, Player sender = null) { -#if DEBUG == false string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{message}"); -#else - Logger.WriteVerbose(message.StripColors()); -#endif + + //Logger.WriteVerbose(message.StripColors()); + var e = new GameEvent() { Type = GameEvent.EventType.Broadcast, -#if DEBUG == true - Data = message, -#else Data = formattedMessage, -#endif Owner = this, Origin = sender, }; diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index 7745d4b88..6c776e18d 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -145,7 +145,7 @@ namespace SharedLibraryCore.Services PunisherId = penalty.PunisherId, Offense = penalty.Offense, Type = penalty.Type.ToString(), - TimeRemaining = now > penalty.Expires ? "" : penalty.Expires.ToString(), + TimeRemaining = penalty.Expires.HasValue ? (now > penalty.Expires ? "" : penalty.Expires.ToString()) : DateTime.MaxValue.ToString(), AutomatedOffense = penalty.AutomatedOffense }, When = penalty.When, @@ -160,7 +160,7 @@ namespace SharedLibraryCore.Services ((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString(); var pi = ((PenaltyInfo)p.Value); - if (pi.TimeRemaining.Length > 0) + if (pi.TimeRemaining?.Length > 0) pi.TimeRemaining = (DateTime.Parse(((PenaltyInfo)p.Value).TimeRemaining) - now).TimeSpanText(); }); diff --git a/WebfrontCore/Controllers/PenaltyController.cs b/WebfrontCore/Controllers/PenaltyController.cs index 00850d79a..2a7c8bb93 100644 --- a/WebfrontCore/Controllers/PenaltyController.cs +++ b/WebfrontCore/Controllers/PenaltyController.cs @@ -52,7 +52,7 @@ namespace WebfrontCore.Controllers PunisherId = p.PunisherId, Type = p.Type.ToString(), TimePunished = p.When.ToString(), - TimeRemaining = p.Expires.ToString(), + TimeRemaining = "", AutomatedOffense = p.AutomatedOffense }).ToList(); diff --git a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs index bc1839cbe..e8a64b9bd 100644 --- a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs +++ b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs @@ -30,7 +30,7 @@ namespace WebfrontCore.ViewComponents Type = p.Type.ToString(), TimePunished = Utilities.GetTimePassed(p.When, false), // show time passed if ban - TimeRemaining = DateTime.UtcNow > p.Expires ? "" : $"{(p.Expires.Value.Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(p.When, true) : Utilities.TimeSpanText(p.Expires.Value - DateTime.UtcNow))}", + TimeRemaining = DateTime.UtcNow > p.Expires ? "" : $"{((p.Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(p.When, true) : Utilities.TimeSpanText((p.Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}", Sensitive = p.Type == Penalty.PenaltyType.Flag, AutomatedOffense = p.AutomatedOffense }); diff --git a/_commands.gsc b/_commands.gsc index 4d90ad4af..344aee645 100644 --- a/_commands.gsc +++ b/_commands.gsc @@ -1,10 +1,65 @@ +#include common_scripts\utility; #include maps\mp\_utility; #include maps\mp\gametypes\_hud_util; -#include common_scripts\utility; +#include maps\mp\gametypes\_playerlogic; init() { - level thread WaitForCommand(); + SetDvarIfUninitialized("sv_team_balance_assignments", ""); + SetDvarIfUninitialized("sv_iw4madmin_serverid", 0); + SetDvarIfUninitialized("sv_iw4madmin_apiurl", "http://127.0.0.1:1624/api/gsc/"); + level.apiUrl = GetDvar("sv_iw4madmin_apiurl"); + //level thread WaitForCommand(); + level thread onPlayerConnect(); + level thread onPlayerDisconnect(); +} + +onPlayerConnect() +{ + for(;;) + { + level waittill( "connected", player ); + player thread onJoinedTeam(); + } +} + +onPlayerDisconnect() +{ + for(;;) + { + level waittill( "disconnected", player ); + logPrint("player disconnected\n"); + level.players[0] SetTeamBalanceAssignments(true); + } +} + +onJoinedTeam() +{ + self endon("disconnect"); + + for(;;) + { + self waittill( "joined_team" ); + self SetTeamBalanceAssignments(false); + } +} + +SetTeamBalanceAssignments(isDisconnect) +{ + assignments = GetDvar("sv_team_balance_assignments"); + dc = ""; + if (isDisconnect) + { + dc = "&isDisconnect=true"; + } + url = level.apiUrl + "GetTeamAssignments/" + self.guid + "/?teams=" + assignments + dc + "&serverId=" + GetDvar("sv_iw4madmin_serverid"); + newAssignments = GetHttpString(url); + SetDvar("sv_team_balance_assignments", newAssignments.data); + + if (newAssignments.success) + { + BalanceTeams(strtok(newAssignments.data, ",")); + } } WaitForCommand() @@ -33,41 +88,79 @@ WaitForCommand() SendAlert(clientId, alertType, sound, message) { - client = playerForClientId(clientId); + client = getPlayerFromClientNum(clientId); client thread playLeaderDialogOnPlayer(sound, client.team); client playLocalSound(sound); client iPrintLnBold("^1" + alertType + ": ^3" + message); } +GetHttpString(url) +{ + response = spawnStruct(); + response.success = false; + response.data = undefined; + + logPrint("Making request to " + url + "\n"); + request = httpGet(url); + request waittill("done", success, data); + + if(success != 0){ + logPrint("Request succeeded\n"); + response.success = true; + response.data = data; + } + + else + { + logPrint("Request failed\n"); + } + + return response; +} + BalanceTeams(commandArgs) { - if (isRoundBased()) + if (level.teamBased) { - iPrintLnBold("Balancing Teams.."); + printOnPlayers("^5Balancing Teams..."); for (i = 0; i < commandArgs.size; i+= 2) { - teamNum = commandArgs[i+1]; - clientNum = commandArgs[i]; - if (teamNum == "0") + teamNum = int(commandArgs[i+1]); + clientNum = int(commandArgs[i]); + + //printOnPlayers("[" + teamNum + "," + clientNum + "]"); + + if (teamNum == 2) + { newTeam = "allies"; + } else + { newTeam = "axis"; - player = level.players[clientNum]; + } + + player = getPlayerFromClientNum(clientNum); - if (!isPlayer(player)) - continue; - - iPrintLnBold(player.name + " " + teamNum); + //if (!isPlayer(player)) + // continue; switch (newTeam) { case "axis": - player[[level.axis]](); + if (player.team != "axis") + { + //printOnPlayers("moving " + player.name + " to axis"); + player[[level.axis]](); + } break; case "allies": - player[[level.allies]](); + if (player.team != "allies") + { + //printOnPlayers("moving " + player.name + " to allies"); + player[[level.allies]](); + } break; } } diff --git a/_customcallbacks.gsc b/_customcallbacks.gsc index c6d3ac409..3754416b1 100644 --- a/_customcallbacks.gsc +++ b/_customcallbacks.gsc @@ -8,10 +8,14 @@ init() SetDvarIfUninitialized("sv_framewaittime", 0.05); SetDvarIfUninitialized("sv_additionalwaittime", 0.05); SetDvarIfUninitialized("sv_maxstoredframes", 3); + level thread onPlayerConnect(); + level waittill("prematch_over"); level.callbackPlayerKilled = ::Callback_PlayerKilled; level.callbackPlayerDamage = ::Callback_PlayerDamage; + level.callbackPlayerDisconnect = ::Callback_PlayerDisconnect; + level.playerTags = []; level.playerTags[0] = "j_head"; level.playerTags[1] = "j_neck"; @@ -240,4 +244,10 @@ Callback_PlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vD { Process_Hit("Kill", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon); self maps\mp\gametypes\_damage::Callback_PlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ); +} + +Callback_PlayerDisconnect() +{ + level notify("disconnected", self); + self maps\mp\gametypes\_playerlogic::Callback_PlayerDisconnect(); } \ No newline at end of file