update stats plugin to IPluginV2
This commit is contained in:
parent
7b8f6421aa
commit
66c0561e7f
@ -1,6 +1,5 @@
|
|||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -10,6 +9,7 @@ using Data.Models.Client;
|
|||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
|
using Stats.Config;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Cheat
|
namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||||
@ -37,6 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
Dictionary<IW4Info.HitLocation, HitInfo> HitLocationCount;
|
Dictionary<IW4Info.HitLocation, HitInfo> HitLocationCount;
|
||||||
double AngleDifferenceAverage;
|
double AngleDifferenceAverage;
|
||||||
EFClientStatistics ClientStats;
|
EFClientStatistics ClientStats;
|
||||||
|
private readonly StatsConfiguration _statsConfiguration;
|
||||||
long LastOffset;
|
long LastOffset;
|
||||||
string LastWeapon;
|
string LastWeapon;
|
||||||
ILogger Log;
|
ILogger Log;
|
||||||
@ -55,7 +56,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
public double Offset { get; set; }
|
public double Offset { get; set; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public Detection(ILogger log, EFClientStatistics clientStats)
|
public Detection(ILogger log, EFClientStatistics clientStats, StatsConfiguration statsConfiguration)
|
||||||
{
|
{
|
||||||
Log = log;
|
Log = log;
|
||||||
HitLocationCount = new Dictionary<IW4Info.HitLocation, HitInfo>();
|
HitLocationCount = new Dictionary<IW4Info.HitLocation, HitInfo>();
|
||||||
@ -65,6 +66,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClientStats = clientStats;
|
ClientStats = clientStats;
|
||||||
|
_statsConfiguration = statsConfiguration;
|
||||||
Strain = new Strain();
|
Strain = new Strain();
|
||||||
Tracker = new ChangeTracking<EFACSnapshot>();
|
Tracker = new ChangeTracking<EFACSnapshot>();
|
||||||
TrackedHits = new List<EFClientKill>();
|
TrackedHits = new List<EFClientKill>();
|
||||||
@ -308,7 +310,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
bool shouldIgnoreDetection = false;
|
bool shouldIgnoreDetection = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Recoil]
|
shouldIgnoreDetection = _statsConfiguration.AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Recoil]
|
||||||
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +342,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
shouldIgnoreDetection = false;
|
shouldIgnoreDetection = false;
|
||||||
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Button]
|
shouldIgnoreDetection = _statsConfiguration.AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Button]
|
||||||
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +455,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
shouldIgnoreDetection = false; // reset previous value
|
shouldIgnoreDetection = false; // reset previous value
|
||||||
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Chest]
|
shouldIgnoreDetection = _statsConfiguration.AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Chest]
|
||||||
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore.Events;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Client.Abstractions
|
namespace IW4MAdmin.Plugins.Stats.Client.Abstractions
|
||||||
{
|
{
|
||||||
public interface IClientStatisticCalculator
|
public interface IClientStatisticCalculator
|
||||||
{
|
{
|
||||||
Task GatherDependencies();
|
Task GatherDependencies();
|
||||||
Task CalculateForEvent(GameEvent gameEvent);
|
Task CalculateForEvent(CoreEvent coreEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
using IW4MAdmin.Plugins.Stats.Client.Game;
|
using Data.Models;
|
||||||
using SharedLibraryCore;
|
using IW4MAdmin.Plugins.Stats.Client.Game;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
namespace Stats.Client.Abstractions
|
namespace Stats.Client.Abstractions
|
||||||
{
|
{
|
||||||
public interface IHitInfoBuilder
|
public interface IHitInfoBuilder
|
||||||
{
|
{
|
||||||
HitInfo Build(string[] log, ParserRegex parserRegex, int entityId, bool isSelf, bool isVictim, Server.Game gameName);
|
HitInfo Build(string[] log, ParserRegex parserRegex, int entityId, bool isSelf, bool isVictim, Reference.Game gameName);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,9 @@
|
|||||||
using SharedLibraryCore;
|
using Data.Models;
|
||||||
using Stats.Client.Game;
|
using Stats.Client.Game;
|
||||||
|
|
||||||
namespace Stats.Client.Abstractions
|
namespace Stats.Client.Abstractions;
|
||||||
{
|
|
||||||
public interface IWeaponNameParser
|
public interface IWeaponNameParser
|
||||||
{
|
{
|
||||||
WeaponInfo Parse(string weaponName, Server.Game gameName);
|
WeaponInfo Parse(string weaponName, Reference.Game gameName);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,15 @@ using IW4MAdmin.Plugins.Stats.Client.Game;
|
|||||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Events;
|
||||||
|
using SharedLibraryCore.Events.Game;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Client.Game;
|
using Stats.Client.Game;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Client
|
namespace IW4MAdmin.Plugins.Stats.Client;
|
||||||
{
|
|
||||||
public class HitState
|
public class HitState
|
||||||
{
|
{
|
||||||
public HitState()
|
public HitState()
|
||||||
@ -46,8 +48,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly ILogger<HitCalculator> _logger;
|
private readonly ILogger<HitCalculator> _logger;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<int, HitState> _clientHitStatistics =
|
private readonly ConcurrentDictionary<int, HitState> _clientHitStatistics = new();
|
||||||
new ConcurrentDictionary<int, HitState>();
|
|
||||||
|
|
||||||
private readonly SemaphoreSlim _onTransaction = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _onTransaction = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
@ -93,9 +94,9 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
await _modCache.InitializeAsync();
|
await _modCache.InitializeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CalculateForEvent(GameEvent gameEvent)
|
public async Task CalculateForEvent(CoreEvent coreEvent)
|
||||||
{
|
{
|
||||||
if (gameEvent.Type == GameEvent.EventType.Connect)
|
if (coreEvent is ClientStateInitializeEvent clientStateInitializeEvent)
|
||||||
{
|
{
|
||||||
// if no servers have been cached yet we need to pull them here
|
// if no servers have been cached yet we need to pull them here
|
||||||
// as they could have gotten added after we've initialized
|
// as they could have gotten added after we've initialized
|
||||||
@ -104,32 +105,32 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
await _serverCache.InitializeAsync();
|
await _serverCache.InitializeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
gameEvent.Origin.SetAdditionalProperty(SessionScores, new List<(int, DateTime)>());
|
clientStateInitializeEvent.Client.SetAdditionalProperty(SessionScores, new List<(int, DateTime)>());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameEvent.Type == GameEvent.EventType.Disconnect)
|
if (coreEvent is ClientStateDisposeEvent clientStateDisposeEvent)
|
||||||
{
|
{
|
||||||
_clientHitStatistics.Remove(gameEvent.Origin.ClientId, out var state);
|
_clientHitStatistics.Remove(clientStateDisposeEvent.Client.ClientId, out var state);
|
||||||
|
|
||||||
if (state == null)
|
if (state == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No client hit state available for disconnecting client {client}",
|
_logger.LogWarning("No client hit state available for disconnecting client {Client}",
|
||||||
gameEvent.Origin.ToString());
|
clientStateDisposeEvent.Client.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await state.OnTransaction.WaitAsync();
|
await state.OnTransaction.WaitAsync();
|
||||||
HandleDisconnectCalculations(gameEvent.Origin, state);
|
HandleDisconnectCalculations(clientStateDisposeEvent.Client, state);
|
||||||
await UpdateClientStatistics(gameEvent.Origin.ClientId, state);
|
await UpdateClientStatistics(clientStateDisposeEvent.Client.ClientId, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Could not handle disconnect calculations for client {client}",
|
_logger.LogError(ex, "Could not handle disconnect calculations for client {Client}",
|
||||||
gameEvent.Origin.ToString());
|
clientStateDisposeEvent.Client.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -143,37 +144,40 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameEvent.Type == GameEvent.EventType.MapEnd)
|
if (coreEvent is MatchEndEvent matchEndEvent)
|
||||||
{
|
{
|
||||||
foreach (var client in gameEvent.Owner.GetClientsAsList())
|
foreach (var client in matchEndEvent.Server.ConnectedClients)
|
||||||
{
|
{
|
||||||
var scores = client.GetAdditionalProperty<List<(int, DateTime)>>(SessionScores);
|
var scores = client.GetAdditionalProperty<List<(int, DateTime)>>(SessionScores);
|
||||||
scores?.Add((client.GetAdditionalProperty<int?>(StatManager.ESTIMATED_SCORE) ?? client.Score, DateTime.Now));
|
scores?.Add((client.GetAdditionalProperty<int?>(StatManager.ESTIMATED_SCORE) ?? client.Score,
|
||||||
|
DateTime.Now));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameEvent.Type != GameEvent.EventType.Kill && gameEvent.Type != GameEvent.EventType.Damage)
|
var damageEvent = coreEvent as ClientKillEvent ?? coreEvent as ClientDamageEvent;
|
||||||
|
|
||||||
|
if (damageEvent is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventRegex = gameEvent.Type == GameEvent.EventType.Kill
|
var eventRegex = damageEvent is ClientKillEvent
|
||||||
? gameEvent.Owner.EventParser.Configuration.Kill
|
? damageEvent.Owner.EventParser.Configuration.Kill
|
||||||
: gameEvent.Owner.EventParser.Configuration.Damage;
|
: damageEvent.Owner.EventParser.Configuration.Damage;
|
||||||
|
|
||||||
var match = eventRegex.PatternMatcher.Match(gameEvent.Data);
|
var match = eventRegex.PatternMatcher.Match(damageEvent.Data);
|
||||||
|
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Log for event type {type} does not match pattern {logLine}", gameEvent.Type,
|
_logger.LogWarning("Log for event type {Type} does not match pattern {LogLine}", damageEvent.Type,
|
||||||
gameEvent.Data);
|
damageEvent.Data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var attackerHitInfo = _hitInfoBuilder.Build(match.Values.ToArray(), eventRegex, gameEvent.Origin.ClientId,
|
var attackerHitInfo = _hitInfoBuilder.Build(match.Values.ToArray(), eventRegex, damageEvent.Attacker.ClientId,
|
||||||
gameEvent.Origin.ClientId == gameEvent.Target.ClientId, false, gameEvent.Owner.GameName);
|
damageEvent.Attacker.ClientId == damageEvent.Victim.ClientId, false, damageEvent.Server.GameCode);
|
||||||
var victimHitInfo = _hitInfoBuilder.Build(match.Values.ToArray(), eventRegex, gameEvent.Target.ClientId,
|
var victimHitInfo = _hitInfoBuilder.Build(match.Values.ToArray(), eventRegex, damageEvent.Victim.ClientId,
|
||||||
gameEvent.Origin.ClientId == gameEvent.Target.ClientId, true, gameEvent.Owner.GameName);
|
damageEvent.Attacker.ClientId == damageEvent.Victim.ClientId, true, damageEvent.Server.GameCode);
|
||||||
|
|
||||||
foreach (var hitInfo in new[] {attackerHitInfo, victimHitInfo})
|
foreach (var hitInfo in new[] {attackerHitInfo, victimHitInfo})
|
||||||
{
|
{
|
||||||
@ -188,14 +192,14 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
await _onTransaction.WaitAsync();
|
await _onTransaction.WaitAsync();
|
||||||
if (!_clientHitStatistics.ContainsKey(hitInfo.EntityId))
|
if (!_clientHitStatistics.ContainsKey(hitInfo.EntityId))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Starting to track hits for {client}", hitInfo.EntityId);
|
_logger.LogDebug("Starting to track hits for {Client}", hitInfo.EntityId);
|
||||||
var clientHits = await GetHitsForClient(hitInfo.EntityId);
|
var clientHits = await GetHitsForClient(hitInfo.EntityId);
|
||||||
_clientHitStatistics.TryAdd(hitInfo.EntityId, new HitState()
|
_clientHitStatistics.TryAdd(hitInfo.EntityId, new HitState
|
||||||
{
|
{
|
||||||
Hits = clientHits,
|
Hits = clientHits,
|
||||||
Server = (await _serverCache
|
Server = await _serverCache
|
||||||
.FirstAsync(server =>
|
.FirstAsync(server =>
|
||||||
server.EndPoint == gameEvent.Owner.ToString() && server.HostName != null))
|
server.EndPoint == damageEvent.Server.Id && server.HostName != null)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +232,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Could not update hit calculations for {client}", hitInfo.EntityId);
|
_logger.LogError(ex, "Could not update hit calculations for {Client}", hitInfo.EntityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -613,4 +617,3 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -3,14 +3,13 @@ using System.Linq;
|
|||||||
using Data.Models;
|
using Data.Models;
|
||||||
using IW4MAdmin.Plugins.Stats.Client.Game;
|
using IW4MAdmin.Plugins.Stats.Client.Game;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Client.Game;
|
using Stats.Client.Game;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace Stats.Client
|
namespace Stats.Client;
|
||||||
{
|
|
||||||
public class HitInfoBuilder : IHitInfoBuilder
|
public class HitInfoBuilder : IHitInfoBuilder
|
||||||
{
|
{
|
||||||
private readonly IWeaponNameParser _weaponNameParser;
|
private readonly IWeaponNameParser _weaponNameParser;
|
||||||
@ -24,7 +23,7 @@ namespace Stats.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HitInfo Build(string[] log, ParserRegex parserRegex, int entityId, bool isSelf, bool isVictim,
|
public HitInfo Build(string[] log, ParserRegex parserRegex, int entityId, bool isSelf, bool isVictim,
|
||||||
Server.Game gameName)
|
Reference.Game gameName)
|
||||||
{
|
{
|
||||||
var eventType = log[(uint)ParserRegex.GroupType.EventType].First();
|
var eventType = log[(uint)ParserRegex.GroupType.EventType].First();
|
||||||
HitType hitType;
|
HitType hitType;
|
||||||
@ -75,10 +74,9 @@ namespace Stats.Client
|
|||||||
MeansOfDeath = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]
|
MeansOfDeath = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]
|
||||||
? log[parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]]
|
? log[parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]]
|
||||||
: "Unknown",
|
: "Unknown",
|
||||||
Game = (Reference.Game) gameName
|
Game = gameName
|
||||||
};
|
};
|
||||||
|
|
||||||
return hitInfo;
|
return hitInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -3,7 +3,7 @@ using Stats.Client.Abstractions;
|
|||||||
using Stats.Client.Game;
|
using Stats.Client.Game;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SharedLibraryCore;
|
using Data.Models;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ namespace Stats.Client
|
|||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WeaponInfo Parse(string weaponName, Server.Game gameName)
|
public WeaponInfo Parse(string weaponName, Reference.Game gameName)
|
||||||
{
|
{
|
||||||
var configForGame = _config.WeaponNameParserConfigurations
|
var configForGame = _config.WeaponNameParserConfigurations
|
||||||
?.FirstOrDefault(config => config.Game == gameName) ?? new WeaponNameParserConfiguration()
|
?.FirstOrDefault(config => config.Game == gameName) ?? new WeaponNameParserConfiguration()
|
||||||
|
@ -12,14 +12,15 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
namespace IW4MAdmin.Plugins.Stats.Commands;
|
||||||
{
|
|
||||||
class MostKillsCommand : Command
|
class MostKillsCommand : Command
|
||||||
{
|
{
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
private readonly StatsConfiguration _statsConfig;
|
||||||
|
|
||||||
public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
IDatabaseContextFactory contextFactory, StatsConfiguration statsConfig) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "mostkills";
|
Name = "mostkills";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
|
||||||
@ -27,11 +28,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
Permission = EFClient.Permission.User;
|
Permission = EFClient.Permission.User;
|
||||||
|
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
|
_statsConfig = statsConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
var mostKills = await GetMostKills(StatManager.GetIdForServer(gameEvent.Owner), Plugin.Config.Configuration(),
|
var mostKills = await GetMostKills(StatManager.GetIdForServer(gameEvent.Owner), _statsConfig,
|
||||||
_contextFactory, _translationLookup);
|
_contextFactory, _translationLookup);
|
||||||
if (!gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
if (!gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
@ -77,4 +79,3 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
|
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -7,15 +7,18 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
|
using Stats.Config;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||||
{
|
{
|
||||||
public class ResetStats : Command
|
public class ResetStats : Command
|
||||||
{
|
{
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
private readonly StatManager _statManager;
|
||||||
|
|
||||||
public ResetStats(CommandConfiguration config, ITranslationLookup translationLookup,
|
public ResetStats(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
IDatabaseContextFactory contextFactory, StatManager statManager) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "resetstats";
|
Name = "resetstats";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_RESET_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_RESET_DESC"];
|
||||||
@ -25,6 +28,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
AllowImpersonation = true;
|
AllowImpersonation = true;
|
||||||
|
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
|
_statManager = statManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
@ -53,7 +57,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reset the cached version
|
// reset the cached version
|
||||||
Plugin.Manager.ResetStats(gameEvent.Origin);
|
_statManager.ResetStats(gameEvent.Origin);
|
||||||
|
|
||||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
gameEvent.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,15 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
{
|
{
|
||||||
public class TopStats : Command
|
public class TopStats : Command
|
||||||
{
|
{
|
||||||
public static async Task<List<string>> GetTopStats(Server s, ITranslationLookup translationLookup)
|
public static async Task<List<string>> GetTopStats(IGameServer server, ITranslationLookup translationLookup, StatManager statManager)
|
||||||
{
|
{
|
||||||
var serverId = StatManager.GetIdForServer(s);
|
var serverId = StatManager.GetIdForServer(server);
|
||||||
var topStatsText = new List<string>()
|
var topStatsText = new List<string>()
|
||||||
{
|
{
|
||||||
$"(Color::Accent)--{translationLookup["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
|
$"(Color::Accent)--{translationLookup["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
|
||||||
};
|
};
|
||||||
|
|
||||||
var stats = await Plugin.Manager.GetTopStats(0, 5, serverId);
|
var stats = await statManager.GetTopStats(0, 5, serverId);
|
||||||
var statsList = stats.Select((stats, index) =>
|
var statsList = stats.Select((stats, index) =>
|
||||||
translationLookup["COMMANDS_TOPSTATS_RESULT"]
|
translationLookup["COMMANDS_TOPSTATS_RESULT"]
|
||||||
.FormatExt(index + 1, stats.Name, stats.KDR, stats.Performance));
|
.FormatExt(index + 1, stats.Name, stats.KDR, stats.Performance));
|
||||||
@ -39,8 +39,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
private new readonly CommandConfiguration _config;
|
private new readonly CommandConfiguration _config;
|
||||||
|
private readonly StatManager _statManager;
|
||||||
|
|
||||||
public TopStats(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
public TopStats(CommandConfiguration config, ITranslationLookup translationLookup, StatManager statManager) : base(config,
|
||||||
translationLookup)
|
translationLookup)
|
||||||
{
|
{
|
||||||
Name = "topstats";
|
Name = "topstats";
|
||||||
@ -50,11 +51,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
RequiresTarget = false;
|
RequiresTarget = false;
|
||||||
|
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_statManager = statManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||||
{
|
{
|
||||||
var topStats = await GetTopStats(gameEvent.Owner, _translationLookup);
|
var topStats = await GetTopStats(gameEvent.Owner, _translationLookup, _statManager);
|
||||||
if (!gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
if (!gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||||
{
|
{
|
||||||
await gameEvent.Origin.TellAsync(topStats, gameEvent.Owner.Manager.CancellationToken);
|
await gameEvent.Origin.TellAsync(topStats, gameEvent.Owner.Manager.CancellationToken);
|
||||||
|
@ -15,9 +15,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
public class ViewStatsCommand : Command
|
public class ViewStatsCommand : Command
|
||||||
{
|
{
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
private readonly StatManager _statManager;
|
||||||
|
|
||||||
public ViewStatsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
public ViewStatsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
IDatabaseContextFactory contextFactory, StatManager statManager) : base(config, translationLookup)
|
||||||
{
|
{
|
||||||
Name = "stats";
|
Name = "stats";
|
||||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_VIEW_DESC"];
|
Description = translationLookup["PLUGINS_STATS_COMMANDS_VIEW_DESC"];
|
||||||
@ -34,6 +35,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
};
|
};
|
||||||
|
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
|
_statManager = statManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent E)
|
public override async Task ExecuteAsync(GameEvent E)
|
||||||
@ -53,12 +55,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
|
|
||||||
var serverId = StatManager.GetIdForServer(E.Owner);
|
var serverId = StatManager.GetIdForServer(E.Owner);
|
||||||
|
|
||||||
var totalRankedPlayers = await Plugin.Manager.GetTotalRankedPlayers(serverId);
|
var totalRankedPlayers = await _statManager.GetTotalRankedPlayers(serverId);
|
||||||
|
|
||||||
// getting stats for a particular client
|
// getting stats for a particular client
|
||||||
if (E.Target != null)
|
if (E.Target != null)
|
||||||
{
|
{
|
||||||
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Target.ClientId, serverId);
|
var performanceRanking = await _statManager.GetClientOverallRanking(E.Target.ClientId, serverId);
|
||||||
var performanceRankingString = performanceRanking == 0
|
var performanceRankingString = performanceRanking == 0
|
||||||
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} (Color::Accent)#{performanceRanking}/{totalRankedPlayers}";
|
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} (Color::Accent)#{performanceRanking}/{totalRankedPlayers}";
|
||||||
@ -87,7 +89,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
// getting self stats
|
// getting self stats
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Origin.ClientId, serverId);
|
var performanceRanking = await _statManager.GetClientOverallRanking(E.Origin.ClientId, serverId);
|
||||||
var performanceRankingString = performanceRanking == 0
|
var performanceRankingString = performanceRanking == 0
|
||||||
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} (Color::Accent)#{performanceRanking}/{totalRankedPlayers}";
|
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} (Color::Accent)#{performanceRanking}/{totalRankedPlayers}";
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Data.Models;
|
||||||
using IW4MAdmin.Plugins.Stats.Config;
|
using IW4MAdmin.Plugins.Stats.Config;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
@ -21,26 +22,26 @@ namespace Stats.Config
|
|||||||
public WeaponNameParserConfiguration[] WeaponNameParserConfigurations { get; set; } = {
|
public WeaponNameParserConfiguration[] WeaponNameParserConfigurations { get; set; } = {
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Game = Server.Game.IW3,
|
Game = Reference.Game.IW3,
|
||||||
WeaponSuffix = "mp",
|
WeaponSuffix = "mp",
|
||||||
Delimiters = new[] {'_'}
|
Delimiters = new[] {'_'}
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Game = Server.Game.IW4,
|
Game = Reference.Game.IW4,
|
||||||
WeaponSuffix = "mp",
|
WeaponSuffix = "mp",
|
||||||
Delimiters = new[] {'_'}
|
Delimiters = new[] {'_'}
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Game = Server.Game.IW5,
|
Game = Reference.Game.IW5,
|
||||||
WeaponSuffix = "mp",
|
WeaponSuffix = "mp",
|
||||||
WeaponPrefix = "iw5",
|
WeaponPrefix = "iw5",
|
||||||
Delimiters = new[] {'_'}
|
Delimiters = new[] {'_'}
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Game = Server.Game.T6,
|
Game = Reference.Game.T6,
|
||||||
WeaponSuffix = "mp",
|
WeaponSuffix = "mp",
|
||||||
Delimiters = new[] {'_', '+'}
|
Delimiters = new[] {'_', '+'}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using SharedLibraryCore;
|
using Data.Models;
|
||||||
|
|
||||||
namespace Stats.Config
|
namespace Stats.Config
|
||||||
{
|
{
|
||||||
public class WeaponNameParserConfiguration
|
public class WeaponNameParserConfiguration
|
||||||
{
|
{
|
||||||
public Server.Game Game { get; set; }
|
public Reference.Game Game { get; set; }
|
||||||
public char[] Delimiters { get; set; }
|
public char[] Delimiters { get; set; }
|
||||||
public string WeaponSuffix { get; set; }
|
public string WeaponSuffix { get; set; }
|
||||||
public string WeaponPrefix { get; set; }
|
public string WeaponPrefix { get; set; }
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using SharedLibraryCore.Events.Game;
|
||||||
using EventGeneratorCallback = System.ValueTuple<string, string,
|
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||||
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
||||||
SharedLibraryCore.GameEvent,
|
SharedLibraryCore.GameEvent,
|
||||||
@ -11,35 +12,41 @@ namespace IW4MAdmin.Plugins.Stats.Events
|
|||||||
{
|
{
|
||||||
public class Script : IRegisterEvent
|
public class Script : IRegisterEvent
|
||||||
{
|
{
|
||||||
private const string EVENT_SCRIPTKILL = "ScriptKill";
|
private const string EventScriptKill = "ScriptKill";
|
||||||
private const string EVENT_SCRIPTDAMAGE = "ScriptDamage";
|
private const string EventScriptDamage = "ScriptDamage";
|
||||||
private const string EVENT_JOINTEAM = "JoinTeam";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private EventGeneratorCallback ScriptKill()
|
private static EventGeneratorCallback ScriptKill()
|
||||||
{
|
{
|
||||||
return (EVENT_SCRIPTKILL, EVENT_SCRIPTKILL, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
return (EventScriptKill, EventScriptKill,
|
||||||
|
(eventLine, config, autoEvent) =>
|
||||||
{
|
{
|
||||||
string[] lineSplit = eventLine.Split(";");
|
var lineSplit = eventLine.Split(";");
|
||||||
|
|
||||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||||
{
|
{
|
||||||
return autoEvent;
|
return autoEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
var originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||||
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
var targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||||
|
|
||||||
autoEvent.Type = GameEvent.EventType.ScriptKill;
|
var anticheatEvent = new AntiCheatDamageEvent
|
||||||
autoEvent.Origin = new EFClient() { NetworkId = originId };
|
{
|
||||||
autoEvent.Target = new EFClient() { NetworkId = targetId };
|
ScriptData = eventLine,
|
||||||
autoEvent.RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target;
|
Type = GameEvent.EventType.ScriptKill,
|
||||||
autoEvent.GameTime = autoEvent.GameTime;
|
Origin = new EFClient { NetworkId = originId },
|
||||||
|
Target = new EFClient { NetworkId = targetId },
|
||||||
|
RequiredEntity =
|
||||||
|
GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
|
||||||
|
GameTime = autoEvent.GameTime,
|
||||||
|
IsKill = true
|
||||||
|
};
|
||||||
|
|
||||||
return autoEvent;
|
return anticheatEvent;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -48,55 +55,34 @@ namespace IW4MAdmin.Plugins.Stats.Events
|
|||||||
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private EventGeneratorCallback ScriptDamage()
|
public EventGeneratorCallback ScriptDamage()
|
||||||
{
|
{
|
||||||
// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
||||||
return (EVENT_SCRIPTDAMAGE, EVENT_SCRIPTDAMAGE, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
return (EventScriptDamage, EventScriptDamage,
|
||||||
|
(eventLine, config, autoEvent) =>
|
||||||
{
|
{
|
||||||
string[] lineSplit = eventLine.Split(";");
|
var lineSplit = eventLine.Split(";");
|
||||||
|
|
||||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||||
{
|
{
|
||||||
return autoEvent;
|
return autoEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
var originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||||
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
var targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||||
|
|
||||||
autoEvent.Type = GameEvent.EventType.ScriptDamage;
|
var anticheatEvent = new AntiCheatDamageEvent
|
||||||
autoEvent.Origin = new EFClient() { NetworkId = originId };
|
|
||||||
autoEvent.Target = new EFClient() { NetworkId = targetId };
|
|
||||||
autoEvent.RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target;
|
|
||||||
|
|
||||||
return autoEvent;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private EventGeneratorCallback JoinTeam()
|
|
||||||
{
|
{
|
||||||
// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
ScriptData = eventLine,
|
||||||
return (EVENT_JOINTEAM, EVENT_JOINTEAM, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
Type = GameEvent.EventType.ScriptDamage,
|
||||||
{
|
Origin = new EFClient { NetworkId = originId },
|
||||||
string[] lineSplit = eventLine.Split(";");
|
Target = new EFClient { NetworkId = targetId },
|
||||||
|
RequiredEntity =
|
||||||
|
GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
|
||||||
|
GameTime = autoEvent.GameTime
|
||||||
|
};
|
||||||
|
|
||||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
return anticheatEvent;
|
||||||
{
|
|
||||||
return autoEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
|
||||||
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
|
||||||
|
|
||||||
autoEvent.Type = GameEvent.EventType.JoinTeam;
|
|
||||||
autoEvent.Origin = new EFClient() { NetworkId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle) };
|
|
||||||
autoEvent.RequiredEntity = GameEvent.EventRequiredEntity.Target;
|
|
||||||
|
|
||||||
return autoEvent;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -105,8 +91,12 @@ namespace IW4MAdmin.Plugins.Stats.Events
|
|||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
ScriptKill(),
|
ScriptKill(),
|
||||||
ScriptDamage(),
|
ScriptDamage()
|
||||||
JoinTeam()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AntiCheatDamageEvent : GameScriptEvent
|
||||||
|
{
|
||||||
|
public bool IsKill { get; init; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using Data.Models;
|
|||||||
using Data.Models.Client;
|
using Data.Models.Client;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using IW4MAdmin.Plugins.Stats;
|
using IW4MAdmin.Plugins.Stats;
|
||||||
using IW4MAdmin.Plugins.Stats.Config;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
@ -114,7 +113,7 @@ namespace Stats.Helpers
|
|||||||
All = hitStats,
|
All = hitStats,
|
||||||
Servers = _manager.GetServers()
|
Servers = _manager.GetServers()
|
||||||
.Select(server => new ServerInfo
|
.Select(server => new ServerInfo
|
||||||
{Name = server.Hostname, IPAddress = server.IP, Port = server.Port, Game = (Reference.Game)server.GameName})
|
{Name = server.Hostname, IPAddress = server.ListenAddress, Port = server.ListenPort, Game = (Reference.Game)server.GameName})
|
||||||
.Where(server => server.Game == clientInfo.GameName)
|
.Where(server => server.Game == clientInfo.GameName)
|
||||||
.ToList(),
|
.ToList(),
|
||||||
Aggregate = hitStats.FirstOrDefault(hit =>
|
Aggregate = hitStats.FirstOrDefault(hit =>
|
||||||
|
@ -46,7 +46,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1);
|
||||||
private readonly IServerDistributionCalculator _serverDistributionCalculator;
|
private readonly IServerDistributionCalculator _serverDistributionCalculator;
|
||||||
|
|
||||||
public StatManager(ILogger<StatManager> logger, IManager mgr, IDatabaseContextFactory contextFactory,
|
public StatManager(ILogger<StatManager> logger, IDatabaseContextFactory contextFactory,
|
||||||
StatsConfiguration statsConfig,
|
StatsConfiguration statsConfig,
|
||||||
IServerDistributionCalculator serverDistributionCalculator)
|
IServerDistributionCalculator serverDistributionCalculator)
|
||||||
{
|
{
|
||||||
@ -360,13 +360,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public async Task EnsureServerAdded(IGameServer gameServer, CancellationToken token)
|
||||||
/// Add a server to the StatManager server pool
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sv"></param>
|
|
||||||
public void AddServer(Server sv)
|
|
||||||
{
|
{
|
||||||
// insert the server if it does not exist
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (serverModels == null)
|
if (serverModels == null)
|
||||||
@ -374,76 +369,75 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
SetupServerIds();
|
SetupServerIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
long serverId = GetIdForServer(sv);
|
var serverId = GetIdForServer(gameServer as Server);
|
||||||
EFServer server;
|
|
||||||
|
|
||||||
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||||
var serverSet = ctx.Set<EFServer>();
|
var serverSet = ctx.Set<EFServer>();
|
||||||
// get the server from the database if it exists, otherwise create and insert a new one
|
// get the server from the database if it exists, otherwise create and insert a new one
|
||||||
server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
|
var cachedServerModel = await serverSet.FirstOrDefaultAsync(s => s.ServerId == serverId, token);
|
||||||
|
|
||||||
// the server might be using legacy server id
|
// the server might be using legacy server id
|
||||||
if (server == null)
|
if (cachedServerModel == null)
|
||||||
{
|
{
|
||||||
server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString());
|
cachedServerModel = await serverSet.FirstOrDefaultAsync(s => s.EndPoint == gameServer.Id, token);
|
||||||
|
|
||||||
if (server != null)
|
if (cachedServerModel != null)
|
||||||
{
|
{
|
||||||
// this provides a way to identify legacy server entries
|
// this provides a way to identify legacy server entries
|
||||||
server.EndPoint = sv.ToString();
|
cachedServerModel.EndPoint = gameServer.Id;
|
||||||
ctx.Update(server);
|
ctx.Update(cachedServerModel);
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// server has never been added before
|
// server has never been added before
|
||||||
if (server == null)
|
if (cachedServerModel == null)
|
||||||
{
|
{
|
||||||
server = new EFServer()
|
cachedServerModel = new EFServer
|
||||||
{
|
{
|
||||||
Port = sv.Port,
|
Port = gameServer.ListenPort,
|
||||||
EndPoint = sv.ToString(),
|
EndPoint = gameServer.Id,
|
||||||
ServerId = serverId,
|
ServerId = serverId,
|
||||||
GameName = (Reference.Game?)sv.GameName,
|
GameName = gameServer.GameCode,
|
||||||
HostName = sv.Hostname
|
HostName = gameServer.ListenAddress
|
||||||
};
|
};
|
||||||
|
|
||||||
server = serverSet.Add(server).Entity;
|
cachedServerModel = serverSet.Add(cachedServerModel).Entity;
|
||||||
// this doesn't need to be async as it's during initialization
|
// this doesn't need to be async as it's during initialization
|
||||||
ctx.SaveChanges();
|
await ctx.SaveChangesAsync(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want to set the gamename up if it's never been set, or it changed
|
// we want to set the gamename up if it's never been set, or it changed
|
||||||
else if (!server.GameName.HasValue || server.GameName.Value != (Reference.Game)sv.GameName)
|
else if (!cachedServerModel.GameName.HasValue || cachedServerModel.GameName.Value != gameServer.GameCode)
|
||||||
{
|
{
|
||||||
server.GameName = (Reference.Game)sv.GameName;
|
cachedServerModel.GameName = gameServer.GameCode;
|
||||||
ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true;
|
ctx.Entry(cachedServerModel).Property(property => property.GameName).IsModified = true;
|
||||||
ctx.SaveChanges();
|
await ctx.SaveChangesAsync(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.HostName == null || server.HostName != sv.Hostname)
|
if (cachedServerModel.HostName == null || cachedServerModel.HostName != gameServer.ServerName)
|
||||||
{
|
{
|
||||||
server.HostName = sv.Hostname;
|
cachedServerModel.HostName = gameServer.ServerName;
|
||||||
ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true;
|
ctx.Entry(cachedServerModel).Property(property => property.HostName).IsModified = true;
|
||||||
ctx.SaveChanges();
|
await ctx.SaveChangesAsync(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true;
|
ctx.Entry(cachedServerModel).Property(property => property.IsPasswordProtected).IsModified = true;
|
||||||
server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword);
|
cachedServerModel.IsPasswordProtected = !string.IsNullOrEmpty(gameServer.GamePassword);
|
||||||
ctx.SaveChanges();
|
await ctx.SaveChangesAsync(token);
|
||||||
|
|
||||||
// check to see if the stats have ever been initialized
|
// check to see if the stats have ever been initialized
|
||||||
var serverStats = InitializeServerStats(server.ServerId);
|
var serverStats = InitializeServerStats(cachedServerModel.ServerId);
|
||||||
|
|
||||||
_servers.TryAdd(serverId, new ServerStats(server, serverStats, sv)
|
_servers.TryAdd(serverId, new ServerStats(cachedServerModel, serverStats, gameServer as Server)
|
||||||
{
|
{
|
||||||
IsTeamBased = sv.Gametype != "dm"
|
IsTeamBased = gameServer.Gametype != "dm"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(e, "{message}",
|
_log.LogError(ex, "{Message}",
|
||||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]);
|
Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -552,7 +546,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
clientStats.SessionScore = pl.Score;
|
clientStats.SessionScore = pl.Score;
|
||||||
clientStats.LastScore = pl.Score;
|
clientStats.LastScore = pl.Score;
|
||||||
|
|
||||||
pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats));
|
pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats, _config));
|
||||||
_log.LogDebug("Added {client} to stats", pl.ToString());
|
_log.LogDebug("Added {client} to stats", pl.ToString());
|
||||||
|
|
||||||
return clientStats;
|
return clientStats;
|
||||||
@ -586,41 +580,42 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform stat updates for disconnecting client
|
/// Perform stat updates for disconnecting client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pl">Disconnecting client</param>
|
/// <param name="client">Disconnecting client</param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task RemovePlayer(EFClient pl)
|
public async Task RemovePlayer(EFClient client, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Removing {client} from stats", pl.ToString());
|
_log.LogDebug("Removing {Client} from stats", client.ToString());
|
||||||
|
|
||||||
if (pl.CurrentServer == null)
|
if (client.CurrentServer == null)
|
||||||
{
|
{
|
||||||
_log.LogWarning("Disconnecting client {client} is not on a server", pl.ToString());
|
_log.LogWarning("Disconnecting client {Client} is not on a server", client.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverId = GetIdForServer(pl.CurrentServer);
|
var serverId = GetIdForServer(client.CurrentServer);
|
||||||
var serverStats = _servers[serverId].ServerStatistics;
|
var serverStats = _servers[serverId].ServerStatistics;
|
||||||
|
|
||||||
// get individual client's stats
|
// get individual client's stats
|
||||||
var clientStats = pl.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
var clientStats = client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
|
||||||
// sync their stats before they leave
|
// sync their stats before they leave
|
||||||
if (clientStats != null)
|
if (clientStats != null)
|
||||||
{
|
{
|
||||||
clientStats = UpdateStats(clientStats, pl);
|
clientStats = UpdateStats(clientStats, client);
|
||||||
await SaveClientStats(clientStats);
|
await SaveClientStats(clientStats);
|
||||||
if (_config.EnableAdvancedMetrics)
|
if (_config.EnableAdvancedMetrics)
|
||||||
{
|
{
|
||||||
await UpdateHistoricalRanking(pl.ClientId, clientStats, serverId);
|
await UpdateHistoricalRanking(client.ClientId, clientStats, serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// increment the total play time
|
// increment the total play time
|
||||||
serverStats.TotalPlayTime += pl.ConnectionLength;
|
serverStats.TotalPlayTime += client.ConnectionLength;
|
||||||
pl.SetAdditionalProperty(CLIENT_STATS_KEY, null);
|
client.SetAdditionalProperty(CLIENT_STATS_KEY, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_log.LogWarning("Disconnecting client {client} has not been added to stats", pl.ToString());
|
_log.LogWarning("Disconnecting client {Client} has not been added to stats", client.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -743,7 +738,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Plugin.Config.Configuration().StoreClientKills)
|
if (_config.StoreClientKills)
|
||||||
{
|
{
|
||||||
var serverWaiter = _servers[serverId].OnSaving;
|
var serverWaiter = _servers[serverId].OnSaving;
|
||||||
try
|
try
|
||||||
@ -772,7 +767,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Plugin.Config.Configuration().AnticheatConfiguration.Enable && !attacker.IsBot &&
|
if (_config.AnticheatConfiguration.Enable && !attacker.IsBot &&
|
||||||
attacker.ClientId != victim.ClientId)
|
attacker.ClientId != victim.ClientId)
|
||||||
{
|
{
|
||||||
clientDetection.TrackedHits.Add(hit);
|
clientDetection.TrackedHits.Add(hit);
|
||||||
@ -857,10 +852,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0612
|
#pragma warning disable CS0612
|
||||||
var serverDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
var serverDetectionTypes = _config.AnticheatConfiguration.ServerDetectionTypes;
|
||||||
#pragma warning restore CS0612
|
#pragma warning restore CS0612
|
||||||
var gameDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.GameDetectionTypes;
|
var gameDetectionTypes = _config.AnticheatConfiguration.GameDetectionTypes;
|
||||||
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
var ignoredClients = _config.AnticheatConfiguration.IgnoredClientIds;
|
||||||
|
|
||||||
if (ignoredClients.Contains(clientId))
|
if (ignoredClients.Contains(clientId))
|
||||||
{
|
{
|
||||||
@ -1011,9 +1006,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
victimStats.LastScore = estimatedVictimScore;
|
victimStats.LastScore = estimatedVictimScore;
|
||||||
|
|
||||||
// show encouragement/discouragement
|
// show encouragement/discouragement
|
||||||
var streakMessage = (attackerStats.ClientId != victimStats.ClientId)
|
var streakMessage = attackerStats.ClientId != victimStats.ClientId
|
||||||
? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak)
|
? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak, _config)
|
||||||
: StreakMessage.MessageOnStreak(-1, -1);
|
: StreakMessage.MessageOnStreak(-1, -1, _config);
|
||||||
|
|
||||||
if (streakMessage != string.Empty)
|
if (streakMessage != string.Empty)
|
||||||
{
|
{
|
||||||
@ -1530,13 +1525,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return serverStats;
|
return serverStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetKillstreaks(Server sv)
|
public void ResetKillstreaks(IGameServer gameServer)
|
||||||
{
|
{
|
||||||
foreach (var session in sv.GetClientsAsList()
|
foreach (var session in gameServer.ConnectedClients
|
||||||
.Select(_client => new
|
.Select(client => new
|
||||||
{
|
{
|
||||||
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
stat = client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||||
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
detection = client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
session.stat?.StartNewSession();
|
session.stat?.StartNewSession();
|
||||||
@ -1563,7 +1558,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
stats.EloRating = 200;
|
stats.EloRating = 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddMessageAsync(int clientId, long serverId, bool sentIngame, string message)
|
public async Task AddMessageAsync(int clientId, long serverId, bool sentIngame, string message,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// the web users can have no account
|
// the web users can have no account
|
||||||
if (clientId < 1)
|
if (clientId < 1)
|
||||||
@ -1571,8 +1567,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||||
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
|
context.Set<EFClientMessage>().Add(new EFClientMessage()
|
||||||
{
|
{
|
||||||
ClientId = clientId,
|
ClientId = clientId,
|
||||||
Message = message,
|
Message = message,
|
||||||
@ -1581,26 +1577,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
SentIngame = sentIngame
|
SentIngame = sentIngame
|
||||||
});
|
});
|
||||||
|
|
||||||
await ctx.SaveChangesAsync();
|
await context.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Sync(Server sv)
|
public async Task Sync(IGameServer gameServer, CancellationToken token)
|
||||||
{
|
{
|
||||||
long serverId = GetIdForServer(sv);
|
var serverId = GetIdForServer(gameServer);
|
||||||
|
|
||||||
var waiter = _servers[serverId].OnSaving;
|
var waiter = _servers[serverId].OnSaving;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await waiter.WaitAsync();
|
await waiter.WaitAsync(token);
|
||||||
|
|
||||||
await using var ctx = _contextFactory.CreateContext();
|
await using var context = _contextFactory.CreateContext();
|
||||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
var serverStatsSet = context.Set<EFServerStatistics>();
|
||||||
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
||||||
await ctx.SaveChangesAsync();
|
await context.SaveChangesAsync(token);
|
||||||
|
|
||||||
foreach (var stats in sv.GetClientsAsList()
|
foreach (var stats in gameServer.ConnectedClients
|
||||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
.Select(client => client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
||||||
.Where(_stats => _stats != null))
|
.Where(stats => stats != null))
|
||||||
{
|
{
|
||||||
await SaveClientStats(stats);
|
await SaveClientStats(stats);
|
||||||
}
|
}
|
||||||
@ -1608,9 +1604,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
await SaveHitCache(serverId);
|
await SaveHitCache(serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(e, "There was a problem syncing server stats");
|
_log.LogError(ex, "There was a problem syncing server stats");
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
@ -1627,28 +1623,24 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
_servers[serverId].IsTeamBased = isTeamBased;
|
_servers[serverId].IsTeamBased = isTeamBased;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long GetIdForServer(Server server)
|
public static long GetIdForServer(IGameServer gameServer)
|
||||||
{
|
{
|
||||||
if ($"{server.IP}:{server.Port.ToString()}" == "66.150.121.184:28965")
|
if (gameServer.Id == "66.150.121.184:28965")
|
||||||
{
|
{
|
||||||
return 886229536;
|
return 886229536;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: this is not stable and will need to be migrated again...
|
// todo: this is not stable and will need to be migrated again...
|
||||||
long id = HashCode.Combine(server.IP, server.Port);
|
long id = HashCode.Combine(gameServer.ListenAddress, gameServer.ListenPort);
|
||||||
id = id < 0 ? Math.Abs(id) : id;
|
id = id < 0 ? Math.Abs(id) : id;
|
||||||
long? serverId;
|
|
||||||
|
|
||||||
serverId = serverModels.FirstOrDefault(_server => _server.ServerId == server.EndPoint ||
|
#pragma warning disable CS0618
|
||||||
_server.EndPoint == server.ToString() ||
|
var serverId = serverModels.FirstOrDefault(cachedServer => cachedServer.ServerId == gameServer.LegacyEndpoint ||
|
||||||
_server.ServerId == id)?.ServerId;
|
#pragma warning restore CS0618
|
||||||
|
cachedServer.EndPoint == gameServer.ToString() ||
|
||||||
|
cachedServer.ServerId == id)?.ServerId;
|
||||||
|
|
||||||
if (!serverId.HasValue)
|
return serverId ?? id;
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverId.Value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,17 @@
|
|||||||
using SharedLibraryCore;
|
using System.Linq;
|
||||||
using SharedLibraryCore.Helpers;
|
using Stats.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
namespace IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
{
|
|
||||||
public class StreakMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get a message from the configuration encouraging or discouraging clients
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="killStreak">how many kills the client has without dying</param>
|
|
||||||
/// <param name="deathStreak">how many deaths the client has without getting a kill</param>
|
|
||||||
/// <returns>message to send to the client</returns>
|
|
||||||
public static string MessageOnStreak(int killStreak, int deathStreak)
|
|
||||||
{
|
|
||||||
var killstreakMessage = Plugin.Config.Configuration().KillstreakMessages;
|
|
||||||
var deathstreakMessage = Plugin.Config.Configuration().DeathstreakMessages;
|
|
||||||
|
|
||||||
string message = killstreakMessage?.FirstOrDefault(m => m.Count == killStreak)?.Message;
|
public static class StreakMessage
|
||||||
message = message ?? deathstreakMessage?.FirstOrDefault(m => m.Count == deathStreak)?.Message;
|
{
|
||||||
|
public static string MessageOnStreak(int killStreak, int deathStreak, StatsConfiguration config)
|
||||||
|
{
|
||||||
|
var killstreakMessage = config.KillstreakMessages;
|
||||||
|
var deathstreakMessage = config.DeathstreakMessages;
|
||||||
|
|
||||||
|
var message = killstreakMessage?.FirstOrDefault(m => m.Count == killStreak)?.Message;
|
||||||
|
message ??= deathstreakMessage?.FirstOrDefault(m => m.Count == deathStreak)?.Message;
|
||||||
return message ?? "";
|
return message ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -12,156 +12,210 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
|
using Data.Models;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using Data.Models.Server;
|
using Data.Models.Server;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
||||||
|
using IW4MAdmin.Plugins.Stats.Events;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SharedLibraryCore.Events.Game;
|
||||||
|
using SharedLibraryCore.Events.Management;
|
||||||
|
using SharedLibraryCore.Interfaces.Events;
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using EFClient = SharedLibraryCore.Database.Models.EFClient;
|
using EFClient = SharedLibraryCore.Database.Models.EFClient;
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Stats
|
namespace IW4MAdmin.Plugins.Stats;
|
||||||
{
|
|
||||||
public class Plugin : IPlugin
|
public class Plugin : IPluginV2
|
||||||
{
|
{
|
||||||
public string Name => "Simple Stats";
|
public string Name => "Simple Stats";
|
||||||
|
public string Version => Utilities.GetVersionAsString();
|
||||||
public float Version => (float)Utilities.GetVersionAsDouble();
|
|
||||||
|
|
||||||
public string Author => "RaidMax";
|
public string Author => "RaidMax";
|
||||||
|
|
||||||
public static StatManager Manager { get; private set; }
|
|
||||||
public static IManager ServerManager;
|
public static IManager ServerManager;
|
||||||
public static IConfigurationHandler<StatsConfiguration> Config { get; private set; }
|
|
||||||
|
|
||||||
private readonly IDatabaseContextFactory _databaseContextFactory;
|
private readonly IDatabaseContextFactory _databaseContextFactory;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly IMetaServiceV2 _metaService;
|
private readonly IMetaServiceV2 _metaService;
|
||||||
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatQueryHelper;
|
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatQueryHelper;
|
||||||
private readonly ILogger<StatManager> _managerLogger;
|
|
||||||
private readonly ILogger<Plugin> _logger;
|
private readonly ILogger<Plugin> _logger;
|
||||||
private readonly List<IClientStatisticCalculator> _statCalculators;
|
private readonly List<IClientStatisticCalculator> _statCalculators;
|
||||||
private readonly IServerDistributionCalculator _serverDistributionCalculator;
|
private readonly IServerDistributionCalculator _serverDistributionCalculator;
|
||||||
private readonly IServerDataViewer _serverDataViewer;
|
private readonly IServerDataViewer _serverDataViewer;
|
||||||
|
private readonly StatsConfiguration _statsConfig;
|
||||||
|
private readonly StatManager _statManager;
|
||||||
|
|
||||||
public Plugin(ILogger<Plugin> logger, IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
|
public static void RegisterDependencies(IServiceCollection serviceCollection)
|
||||||
ITranslationLookup translationLookup, IMetaServiceV2 metaService, IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper, ILogger<StatManager> managerLogger,
|
{
|
||||||
IEnumerable<IClientStatisticCalculator> statCalculators, IServerDistributionCalculator serverDistributionCalculator, IServerDataViewer serverDataViewer)
|
serviceCollection.AddConfiguration<StatsConfiguration>("StatsPluginSettings");
|
||||||
|
serviceCollection.AddSingleton<StatManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugin(ILogger<Plugin> logger, IDatabaseContextFactory databaseContextFactory,
|
||||||
|
ITranslationLookup translationLookup, IMetaServiceV2 metaService,
|
||||||
|
IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper,
|
||||||
|
IEnumerable<IClientStatisticCalculator> statCalculators,
|
||||||
|
IServerDistributionCalculator serverDistributionCalculator, IServerDataViewer serverDataViewer,
|
||||||
|
StatsConfiguration statsConfig, StatManager statManager)
|
||||||
{
|
{
|
||||||
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
|
||||||
_databaseContextFactory = databaseContextFactory;
|
_databaseContextFactory = databaseContextFactory;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
_chatQueryHelper = chatQueryHelper;
|
_chatQueryHelper = chatQueryHelper;
|
||||||
_managerLogger = managerLogger;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_statCalculators = statCalculators.ToList();
|
_statCalculators = statCalculators.ToList();
|
||||||
_serverDistributionCalculator = serverDistributionCalculator;
|
_serverDistributionCalculator = serverDistributionCalculator;
|
||||||
_serverDataViewer = serverDataViewer;
|
_serverDataViewer = serverDataViewer;
|
||||||
|
_statsConfig = statsConfig;
|
||||||
|
_statManager = statManager;
|
||||||
|
|
||||||
|
IGameServerEventSubscriptions.MonitoringStarted +=
|
||||||
|
async (monitorEvent, token) => await _statManager.EnsureServerAdded(monitorEvent.Server, token);
|
||||||
|
IGameServerEventSubscriptions.MonitoringStopped +=
|
||||||
|
async (monitorEvent, token) => await _statManager.Sync(monitorEvent.Server, token);
|
||||||
|
IManagementEventSubscriptions.ClientStateInitialized += async (clientEvent, token) =>
|
||||||
|
{
|
||||||
|
if (!_statsConfig.EnableAdvancedMetrics)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
foreach (var calculator in _statCalculators)
|
||||||
{
|
{
|
||||||
switch (gameEvent.Type)
|
await calculator.CalculateForEvent(clientEvent);
|
||||||
{
|
|
||||||
case GameEvent.EventType.Start:
|
|
||||||
Manager.AddServer(server);
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Disconnect:
|
|
||||||
await Manager.RemovePlayer(gameEvent.Origin);
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Say:
|
|
||||||
if (!string.IsNullOrEmpty(gameEvent.Data) &&
|
|
||||||
gameEvent.Origin.ClientId > 1)
|
|
||||||
{
|
|
||||||
await Manager.AddMessageAsync(gameEvent.Origin.ClientId, StatManager.GetIdForServer(server), true, gameEvent.Data);
|
|
||||||
}
|
}
|
||||||
break;
|
};
|
||||||
case GameEvent.EventType.MapChange:
|
IManagementEventSubscriptions.ClientStateDisposed +=
|
||||||
Manager.SetTeamBased(StatManager.GetIdForServer(server), server.Gametype != "dm");
|
async (clientEvent, token) =>
|
||||||
Manager.ResetKillstreaks(server);
|
{
|
||||||
await Manager.Sync(server);
|
await _statManager.RemovePlayer(clientEvent.Client, token);
|
||||||
break;
|
|
||||||
case GameEvent.EventType.MapEnd:
|
if (!_statsConfig.EnableAdvancedMetrics)
|
||||||
Manager.ResetKillstreaks(server);
|
{
|
||||||
await Manager.Sync(server);
|
return;
|
||||||
break;
|
}
|
||||||
case GameEvent.EventType.Command:
|
|
||||||
var shouldPersist = !string.IsNullOrEmpty(gameEvent.Data) &&
|
foreach (var calculator in _statCalculators)
|
||||||
gameEvent.Extra?.GetType().Name == "SayCommand";
|
{
|
||||||
|
await calculator.CalculateForEvent(clientEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IGameEventSubscriptions.ClientMessaged += async (messageEvent, token) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(messageEvent.Message) &&
|
||||||
|
messageEvent.Client.ClientId > 1)
|
||||||
|
{
|
||||||
|
await _statManager.AddMessageAsync(messageEvent.Client.ClientId,
|
||||||
|
StatManager.GetIdForServer(messageEvent.Server), true, messageEvent.Message, token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IGameEventSubscriptions.MatchEnded += OnMatchEvent;
|
||||||
|
IGameEventSubscriptions.MatchStarted += OnMatchEvent;
|
||||||
|
IGameEventSubscriptions.ScriptEventTriggered += OnScriptEvent;
|
||||||
|
IGameEventSubscriptions.ClientKilled += OnClientKilled;
|
||||||
|
IGameEventSubscriptions.ClientDamaged += OnClientDamaged;
|
||||||
|
IManagementEventSubscriptions.ClientCommandExecuted += OnClientCommandExecute;
|
||||||
|
IManagementEventSubscriptions.Load += OnLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnClientKilled(ClientKillEvent killEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (!ShouldIgnoreEvent(killEvent.Attacker, killEvent.Victim))
|
||||||
|
{
|
||||||
|
// this treats "world" damage as self damage
|
||||||
|
if (IsWorldDamage(killEvent.Attacker))
|
||||||
|
{
|
||||||
|
killEvent.UpdateAttacker(killEvent.Victim);
|
||||||
|
}
|
||||||
|
|
||||||
|
await EnsureClientsAdded(killEvent.Attacker, killEvent.Victim);
|
||||||
|
await _statManager.AddStandardKill(killEvent.Attacker, killEvent.Victim);
|
||||||
|
|
||||||
|
if (!_statsConfig.EnableAdvancedMetrics)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var calculator in _statCalculators)
|
||||||
|
{
|
||||||
|
await calculator.CalculateForEvent(killEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnClientDamaged(ClientDamageEvent damageEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (ShouldIgnoreEvent(damageEvent.Attacker, damageEvent.Victim))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_statsConfig.EnableAdvancedMetrics)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this treats "world" damage as self damage
|
||||||
|
if (IsWorldDamage(damageEvent.Attacker))
|
||||||
|
{
|
||||||
|
damageEvent.UpdateAttacker(damageEvent.Victim);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var calculator in _statCalculators)
|
||||||
|
{
|
||||||
|
await calculator.CalculateForEvent(damageEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnScriptEvent(GameScriptEvent scriptEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (scriptEvent is not AntiCheatDamageEvent antiCheatDamageEvent)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var killInfo = scriptEvent.ScriptData?.Split(';') ?? Array.Empty<string>();
|
||||||
|
if ((scriptEvent.Server.IsLegacyGameIntegrationEnabled ||
|
||||||
|
ShouldOverrideAnticheatSetting(scriptEvent.Server)) && killInfo.Length >= 18 &&
|
||||||
|
!ShouldIgnoreEvent(antiCheatDamageEvent.Origin, antiCheatDamageEvent.Target))
|
||||||
|
{
|
||||||
|
// this treats "world" damage as self damage
|
||||||
|
if (IsWorldDamage(antiCheatDamageEvent.Origin))
|
||||||
|
{
|
||||||
|
antiCheatDamageEvent.Origin = antiCheatDamageEvent.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
await EnsureClientsAdded(antiCheatDamageEvent.Origin, antiCheatDamageEvent.Target);
|
||||||
|
await _statManager.AddScriptHit(!antiCheatDamageEvent.IsKill, antiCheatDamageEvent.CreatedAt.DateTime,
|
||||||
|
antiCheatDamageEvent.Origin,
|
||||||
|
antiCheatDamageEvent.Target,
|
||||||
|
StatManager.GetIdForServer(antiCheatDamageEvent.Server), antiCheatDamageEvent.Server.Map.Name,
|
||||||
|
killInfo[7], killInfo[8],
|
||||||
|
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11],
|
||||||
|
killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnClientCommandExecute(ClientExecuteCommandEvent commandEvent, CancellationToken token)
|
||||||
|
{
|
||||||
|
var shouldPersist = !string.IsNullOrEmpty(commandEvent.CommandText) && commandEvent.Command.Name == "say";
|
||||||
|
|
||||||
if (shouldPersist)
|
if (shouldPersist)
|
||||||
{
|
{
|
||||||
await Manager.AddMessageAsync(gameEvent.Origin.ClientId, StatManager.GetIdForServer(server), false, gameEvent.Data);
|
await _statManager.AddMessageAsync(commandEvent.Client.ClientId,
|
||||||
|
StatManager.GetIdForServer(commandEvent.Client.CurrentServer), false, commandEvent.CommandText, token);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case GameEvent.EventType.ScriptKill:
|
|
||||||
var killInfo = (gameEvent.Data != null) ? gameEvent.Data.Split(';') : Array.Empty<string>();
|
|
||||||
if ((server.CustomCallback || ShouldOverrideAnticheatSetting(server)) && killInfo.Length >= 18 && !ShouldIgnoreEvent(gameEvent.Origin, gameEvent.Target))
|
|
||||||
{
|
|
||||||
// this treats "world" damage as self damage
|
|
||||||
if (IsWorldDamage(gameEvent.Origin))
|
|
||||||
{
|
|
||||||
gameEvent.Origin = gameEvent.Target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await EnsureClientsAdded(gameEvent.Origin, gameEvent.Target);
|
private async Task OnMatchEvent(GameEventV2 gameEvent, CancellationToken token)
|
||||||
await Manager.AddScriptHit(false, gameEvent.Time, gameEvent.Origin, gameEvent.Target, StatManager.GetIdForServer(server), server.CurrentMap.Name, killInfo[7], killInfo[8],
|
{
|
||||||
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
_statManager.SetTeamBased(StatManager.GetIdForServer(gameEvent.Server), gameEvent.Server.Gametype != "dm");
|
||||||
}
|
_statManager.ResetKillstreaks(gameEvent.Server);
|
||||||
|
await _statManager.Sync(gameEvent.Server, token);
|
||||||
|
|
||||||
else
|
if (!_statsConfig.EnableAdvancedMetrics)
|
||||||
{
|
|
||||||
_logger.LogDebug("Skipping script kill as it is ignored or data in customcallbacks is outdated/missing");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Kill:
|
|
||||||
if (!ShouldIgnoreEvent(gameEvent.Origin, gameEvent.Target))
|
|
||||||
{
|
|
||||||
// this treats "world" damage as self damage
|
|
||||||
if (IsWorldDamage(gameEvent.Origin))
|
|
||||||
{
|
|
||||||
gameEvent.Origin = gameEvent.Target;
|
|
||||||
}
|
|
||||||
|
|
||||||
await EnsureClientsAdded(gameEvent.Origin, gameEvent.Target);
|
|
||||||
await Manager.AddStandardKill(gameEvent.Origin, gameEvent.Target);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.Damage:
|
|
||||||
if (!ShouldIgnoreEvent(gameEvent.Origin, gameEvent.Target))
|
|
||||||
{
|
|
||||||
// this treats "world" damage as self damage
|
|
||||||
if (IsWorldDamage(gameEvent.Origin))
|
|
||||||
{
|
|
||||||
gameEvent.Origin = gameEvent.Target;
|
|
||||||
}
|
|
||||||
|
|
||||||
Manager.AddDamageEvent(gameEvent.Data, gameEvent.Origin.ClientId, gameEvent.Target.ClientId, StatManager.GetIdForServer(server));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GameEvent.EventType.ScriptDamage:
|
|
||||||
killInfo = (gameEvent.Data != null) ? gameEvent.Data.Split(';') : new string[0];
|
|
||||||
if ((server.CustomCallback || ShouldOverrideAnticheatSetting(server)) && killInfo.Length >= 18 && !ShouldIgnoreEvent(gameEvent.Origin, gameEvent.Target))
|
|
||||||
{
|
|
||||||
// this treats "world" damage as self damage
|
|
||||||
if (IsWorldDamage(gameEvent.Origin))
|
|
||||||
{
|
|
||||||
gameEvent.Origin = gameEvent.Target;
|
|
||||||
}
|
|
||||||
|
|
||||||
await EnsureClientsAdded(gameEvent.Origin, gameEvent.Target);
|
|
||||||
await Manager.AddScriptHit(true, gameEvent.Time, gameEvent.Origin, gameEvent.Target, StatManager.GetIdForServer(server), server.CurrentMap.Name, killInfo[7], killInfo[8],
|
|
||||||
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15], killInfo[16], killInfo[17]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Skipping script damage as it is ignored or data in customcallbacks is outdated/missing");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Config.Configuration().EnableAdvancedMetrics)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -172,17 +226,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnLoadAsync(IManager manager)
|
private async Task OnLoad(IManager manager, CancellationToken token)
|
||||||
{
|
{
|
||||||
await Config.BuildAsync();
|
|
||||||
// load custom configuration
|
|
||||||
if (Config.Configuration() == null)
|
|
||||||
{
|
|
||||||
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
|
||||||
}
|
|
||||||
Config.Configuration().ApplyMigration();
|
|
||||||
await Config.Save();
|
|
||||||
|
|
||||||
// register the topstats page
|
// register the topstats page
|
||||||
// todo:generate the URL/Location instead of hardcoding
|
// todo:generate the URL/Location instead of hardcoding
|
||||||
manager.GetPageList()
|
manager.GetPageList()
|
||||||
@ -191,28 +236,37 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
"/Stats/TopPlayers");
|
"/Stats/TopPlayers");
|
||||||
|
|
||||||
// meta data info
|
// meta data info
|
||||||
async Task<IEnumerable<InformationResponse>> GetStats(ClientPaginationRequest request, CancellationToken token = default)
|
async Task<IEnumerable<InformationResponse>> GetStats(ClientPaginationRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||||
IList<EFClientStatistics> clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync(token);
|
IList<EFClientStatistics> clientStats = await ctx.Set<EFClientStatistics>()
|
||||||
|
.Where(c => c.ClientId == request.ClientId).ToListAsync(token);
|
||||||
|
|
||||||
var kills = clientStats.Sum(c => c.Kills);
|
var kills = clientStats.Sum(c => c.Kills);
|
||||||
var deaths = clientStats.Sum(c => c.Deaths);
|
var deaths = clientStats.Sum(c => c.Deaths);
|
||||||
var kdr = Math.Round(kills / (double)deaths, 2);
|
var kdr = Math.Round(kills / (double)deaths, 2);
|
||||||
var validPerformanceValues = clientStats.Where(c => c.Performance > 0).ToList();
|
var validPerformanceValues = clientStats.Where(c => c.Performance > 0).ToList();
|
||||||
var performancePlayTime = validPerformanceValues.Sum(s => s.TimePlayed);
|
var performancePlayTime = validPerformanceValues.Sum(s => s.TimePlayed);
|
||||||
var performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2);
|
var performance =
|
||||||
|
Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2);
|
||||||
var spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Count(c => c.SPM > 0), 1);
|
var spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Count(c => c.SPM > 0), 1);
|
||||||
var overallRanking = await Manager.GetClientOverallRanking(request.ClientId);
|
var overallRanking = await _statManager.GetClientOverallRanking(request.ClientId);
|
||||||
|
|
||||||
return new List<InformationResponse>
|
return new List<InformationResponse>
|
||||||
{
|
{
|
||||||
new InformationResponse
|
new InformationResponse
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
|
||||||
Value = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING_FORMAT"].FormatExt((overallRanking == 0 ? "--" :
|
Value = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING_FORMAT"]
|
||||||
overallRanking.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))),
|
.FormatExt(
|
||||||
(await _serverDataViewer.RankedClientsCountAsync(token: token)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))
|
(overallRanking == 0
|
||||||
|
? "--"
|
||||||
|
: overallRanking.ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization
|
||||||
|
.LocalizationName))),
|
||||||
|
(await _serverDataViewer.RankedClientsCountAsync(token: token)).ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))
|
||||||
),
|
),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 0,
|
Order = 0,
|
||||||
@ -221,7 +275,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse
|
new InformationResponse
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
|
||||||
Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = kills.ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 1,
|
Order = 1,
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
@ -229,7 +284,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse
|
new InformationResponse
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
|
||||||
Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = deaths.ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 2,
|
Order = 2,
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
@ -237,7 +293,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse
|
new InformationResponse
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
|
||||||
Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = kdr.ToString(
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 3,
|
Order = 3,
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
@ -245,7 +302,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse
|
new InformationResponse
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PERFORMANCE"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PERFORMANCE"],
|
||||||
Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = performance.ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 4,
|
Order = 4,
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
@ -253,7 +311,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse
|
new InformationResponse
|
||||||
{
|
{
|
||||||
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
|
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
|
||||||
Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = spm.ToString(
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Column = 0,
|
Column = 0,
|
||||||
Order = 5,
|
Order = 5,
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
@ -261,7 +320,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<IEnumerable<InformationResponse>> GetAnticheatInfo(ClientPaginationRequest request, CancellationToken token = default)
|
async Task<IEnumerable<InformationResponse>> GetAnticheatInfo(ClientPaginationRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
await using var context = _databaseContextFactory.CreateContext(enableTracking: false);
|
await using var context = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||||
IList<EFClientStatistics> clientStats = await context.Set<EFClientStatistics>()
|
IList<EFClientStatistics> clientStats = await context.Set<EFClientStatistics>()
|
||||||
@ -280,24 +340,43 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
if (clientStats.Any(cs => cs.HitLocations.Count > 0))
|
if (clientStats.Any(cs => cs.HitLocations.Count > 0))
|
||||||
{
|
{
|
||||||
chestRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
chestRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
||||||
c.HitLocations.First(hl => hl.Location == (int)IW4Info.HitLocation.torso_upper).HitCount) /
|
c.HitLocations.First(hl =>
|
||||||
|
hl.Location == (int)IW4Info.HitLocation.torso_upper).HitCount) /
|
||||||
(double)clientStats.Where(c => c.HitLocations.Count > 0)
|
(double)clientStats.Where(c => c.HitLocations.Count > 0)
|
||||||
.Sum(c => c.HitLocations.Where(hl => hl.Location != (int)IW4Info.HitLocation.none).Sum(f => f.HitCount))) * 100.0, 0);
|
.Sum(c => c.HitLocations
|
||||||
|
.Where(hl => hl.Location != (int)IW4Info.HitLocation.none)
|
||||||
|
.Sum(f => f.HitCount))) * 100.0, 0);
|
||||||
|
|
||||||
abdomenRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
abdomenRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
||||||
c.HitLocations.First(hl => hl.Location == (int)IW4Info.HitLocation.torso_lower).HitCount) /
|
c.HitLocations.First(hl =>
|
||||||
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => c.HitLocations.Where(hl => hl.Location != (int)IW4Info.HitLocation.none).Sum(f => f.HitCount))) * 100.0, 0);
|
hl.Location == (int)IW4Info.HitLocation.torso_lower).HitCount) /
|
||||||
|
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
||||||
|
c.HitLocations.Where(hl => hl.Location != (int)IW4Info.HitLocation.none)
|
||||||
|
.Sum(f => f.HitCount))) * 100.0, 0);
|
||||||
|
|
||||||
chestAbdomenRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == (int)IW4Info.HitLocation.torso_upper).HitCount) /
|
chestAbdomenRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs =>
|
||||||
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == (int)IW4Info.HitLocation.torso_lower).HitCount)) * 100.0, 0);
|
cs.HitLocations.First(hl =>
|
||||||
|
hl.Location == (int)IW4Info.HitLocation.torso_upper).HitCount) /
|
||||||
|
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs =>
|
||||||
|
cs.HitLocations.First(hl =>
|
||||||
|
hl.Location == (int)IW4Info.HitLocation.torso_lower)
|
||||||
|
.HitCount)) * 100.0, 0);
|
||||||
|
|
||||||
headRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == (int)IW4Info.HitLocation.head).HitCount) /
|
headRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs =>
|
||||||
|
cs.HitLocations.First(hl => hl.Location == (int)IW4Info.HitLocation.head)
|
||||||
|
.HitCount) /
|
||||||
(double)clientStats.Where(c => c.HitLocations.Count > 0)
|
(double)clientStats.Where(c => c.HitLocations.Count > 0)
|
||||||
.Sum(c => c.HitLocations.Where(hl => hl.Location != (int)IW4Info.HitLocation.none).Sum(f => f.HitCount))) * 100.0, 0);
|
.Sum(c => c.HitLocations
|
||||||
|
.Where(hl => hl.Location != (int)IW4Info.HitLocation.none)
|
||||||
|
.Sum(f => f.HitCount))) * 100.0, 0);
|
||||||
|
|
||||||
var validOffsets = clientStats.Where(c => c.HitLocations.Count(hl => hl.HitCount > 0) > 0).SelectMany(hl => hl.HitLocations).ToList();
|
var validOffsets = clientStats.Where(c => c.HitLocations.Count(hl => hl.HitCount > 0) > 0)
|
||||||
hitOffsetAverage = validOffsets.Sum(o => o.HitCount * o.HitOffsetAverage) / (double)validOffsets.Sum(o => o.HitCount);
|
.SelectMany(hl => hl.HitLocations).ToList();
|
||||||
averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0;
|
hitOffsetAverage = validOffsets.Sum(o => o.HitCount * o.HitOffsetAverage) /
|
||||||
|
(double)validOffsets.Sum(o => o.HitCount);
|
||||||
|
averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0)
|
||||||
|
? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue)
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<InformationResponse>
|
return new List<InformationResponse>
|
||||||
@ -305,7 +384,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
|
||||||
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = chestRatio.ToString(
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 100,
|
Order = 100,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
|
||||||
@ -314,7 +394,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
|
||||||
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = abdomenRatio.ToString(
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 101,
|
Order = 101,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
|
||||||
@ -323,7 +404,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
|
||||||
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = chestAbdomenRatio.ToString(
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 102,
|
Order = 102,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
|
||||||
@ -332,7 +414,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
|
||||||
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = headRatio.ToString(
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 103,
|
Order = 103,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
|
||||||
@ -342,7 +425,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5",
|
||||||
// todo: make sure this is wrapped somewhere else
|
// todo: make sure this is wrapped somewhere else
|
||||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
Value =
|
||||||
|
$"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 104,
|
Order = 104,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
|
||||||
@ -351,7 +435,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
|
||||||
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = Math.Round(maxStrain, 3)
|
||||||
|
.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 105,
|
Order = 105,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
|
||||||
@ -360,7 +445,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
new InformationResponse()
|
new InformationResponse()
|
||||||
{
|
{
|
||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
|
||||||
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = Math.Round(averageSnapValue, 3)
|
||||||
|
.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Order = 106,
|
Order = 106,
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
|
||||||
@ -369,7 +455,8 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<IEnumerable<MessageResponse>> GetMessages(ClientPaginationRequest request, CancellationToken token = default)
|
async Task<IEnumerable<MessageResponse>> GetMessages(ClientPaginationRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var query = new ChatSearchQuery
|
var query = new ChatSearchQuery
|
||||||
{
|
{
|
||||||
@ -383,9 +470,10 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
return (await _chatQueryHelper.QueryResource(query)).Results;
|
return (await _chatQueryHelper.QueryResource(query)).Results;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Configuration().AnticheatConfiguration.Enable)
|
if (_statsConfig.AnticheatConfiguration.Enable)
|
||||||
{
|
{
|
||||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetAnticheatInfo);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information,
|
||||||
|
GetAnticheatInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetStats);
|
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetStats);
|
||||||
@ -395,32 +483,38 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
{
|
{
|
||||||
await using var context = _databaseContextFactory.CreateContext(false);
|
await using var context = _databaseContextFactory.CreateContext(false);
|
||||||
var kills = await context.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
|
var kills = await context.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
|
||||||
return kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
return kills.ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> TotalPlayTime(Server server)
|
async Task<string> TotalPlayTime(Server server)
|
||||||
{
|
{
|
||||||
await using var context = _databaseContextFactory.CreateContext(false);
|
await using var context = _databaseContextFactory.CreateContext(false);
|
||||||
var playTime = await context.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
|
var playTime = await context.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
|
||||||
return (playTime / 3600.0).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
return (playTime / 3600.0).ToString("#,##0",
|
||||||
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> TopStats(Server s)
|
async Task<string> TopStats(Server s)
|
||||||
{
|
{
|
||||||
// todo: this needs to needs to be updated when we DI the lookup
|
// todo: this needs to needs to be updated when we DI the lookup
|
||||||
return string.Join(Environment.NewLine, await Commands.TopStats.GetTopStats(s, Utilities.CurrentLocalization.LocalizationIndex));
|
return string.Join(Environment.NewLine,
|
||||||
|
await Commands.TopStats.GetTopStats(s, Utilities.CurrentLocalization.LocalizationIndex, _statManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> MostPlayed(Server s)
|
async Task<string> MostPlayed(Server s)
|
||||||
{
|
{
|
||||||
// todo: this needs to needs to be updated when we DI the lookup
|
// todo: this needs to needs to be updated when we DI the lookup
|
||||||
return string.Join(Environment.NewLine, await Commands.MostPlayedCommand.GetMostPlayed(s, Utilities.CurrentLocalization.LocalizationIndex, _databaseContextFactory));
|
return string.Join(Environment.NewLine,
|
||||||
|
await Commands.MostPlayedCommand.GetMostPlayed(s, Utilities.CurrentLocalization.LocalizationIndex,
|
||||||
|
_databaseContextFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string> MostKills(Server gameServer)
|
async Task<string> MostKills(Server gameServer)
|
||||||
{
|
{
|
||||||
return string.Join(Environment.NewLine,
|
return string.Join(Environment.NewLine,
|
||||||
await Commands.MostKillsCommand.GetMostKills(StatManager.GetIdForServer(gameServer), Config.Configuration(), _databaseContextFactory, _translationLookup));
|
await Commands.MostKillsCommand.GetMostKills(StatManager.GetIdForServer(gameServer), _statsConfig,
|
||||||
|
_databaseContextFactory, _translationLookup));
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", TotalKills));
|
manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", TotalKills));
|
||||||
@ -429,7 +523,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
manager.GetMessageTokens().Add(new MessageToken("MOSTPLAYED", MostPlayed));
|
manager.GetMessageTokens().Add(new MessageToken("MOSTPLAYED", MostPlayed));
|
||||||
manager.GetMessageTokens().Add(new MessageToken("MOSTKILLS", MostKills));
|
manager.GetMessageTokens().Add(new MessageToken("MOSTKILLS", MostKills));
|
||||||
|
|
||||||
if (Config.Configuration().EnableAdvancedMetrics)
|
if (_statsConfig.EnableAdvancedMetrics)
|
||||||
{
|
{
|
||||||
foreach (var calculator in _statCalculators)
|
foreach (var calculator in _statCalculators)
|
||||||
{
|
{
|
||||||
@ -438,23 +532,9 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServerManager = manager;
|
ServerManager = manager;
|
||||||
Manager = new StatManager(_managerLogger, manager, _databaseContextFactory, Config.Configuration(), _serverDistributionCalculator);
|
|
||||||
await _serverDistributionCalculator.Initialize();
|
await _serverDistributionCalculator.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnTickAsync(Server server)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnUnloadAsync()
|
|
||||||
{
|
|
||||||
foreach (var sv in ServerManager.GetServers())
|
|
||||||
{
|
|
||||||
await Manager.Sync(sv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the event should be ignored
|
/// Indicates if the event should be ignored
|
||||||
/// (If the client id or target id is not a real client or the target/origin is a bot and ignore bots is turned on)
|
/// (If the client id or target id is not a real client or the target/origin is a bot and ignore bots is turned on)
|
||||||
@ -464,7 +544,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private bool ShouldIgnoreEvent(EFClient origin, EFClient target)
|
private bool ShouldIgnoreEvent(EFClient origin, EFClient target)
|
||||||
{
|
{
|
||||||
return ((origin?.NetworkId == Utilities.WORLD_ID && target?.NetworkId == Utilities.WORLD_ID));
|
return origin?.NetworkId == Utilities.WORLD_ID && target?.NetworkId == Utilities.WORLD_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -472,14 +552,16 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="origin"></param>
|
/// <param name="origin"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private bool IsWorldDamage(EFClient origin) => origin?.NetworkId == Utilities.WORLD_ID || origin?.ClientId == Utilities.WORLD_ID;
|
private bool IsWorldDamage(EFClient origin) =>
|
||||||
|
origin?.NetworkId == Utilities.WORLD_ID || origin?.ClientId == Utilities.WORLD_ID;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if we should try to use anticheat even if sv_customcallbacks is not defined
|
/// Indicates if we should try to use anticheat even if sv_customcallbacks is not defined
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="s"></param>
|
/// <param name="gameServer"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private bool ShouldOverrideAnticheatSetting(Server s) => Config.Configuration().AnticheatConfiguration.Enable && s.GameName == Server.Game.IW5;
|
private bool ShouldOverrideAnticheatSetting(IGameServer gameServer) => _statsConfig.AnticheatConfiguration.Enable &&
|
||||||
|
gameServer.GameCode == Reference.Game.IW5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Makes sure both clients are added
|
/// Makes sure both clients are added
|
||||||
@ -489,12 +571,11 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task EnsureClientsAdded(EFClient origin, EFClient target)
|
private async Task EnsureClientsAdded(EFClient origin, EFClient target)
|
||||||
{
|
{
|
||||||
await Manager.AddPlayer(origin);
|
await _statManager.AddPlayer(origin);
|
||||||
|
|
||||||
if (!origin.Equals(target))
|
if (!origin.Equals(target))
|
||||||
{
|
{
|
||||||
await Manager.AddPlayer(target);
|
await _statManager.AddPlayer(target);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2023.2.11.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user