From 0a55c54c424a8f79f544189fb3dfb81a492a0b8d Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 13 Jul 2022 16:10:16 -0500 Subject: [PATCH] update to game interface/integration for persistent stat data --- Application/IW4MServer.cs | 19 +++++++++++++- Application/Misc/EventPublisher.cs | 6 +++++ Application/Misc/MetaServiceV2.cs | 27 +++++++++++++++++++- Application/Misc/ScriptPlugin.cs | 3 ++- GameFiles/_integration.gsc | 25 ++++++++++++++++-- Plugins/ScriptPlugins/GameInterface.js | 20 +++++++++------ SharedLibraryCore/Events/GameEvent.cs | 5 ++++ SharedLibraryCore/Services/PenaltyService.cs | 10 ++++++++ 8 files changed, 102 insertions(+), 13 deletions(-) diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index f703cf2e1..5abcdc502 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -373,7 +373,7 @@ namespace IW4MAdmin var clientTag = await _metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, E.Origin.ClientId, Manager.CancellationToken); - if (clientTag.Value != null) + if (clientTag?.Value != null) { E.Origin.Tag = clientTag.Value; } @@ -768,6 +768,23 @@ namespace IW4MAdmin { E.Origin.UpdateTeam(E.Extra as string); } + + else if (E.Type == GameEvent.EventType.MetaUpdated) + { + if (E.Extra is "PersistentStatClientId" && int.TryParse(E.Data, out var persistentClientId)) + { + var penalties = await Manager.GetPenaltyService().GetActivePenaltiesByClientId(persistentClientId); + var banPenalty = penalties.FirstOrDefault(penalty => penalty.Type == EFPenalty.PenaltyType.Ban); + + if (banPenalty is not null && E.Origin.Level != Permission.Banned) + { + ServerLogger.LogInformation( + "Banning {Client} as they have have provided a persistent clientId of {PersistentClientId}, which is banned", + E.Origin.ToString(), persistentClientId); + E.Origin.Ban(loc["SERVER_BAN_EVADE"].FormatExt(persistentClientId), Utilities.IW4MAdminClient(this), true); + } + } + } lock (ChatHistory) { diff --git a/Application/Misc/EventPublisher.cs b/Application/Misc/EventPublisher.cs index f28f70fc2..8496517f4 100644 --- a/Application/Misc/EventPublisher.cs +++ b/Application/Misc/EventPublisher.cs @@ -10,6 +10,7 @@ namespace IW4MAdmin.Application.Misc { public event EventHandler OnClientDisconnect; public event EventHandler OnClientConnect; + public event EventHandler OnClientMetaUpdated; private readonly ILogger _logger; @@ -33,6 +34,11 @@ namespace IW4MAdmin.Application.Misc { OnClientDisconnect?.Invoke(this, gameEvent); } + + if (gameEvent.Type == GameEvent.EventType.MetaUpdated) + { + OnClientMetaUpdated?.Invoke(this, gameEvent); + } } catch (Exception ex) diff --git a/Application/Misc/MetaServiceV2.cs b/Application/Misc/MetaServiceV2.cs index 6f925db69..076ba1260 100644 --- a/Application/Misc/MetaServiceV2.cs +++ b/Application/Misc/MetaServiceV2.cs @@ -7,7 +7,10 @@ using System.Threading.Tasks; using Data.Abstractions; using Data.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SharedLibraryCore; +using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; using SharedLibraryCore.QueryHelper; @@ -19,13 +22,15 @@ public class MetaServiceV2 : IMetaServiceV2 { private readonly IDictionary> _metaActions; private readonly IDatabaseContextFactory _contextFactory; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; - public MetaServiceV2(ILogger logger, IDatabaseContextFactory contextFactory) + public MetaServiceV2(ILogger logger, IDatabaseContextFactory contextFactory, IServiceProvider serviceProvider) { _logger = logger; _metaActions = new Dictionary>(); _contextFactory = contextFactory; + _serviceProvider = serviceProvider; } public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId, @@ -64,6 +69,26 @@ public class MetaServiceV2 : IMetaServiceV2 } await context.SaveChangesAsync(token); + + + var manager = _serviceProvider.GetRequiredService(); + var matchingClient = manager.GetActiveClients().FirstOrDefault(client => client.ClientId == clientId); + var server = matchingClient?.CurrentServer ?? manager.GetServers().FirstOrDefault(); + + if (server is not null) + { + manager.AddEvent(new GameEvent + { + Type = GameEvent.EventType.MetaUpdated, + Origin = matchingClient ?? new EFClient + { + ClientId = clientId + }, + Data = metaValue, + Extra = metaKey, + Owner = server + }); + } } public async Task SetPersistentMetaValue(string metaKey, T metaValue, int clientId, diff --git a/Application/Misc/ScriptPlugin.cs b/Application/Misc/ScriptPlugin.cs index 053d0e2d0..c484a0ae7 100644 --- a/Application/Misc/ScriptPlugin.cs +++ b/Application/Misc/ScriptPlugin.cs @@ -116,7 +116,8 @@ namespace IW4MAdmin.Application.Misc typeof(System.Net.Http.HttpClient).Assembly, typeof(EFClient).Assembly, typeof(Utilities).Assembly, - typeof(Encoding).Assembly + typeof(Encoding).Assembly, + typeof(CancellationTokenSource).Assembly }) .CatchClrExceptions() .AddObjectConverter(new PermissionLevelToStringConverter())); diff --git a/GameFiles/_integration.gsc b/GameFiles/_integration.gsc index 80e32395a..f4b382fd5 100644 --- a/GameFiles/_integration.gsc +++ b/GameFiles/_integration.gsc @@ -53,8 +53,6 @@ init() level thread OnPlayerConnect(); } - - ////////////////////////////////// // Client Methods ////////////////////////////////// @@ -167,6 +165,28 @@ DisplayWelcomeData() self IPrintLnBold( "You were last seen ^5" + clientData.lastConnection ); } +SetPersistentData() +{ + storedClientId = self GetPlayerData( "bests", "none" ); + + if ( storedClientId != 0 ) + { + if ( level.iw4adminIntegrationDebug == 1 ) + { + IPrintLn( "Uploading persistent client id " + storedClientId ); + } + + SetClientMeta( "PersistentStatClientId", storedClientId ); + } + + if ( level.iw4adminIntegrationDebug == 1 ) + { + IPrintLn( "Persisting client id " + self.persistentClientId ); + } + + self SetPlayerData( "bests", "none", int( self.persistentClientId ) ); +} + PlayerConnectEvents() { self endon( "disconnect" ); @@ -643,6 +663,7 @@ OnClientDataReceived( event ) self.persistentClientId = event.data["clientId"]; self thread DisplayWelcomeData(); + self setPersistentData(); } OnExecuteCommand( event ) diff --git a/Plugins/ScriptPlugins/GameInterface.js b/Plugins/ScriptPlugins/GameInterface.js index 03740c898..57439e865 100644 --- a/Plugins/ScriptPlugins/GameInterface.js +++ b/Plugins/ScriptPlugins/GameInterface.js @@ -463,7 +463,11 @@ function onReceivedDvar(server, dvarName, dvarValue, success) { if (input.length > 0) { const event = parseEvent(input) - logger.WriteDebug(`Processing input... ${event.eventType} ${event.subType} ${event.data} ${event.clientNumber}`); + logger.WriteDebug(`Processing input... ${event.eventType} ${event.subType} ${event.data.toString()} ${event.clientNumber}`); + + const metaService = _serviceResolver.ResolveService('IMetaServiceV2'); + const threading = importNamespace('System.Threading'); + const token = new threading.CancellationTokenSource().Token; // todo: refactor to mapping if possible if (event.eventType === 'ClientDataRequested') { @@ -475,8 +479,8 @@ function onReceivedDvar(server, dvarName, dvarValue, success) { let data = []; if (event.subType === 'Meta') { - const metaService = _serviceResolver.ResolveService('IMetaService'); - const meta = metaService.GetPersistentMeta(event.data, client).GetAwaiter().GetResult(); + const metaService = _serviceResolver.ResolveService('IMetaServiceV2'); + const meta = metaService.GetPersistentMeta(event.data, client, token).GetAwaiter().GetResult(); data[event.data] = meta === null ? '' : meta.Value; } else { data = { @@ -510,19 +514,19 @@ function onReceivedDvar(server, dvarName, dvarValue, success) { sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Fail'}); } else { if (event.subType === 'Meta') { - const metaService = _serviceResolver.ResolveService('IMetaService'); try { - logger.WriteDebug(`Key=${event.data['key']}, Value=${event.data['value']}`); + logger.WriteDebug(`Key=${event.data['key']}, Value=${event.data['value']}, Direction=${event.data['direction']} ${token}`); if (event.data['direction'] != null) { event.data['direction'] = 'up' - ? metaService.IncrementPersistentMeta(event.data['key'], event.data['value'], clientId).GetAwaiter().GetResult() - : metaService.DecrementPersistentMeta(event.data['key'], event.data['value'], clientId).GetAwaiter().GetResult(); + ? metaService.IncrementPersistentMeta(event.data['key'], event.data['value'], clientId, token).GetAwaiter().GetResult() + : metaService.DecrementPersistentMeta(event.data['key'], event.data['value'], clientId, token).GetAwaiter().GetResult(); } else { - metaService.SetPersistentMeta(event.data['key'], event.data['value'], clientId).GetAwaiter().GetResult(); + metaService.SetPersistentMeta(event.data['key'], event.data['value'], clientId, token).GetAwaiter().GetResult(); } sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Complete'}); } catch (error) { sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Fail'}); + logger.WriteError('Could not persist client meta ' + error.toString()); } } } diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs index e6e029c63..dacf84565 100644 --- a/SharedLibraryCore/Events/GameEvent.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -204,6 +204,11 @@ namespace SharedLibraryCore /// client logged out of webfront /// Logout = 113, + + /// + /// meta value updated on client + /// + MetaUpdated = 114, // events "generated" by IW4MAdmin /// diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index d5ccc5b5f..60a6f4cfd 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -193,6 +193,16 @@ namespace SharedLibraryCore.Services return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); } + public async Task> GetActivePenaltiesByClientId(int clientId) + { + await using var context = _contextFactory.CreateContext(false); + return await context.PenaltyIdentifiers + .Where(identifier => identifier.Penalty.Offender.ClientId == clientId) + .Select(identifier => identifier.Penalty) + .Where(Filter) + .ToListAsync(); + } + public async Task> ActivePenaltiesByRecentIdentifiers(int linkId) { await using var context = _contextFactory.CreateContext(false);