From f186e3ae4dd305ca44d2092c031f39268e8998ae Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 26 May 2023 21:14:49 -0500 Subject: [PATCH 01/11] add command to set log level and develop mode dynamically --- Application/Commands/SetLogLevelCommand.cs | 80 +++++++++++++++++++++ Application/Extensions/StartupExtensions.cs | 32 +++++++-- SharedLibraryCore/Utilities.cs | 2 +- 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 Application/Commands/SetLogLevelCommand.cs diff --git a/Application/Commands/SetLogLevelCommand.cs b/Application/Commands/SetLogLevelCommand.cs new file mode 100644 index 000000000..250b139fe --- /dev/null +++ b/Application/Commands/SetLogLevelCommand.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using Data.Models.Client; +using Serilog.Core; +using Serilog.Events; +using SharedLibraryCore; +using SharedLibraryCore.Commands; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Interfaces; + +namespace IW4MAdmin.Application.Commands; + +public class SetLogLevelCommand : Command +{ + private readonly Func _levelSwitchResolver; + + public SetLogLevelCommand(CommandConfiguration config, ITranslationLookup layout, Func levelSwitchResolver) : base(config, layout) + { + _levelSwitchResolver = levelSwitchResolver; + + Name = "loglevel"; + Alias = "ll"; + Description = "set minimum logging level"; + Permission = EFClient.Permission.Owner; + Arguments = new CommandArgument[] + { + new() + { + Name = "Log Level", + Required = true + }, + new() + { + Name = "Override", + Required = false + }, + new() + { + Name = "IsDevelopment", + Required = false + } + }; + + } + + public override async Task ExecuteAsync(GameEvent gameEvent) + { + var args = gameEvent.Data.Split(" "); + if (!Enum.TryParse(args[0], out var minLevel)) + { + await gameEvent.Origin.TellAsync(new[] + { + $"Valid log values: {string.Join(",", Enum.GetValues())}" + }); + return; + } + + var context = string.Empty; + + if (args.Length > 1) + { + context = args[1]; + } + + var loggingSwitch = _levelSwitchResolver(context); + loggingSwitch.MinimumLevel = minLevel; + + if (args.Length > 2 && (args[2] == "1" || args[2].ToLower() == "true")) + { + AppContext.SetSwitch("IsDevelop", true); + } + else + { + AppContext.SetSwitch("IsDevelop", false); + } + + await gameEvent.Origin.TellAsync(new[] + { $"Set minimum log level to {loggingSwitch.MinimumLevel.ToString()}" }); + } +} diff --git a/Application/Extensions/StartupExtensions.cs b/Application/Extensions/StartupExtensions.cs index 907ac8899..ff4498ee2 100644 --- a/Application/Extensions/StartupExtensions.cs +++ b/Application/Extensions/StartupExtensions.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; +using Serilog.Core; using Serilog.Events; using SharedLibraryCore; using SharedLibraryCore.Configuration; @@ -17,7 +18,10 @@ namespace IW4MAdmin.Application.Extensions { public static class StartupExtensions { - private static ILogger _defaultLogger = null; + private static ILogger _defaultLogger; + private static readonly LoggingLevelSwitch LevelSwitch = new(); + private static readonly LoggingLevelSwitch MicrosoftLevelSwitch = new(); + private static readonly LoggingLevelSwitch SystemLevelSwitch = new(); public static IServiceCollection AddBaseLogger(this IServiceCollection services, ApplicationConfiguration appConfig) @@ -29,21 +33,37 @@ namespace IW4MAdmin.Application.Extensions .Build(); var loggerConfig = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning); + .ReadFrom.Configuration(configuration); + + LevelSwitch.MinimumLevel = Enum.Parse(configuration["Serilog:MinimumLevel:Default"]); + MicrosoftLevelSwitch.MinimumLevel = Enum.Parse(configuration["Serilog:MinimumLevel:Override:Microsoft"]); + SystemLevelSwitch.MinimumLevel = Enum.Parse(configuration["Serilog:MinimumLevel:Override:System"]); + + loggerConfig = loggerConfig.MinimumLevel.ControlledBy(LevelSwitch); + loggerConfig = loggerConfig.MinimumLevel.Override("Microsoft", MicrosoftLevelSwitch) + .MinimumLevel.Override("System", SystemLevelSwitch); if (Utilities.IsDevelopment) { loggerConfig = loggerConfig.WriteTo.Console( outputTemplate: - "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}") - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .MinimumLevel.Debug(); + "[{Timestamp:HH:mm:ss} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}") + ; //.MinimumLevel.Override("Microsoft", LogEventLevel.Information) + //.MinimumLevel.Debug(); } _defaultLogger = loggerConfig.CreateLogger(); } + services.AddSingleton((string context) => + { + return context.ToLower() switch + { + "microsoft" => MicrosoftLevelSwitch, + "system" => SystemLevelSwitch, + _ => LevelSwitch + }; + }); services.AddLogging(builder => builder.AddSerilog(_defaultLogger, dispose: true)); services.AddSingleton(new LoggerFactory() .AddSerilog(_defaultLogger, true)); diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 41437d4da..2a30ac1e0 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -1145,7 +1145,7 @@ namespace SharedLibraryCore /// /// public static bool IsDevelopment => - Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development"; + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" || AppContext.TryGetSwitch("IsDevelop", out _); /// /// replaces any directory separator chars with the platform specific character From cae77357cae555da34e026443a1120cafe27636c Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 27 May 2023 11:02:57 -0500 Subject: [PATCH 02/11] fix rule spacing on about page --- WebfrontCore/Views/About/Index.cshtml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebfrontCore/Views/About/Index.cshtml b/WebfrontCore/Views/About/Index.cshtml index 5b3379eef..f249012d6 100644 --- a/WebfrontCore/Views/About/Index.cshtml +++ b/WebfrontCore/Views/About/Index.cshtml @@ -70,7 +70,7 @@ } var start = 1; -
+
@foreach (var rule in rules) @@ -86,6 +86,7 @@ start++; } +
} From 35c4bbd2d52bd6b4e6d4c315cde97ecc0f083c21 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 27 May 2023 12:15:22 -0500 Subject: [PATCH 03/11] possible fix for remotely loaded plugins --- Application/Misc/RemoteAssemblyHandler.cs | 28 ++--- Application/Plugin/PluginImporter.cs | 141 ++++++++++++---------- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/Application/Misc/RemoteAssemblyHandler.cs b/Application/Misc/RemoteAssemblyHandler.cs index f2214d99c..6b7671b1e 100644 --- a/Application/Misc/RemoteAssemblyHandler.cs +++ b/Application/Misc/RemoteAssemblyHandler.cs @@ -13,10 +13,10 @@ namespace IW4MAdmin.Application.Misc { public class RemoteAssemblyHandler : IRemoteAssemblyHandler { - private const int keyLength = 32; - private const int tagLength = 16; - private const int nonceLength = 12; - private const int iterationCount = 10000; + private const int KeyLength = 32; + private const int TagLength = 16; + private const int NonceLength = 12; + private const int IterationCount = 10000; private readonly ApplicationConfiguration _appconfig; private readonly ILogger _logger; @@ -30,7 +30,7 @@ namespace IW4MAdmin.Application.Misc public IEnumerable DecryptAssemblies(string[] encryptedAssemblies) { return DecryptContent(encryptedAssemblies) - .Select(decryptedAssembly => Assembly.Load(decryptedAssembly)); + .Select(Assembly.Load); } public IEnumerable DecryptScripts(string[] encryptedScripts) @@ -38,24 +38,24 @@ namespace IW4MAdmin.Application.Misc return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript)); } - private byte[][] DecryptContent(string[] content) + private IEnumerable DecryptContent(string[] content) { if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId)) { _logger.LogWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts"); - return new byte[0][]; + return Array.Empty(); } var assemblies = content.Select(piece => { - byte[] byteContent = Convert.FromBase64String(piece); - byte[] encryptedContent = byteContent.Take(byteContent.Length - (tagLength + nonceLength)).ToArray(); - byte[] tag = byteContent.Skip(byteContent.Length - (tagLength + nonceLength)).Take(tagLength).ToArray(); - byte[] nonce = byteContent.Skip(byteContent.Length - nonceLength).Take(nonceLength).ToArray(); - byte[] decryptedContent = new byte[encryptedContent.Length]; + var byteContent = Convert.FromBase64String(piece); + var encryptedContent = byteContent.Take(byteContent.Length - (TagLength + NonceLength)).ToArray(); + var tag = byteContent.Skip(byteContent.Length - (TagLength + NonceLength)).Take(TagLength).ToArray(); + var nonce = byteContent.Skip(byteContent.Length - NonceLength).Take(NonceLength).ToArray(); + var decryptedContent = new byte[encryptedContent.Length]; - var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id.ToString()), iterationCount, HashAlgorithmName.SHA512); - var encryption = new AesGcm(keyGen.GetBytes(keyLength)); + var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id), IterationCount, HashAlgorithmName.SHA512); + var encryption = new AesGcm(keyGen.GetBytes(KeyLength)); try { diff --git a/Application/Plugin/PluginImporter.cs b/Application/Plugin/PluginImporter.cs index cc5cb0804..9df88890a 100644 --- a/Application/Plugin/PluginImporter.cs +++ b/Application/Plugin/PluginImporter.cs @@ -20,13 +20,21 @@ namespace IW4MAdmin.Application.Plugin public class PluginImporter : IPluginImporter { private IEnumerable _pluginSubscription; - private static readonly string PluginDir = "Plugins"; + private const string PluginDir = "Plugins"; private const string PluginV2Match = "^ *((?:var|const|let) +init)|function init"; private readonly ILogger _logger; private readonly IRemoteAssemblyHandler _remoteAssemblyHandler; private readonly IMasterApi _masterApi; private readonly ApplicationConfiguration _appConfig; + private static readonly Type[] FilterTypes = + { + typeof(IPlugin), + typeof(IPluginV2), + typeof(Command), + typeof(IBaseConfiguration) + }; + public PluginImporter(ILogger logger, ApplicationConfiguration appConfig, IMasterApi masterApi, IRemoteAssemblyHandler remoteAssemblyHandler) { @@ -77,74 +85,80 @@ namespace IW4MAdmin.Application.Plugin public (IEnumerable, IEnumerable, IEnumerable) DiscoverAssemblyPluginImplementations() { var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}"; - var pluginTypes = Enumerable.Empty(); - var commandTypes = Enumerable.Empty(); - var configurationTypes = Enumerable.Empty(); + var pluginTypes = new List(); + var commandTypes = new List(); + var configurationTypes = new List(); - if (Directory.Exists(pluginDir)) + if (!Directory.Exists(pluginDir)) { - var dllFileNames = Directory.GetFiles(pluginDir, "*.dll"); - _logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length); + return (pluginTypes, commandTypes, configurationTypes); + } - if (dllFileNames.Length > 0) + var dllFileNames = Directory.GetFiles(pluginDir, "*.dll"); + _logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length); + + if (!dllFileNames.Any()) + { + return (pluginTypes, commandTypes, configurationTypes); + } + + // we only want to load the most recent assembly in case of duplicates + var assemblies = dllFileNames.Select(Assembly.LoadFrom) + .Union(GetRemoteAssemblies()) + .GroupBy(assembly => assembly.FullName).Select(assembly => + assembly.OrderByDescending(asm => asm.GetName().Version).First()); + + var eligibleAssemblyTypes = assemblies + .SelectMany(asm => { - // we only want to load the most recent assembly in case of duplicates - var assemblies = dllFileNames.Select(name => Assembly.LoadFrom(name)) - .Union(GetRemoteAssemblies()) - .GroupBy(assembly => assembly.FullName).Select(assembly => assembly.OrderByDescending(asm => asm.GetName().Version).First()); + try + { + return asm.GetTypes(); + } + catch + { + return Enumerable.Empty(); + } + }).Where(type => + FilterTypes.Any(filterType => type.GetInterface(filterType.Name, false) != null) || + (type.IsClass && FilterTypes.Contains(type.BaseType))); + + foreach (var assemblyType in eligibleAssemblyTypes) + { + var isPlugin = + (assemblyType.GetInterface(nameof(IPlugin), false) ?? + assemblyType.GetInterface(nameof(IPluginV2), false)) != null && + (!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false); - pluginTypes = assemblies - .SelectMany(asm => - { - try - { - return asm.GetTypes(); - } - catch - { - return Enumerable.Empty(); - } - }) - .Where(assemblyType => (assemblyType.GetInterface(nameof(IPlugin), false) ?? assemblyType.GetInterface(nameof(IPluginV2), false)) != null) - .Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false); + if (isPlugin) + { + pluginTypes.Add(assemblyType); + continue; + } - _logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count()); + var isCommand = assemblyType.IsClass && assemblyType.BaseType == typeof(Command) && + (!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false); - commandTypes = assemblies - .SelectMany(asm =>{ - try - { - return asm.GetTypes(); - } - catch - { - return Enumerable.Empty(); - } - }) - .Where(assemblyType => assemblyType.IsClass && assemblyType.BaseType == typeof(Command)) - .Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false); + if (isCommand) + { + commandTypes.Add(assemblyType); + continue; + } - _logger.LogDebug("Discovered {Count} plugin commands", commandTypes.Count()); + var isConfiguration = assemblyType.IsClass && + assemblyType.GetInterface(nameof(IBaseConfiguration), false) != null && + (!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false); - configurationTypes = assemblies - .SelectMany(asm => { - try - { - return asm.GetTypes(); - } - catch - { - return Enumerable.Empty(); - } - }) - .Where(asmType => - asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null) - .Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false); - - _logger.LogDebug("Discovered {Count} configuration implementations", configurationTypes.Count()); + if (isConfiguration) + { + configurationTypes.Add(assemblyType); } } + _logger.LogDebug("Discovered {Count} plugin implementations", pluginTypes.Count); + _logger.LogDebug("Discovered {Count} plugin commands", pluginTypes.Count); + _logger.LogDebug("Discovered {Count} configuration implementations", pluginTypes.Count); + return (pluginTypes, commandTypes, configurationTypes); } @@ -152,10 +166,11 @@ namespace IW4MAdmin.Application.Plugin { try { - if (_pluginSubscription == null) - _pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; + _pluginSubscription ??= _masterApi + .GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; - return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray()); + return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription + .Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray()); } catch (Exception ex) @@ -169,9 +184,11 @@ namespace IW4MAdmin.Application.Plugin { try { - _pluginSubscription ??= _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; + _pluginSubscription ??= _masterApi + .GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; - return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray()); + return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription + .Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray()); } catch (Exception ex) From 399e082b613b0d304a43b803611fdc4e015f8ed2 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 27 May 2023 14:01:16 -0500 Subject: [PATCH 04/11] add assembly resolver to help with more permissive plugin references to iw4madmin libraries --- Application/Main.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Application/Main.cs b/Application/Main.cs index 73ad77e95..24797f074 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -61,6 +61,19 @@ namespace IW4MAdmin.Application public static async Task Main(string[] args) { AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); + AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) => + { + var libraryName = eventArgs.Name.Split(",").First(); + + var overrides = new[] { nameof(SharedLibraryCore), nameof(Stats) }; + if (!overrides.Contains(libraryName)) + { + return AppDomain.CurrentDomain.GetAssemblies().First(asm => asm.FullName == eventArgs.Name); + } + // added to be a bit more permissive with plugin references + return AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(asm => asm.FullName?.StartsWith(libraryName) ?? false); + }; Console.OutputEncoding = Encoding.UTF8; Console.ForegroundColor = ConsoleColor.Gray; @@ -413,9 +426,9 @@ namespace IW4MAdmin.Application commandConfigHandler.BuildAsync().GetAwaiter().GetResult(); var appConfig = appConfigHandler.Configuration(); - var masterUri = Utilities.IsDevelopment + var masterUri = /*Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") - : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl; + : appConfig?.MasterUrl ?? */new ApplicationConfiguration().MasterUrl; var httpClient = new HttpClient { BaseAddress = masterUri, From 108dddb5cca18f5b56a0460a9a1a5e2af59a7084 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 27 May 2023 14:09:57 -0500 Subject: [PATCH 05/11] update filter on assembly resolver --- Application/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Application/Main.cs b/Application/Main.cs index 24797f074..83101a043 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -68,7 +68,7 @@ namespace IW4MAdmin.Application var overrides = new[] { nameof(SharedLibraryCore), nameof(Stats) }; if (!overrides.Contains(libraryName)) { - return AppDomain.CurrentDomain.GetAssemblies().First(asm => asm.FullName == eventArgs.Name); + return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(asm => asm.FullName == eventArgs.Name); } // added to be a bit more permissive with plugin references return AppDomain.CurrentDomain.GetAssemblies() From cdf9485903d885d261f0ef21f2402bc09d6e23ea Mon Sep 17 00:00:00 2001 From: Amos Date: Sun, 28 May 2023 17:37:27 +0100 Subject: [PATCH 06/11] Resolves issue where muted player would be unmuted when flag penalty was removed (#303) * resolves issue where muted player would be unmuted when flag penalty was removed * Revert accidental code format --- Application/IW4MServer.cs | 2 +- SharedLibraryCore/Services/PenaltyService.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 4de860d4a..7c9b79dd3 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -540,7 +540,7 @@ namespace IW4MAdmin E.Target.SetLevel(Permission.User, E.Origin); await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId, - E.Target.GameName, E.Target.CurrentAlias?.IPAddress); + E.Target.GameName, E.Target.CurrentAlias?.IPAddress, new[] {EFPenalty.PenaltyType.Flag}); await Manager.GetPenaltyService().Create(unflagPenalty); Manager.QueueEvent(new ClientPenaltyRevokeEvent diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index a226fb784..aaef0708e 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -215,7 +215,8 @@ namespace SharedLibraryCore.Services return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); } - public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, Reference.Game game, int? ipAddress = null) + public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, Reference.Game game, int? ipAddress = null, + EFPenalty.PenaltyType[] penaltyTypes = null) { await using var context = _contextFactory.CreateContext(); var now = DateTime.UtcNow; @@ -226,6 +227,7 @@ namespace SharedLibraryCore.Services { var ids = activePenalties.Select(penalty => penalty.PenaltyId); await context.Penalties.Where(penalty => ids.Contains(penalty.PenaltyId)) + .Where(pen => penaltyTypes == null || penaltyTypes.Contains(pen.Type)) .ForEachAsync(penalty => { penalty.Active = false; From c26489d71ffa8631444cf2b7e0082ee43881534f Mon Sep 17 00:00:00 2001 From: SwordSWD <62721069+SwordSWD@users.noreply.github.com> Date: Sat, 22 Apr 2023 23:58:20 -0400 Subject: [PATCH 07/11] T5ZM Gametype and Maps --- Application/DefaultSettings.json | 42 +++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/Application/DefaultSettings.json b/Application/DefaultSettings.json index 7a6cc131c..a454bca99 100644 --- a/Application/DefaultSettings.json +++ b/Application/DefaultSettings.json @@ -311,6 +311,10 @@ { "Name": "tdm", "Alias": "Team Deathmatch" + }, + { + "Name": "zom", + "Alias": "Zombies" } ] }, @@ -1230,7 +1234,43 @@ { "Alias": "Zoo", "Name": "mp_zoo" - } + }, + { + "Alias": "Kino der Toten", + "Name": "zombie_theater" + }, + { + "Alias": "Five", + "Name": "zombie_pentagon" + }, + { + "Alias": "Ascension", + "Name": "zombie_cosmodrome" + }, + { + "Alias": "Call of the Dead", + "Name": "zombie_coast" + }, + { + "Alias": "Moon", + "Name": "zombie_moon" + }, + { + "Alias": "Nacht Der Untoten", + "Name": "zombie_cod5_prototype" + }, + { + "Alias": "Verrückt", + "Name": "zombie_cod5_asylum" + }, + { + "Alias": "Shi No Numa", + "Name": "zombie_cod5_sumpf" + }, + { + "Alias": "Der Riese", + "Name": "zombie_cod5_factory" + } ] }, { From 7d436ac0c5614639042ac0871df3dea45ff504b3 Mon Sep 17 00:00:00 2001 From: xerxes-at <5236639+xerxes-at@users.noreply.github.com> Date: Mon, 29 May 2023 03:15:52 +0200 Subject: [PATCH 08/11] Fix Game Interface / AC Callbacks + support for T5ZM, T6MP and T6ZM for the Game Interface. (#288) * Fix trying to write to a struct before its initialized. Same issue on IW4, IW5 and T5 game modules. * Fix path issues in the scripts + add support for t5zm. * Fix deploy.bat * Change paths inside the gsc scripts used to call functions in other scripts * Remove mp includes from base gsc file. #include maps\mp\_utility; #include maps\mp\gametypes\_hud_util; * Define GetXuid as overrideMethod as t5zm doesn't have it. * Define GetPlayerFromClientNum as getting all players is slightly different on t5zm. * Remove the precompiled gsc file for T6 as PlutoT6 can load uncompiled GSC now. * Fix _customcallbacks.gsc for T6 * Add T6 support to the game interface. * Update _integration_base.gsc use camelCase for functionName * Make sure the Setup functions are always called in the right order. Base -> shared -> game Otherwise we might write to structs before they are created. * Move functions interacting with the game from _base to _shared GetPlayerFromClientNum OnPlayerJoinedTeam OnPlayerJoinedSpectators GenerateJoinTeamString PlayerTrackingOnInterval SaveTrackingMetrics * Block execution until game specific setup is done Block _shared execution until the game specific file finished. This allows the game specific file to override the events in _shared. * Fix setup event flow Move check of sv_iw4madmin_integration_enabled dvar after waittill in _shared so _base has a chance to set it to 1. Move check of sv_iw4madmin_autobalance dvar to OnPlayerConnect in _shared so the game specific script has a chance to set the dvar. * ignore bots * add more spaces --- .../t6/scripts/mp/_customcallbacks.gsc | Bin 5522 -> 6696 bytes .../t6/scripts/mp/_customcallbacks.gsc.src | 263 -------- GameFiles/GameInterface/_integration_base.gsc | 138 +---- GameFiles/GameInterface/_integration_iw4x.gsc | 13 +- GameFiles/GameInterface/_integration_iw5.gsc | 47 +- .../GameInterface/_integration_shared.gsc | 147 ++++- GameFiles/GameInterface/_integration_t5.gsc | 51 +- GameFiles/GameInterface/_integration_t5zm.gsc | 552 +++++++++++++++++ GameFiles/GameInterface/_integration_t6.gsc | 571 ++++++++++++++++++ .../_integration_t6zm_helper.gsc | 86 +++ GameFiles/deploy.bat | 14 +- Plugins/ScriptPlugins/GameInterface.js | 22 +- 12 files changed, 1446 insertions(+), 458 deletions(-) delete mode 100644 GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src create mode 100644 GameFiles/GameInterface/_integration_t5zm.gsc create mode 100644 GameFiles/GameInterface/_integration_t6.gsc create mode 100644 GameFiles/GameInterface/_integration_t6zm_helper.gsc diff --git a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc b/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc index 909b029219601f7f5d88d23fa23a00cff3c5a0e9..133a6a59f366d87646ca71643872a1dd4e1cf06f 100644 GIT binary patch literal 6696 zcmcgxYfmFN7X6(33QtBmn>2I?XdY71jKp+AFIr|AEsXX9ppr?s5;sm&RCb`*nf~u{ z?sdIW1?=oYYBiMYd+qx=_rcdXUYKpD#9D6b&3bbaY@IgRJih+Fy7F+9* z^+FnRCl~j2urFM?Rbn)Io-9i7s7I-t(`yBy$$0YJi$pREaeER=Q=>e5k#H?Pgk1WqSSi?OKLw9S7T#Xa+0i zHq(QH!=oR@P}2Btdied(w*z`dzh~5gGQzD=B}0+K7e+p*^nDV?Y5~*11UeGKWoEtD z4as1;VvWS{u{a#{doXpKWx9?3K??ZQD}Y&kDem<~Fb0mWN!41q#VSZ1RLYO@jK;ZR z_^16{8>2#TET+@vQ(%qkazh=xE%!8XR%kiPZ>OuR2rg@sYSBVdYy9912R zzz3GYi<+IRMM?>RuR0ZY8(|GlzYETDIou;;HdIp`S%y-qTXK^r%* zcfVfm3xyUTV~>1m2(a*=FAl`vBt!i#6x1v2&Xa|tEOwRrl*DQ%uw;^j4{Q6VfufZ) zHq$~{g+!nX^7vp%&r6ltrm+}&3bIvtbbd^RyN9q=9zlAzZyw>b@(8k}P2i{-z2amw5!j~%x> zL(p=?3NY1F)nlryYvJB~wuP}#%XTHL^rnUPv&TEww@P{+E7ifj`+Umhe6 zO&jy>vl-)1MRIFgjh&Orz(W+-ox`jxue~n+j29kW7}PEj%LS)TCO$JrLc4s!V&|IS zmf^ZY>mB%SmY@KA0&`&bZwOsZV0F%baWLn6wS%-QW@~O6H<&QvHj$oXo}pNl!cLJP z2nSI-*2{K*RKi6?=Pi(6*uA-js#E;sHL9wc^`z(n*h+RNfYz4|aJ}3YcM7IZT+zHw zwz1n6G6EQv3m$1Rot(v?`kd*x)$~xchji{x-t01?T+1S|<}YLPV<=IoelZlE@Hv^` z$2&G?!$@O`b_%;bwb$Jt=Xd=nH&}xh8<{uo{T6D#Wdn+YMS@y(;Z(@7ldGa4>XDDu zgnzrEG7{-=rHvB9CXaVJhcge3^k)_3+SIs*%Ma8oVuBOC+Nhn`)fwwSycP1!4mEWz zh~-|d6l>3igI7SI7|PKlbTwpi3(}i#_1~H{Vn{jGSnkJjN=e^x)H58)JVa zdxygFX)jI`KlYDWn!b1xRT3{+}m))-7L&A%W#$>2c_wSb(po> z({ErDNjw`D;NjgGZ=s_JnSU23^8p@rM$8>^y2{6Ns%`oC=)0cGCvvMkcL&GMIZAds zcyP&6K5FgA4T~@;>{D<5yrSxB#ZhX-O8pI}2vbjLPzJv$$^Wd&wyB|^&H z7f1NV2+;30W?eq8heDmjkwFM1K!KcpCGPe7)X1gX0ftO%z@Uo+g5u{~S8-$m42dvY zB49;1gdWqUWu6;xDEn~%wdX&fvkW_e*hlw}!9jdgB0_i6bHmS=;Hk#w9__Ul(E322 zfGL^y;XnZpHCW;|a+b(?`L{V3iXUkL>oJ86h#ImEykapODz*7~bmf4|CREqgc~n+r z=S!2^VLF&B^&;?IGM&=6kmlamLdb*4+uQ%YZFyqY@H0Xw%za*Pvl_4#+2#eW;)%^k ztk?5x%EJtHuDUho`af?AHt(Oc1L36#Utx#;j_@5}UW_;WUs3$9-j6qH(@!4KvbkEcbp;ekyepAKNyROStS$FB5g_) z14)logRoQ>b@TZVcXV4I1-Be5EtHq0tD&Fw!yR=;&MkQ1j+AFH&gJ7>1rh} zV_{hCU%GVJMJsxG`M0cp+476}q*N^D-5p+Kv{Wp5qoGu)#W`b?D_+43N5|5o39lm4 zs~7j#=I8S~=(IEazTViGaSLwFJ3aI?Z!OdhrYCb+=FCisT5yRT(+kteNc-7zwa8XT zoP`~W9*Fs=b0qoB z%7wKY^>s%PzZMcm+1-&ZF<`FhXCx)1il6g~lJmlNmTobZXCp(;Er?ppKr)^ij^rJ`5!kSN)%X`5*SYhWxwdfM%<7uP0lpL z2G2Pbrrkdr`HIma0^vo8?NFW!HxBiy7~_IDJs$ zE>zV#Ub9IhoFI<(Xjs34vi@~mChpZQGY3T`2r5-5S(nEtS4yK^5TvO&QJ1L1=Y#qP z;xd$=;JL-1l#MD~{MsE5)GGXnjjPjZhBj3XixWRZ;1MXk;W z%UD}QKZs6J$U)zxwEDe1iB*}R(zWTISBH^4;3uSl7v%yK0~SBW}z@#3k|KH>)4Oq{1cR>xQ4^y_HW z+b5SVjqtiE<^0jw-5OsHZqYAfs+H(K%g5Ct&fH*R{RD5j5#+PThm)q3kcXv5rm&VL zo8%YAHmpp@Gk9c{?-;%pX8FwKguF4!HWL4-=dk4jITvWtITJF3-CMWk_mNt*XpBcM z-_+Kmq)C#vbCGXSn}5xc4^E-CEZ(Jc@yym}~8TtY}>4-yWEb_@h zty^a-ef~8ck|i|tNx$+(n#dJOAN=Dfo;$HsZ{1?FFP5y{l}yN2>Gfs3wO6F7ViM@r zEXlPmrgiXT;uo6t>ihL>{h*#qEYQXnZy#};Mwij4cR9Lw=K7Qz7DsRCIH0$6YF&<& zPJNe&t>}1y%unk|I}&M0M`IL`WAvYHIbM(Xb6tC_uDv;@--%iMEJI}=BlBtIBSu*X zX*bkpXBln7@AK;v9=FGEh`q!Z9`2DP^jqGk4-LwSQ;uQF>&7Q89F$J=%-R*ieTunm zKoYXm&=NjUMQ%p!N1i}lLjHv;pp?3i0pwH2cH|c1TgV~g7s&5S?b9M3Am7!DyYJMg zQyKUjScdn*8{jwLUGVGh0K5wxgI|GvgSSG{6!{W75AK3}@N;k-yb+GT9q@Wsg}dQ) z_zs~;*%yjEL|+u8{)4`$=Q;mNYvgrqFT@C>XacW zM0w*qZSQ#1j9b(x8oyB4rZ%klZ)#sm*1_l0+RkgYx~+a|z2#Xst77ffZ?bN;nhh1* z+0fM_(K&P)ue^{+$QFxP*j@XohEu*Do#|ZhOsu)EbSdztg9R&yHS;|m2`A<+@qRSEp3Tgt>Izvbecn*#?au8 zs0^oJP3kOhEQdMHjxdIz!Az%dZEDr@7|fIWrnYfiY9795v`sTZsk1XTOFYh@Q^}l~ zh-y!gBg?duzL67^Z0(6^TotgBh)#|b{a-spaXm*BA3aM{*W3zI*8P^r`D=6JN>hu; z0a4~wjeU63I%GX*J!}2edf9r^s@ArN zg^gPx|H=k&npSkQ*lE@_)(99px7NEJ6K73X;+QHy(rJuxr>JSi?3Hu)>xES^)Z?mQ z<1sBUwa8Nkx!ttAsT+K!>>Qss@-D!xC8*rBPw|M zUQ#=u);?Z~~{(I*Y88~5_O-S#2Y_+I;2)_Jdez<$zt z)IMytbR4x;^7oh1lwR2?txnVBD%jLtLY)zcoFn@Crr+Pmvw2WMGv}qFBL1d{q!^F>K*vAyWf3b6L&z$(BzkEf7W+dLZbJW zb?~h{2hbZVL;kUHi+^@A$;o;CD}%?EqwlqB`N5)vZ`{+O-kI~HIq z<7Rb3yBK{l@;s~iRbmFvzf8{l=JOKIDl0EQh@;(AH@Nrm%Pr)1D zQFs@84jzENhsWS6@Ne+%kk1!+3!Vqxg?*40iL8UXh-CzR0A3H<;BI(6{64$@J`FE| zubP&2CAba2Kbt#%OX2AKZ3p*{V?s1p&x~Z;Bn6U@8I*;$Ki|QHi@$vm@8b=pvl$n IT(}ed7n)%BwEzGB diff --git a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src b/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src deleted file mode 100644 index fbdf34fff..000000000 --- a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src +++ /dev/null @@ -1,263 +0,0 @@ -#include maps\mp\_utility; -#include maps\mp\gametypes\_hud_util; -#include common_scripts\utility; - -init() -{ - SetDvarIfUninitialized( "sv_customcallbacks", true ); - SetDvarIfUninitialized( "sv_framewaittime", 0.05 ); - SetDvarIfUninitialized( "sv_additionalwaittime", 0.1 ); - SetDvarIfUninitialized( "sv_maxstoredframes", 12 ); - SetDvarIfUninitialized( "sv_printradarupdates", 0 ); - SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 ); - SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" ); - - level thread onPlayerConnect(); - if (getDvarInt("sv_printradarupdates") == 1) - { - level thread runRadarUpdates(); - } - - level waittill( "prematch_over" ); - level.callbackPlayerKilled = ::Callback_PlayerKilled; - level.callbackPlayerDamage = ::Callback_PlayerDamage; - level.callbackPlayerDisconnect = ::Callback_PlayerDisconnect; -} - -//It's called slightly different in T6 -//set_dvar_if_unset(dvar, val, reset) -SetDvarIfUninitialized(dvar, val) -{ - set_dvar_if_unset(dvar,val); -} - -onPlayerConnect( player ) -{ - for( ;; ) - { - level waittill( "connected", player ); - player thread waitForFrameThread(); - player thread waitForAttack(); - } -} - -//Got added to T6 on April 2020 -waitForAttack() -{ - self endon( "disconnect" ); - - self notifyOnPlayerCommand( "player_shot", "+attack" ); - self.lastAttackTime = 0; - - for( ;; ) - { - self waittill( "player_shot" ); - - self.lastAttackTime = getTime(); - } -} - -runRadarUpdates() -{ - interval = getDvarInt( "sv_printradar_updateinterval" ); - - for ( ;; ) - { - for ( i = 0; i <= 17; i++ ) - { - player = level.players[i]; - - if ( isDefined( player ) ) - { - payload = player.guid + ";" + player.origin + ";" + player getPlayerAngles() + ";" + player.team + ";" + player.kills + ";" + player.deaths + ";" + player.score + ";" + player GetCurrentWeapon() + ";" + player.health + ";" + isAlive(player) + ";" + player.timePlayed["total"]; - logPrint( "LiveRadar;" + payload + "\n" ); - } - } - - wait( interval / 1000 ); - } -} - -hitLocationToBone( hitloc ) -{ - switch( hitloc ) - { - case "helmet": - return "j_helmet"; - case "head": - return "j_head"; - case "neck": - return "j_neck"; - case "torso_upper": - return "j_spineupper"; - case "torso_lower": - return "j_spinelower"; - case "right_arm_upper": - return "j_shoulder_ri"; - case "left_arm_upper": - return "j_shoulder_le"; - case "right_arm_lower": - return "j_elbow_ri"; - case "left_arm_lower": - return "j_elbow_le"; - case "right_hand": - return "j_wrist_ri"; - case "left_hand": - return "j_wrist_le"; - case "right_leg_upper": - return "j_hip_ri"; - case "left_leg_upper": - return "j_hip_le"; - case "right_leg_lower": - return "j_knee_ri"; - case "left_leg_lower": - return "j_knee_le"; - case "right_foot": - return "j_ankle_ri"; - case "left_foot": - return "j_ankle_le"; - default: - return "tag_origin"; - } -} - -waitForFrameThread() -{ - self endon( "disconnect" ); - - self.currentAnglePosition = 0; - self.anglePositions = []; - - for (i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++) - { - self.anglePositions[i] = self getPlayerAngles(); - } - - for( ;; ) - { - self.anglePositions[self.currentAnglePosition] = self getPlayerAngles(); - wait( getDvarFloat( "sv_framewaittime" ) ); - self.currentAnglePosition = (self.currentAnglePosition + 1) % getDvarInt( "sv_maxstoredframes" ); - } -} - -waitForAdditionalAngles( logString, beforeFrameCount, afterFrameCount ) -{ - currentIndex = self.currentAnglePosition; - wait( 0.05 * afterFrameCount ); - - self.angleSnapshot = []; - - for( j = 0; j < self.anglePositions.size; j++ ) - { - self.angleSnapshot[j] = self.anglePositions[j]; - } - - anglesStr = ""; - collectedFrames = 0; - i = currentIndex - beforeFrameCount; - - while (collectedFrames < beforeFrameCount) - { - fixedIndex = i; - if (i < 0) - { - fixedIndex = self.angleSnapshot.size - abs(i); - } - anglesStr += self.angleSnapshot[int(fixedIndex)] + ":"; - collectedFrames++; - i++; - } - - if (i == currentIndex) - { - anglesStr += self.angleSnapshot[i] + ":"; - i++; - } - - collectedFrames = 0; - - while (collectedFrames < afterFrameCount) - { - fixedIndex = i; - if (i > self.angleSnapshot.size - 1) - { - fixedIndex = i % self.angleSnapshot.size; - } - anglesStr += self.angleSnapshot[int(fixedIndex)] + ":"; - collectedFrames++; - i++; - } - - lastAttack = getTime() - self.lastAttackTime; - isAlive = isAlive(self); - - logPrint(logString + ";" + anglesStr + ";" + isAlive + ";" + lastAttack + "\n" ); -} - -vectorScale( vector, scale ) -{ - return ( vector[0] * scale, vector[1] * scale, vector[2] * scale ); -} - -Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ) -{ - if (sMeansOfDeath == "MOD_FALLING" || !isPlayer(attacker)) - { - return; - } - - victim = self; - _attacker = attacker; - - if ( !isPlayer( attacker ) && isDefined( attacker.owner ) ) - { - _attacker = attacker.owner; - } - - else if( !isPlayer( attacker ) && sMeansOfDeath == "MOD_FALLING" ) - { - _attacker = victim; - } - - location = victim GetTagOrigin( hitLocationToBone( sHitLoc ) ); - isKillstreakKill = false; - if(!isPlayer(attacker)) - { - isKillstreakKill = true; - } - if(maps/mp/killstreaks/_killstreaks::iskillstreakweapon(sWeapon)) - { - isKillstreakKill = true; - } - - logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + int(gettime()) + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0"; - attacker thread waitForAdditionalAngles( logLine, 2, 2 ); -} - -Callback_PlayerDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) -{ - if ( level.teamBased && isDefined( attacker ) && ( self != attacker ) && isDefined( attacker.team ) && ( self.pers[ "team" ] == attacker.team ) ) - { - return; - } - - if ( self.health - iDamage > 0 ) - { - self Process_Hit( "Damage", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ); - } - - self [[maps/mp/gametypes/_globallogic_player::callback_playerdamage]]( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); -} - -Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) -{ - Process_Hit( "Kill", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ); - self [[maps/mp/gametypes/_globallogic_player::callback_playerkilled]]( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ); -} - -Callback_PlayerDisconnect() -{ - level notify( "disconnected", self ); - self [[maps/mp/gametypes/_globallogic_player::callback_playerdisconnect]](); -} \ No newline at end of file diff --git a/GameFiles/GameInterface/_integration_base.gsc b/GameFiles/GameInterface/_integration_base.gsc index ef16b1047..fac8a6688 100644 --- a/GameFiles/GameInterface/_integration_base.gsc +++ b/GameFiles/GameInterface/_integration_base.gsc @@ -1,6 +1,4 @@ #include common_scripts\utility; -#include maps\mp\_utility; -#include maps\mp\gametypes\_hud_util; Init() { @@ -19,14 +17,17 @@ Setup() level.eventBus.timeoutKey = "timeout"; level.eventBus.timeout = 30; - level.commonFunctions = spawnstruct(); - level.commonFunctions.setDvar = "SetDvarIfUninitialized"; - level.commonFunctions.isBot = "IsBot"; + level.commonFunctions = spawnstruct(); + level.commonFunctions.setDvar = "SetDvarIfUninitialized"; + level.commonFunctions.isBot = "IsBot"; + level.commonFunctions.getXuid = "GetXuid"; + level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum"; level.commonKeys = spawnstruct(); level.notifyTypes = spawnstruct(); level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized"; + level.notifyTypes.sharedFunctionsInitialized = "SharedFunctionsInitialized"; level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized"; level.clientDataKey = "clientData"; @@ -102,9 +103,6 @@ OnPlayerConnect() } player thread OnPlayerSpawned(); - player thread OnPlayerJoinedTeam(); - player thread OnPlayerJoinedSpectators(); - player thread PlayerTrackingOnInterval(); } } @@ -119,30 +117,6 @@ OnPlayerSpawned() } } -OnPlayerJoinedTeam() -{ - self endon( "disconnect" ); - - for( ;; ) - { - self waittill( "joined_team" ); - // join spec and join team occur at the same moment - out of order logging would be problematic - wait( 0.25 ); - LogPrint( GenerateJoinTeamString( false ) ); - } -} - -OnPlayerJoinedSpectators() -{ - self endon( "disconnect" ); - - for( ;; ) - { - self waittill( "joined_spectators" ); - LogPrint( GenerateJoinTeamString( true ) ); - } -} - OnGameEnded() { for ( ;; ) @@ -187,20 +161,6 @@ PlayerSpawnEvents() self RequestClientBasicData(); } -PlayerTrackingOnInterval() -{ - self endon( "disconnect" ); - - for ( ;; ) - { - wait ( 120 ); - if ( IsAlive( self ) ) - { - self SaveTrackingMetrics(); - } - } -} - MonitorClientEvents() { level endon( "game_ended" ); @@ -344,37 +304,6 @@ DecrementClientMeta( metaKey, decrementValue, clientId ) SetClientMeta( metaKey, decrementValue, clientId, "decrement" ); } -GenerateJoinTeamString( isSpectator ) -{ - team = self.team; - - if ( IsDefined( self.joining_team ) ) - { - team = self.joining_team; - } - else - { - if ( isSpectator || !IsDefined( team ) ) - { - team = "spectator"; - } - } - - guid = self GetXuid(); - - if ( guid == "0" ) - { - guid = self.guid; - } - - if ( !IsDefined( guid ) || guid == "0" ) - { - guid = "undefined"; - } - - return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n"; -} - SetClientMeta( metaKey, metaValue, clientId, direction ) { data = "key=" + metaKey + "|value=" + metaValue; @@ -400,39 +329,6 @@ SetClientMeta( metaKey, metaValue, clientId, direction ) level thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self ); } -SaveTrackingMetrics() -{ - if ( !IsDefined( self.persistentClientId ) ) - { - return; - } - - LogDebug( "Saving tracking metrics for " + self.persistentClientId ); - - if ( !IsDefined( self.lastShotCount ) ) - { - self.lastShotCount = 0; - } - - currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]](); - change = currentShotCount - self.lastShotCount; - self.lastShotCount = currentShotCount; - - LogDebug( "Total Shots Fired increased by " + change ); - - if ( !IsDefined( change ) ) - { - change = 0; - } - - if ( change == 0 ) - { - return; - } - - IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId ); -} - BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) { if ( !IsDefined( data ) ) @@ -565,8 +461,8 @@ NotifyClientEventTimeout( eventType ) NotifyClientEvent( eventInfo ) { - origin = getPlayerFromClientNum( int( eventInfo[3] ) ); - target = getPlayerFromClientNum( int( eventInfo[4] ) ); + origin = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[3] ) ); + target = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[4] ) ); event = spawnstruct(); event.type = eventInfo[1]; @@ -608,24 +504,6 @@ NotifyClientEvent( eventInfo ) level notify( level.eventTypes.localClientEvent, client ); } -GetPlayerFromClientNum( clientNum ) -{ - if ( clientNum < 0 ) - { - return undefined; - } - - for ( i = 0; i < level.players.size; i++ ) - { - if ( level.players[i] getEntityNumber() == clientNum ) - { - return level.players[i]; - } - } - - return undefined; -} - AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite ) { if ( IsDefined( level.clientCommandCallbacks[commandName] ) && IsDefined( shouldOverwrite ) && !shouldOverwrite ) diff --git a/GameFiles/GameInterface/_integration_iw4x.gsc b/GameFiles/GameInterface/_integration_iw4x.gsc index 34970de19..a072e7347 100644 --- a/GameFiles/GameInterface/_integration_iw4x.gsc +++ b/GameFiles/GameInterface/_integration_iw4x.gsc @@ -2,8 +2,6 @@ Init() { - level.eventBus.gamename = "IW4"; - thread Setup(); } @@ -12,13 +10,15 @@ Setup() level endon( "game_ended" ); // it's possible that the notify type has not been defined yet so we have to hard code it - level waittill( "IntegrationBootstrapInitialized" ); + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "IW4"; scripts\_integration_base::RegisterLogger( ::Log2Console ); level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired; level.overrideMethods[level.commonFunctions.setDvar] = ::_SetDvarIfUninitialized; level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient; + level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID; level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout; level.overrideMethods[level.commonFunctions.changeTeam] = ::ChangeTeam; level.overrideMethods[level.commonFunctions.getTeamCounts] = ::CountPlayers; @@ -31,6 +31,8 @@ Setup() RegisterClientCommands(); + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + level notify( level.notifyTypes.gameFunctionsInitialized ); if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) @@ -199,6 +201,11 @@ Log2Console( logLevel, message ) PrintConsole( "[" + logLevel + "] " + message + "\n" ); } +_GetXUID() +{ + return self GetXUID(); +} + ////////////////////////////////// // GUID helpers ///////////////////////////////// diff --git a/GameFiles/GameInterface/_integration_iw5.gsc b/GameFiles/GameInterface/_integration_iw5.gsc index ed92e8c9b..75057e375 100644 --- a/GameFiles/GameInterface/_integration_iw5.gsc +++ b/GameFiles/GameInterface/_integration_iw5.gsc @@ -2,8 +2,6 @@ Init() { - level.eventBus.gamename = "IW5"; - thread Setup(); } @@ -12,17 +10,21 @@ Setup() level endon( "game_ended" ); // it's possible that the notify type has not been defined yet so we have to hard code it - level waittill( "IntegrationBootstrapInitialized" ); + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "IW5"; - scripts\mp\_integration_base::RegisterLogger( ::Log2Console ); + scripts\_integration_base::RegisterLogger( ::Log2Console ); level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired; level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized; level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout; level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient; + level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID; RegisterClientCommands(); + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + level notify( level.notifyTypes.gameFunctionsInitialized ); if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) @@ -54,17 +56,17 @@ OnPlayerConnect() RegisterClientCommands() { - scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); - scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); - scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); - scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); - scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); - scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); - scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); - scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); - scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); - scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); - scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); } WaitForClientEvents() @@ -73,13 +75,13 @@ WaitForClientEvents() // example of requesting a meta value lastServerMetaKey = "LastServerPlayed"; - // self scripts\mp\_integration_base::RequestClientMeta( lastServerMetaKey ); + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); for ( ;; ) { self waittill( level.eventTypes.localClientEvent, event ); - scripts\mp\_integration_base::LogDebug( "Received client event " + event.type ); + scripts\_integration_base::LogDebug( "Received client event " + event.type ); if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) { @@ -109,6 +111,11 @@ Log2Console( logLevel, message ) Print( "[" + logLevel + "] " + message + "\n" ); } +_GetXUID() +{ + return self GetXUID(); +} + ////////////////////////////////// // GUID helpers ///////////////////////////////// @@ -126,14 +133,14 @@ SetPersistentData() { // give IW4MAdmin time to collect IP wait( 15 ); - scripts\mp\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); - scripts\mp\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); return; } guid = self SplitGuid(); - scripts\mp\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); self SetPlayerData( "bests", "none", guid["high"] ); self SetPlayerData( "awards", "none", guid["low"] ); diff --git a/GameFiles/GameInterface/_integration_shared.gsc b/GameFiles/GameInterface/_integration_shared.gsc index 36a0ded01..2c0d87bd1 100644 --- a/GameFiles/GameInterface/_integration_shared.gsc +++ b/GameFiles/GameInterface/_integration_shared.gsc @@ -8,6 +8,9 @@ Setup() { level endon( "game_ended" ); + // it's possible that the notify type has not been defined yet so we have to hard code it + level waittill( "IntegrationBootstrapInitialized" ); + level.commonFunctions.changeTeam = "ChangeTeam"; level.commonFunctions.getTeamCounts = "GetTeamCounts"; level.commonFunctions.getMaxClients = "GetMaxClients"; @@ -25,6 +28,7 @@ Setup() level.overrideMethods[level.commonFunctions.getClientKillStreak] = scripts\_integration_base::NotImplementedFunction; level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = scripts\_integration_base::NotImplementedFunction; level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = scripts\_integration_base::NotImplementedFunction; + level.overrideMethods["GetPlayerFromClientNum"] = ::GetPlayerFromClientNum; // these can be overridden per game if needed level.commonKeys.team1 = "allies"; @@ -39,15 +43,13 @@ Setup() level.iw4madminIntegrationDefaultPerformance = 200; + level notify( level.notifyTypes.sharedFunctionsInitialized ); + level waittill( level.notifyTypes.gameFunctionsInitialized ); + if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) { return; } - - if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 ) - { - return; - } level thread OnPlayerConnect(); } @@ -59,7 +61,22 @@ OnPlayerConnect() for ( ;; ) { level waittill( level.eventTypes.connect, player ); + + if ( scripts\_integration_base::_IsBot( player ) ) + { + // we don't want to track bots + continue; + } + + player thread OnPlayerJoinedTeam(); + player thread OnPlayerJoinedSpectators(); + player thread PlayerTrackingOnInterval(); + if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 || !IsDefined( [[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) ) + { + continue; + } + if ( ![[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) { continue; @@ -449,3 +466,123 @@ GetClientPerformanceOrDefault() return performance; } + +GetPlayerFromClientNum( clientNum ) +{ + if ( clientNum < 0 ) + { + return undefined; + } + + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i] getEntityNumber() == clientNum ) + { + return level.players[i]; + } + } + + return undefined; +} + +OnPlayerJoinedTeam() +{ + self endon( "disconnect" ); + + for( ;; ) + { + self waittill( "joined_team" ); + // join spec and join team occur at the same moment - out of order logging would be problematic + wait( 0.25 ); + LogPrint( GenerateJoinTeamString( false ) ); + } +} + +OnPlayerJoinedSpectators() +{ + self endon( "disconnect" ); + + for( ;; ) + { + self waittill( "joined_spectators" ); + LogPrint( GenerateJoinTeamString( true ) ); + } +} + +GenerateJoinTeamString( isSpectator ) +{ + team = self.team; + + if ( IsDefined( self.joining_team ) ) + { + team = self.joining_team; + } + else + { + if ( isSpectator || !IsDefined( team ) ) + { + team = "spectator"; + } + } + + guid = self [[level.overrideMethods[level.commonFunctions.getXuid]]](); + + if ( guid == "0" ) + { + guid = self.guid; + } + + if ( !IsDefined( guid ) || guid == "0" ) + { + guid = "undefined"; + } + + return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n"; +} + +PlayerTrackingOnInterval() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + wait ( 120 ); + if ( IsAlive( self ) ) + { + self SaveTrackingMetrics(); + } + } +} + +SaveTrackingMetrics() +{ + if ( !IsDefined( self.persistentClientId ) ) + { + return; + } + + scripts\_integration_base::LogDebug( "Saving tracking metrics for " + self.persistentClientId ); + + if ( !IsDefined( self.lastShotCount ) ) + { + self.lastShotCount = 0; + } + + currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]](); + change = currentShotCount - self.lastShotCount; + self.lastShotCount = currentShotCount; + + scripts\_integration_base::LogDebug( "Total Shots Fired increased by " + change ); + + if ( !IsDefined( change ) ) + { + change = 0; + } + + if ( change == 0 ) + { + return; + } + + scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId ); +} \ No newline at end of file diff --git a/GameFiles/GameInterface/_integration_t5.gsc b/GameFiles/GameInterface/_integration_t5.gsc index 9d5e5f3f7..139847179 100644 --- a/GameFiles/GameInterface/_integration_t5.gsc +++ b/GameFiles/GameInterface/_integration_t5.gsc @@ -2,8 +2,6 @@ Init() { - level.eventBus.gamename = "T5"; - thread Setup(); } @@ -12,16 +10,20 @@ Setup() level endon( "game_ended" ); // it's possible that the notify type has not been defined yet so we have to hard code it - level waittill( "IntegrationBootstrapInitialized" ); + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "T5"; - scripts\mp\_integration_base::RegisterLogger( ::Log2Console ); + scripts\_integration_base::RegisterLogger( ::Log2Console ); level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired; level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized; level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout; + level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID; RegisterClientCommands(); + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + level notify( level.notifyTypes.gameFunctionsInitialized ); if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) @@ -40,7 +42,7 @@ OnPlayerConnect() { level waittill( "connected", player ); - if ( scripts\mp\_integration_base::_IsBot( player ) ) + if ( scripts\_integration_base::_IsBot( player ) ) { // we don't want to track bots continue; @@ -53,17 +55,17 @@ OnPlayerConnect() RegisterClientCommands() { - scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); - scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); - scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); - scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); - scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); - scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); - scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); - scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); - scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); - scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); - scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); } WaitForClientEvents() @@ -72,13 +74,13 @@ WaitForClientEvents() // example of requesting a meta value lastServerMetaKey = "LastServerPlayed"; - // self scripts\mp\_integration_base::RequestClientMeta( lastServerMetaKey ); + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); for ( ;; ) { self waittill( level.eventTypes.localClientEvent, event ); - scripts\mp\_integration_base::LogDebug( "Received client event " + event.type ); + scripts\_integration_base::LogDebug( "Received client event " + event.type ); if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) { @@ -129,6 +131,11 @@ God() } } +_GetXUID() +{ + return self GetXUID(); +} + ////////////////////////////////// // GUID helpers ///////////////////////////////// @@ -146,14 +153,14 @@ God() { // give IW4MAdmin time to collect IP wait( 15 ); - scripts\mp\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); - scripts\mp\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); return; } guid = self SplitGuid(); - scripts\mp\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); self SetPlayerData( "bests", "none", guid["high"] ); self SetPlayerData( "awards", "none", guid["low"] ); @@ -395,7 +402,7 @@ NoClipImpl( event, data ) self IPrintLnBold( "NoClip enabled" );*/ - scripts\mp\_integration_base::LogWarning( "NoClip is not supported on T5!" ); + scripts\_integration_base::LogWarning( "NoClip is not supported on T5!" ); } diff --git a/GameFiles/GameInterface/_integration_t5zm.gsc b/GameFiles/GameInterface/_integration_t5zm.gsc new file mode 100644 index 000000000..d01e9dc28 --- /dev/null +++ b/GameFiles/GameInterface/_integration_t5zm.gsc @@ -0,0 +1,552 @@ +#include common_scripts\utility; + +Init() +{ + thread Setup(); +} + +Setup() +{ + level endon( "game_ended" ); + + // it's possible that the notify type has not been defined yet so we have to hard code it + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "T5"; + + scripts\_integration_base::RegisterLogger( ::Log2Console ); + + level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired; + level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized; + level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout; + level.overrideMethods["GetPlayerFromClientNum"] = ::_GetPlayerFromClientNum; + + RegisterClientCommands(); + + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + + level notify( level.notifyTypes.gameFunctionsInitialized ); + + if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) + { + return; + } + + level thread OnPlayerConnect(); +} + +OnPlayerConnect() +{ + level endon ( "game_ended" ); + + for ( ;; ) + { + level waittill( "connected", player ); + + if ( scripts\_integration_base::_IsBot( player ) ) + { + // we don't want to track bots + continue; + } + + //player thread SetPersistentData(); + player thread WaitForClientEvents(); + } +} + +RegisterClientCommands() +{ + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); +} + +WaitForClientEvents() +{ + self endon( "disconnect" ); + + // example of requesting a meta value + lastServerMetaKey = "LastServerPlayed"; + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); + + for ( ;; ) + { + self waittill( level.eventTypes.localClientEvent, event ); + + scripts\_integration_base::LogDebug( "Received client event " + event.type ); + + if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) + { + clientData = self.pers[level.clientDataKey]; + lastServerPlayed = clientData.meta[lastServerMetaKey]; + } + } +} + +GetTotalShotsFired() +{ + return 0; //ZM has no shot tracking. TODO: add tracking function for event weapon_fired +} + +_SetDvarIfUninitialized(dvar, value) +{ + if (GetDvar(dvar)=="" ) + { + SetDvar(dvar, value); + return value; + } + + return GetDvar(dvar); +} + +_waittill_notify_or_timeout( msg, timer ) +{ + self endon( msg ); + wait( timer ); +} + +Log2Console( logLevel, message ) +{ + Print( "[" + logLevel + "] " + message + "\n" ); +} + +God() +{ + + if ( !IsDefined( self.godmode ) ) + { + self.godmode = false; + } + + if (!self.godmode ) + { + self enableInvulnerability(); + self.godmode = true; + } + else + { + self.godmode = false; + self disableInvulnerability(); + } +} + +_GetPlayerFromClientNum( clientNum ) +{ + if ( clientNum < 0 ) + { + return undefined; + } + + players = GetPlayers("all"); + + for ( i = 0; i < players.size; i++ ) + { + scripts\_integration_base::LogDebug(i+"/"+players.size+ "=" + players[i].name); + if ( players[i] getEntityNumber() == clientNum ) + { + return players[i]; + } + } + + return undefined; +} + +////////////////////////////////// +// GUID helpers +///////////////////////////////// + +/*SetPersistentData() +{ + self endon( "disconnect" ); + + guidHigh = self GetPlayerData( "bests", "none" ); + guidLow = self GetPlayerData( "awards", "none" ); + persistentGuid = guidHigh + "," + guidLow; + guidIsStored = guidHigh != 0 && guidLow != 0; + + if ( guidIsStored ) + { + // give IW4MAdmin time to collect IP + wait( 15 ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + return; + } + + guid = self SplitGuid(); + + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + + self SetPlayerData( "bests", "none", guid["high"] ); + self SetPlayerData( "awards", "none", guid["low"] ); +} + +SplitGuid() +{ + guid = self GetGuid(); + + if ( isDefined( self.guid ) ) + { + guid = self.guid; + } + + firstPart = 0; + secondPart = 0; + stringLength = 17; + firstPartExp = 0; + secondPartExp = 0; + + for ( i = stringLength - 1; i > 0; i-- ) + { + char = GetSubStr( guid, i - 1, i ); + if ( char == "" ) + { + char = "0"; + } + + if ( i > stringLength / 2 ) + { + value = GetIntForHexChar( char ); + power = Pow( 16, secondPartExp ); + secondPart = secondPart + ( value * power ); + secondPartExp++; + } + else + { + value = GetIntForHexChar( char ); + power = Pow( 16, firstPartExp ); + firstPart = firstPart + ( value * power ); + firstPartExp++; + } + } + + split = []; + split["low"] = int( secondPart ); + split["high"] = int( firstPart ); + + return split; +} + +Pow( num, exponent ) +{ + result = 1; + while( exponent != 0 ) + { + result = result * num; + exponent--; + } + + return result; +} + +GetIntForHexChar( char ) +{ + char = ToLower( char ); + // generated by co-pilot because I can't be bothered to make it more "elegant" + switch( char ) + { + case "0": + return 0; + case "1": + return 1; + case "2": + return 2; + case "3": + return 3; + case "4": + return 4; + case "5": + return 5; + case "6": + return 6; + case "7": + return 7; + case "8": + return 8; + case "9": + return 9; + case "a": + return 10; + case "b": + return 11; + case "c": + return 12; + case "d": + return 13; + case "e": + return 14; + case "f": + return 15; + default: + return 0; + } +}*/ + +////////////////////////////////// +// Command Implementations +///////////////////////////////// + +GiveWeaponImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + self IPrintLnBold( "You have been given a new weapon" ); + self GiveWeapon( data["weaponName"] ); + self SwitchToWeapon( data["weaponName"] ); + + return self.name + "^7 has been given ^5" + data["weaponName"]; +} + +TakeWeaponsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + self TakeAllWeapons(); + self IPrintLnBold( "All your weapons have been taken" ); + + return "Took weapons from " + self.name; +} + +TeamSwitchImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self + "^7 is not alive"; + } + + team = level.allies; + + if ( self.team == "allies" ) + { + team = level.axis; + } + + self IPrintLnBold( "You are being team switched" ); + wait( 2 ); + self [[team]](); + + return self.name + "^7 switched to " + self.team; +} + +LockControlsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + if ( !IsDefined ( self.isControlLocked ) ) + { + self.isControlLocked = false; + } + + if ( !self.isControlLocked ) + { + self freezeControls( true ); + self God(); + self Hide(); + + info = []; + info[ "alertType" ] = "Alert!"; + info[ "message" ] = "You have been frozen!"; + + self AlertImpl( undefined, info ); + + self.isControlLocked = true; + + return self.name + "\'s controls are locked"; + } + else + { + self freezeControls( false ); + self God(); + self Show(); + + self.isControlLocked = false; + + return self.name + "\'s controls are unlocked"; + } +} + +NoClipImpl( event, data ) +{ + /*if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + } + + if ( !IsDefined ( self.isNoClipped ) ) + { + self.isNoClipped = false; + } + + if ( !self.isNoClipped ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = true; + + self IPrintLnBold( "NoClip enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = false; + + self IPrintLnBold( "NoClip disabled" ); + } + + self IPrintLnBold( "NoClip enabled" );*/ + + scripts\_integration_base::LogWarning( "NoClip is not supported on T5!" ); + +} + +HideImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + if ( !IsDefined ( self.isHidden ) ) + { + self.isHidden = false; + } + + if ( !self.isHidden ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Hide(); + + self.isHidden = true; + + self IPrintLnBold( "Hide enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 0 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Show(); + + self.isHidden = false; + + self IPrintLnBold( "Hide disabled" ); + } +} + +AlertImpl( event, data ) +{ + //self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 ); + self IPrintLnBold(data["message"]); + + return "Sent alert to " + self.name; +} + +GotoImpl( event, data ) +{ + if ( IsDefined( event.target ) ) + { + return self GotoPlayerImpl( event.target ); + } + else + { + return self GotoCoordImpl( data ); + } +} + +GotoCoordImpl( data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + position = ( int( data["x"] ), int( data["y"] ), int( data["z"]) ); + self SetOrigin( position ); + self IPrintLnBold( "Moved to " + "("+ position[0] + "," + position[1] + "," + position[2] + ")" ); +} + +GotoPlayerImpl( target ) +{ + if ( !IsAlive( target ) ) + { + self IPrintLnBold( target.name + " is not alive" ); + return; + } + + self SetOrigin( target GetOrigin() ); + self IPrintLnBold( "Moved to " + target.name ); +} + +PlayerToMeImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self SetOrigin( event.origin GetOrigin() ); + return "Moved here " + self.name; +} + +KillImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self Suicide(); + self IPrintLnBold( "You were killed by " + self.name ); + + return "You killed " + self.name; +} + +SetSpectatorImpl( event, data ) +{ + if ( self.pers["team"] == "spectator" ) + { + return self.name + " is already spectating"; + } + + self [[level.spectator]](); + self IPrintLnBold( "You have been moved to spectator" ); + + return self.name + " has been moved to spectator"; +} diff --git a/GameFiles/GameInterface/_integration_t6.gsc b/GameFiles/GameInterface/_integration_t6.gsc new file mode 100644 index 000000000..07b67b649 --- /dev/null +++ b/GameFiles/GameInterface/_integration_t6.gsc @@ -0,0 +1,571 @@ +#include common_scripts\utility; +#include maps\mp\_utility; + +Init() +{ + thread Setup(); +} + +Setup() +{ + level endon( "game_ended" ); + + // it's possible that the notify type has not been defined yet so we have to hard code it + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "T6"; + + scripts\_integration_base::RegisterLogger( ::Log2Console ); + + level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired; + level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized; + level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout; + level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID; + + RegisterClientCommands(); + + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + + level notify( level.notifyTypes.gameFunctionsInitialized ); + + if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) + { + return; + } + + level thread OnPlayerConnect(); +} + +OnPlayerConnect() +{ + level endon ( "game_ended" ); + + for ( ;; ) + { + level waittill( "connected", player ); + + if ( scripts\_integration_base::_IsBot( player ) ) + { + // we don't want to track bots + continue; + } + + //player thread SetPersistentData(); + player thread WaitForClientEvents(); + } +} + +RegisterClientCommands() +{ + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); +} + +WaitForClientEvents() +{ + self endon( "disconnect" ); + + // example of requesting a meta value + lastServerMetaKey = "LastServerPlayed"; + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); + + for ( ;; ) + { + self waittill( level.eventTypes.localClientEvent, event ); + + scripts\_integration_base::LogDebug( "Received client event " + event.type ); + + if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) + { + clientData = self.pers[level.clientDataKey]; + lastServerPlayed = clientData.meta[lastServerMetaKey]; + } + } +} + +GetTotalShotsFired() +{ + return self.pers[ "total_shots" ]; +} + +_SetDvarIfUninitialized(dvar, value) +{ + maps\mp\_utility::set_dvar_if_unset(dvar, value); +} + +_waittill_notify_or_timeout( msg, timer ) +{ + self endon( msg ); + wait( timer ); +} + +Log2Console( logLevel, message ) +{ + Print( "[" + logLevel + "] " + message + "\n" ); +} + +God() +{ + + if ( !IsDefined( self.godmode ) ) + { + self.godmode = false; + } + + if (!self.godmode ) + { + self enableInvulnerability(); + self.godmode = true; + } + else + { + self.godmode = false; + self disableInvulnerability(); + } +} + +_GetXUID() +{ + return self GetXUID(); +} + +////////////////////////////////// +// GUID helpers +///////////////////////////////// + +/*SetPersistentData() +{ + self endon( "disconnect" ); + + guidHigh = self GetPlayerData( "bests", "none" ); + guidLow = self GetPlayerData( "awards", "none" ); + persistentGuid = guidHigh + "," + guidLow; + guidIsStored = guidHigh != 0 && guidLow != 0; + + if ( guidIsStored ) + { + // give IW4MAdmin time to collect IP + wait( 15 ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + return; + } + + guid = self SplitGuid(); + + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + + self SetPlayerData( "bests", "none", guid["high"] ); + self SetPlayerData( "awards", "none", guid["low"] ); +} + +SplitGuid() +{ + guid = self GetGuid(); + + if ( isDefined( self.guid ) ) + { + guid = self.guid; + } + + firstPart = 0; + secondPart = 0; + stringLength = 17; + firstPartExp = 0; + secondPartExp = 0; + + for ( i = stringLength - 1; i > 0; i-- ) + { + char = GetSubStr( guid, i - 1, i ); + if ( char == "" ) + { + char = "0"; + } + + if ( i > stringLength / 2 ) + { + value = GetIntForHexChar( char ); + power = Pow( 16, secondPartExp ); + secondPart = secondPart + ( value * power ); + secondPartExp++; + } + else + { + value = GetIntForHexChar( char ); + power = Pow( 16, firstPartExp ); + firstPart = firstPart + ( value * power ); + firstPartExp++; + } + } + + split = []; + split["low"] = int( secondPart ); + split["high"] = int( firstPart ); + + return split; +} + +Pow( num, exponent ) +{ + result = 1; + while( exponent != 0 ) + { + result = result * num; + exponent--; + } + + return result; +} + +GetIntForHexChar( char ) +{ + char = ToLower( char ); + // generated by co-pilot because I can't be bothered to make it more "elegant" + switch( char ) + { + case "0": + return 0; + case "1": + return 1; + case "2": + return 2; + case "3": + return 3; + case "4": + return 4; + case "5": + return 5; + case "6": + return 6; + case "7": + return 7; + case "8": + return 8; + case "9": + return 9; + case "a": + return 10; + case "b": + return 11; + case "c": + return 12; + case "d": + return 13; + case "e": + return 14; + case "f": + return 15; + default: + return 0; + } +}*/ + +////////////////////////////////// +// Command Implementations +///////////////////////////////// + +GiveWeaponImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + if ( isDefined( level.player_too_many_weapons_monitor ) && level.player_too_many_weapons_monitor ) + { + level.player_too_many_weapons_monitor = false; + self notify( "stop_player_too_many_weapons_monitor" ); + } + + self IPrintLnBold( "You have been given a new weapon" ); + self GiveWeapon( data["weaponName"] ); + self SwitchToWeapon( data["weaponName"] ); + + return self.name + "^7 has been given ^5" + data["weaponName"]; +} + +TakeWeaponsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + self TakeAllWeapons(); + self IPrintLnBold( "All your weapons have been taken" ); + + return "Took weapons from " + self.name; +} + +TeamSwitchImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self + "^7 is not alive"; + } + + team = level.allies; + + if ( self.team == "allies" ) + { + team = level.axis; + } + + self IPrintLnBold( "You are being team switched" ); + wait( 2 ); + self [[team]](); + + return self.name + "^7 switched to " + self.team; +} + +LockControlsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + if ( !IsDefined ( self.isControlLocked ) ) + { + self.isControlLocked = false; + } + + if ( !self.isControlLocked ) + { + self freezeControls( true ); + self God(); + self Hide(); + + info = []; + info[ "alertType" ] = "Alert!"; + info[ "message" ] = "You have been frozen!"; + + self AlertImpl( undefined, info ); + + self.isControlLocked = true; + + return self.name + "\'s controls are locked"; + } + else + { + self freezeControls( false ); + self God(); + self Show(); + + self.isControlLocked = false; + + return self.name + "\'s controls are unlocked"; + } +} + +NoClipImpl( event, data ) +{ + /*if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + } + + if ( !IsDefined ( self.isNoClipped ) ) + { + self.isNoClipped = false; + } + + if ( !self.isNoClipped ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = true; + + self IPrintLnBold( "NoClip enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = false; + + self IPrintLnBold( "NoClip disabled" ); + } + + self IPrintLnBold( "NoClip enabled" );*/ + + scripts\_integration_base::LogWarning( "NoClip is not supported on T6!" ); + +} + +HideImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + if ( !IsDefined ( self.isHidden ) ) + { + self.isHidden = false; + } + + if ( !self.isHidden ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Hide(); + + self.isHidden = true; + + self IPrintLnBold( "Hide enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 0 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Show(); + + self.isHidden = false; + + self IPrintLnBold( "Hide disabled" ); + } +} + +AlertImpl( event, data ) +{ + /*if ( !sessionmodeiszombiesgame() ) + {*/ + self thread oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 ); + /*} + else + { + self IPrintLnBold( data["alertType"] ); + self IPrintLnBold( data["message"] ); + }*/ + + + return "Sent alert to " + self.name; +} + +GotoImpl( event, data ) +{ + if ( IsDefined( event.target ) ) + { + return self GotoPlayerImpl( event.target ); + } + else + { + return self GotoCoordImpl( data ); + } +} + +GotoCoordImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + position = ( int( data["x"] ), int( data["y"] ), int( data["z"]) ); + self SetOrigin( position ); + self IPrintLnBold( "Moved to " + "("+ position[0] + "," + position[1] + "," + position[2] + ")" ); +} + +GotoPlayerImpl( target ) +{ + if ( !IsAlive( target ) ) + { + self IPrintLnBold( target.name + " is not alive" ); + return; + } + + self SetOrigin( target GetOrigin() ); + self IPrintLnBold( "Moved to " + target.name ); +} + +PlayerToMeImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self SetOrigin( event.origin GetOrigin() ); + return "Moved here " + self.name; +} + +KillImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self Suicide(); + self IPrintLnBold( "You were killed by " + self.name ); + + return "You killed " + self.name; +} + +SetSpectatorImpl( event, data ) +{ + if ( self.pers["team"] == "spectator" ) + { + return self.name + " is already spectating"; + } + + self [[level.spectator]](); + self IPrintLnBold( "You have been moved to spectator" ); + + return self.name + " has been moved to spectator"; +} + + +////////////////////////////////// +// T6 specific functions +///////////////////////////////// + +/* +1:1 the same on MP and ZM but in different includes. Since we probably want to be able to send Alerts on non teambased wagermatechs use our own copy. +*/ +oldnotifymessage( titletext, notifytext, iconname, glowcolor, sound, duration ) +{ + /*if ( level.wagermatch && !level.teambased ) + { + return; + }*/ + notifydata = spawnstruct(); + notifydata.titletext = titletext; + notifydata.notifytext = notifytext; + notifydata.iconname = iconname; + notifydata.sound = sound; + notifydata.duration = duration; + self.startmessagenotifyqueue[ self.startmessagenotifyqueue.size ] = notifydata; + self notify( "received award" ); +} + + diff --git a/GameFiles/GameInterface/_integration_t6zm_helper.gsc b/GameFiles/GameInterface/_integration_t6zm_helper.gsc new file mode 100644 index 000000000..69a26dbda --- /dev/null +++ b/GameFiles/GameInterface/_integration_t6zm_helper.gsc @@ -0,0 +1,86 @@ +init() +{ + + level.startmessagedefaultduration = 2; + level.regulargamemessages = spawnstruct(); + level.regulargamemessages.waittime = 6; + + + level thread onplayerconnect(); +} + +onplayerconnect() +{ + for ( ;; ) + { + level waittill( "connecting", player ); + player thread displaypopupswaiter(); + } +} + +displaypopupswaiter() +{ + self endon( "disconnect" ); + self.ranknotifyqueue = []; + if ( !isDefined( self.pers[ "challengeNotifyQueue" ] ) ) + { + self.pers[ "challengeNotifyQueue" ] = []; + } + if ( !isDefined( self.pers[ "contractNotifyQueue" ] ) ) + { + self.pers[ "contractNotifyQueue" ] = []; + } + self.messagenotifyqueue = []; + self.startmessagenotifyqueue = []; + self.wagernotifyqueue = []; + while ( !level.gameended ) + { + if ( self.startmessagenotifyqueue.size == 0 && self.messagenotifyqueue.size == 0 ) + { + self waittill( "received award" ); + } + waittillframeend; + if ( level.gameended ) + { + return; + } + else + { + if ( self.startmessagenotifyqueue.size > 0 ) + { + nextnotifydata = self.startmessagenotifyqueue[ 0 ]; + arrayremoveindex( self.startmessagenotifyqueue, 0, 0 ); + if ( isDefined( nextnotifydata.duration ) ) + { + duration = nextnotifydata.duration; + } + else + { + duration = level.startmessagedefaultduration; + } + self maps\mp\gametypes_zm\_hud_message::shownotifymessage( nextnotifydata, duration ); + wait duration; + continue; + } + else if ( self.messagenotifyqueue.size > 0 ) + { + nextnotifydata = self.messagenotifyqueue[ 0 ]; + arrayremoveindex( self.messagenotifyqueue, 0, 0 ); + if ( isDefined( nextnotifydata.duration ) ) + { + duration = nextnotifydata.duration; + } + else + { + duration = level.regulargamemessages.waittime; + } + self maps\mp\gametypes_zm\_hud_message::shownotifymessage( nextnotifydata, duration ); + continue; + } + else + { + wait 1; + } + } + } +} \ No newline at end of file diff --git a/GameFiles/deploy.bat b/GameFiles/deploy.bat index cbb7b1f60..7f9cd833d 100644 --- a/GameFiles/deploy.bat +++ b/GameFiles/deploy.bat @@ -1,14 +1,20 @@ @echo off ECHO "Pluto IW5" -xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" -xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" +xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" +xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" +xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" xcopy /y .\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" ECHO "Pluto T5" -xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp" +xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts" +xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts" xcopy /y .\GameInterface\_integration_t5.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp" +xcopy /y .\GameInterface\_integration_t5zm.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\sp\zom" ECHO "Pluto T6" xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp" -xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc.src "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp" +xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" +xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" +xcopy /y .\GameInterface\_integration_t6.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" +xcopy /y .\GameInterface\_integration_t6zm_helper.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\zm" diff --git a/Plugins/ScriptPlugins/GameInterface.js b/Plugins/ScriptPlugins/GameInterface.js index 79f49409b..5fd82d993 100644 --- a/Plugins/ScriptPlugins/GameInterface.js +++ b/Plugins/ScriptPlugins/GameInterface.js @@ -385,7 +385,7 @@ const commands = [{ required: true } ], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -405,7 +405,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -423,7 +423,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -441,7 +441,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -471,7 +471,7 @@ const commands = [{ permission: 'SeniorAdmin', targetRequired: false, arguments: [], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -494,7 +494,7 @@ const commands = [{ required: true } ], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -515,7 +515,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -533,7 +533,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -560,7 +560,7 @@ const commands = [{ required: true } ], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -584,7 +584,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -602,7 +602,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; From 088f7a51be1dc999de30a8b6ddf3edf05c672f98 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 30 May 2023 14:58:17 -0500 Subject: [PATCH 09/11] remove some old web components, add command line args for no-confirm (skip unreachable server prompt) and kestrel request settings --- Application/Application.csproj | 4 +- Application/ApplicationManager.cs | 4 +- Application/Main.cs | 22 ++-- .../Controllers/DynamicFileController.cs | 50 --------- .../CustomCssAccentMiddlewareAction.cs | 104 ------------------ WebfrontCore/Program.cs | 13 +-- WebfrontCore/Startup.cs | 11 +- WebfrontCore/WebfrontCore.csproj | 3 +- 8 files changed, 35 insertions(+), 176 deletions(-) delete mode 100644 WebfrontCore/Controllers/DynamicFileController.cs delete mode 100644 WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index 78ebd5fb8..fffa5b9ce 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -32,11 +32,13 @@ + + - false + true true true Latest diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index fe1de1f9b..98e395127 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -581,9 +581,9 @@ namespace IW4MAdmin.Application throw lastException; } - if (successServers != config.Servers.Length) + if (successServers != config.Servers.Length && !AppContext.TryGetSwitch("NoConfirmPrompt", out _)) { - if (!Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"])) + if (!Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"].PromptBool()) { throw lastException; } diff --git a/Application/Main.cs b/Application/Main.cs index 83101a043..41eb9901c 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -58,7 +58,7 @@ namespace IW4MAdmin.Application /// entrypoint of the application ///
/// - public static async Task Main(string[] args) + public static async Task Main(bool noConfirm = false, int? maxConcurrentRequests = 25, int? requestQueueLimit = 25) { AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) => @@ -73,7 +73,15 @@ namespace IW4MAdmin.Application // added to be a bit more permissive with plugin references return AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(asm => asm.FullName?.StartsWith(libraryName) ?? false); - }; + }; + + if (noConfirm) + { + AppContext.SetSwitch("NoConfirmPrompt", true); + } + + Environment.SetEnvironmentVariable("MaxConcurrentRequests", (maxConcurrentRequests * Environment.ProcessorCount).ToString()); + Environment.SetEnvironmentVariable("RequestQueueLimit", requestQueueLimit.ToString()); Console.OutputEncoding = Encoding.UTF8; Console.ForegroundColor = ConsoleColor.Gray; @@ -86,7 +94,7 @@ namespace IW4MAdmin.Application Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine("====================================================="); - await LaunchAsync(args); + await LaunchAsync(); } /// @@ -112,13 +120,13 @@ namespace IW4MAdmin.Application /// task that initializes application and starts the application monitoring and runtime tasks /// /// - private static async Task LaunchAsync(string[] args) + private static async Task LaunchAsync() { restart: ITranslationLookup translationLookup = null; var logger = BuildDefaultLogger(new ApplicationConfiguration()); Utilities.DefaultLogger = logger; - logger.LogInformation("Begin IW4MAdmin startup. Version is {Version} {@Args}", Version, args); + logger.LogInformation("Begin IW4MAdmin startup. Version is {Version}", Version); try { @@ -426,9 +434,9 @@ namespace IW4MAdmin.Application commandConfigHandler.BuildAsync().GetAwaiter().GetResult(); var appConfig = appConfigHandler.Configuration(); - var masterUri = /*Utilities.IsDevelopment + var masterUri = Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") - : appConfig?.MasterUrl ?? */new ApplicationConfiguration().MasterUrl; + : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl; var httpClient = new HttpClient { BaseAddress = masterUri, diff --git a/WebfrontCore/Controllers/DynamicFileController.cs b/WebfrontCore/Controllers/DynamicFileController.cs deleted file mode 100644 index 9b5bbb00a..000000000 --- a/WebfrontCore/Controllers/DynamicFileController.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using SharedLibraryCore; -using SharedLibraryCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace WebfrontCore.Controllers -{ - [Route("dynamic")] - public class DynamicFileController : BaseController - { - private static readonly IDictionary _fileCache = new Dictionary(); - - public DynamicFileController(IManager manager) : base(manager) - { - - } - - [Route("css/{fileName}")] - public async Task Css(string fileName) - { - if (fileName.EndsWith(".css")) - { - if (Utilities.IsDevelopment) - { - var path = Path.Join(Utilities.OperatingDirectory, "..", "..", "..", "..", "WebfrontCore", "wwwroot", "css", fileName); - string cssData = await System.IO.File.ReadAllTextAsync(path); - cssData = await Manager.MiddlewareActionHandler.Execute(cssData, "custom_css_accent"); - return Content(cssData, "text/css"); - } - - if (!_fileCache.ContainsKey(fileName)) - { - - string path = $"wwwroot{Path.DirectorySeparatorChar}css{Path.DirectorySeparatorChar}{fileName}"; - string data = await System.IO.File.ReadAllTextAsync(path); - data = await Manager.MiddlewareActionHandler.Execute(data, "custom_css_accent"); - _fileCache.Add(fileName, data); - } - - return Content(_fileCache[fileName], "text/css"); - } - - return StatusCode(400); - } - } -} diff --git a/WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs b/WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs deleted file mode 100644 index cf98a45bd..000000000 --- a/WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs +++ /dev/null @@ -1,104 +0,0 @@ -using SharedLibraryCore.Interfaces; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; - -namespace WebfrontCore.Middleware -{ - public class CustomCssAccentMiddlewareAction : IMiddlewareAction - { - private readonly List ColorReplacements = new List(); - - private class ColorMap - { - public Color Original { get; set; } - public Color Replacement { get; set; } - } - - public CustomCssAccentMiddlewareAction(string originalPrimaryColor, string originalSecondaryColor, string primaryColor, string secondaryColor) - { - primaryColor = string.IsNullOrWhiteSpace(primaryColor) ? originalPrimaryColor : primaryColor; - secondaryColor = string.IsNullOrWhiteSpace(secondaryColor) ? originalSecondaryColor : secondaryColor; - try - { - ColorReplacements.AddRange(new[] - { - new ColorMap() - { - Original = Color.FromArgb(Convert.ToInt32(originalPrimaryColor.Substring(1).ToString(), 16)), - Replacement = Color.FromArgb(Convert.ToInt32(primaryColor.Substring(1).ToString(), 16)) - }, - new ColorMap() - { - Original = Color.FromArgb(Convert.ToInt32(originalSecondaryColor.Substring(1).ToString(), 16)), - Replacement = Color.FromArgb(Convert.ToInt32(secondaryColor.Substring(1).ToString(), 16)) - } - }); - } - - catch (FormatException) - { - - } - } - - public Task Invoke(string original) - { - foreach (var color in ColorReplacements) - { - foreach (var shade in new[] { 0, -19, -25 }) - { - original = original - .Replace(ColorToHex(LightenDarkenColor(color.Original, shade)), ColorToHex(LightenDarkenColor(color.Replacement, shade)), StringComparison.OrdinalIgnoreCase) - .Replace(ColorToDec(LightenDarkenColor(color.Original, shade)), ColorToDec(LightenDarkenColor(color.Replacement, shade)), StringComparison.OrdinalIgnoreCase); - } - } - - return Task.FromResult(original); - } - - /// - /// converts color to the hex string representation - /// - /// - /// - private string ColorToHex(Color color) => $"#{color.R.ToString("X2")}{color.G.ToString("X2")}{color.B.ToString("X2")}"; - - /// - /// converts color to the rgb tuples representation - /// - /// - /// - private string ColorToDec(Color color) => $"{(int)color.R}, {(int)color.G}, {(int)color.B}"; - - /// - /// lightens or darkens a color on the given amount - /// Based off SASS darken/lighten function - /// - /// - /// - /// - private Color LightenDarkenColor(Color color, float amount) - { - int r = color.R + (int)((amount / 100.0f) * color.R); - - if (r > 255) r = 255; - else if (r < 0) r = 0; - - int g = color.G + (int)((amount / 100.0f) * color.G); - - if (g > 255) g = 255; - else if (g < 0) g = 0; - - int b = color.B + (int)((amount / 100.0f) * color.B); - - if (b > 255) b = 255; - else if (b < 0) b = 0; - - return Color.FromArgb(r, g, b); - } - } -} diff --git a/WebfrontCore/Program.cs b/WebfrontCore/Program.cs index 12e6784bf..e4127878a 100644 --- a/WebfrontCore/Program.cs +++ b/WebfrontCore/Program.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; -using WebfrontCore.Middleware; namespace WebfrontCore { @@ -24,11 +23,6 @@ namespace WebfrontCore public static Task GetWebHostTask(CancellationToken cancellationToken) { - var config = _webHost.Services.GetRequiredService(); - Manager.MiddlewareActionHandler.Register(null, - new CustomCssAccentMiddlewareAction("#007ACC", "#fd7e14", config.WebfrontPrimaryColor, - config.WebfrontSecondaryColor), "custom_css_accent"); - return _webHost?.RunAsync(cancellationToken); } @@ -41,7 +35,12 @@ namespace WebfrontCore .UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory) #endif .UseUrls(bindUrl) - .UseKestrel() + .UseKestrel(cfg => + { + cfg.Limits.MaxConcurrentConnections = + int.Parse(Environment.GetEnvironmentVariable("MaxConcurrentRequests") ?? "1"); + cfg.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(30); + }) .ConfigureServices(registerDependenciesAction) .UseStartup() .Build(); diff --git a/WebfrontCore/Startup.cs b/WebfrontCore/Startup.cs index 2eb8ddd8f..5d6bed659 100644 --- a/WebfrontCore/Startup.cs +++ b/WebfrontCore/Startup.cs @@ -4,7 +4,6 @@ using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -24,9 +23,6 @@ using System.Reflection; using System.Threading.Tasks; using Data.Abstractions; using Data.Helpers; -using IW4MAdmin.Plugins.Stats.Helpers; -using Stats.Client.Abstractions; -using Stats.Config; using WebfrontCore.Controllers.API.Validation; using WebfrontCore.Middleware; using WebfrontCore.QueryHelpers; @@ -50,6 +46,12 @@ namespace WebfrontCore .AllowAnyHeader(); }); }); + + services.AddStackPolicy(options => + { + options.MaxConcurrentRequests = int.Parse(Environment.GetEnvironmentVariable("MaxConcurrentRequests") ?? "1"); + options.RequestQueueLimit = int.Parse(Environment.GetEnvironmentVariable("RequestQueueLimit") ?? "1"); + }); IEnumerable pluginAssemblies() { @@ -132,6 +134,7 @@ namespace WebfrontCore app.UseMiddleware(serviceProvider.GetService>(), serviceProvider.GetRequiredService().WebfrontConnectionWhitelist); } + app.UseConcurrencyLimiter(); app.UseStaticFiles(); app.UseAuthentication(); app.UseCors("AllowAll"); diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index 910054a7c..510703229 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -25,7 +25,7 @@ - false + true true true Latest @@ -47,6 +47,7 @@ + From 81e2a2f6d4af34dfc56abaa916a9960668ee9676 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 30 May 2023 15:01:01 -0500 Subject: [PATCH 10/11] tweak script plugin web request concurrency --- Application/Plugin/Script/ScriptPluginHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Application/Plugin/Script/ScriptPluginHelper.cs b/Application/Plugin/Script/ScriptPluginHelper.cs index 660a91d2f..be15c580a 100644 --- a/Application/Plugin/Script/ScriptPluginHelper.cs +++ b/Application/Plugin/Script/ScriptPluginHelper.cs @@ -14,8 +14,8 @@ public class ScriptPluginHelper { private readonly IManager _manager; private readonly ScriptPluginV2 _scriptPlugin; - private readonly SemaphoreSlim _onRequestRunning = new(1, 5); - private const int RequestTimeout = 500; + private readonly SemaphoreSlim _onRequestRunning = new(1, 1); + private const int RequestTimeout = 5000; public ScriptPluginHelper(IManager manager, ScriptPluginV2 scriptPlugin) { From 84ed9c8d8f428a603ee695b54ec46ee326461aca Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 30 May 2023 18:12:57 -0500 Subject: [PATCH 11/11] optimize player history retrieval --- SharedLibraryCore/Dtos/ClientHistoryInfo.cs | 7 +++ WebfrontCore/Controllers/API/Server.cs | 56 ++++++++++++++++++- .../ViewComponents/ServerListViewComponent.cs | 37 +----------- WebfrontCore/Views/Server/_Server.cshtml | 3 +- WebfrontCore/wwwroot/js/server.js | 25 +++++---- 5 files changed, 78 insertions(+), 50 deletions(-) diff --git a/SharedLibraryCore/Dtos/ClientHistoryInfo.cs b/SharedLibraryCore/Dtos/ClientHistoryInfo.cs index 876be62f5..cf764219e 100644 --- a/SharedLibraryCore/Dtos/ClientHistoryInfo.cs +++ b/SharedLibraryCore/Dtos/ClientHistoryInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace SharedLibraryCore.Dtos { @@ -11,11 +12,17 @@ namespace SharedLibraryCore.Dtos public class ClientCountSnapshot { + [JsonIgnore] public DateTime Time { get; set; } + [JsonPropertyName("ts")] public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ"); + [JsonPropertyName("cc")] public int ClientCount { get; set; } + [JsonPropertyName("ci")] public bool ConnectionInterrupted { get;set; } + [JsonIgnore] public string Map { get; set; } + [JsonPropertyName("ma")] public string MapAlias { get; set; } } } diff --git a/WebfrontCore/Controllers/API/Server.cs b/WebfrontCore/Controllers/API/Server.cs index 66ca820aa..023274fbd 100644 --- a/WebfrontCore/Controllers/API/Server.cs +++ b/WebfrontCore/Controllers/API/Server.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; using WebfrontCore.Controllers.API.Models; @@ -12,9 +16,14 @@ namespace WebfrontCore.Controllers.API [Route("api/[controller]")] public class Server : BaseController { - - public Server(IManager manager) : base(manager) + private readonly IServerDataViewer _serverDataViewer; + private readonly ApplicationConfiguration _applicationConfiguration; + + public Server(IManager manager, IServerDataViewer serverDataViewer, + ApplicationConfiguration applicationConfiguration) : base(manager) { + _serverDataViewer = serverDataViewer; + _applicationConfiguration = applicationConfiguration; } [HttpGet] @@ -110,5 +119,48 @@ namespace WebfrontCore.Controllers.API completedEvent.Output }); } + + [HttpGet("{id}/history")] + public async Task GetClientHistory(string id) + { + var foundServer = Manager.GetServers().FirstOrDefault(server => server.Id == id); + + if (foundServer == null) + { + return new NotFoundResult(); + } + + var clientHistory = (await _serverDataViewer.ClientHistoryAsync(_applicationConfiguration.MaxClientHistoryTime, + CancellationToken.None))? + .FirstOrDefault(history => history.ServerId == foundServer.LegacyDatabaseId) ?? + new ClientHistoryInfo + { + ServerId = foundServer.LegacyDatabaseId, + ClientCounts = new List() + }; + + var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty(); + + if (foundServer.ClientHistory.ClientCounts.Any()) + { + counts = counts.Union(foundServer.ClientHistory.ClientCounts.Where(history => + history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue))) + .Where(history => history.Time >= DateTime.UtcNow - _applicationConfiguration.MaxClientHistoryTime); + } + + if (ViewBag.Maps?.Count == 0) + { + return Json(counts.ToList()); + } + + var clientCountSnapshots = counts.ToList(); + foreach (var count in clientCountSnapshots) + { + count.MapAlias = foundServer.Maps.FirstOrDefault(map => map.Name == count.Map)?.Alias ?? + count.Map; + } + + return Json(clientCountSnapshots); + } } } diff --git a/WebfrontCore/ViewComponents/ServerListViewComponent.cs b/WebfrontCore/ViewComponents/ServerListViewComponent.cs index aac16b78e..94970cc5b 100644 --- a/WebfrontCore/ViewComponents/ServerListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ServerListViewComponent.cs @@ -1,29 +1,21 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Dtos; using System.Linq; -using System.Threading; using Data.Models; using Data.Models.Client.Stats; using IW4MAdmin.Plugins.Stats.Helpers; using SharedLibraryCore.Configuration; -using SharedLibraryCore.Interfaces; namespace WebfrontCore.ViewComponents { public class ServerListViewComponent : ViewComponent { - private readonly IServerDataViewer _serverDataViewer; - private readonly ApplicationConfiguration _appConfig; private readonly DefaultSettings _defaultSettings; - public ServerListViewComponent(IServerDataViewer serverDataViewer, - ApplicationConfiguration applicationConfiguration, DefaultSettings defaultSettings) + public ServerListViewComponent(DefaultSettings defaultSettings) { - _serverDataViewer = serverDataViewer; - _appConfig = applicationConfiguration; _defaultSettings = defaultSettings; } @@ -46,25 +38,6 @@ namespace WebfrontCore.ViewComponents foreach (var server in servers) { - var serverId = server.GetIdForServer().Result; - var clientHistory = _serverDataViewer.ClientHistoryAsync(_appConfig.MaxClientHistoryTime, - CancellationToken.None).Result? - .FirstOrDefault(history => history.ServerId == serverId) ?? - new ClientHistoryInfo - { - ServerId = serverId, - ClientCounts = new List() - }; - - var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty(); - - if (server.ClientHistory.ClientCounts.Any()) - { - counts = counts.Union(server.ClientHistory.ClientCounts.Where(history => - history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue))) - .Where(history => history.Time >= DateTime.UtcNow - _appConfig.MaxClientHistoryTime); - } - serverInfo.Add(new ServerInfo { Name = server.Hostname, @@ -76,11 +49,7 @@ namespace WebfrontCore.ViewComponents MaxClients = server.MaxClients, PrivateClientSlots = server.PrivateClientSlots, GameType = server.GametypeName, - ClientHistory = new ClientHistoryInfo - { - ServerId = server.EndPoint, - ClientCounts = counts.ToList() - }, + ClientHistory = new ClientHistoryInfo(), Players = server.GetClientsAsList() .Select(client => new PlayerInfo { diff --git a/WebfrontCore/Views/Server/_Server.cshtml b/WebfrontCore/Views/Server/_Server.cshtml index 77674ab25..3ee7d02fe 100644 --- a/WebfrontCore/Views/Server/_Server.cshtml +++ b/WebfrontCore/Views/Server/_Server.cshtml @@ -82,8 +82,7 @@ }
-
diff --git a/WebfrontCore/wwwroot/js/server.js b/WebfrontCore/wwwroot/js/server.js index a60db1d26..c7cace248 100644 --- a/WebfrontCore/wwwroot/js/server.js +++ b/WebfrontCore/wwwroot/js/server.js @@ -27,25 +27,25 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { let lastMap = ''; playerHistory.forEach((elem, i) => { - if (elem.map !== lastMap) { + if (elem.ma !== lastMap) { mapChange.push(i); - lastMap = elem.map; + lastMap = elem; } if (elem.connectionInterrupted) { offlineTime.push({ clientCount: maxClients, - timeString: elem.timeString + timeString: elem.ts }); onlineTime.push({ clientCount: 0, - timeString: elem.timeString + timeString: elem.ts }) } else { offlineTime.push({ clientCount: 0, - timeString: elem.timeString + timeString: elem.ts }); onlineTime.push(elem) @@ -60,9 +60,9 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { return new Chart(document.getElementById(`server_history_canvas_${i}`), { type: 'line', data: { - labels: playerHistory.map(history => history.timeString), + labels: playerHistory.map(history => history.ts), datasets: [{ - data: onlineTime.map(history => history.clientCount), + data: onlineTime.map(history => history.cc), backgroundColor: fillColor, borderColor: primaryColor, borderWidth: 2, @@ -70,7 +70,7 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { hoverBorderWidth: 2 }, { - data: offlineTime.map(history => history.clientCount), + data: offlineTime.map(history => history.cc), backgroundColor: createDiagonalPattern(offlineFillColor), borderColor: offlineFillColor, borderWidth: 2, @@ -88,7 +88,7 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { callbacks: { // todo: localization at some point title: context => moment(context[0].label).local().calendar(), - label: context => context.datasetIndex !== 1 ? `${context.value} ${_localization['WEBFRONT_SCRIPT_SERVER_PLAYERS']} | ${playerHistory[context.index].mapAlias}` : context.value === '0' ? '' : _localization['WEBFRONT_SCRIPT_SERVER_UNREACHABLE'], + label: context => context.datasetIndex !== 1 ? `${context.value} ${_localization['WEBFRONT_SCRIPT_SERVER_PLAYERS']} | ${playerHistory[context.index].ma}` : context.value === '0' ? '' : _localization['WEBFRONT_SCRIPT_SERVER_UNREACHABLE'], }, mode: 'nearest', intersect: false, @@ -153,13 +153,14 @@ $(document).ready(function () { $(this).parent().parent().find('.server-header-ip-address').show(); }); - $('.server-history-row').each(function (index, element) { - let clientHistory = $(this).data('clienthistory-ex'); + $('.server-history-row').each(async function (index, element) { const serverId = $(this).data('serverid'); + const serverEp = $(this).data('server-endpoint'); setInterval(() => refreshClientActivity(serverId), 2000 + (index * 100)); let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text()); let width = $('.server-header').first().width(); - getPlayerHistoryChart(clientHistory, serverId, width, maxClients); + const clientHistory = await fetch(`/api/server/${serverEp}/history`); + getPlayerHistoryChart(await clientHistory.json(), serverId, width, maxClients); }); $('.moment-date').each((index, element) => {