update stats plugin to IPluginV2
This commit is contained in:
parent
7b8f6421aa
commit
66c0561e7f
@ -1,6 +1,5 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -10,6 +9,7 @@ using Data.Models.Client;
|
||||
using Data.Models.Client.Stats;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using Stats.Config;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
@ -37,6 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
Dictionary<IW4Info.HitLocation, HitInfo> HitLocationCount;
|
||||
double AngleDifferenceAverage;
|
||||
EFClientStatistics ClientStats;
|
||||
private readonly StatsConfiguration _statsConfiguration;
|
||||
long LastOffset;
|
||||
string LastWeapon;
|
||||
ILogger Log;
|
||||
@ -55,7 +56,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
public double Offset { get; set; }
|
||||
};
|
||||
|
||||
public Detection(ILogger log, EFClientStatistics clientStats)
|
||||
public Detection(ILogger log, EFClientStatistics clientStats, StatsConfiguration statsConfiguration)
|
||||
{
|
||||
Log = log;
|
||||
HitLocationCount = new Dictionary<IW4Info.HitLocation, HitInfo>();
|
||||
@ -65,6 +66,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
|
||||
ClientStats = clientStats;
|
||||
_statsConfiguration = statsConfiguration;
|
||||
Strain = new Strain();
|
||||
Tracker = new ChangeTracking<EFACSnapshot>();
|
||||
TrackedHits = new List<EFClientKill>();
|
||||
@ -308,7 +310,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
bool shouldIgnoreDetection = false;
|
||||
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));
|
||||
}
|
||||
|
||||
@ -340,7 +342,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
try
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
@ -453,7 +455,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
try
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Events;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Client.Abstractions
|
||||
{
|
||||
public interface IClientStatisticCalculator
|
||||
{
|
||||
Task GatherDependencies();
|
||||
Task CalculateForEvent(GameEvent gameEvent);
|
||||
Task CalculateForEvent(CoreEvent coreEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
using IW4MAdmin.Plugins.Stats.Client.Game;
|
||||
using SharedLibraryCore;
|
||||
using Data.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Client.Game;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace Stats.Client.Abstractions
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,82 +3,80 @@ using System.Linq;
|
||||
using Data.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using Stats.Client.Abstractions;
|
||||
using Stats.Client.Game;
|
||||
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 ILogger _logger;
|
||||
private const int MaximumDamage = 1000;
|
||||
|
||||
public HitInfoBuilder(ILogger<HitInfoBuilder> logger, IWeaponNameParser weaponNameParser)
|
||||
{
|
||||
private readonly IWeaponNameParser _weaponNameParser;
|
||||
private readonly ILogger _logger;
|
||||
private const int MaximumDamage = 1000;
|
||||
_weaponNameParser = weaponNameParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public HitInfoBuilder(ILogger<HitInfoBuilder> logger, IWeaponNameParser weaponNameParser)
|
||||
public HitInfo Build(string[] log, ParserRegex parserRegex, int entityId, bool isSelf, bool isVictim,
|
||||
Reference.Game gameName)
|
||||
{
|
||||
var eventType = log[(uint)ParserRegex.GroupType.EventType].First();
|
||||
HitType hitType;
|
||||
|
||||
if (isVictim)
|
||||
{
|
||||
_weaponNameParser = weaponNameParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public HitInfo Build(string[] log, ParserRegex parserRegex, int entityId, bool isSelf, bool isVictim,
|
||||
Server.Game gameName)
|
||||
{
|
||||
var eventType = log[(uint) ParserRegex.GroupType.EventType].First();
|
||||
HitType hitType;
|
||||
|
||||
if (isVictim)
|
||||
if (isSelf)
|
||||
{
|
||||
if (isSelf)
|
||||
{
|
||||
hitType = HitType.Suicide;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
hitType = eventType == 'D' ? HitType.WasDamaged : HitType.WasKilled;
|
||||
}
|
||||
hitType = HitType.Suicide;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
hitType = eventType == 'D' ? HitType.Damage : HitType.Kill;
|
||||
hitType = eventType == 'D' ? HitType.WasDamaged : HitType.WasKilled;
|
||||
}
|
||||
|
||||
var damage = 0;
|
||||
try
|
||||
{
|
||||
damage = Math.Min(MaximumDamage,
|
||||
log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.Damage]
|
||||
? int.Parse(log[parserRegex.GroupMapping[ParserRegex.GroupType.Damage]])
|
||||
: 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var hitInfo = new HitInfo()
|
||||
{
|
||||
EntityId = entityId,
|
||||
IsVictim = isVictim,
|
||||
HitType = hitType,
|
||||
Damage = damage,
|
||||
Location = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.HitLocation]
|
||||
? log[parserRegex.GroupMapping[ParserRegex.GroupType.HitLocation]]
|
||||
: "Unknown",
|
||||
Weapon = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.Weapon]
|
||||
? _weaponNameParser.Parse(log[parserRegex.GroupMapping[ParserRegex.GroupType.Weapon]], gameName)
|
||||
: new WeaponInfo {Name = "Unknown"},
|
||||
MeansOfDeath = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]
|
||||
? log[parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]]
|
||||
: "Unknown",
|
||||
Game = (Reference.Game) gameName
|
||||
};
|
||||
|
||||
return hitInfo;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
hitType = eventType == 'D' ? HitType.Damage : HitType.Kill;
|
||||
}
|
||||
|
||||
var damage = 0;
|
||||
try
|
||||
{
|
||||
damage = Math.Min(MaximumDamage,
|
||||
log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.Damage]
|
||||
? int.Parse(log[parserRegex.GroupMapping[ParserRegex.GroupType.Damage]])
|
||||
: 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var hitInfo = new HitInfo()
|
||||
{
|
||||
EntityId = entityId,
|
||||
IsVictim = isVictim,
|
||||
HitType = hitType,
|
||||
Damage = damage,
|
||||
Location = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.HitLocation]
|
||||
? log[parserRegex.GroupMapping[ParserRegex.GroupType.HitLocation]]
|
||||
: "Unknown",
|
||||
Weapon = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.Weapon]
|
||||
? _weaponNameParser.Parse(log[parserRegex.GroupMapping[ParserRegex.GroupType.Weapon]], gameName)
|
||||
: new WeaponInfo { Name = "Unknown" },
|
||||
MeansOfDeath = log.Length > parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]
|
||||
? log[parserRegex.GroupMapping[ParserRegex.GroupType.MeansOfDeath]]
|
||||
: "Unknown",
|
||||
Game = gameName
|
||||
};
|
||||
|
||||
return hitInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ using Stats.Client.Abstractions;
|
||||
using Stats.Client.Game;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SharedLibraryCore;
|
||||
using Data.Models;
|
||||
using Stats.Config;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
@ -20,7 +20,7 @@ namespace Stats.Client
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public WeaponInfo Parse(string weaponName, Server.Game gameName)
|
||||
public WeaponInfo Parse(string weaponName, Reference.Game gameName)
|
||||
{
|
||||
var configForGame = _config.WeaponNameParserConfigurations
|
||||
?.FirstOrDefault(config => config.Game == gameName) ?? new WeaponNameParserConfiguration()
|
||||
|
@ -12,69 +12,70 @@ using SharedLibraryCore.Interfaces;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
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 StatsConfiguration _statsConfig;
|
||||
|
||||
public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory, StatsConfiguration statsConfig) : base(config, translationLookup)
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
Name = "mostkills";
|
||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
|
||||
Alias = "mk";
|
||||
Permission = EFClient.Permission.User;
|
||||
|
||||
public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||
_contextFactory = contextFactory;
|
||||
_statsConfig = statsConfig;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var mostKills = await GetMostKills(StatManager.GetIdForServer(gameEvent.Owner), _statsConfig,
|
||||
_contextFactory, _translationLookup);
|
||||
if (!gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||
{
|
||||
Name = "mostkills";
|
||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
|
||||
Alias = "mk";
|
||||
Permission = EFClient.Permission.User;
|
||||
|
||||
_contextFactory = contextFactory;
|
||||
await gameEvent.Origin.TellAsync(mostKills, gameEvent.Owner.Manager.CancellationToken);
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
else
|
||||
{
|
||||
var mostKills = await GetMostKills(StatManager.GetIdForServer(gameEvent.Owner), Plugin.Config.Configuration(),
|
||||
_contextFactory, _translationLookup);
|
||||
if (!gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
|
||||
foreach (var stat in mostKills)
|
||||
{
|
||||
await gameEvent.Origin.TellAsync(mostKills, gameEvent.Owner.Manager.CancellationToken);
|
||||
await gameEvent.Owner.Broadcast(stat).WaitAsync(Utilities.DefaultCommandTimeout,
|
||||
gameEvent.Owner.Manager.CancellationToken);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
foreach (var stat in mostKills)
|
||||
{
|
||||
await gameEvent.Owner.Broadcast(stat).WaitAsync(Utilities.DefaultCommandTimeout,
|
||||
gameEvent.Owner.Manager.CancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<string>> GetMostKills(long? serverId, StatsConfiguration config,
|
||||
IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup)
|
||||
{
|
||||
await using var ctx = contextFactory.CreateContext(enableTracking: false);
|
||||
var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays);
|
||||
|
||||
var iqStats = (from stats in ctx.Set<EFClientStatistics>()
|
||||
join client in ctx.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in ctx.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= dayInPast
|
||||
orderby stats.Kills descending
|
||||
select new
|
||||
{
|
||||
alias.Name,
|
||||
stats.Kills
|
||||
})
|
||||
.Take(config.MostKillsClientLimit);
|
||||
|
||||
var iqList = await iqStats.ToListAsync();
|
||||
|
||||
return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT_V2"]
|
||||
.FormatExt(index + 1, stats.Name, stats.Kills))
|
||||
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<string>> GetMostKills(long? serverId, StatsConfiguration config,
|
||||
IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup)
|
||||
{
|
||||
await using var ctx = contextFactory.CreateContext(enableTracking: false);
|
||||
var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays);
|
||||
|
||||
var iqStats = (from stats in ctx.Set<EFClientStatistics>()
|
||||
join client in ctx.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in ctx.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= dayInPast
|
||||
orderby stats.Kills descending
|
||||
select new
|
||||
{
|
||||
alias.Name,
|
||||
stats.Kills
|
||||
})
|
||||
.Take(config.MostKillsClientLimit);
|
||||
|
||||
var iqList = await iqStats.ToListAsync();
|
||||
|
||||
return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT_V2"]
|
||||
.FormatExt(index + 1, stats.Name, stats.Kills))
|
||||
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,18 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models.Client.Stats;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using Stats.Config;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
public class ResetStats : Command
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
|
||||
private readonly StatManager _statManager;
|
||||
|
||||
public ResetStats(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||
IDatabaseContextFactory contextFactory, StatManager statManager) : base(config, translationLookup)
|
||||
{
|
||||
Name = "resetstats";
|
||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_RESET_DESC"];
|
||||
@ -25,6 +28,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
AllowImpersonation = true;
|
||||
|
||||
_contextFactory = contextFactory;
|
||||
_statManager = statManager;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
@ -53,7 +57,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
}
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(gameEvent.Origin);
|
||||
_statManager.ResetStats(gameEvent.Origin);
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||
}
|
||||
|
@ -11,15 +11,15 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
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>()
|
||||
{
|
||||
$"(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) =>
|
||||
translationLookup["COMMANDS_TOPSTATS_RESULT"]
|
||||
.FormatExt(index + 1, stats.Name, stats.KDR, stats.Performance));
|
||||
@ -39,8 +39,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Name = "topstats";
|
||||
@ -50,11 +51,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
RequiresTarget = false;
|
||||
|
||||
_config = config;
|
||||
_statManager = statManager;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
await gameEvent.Origin.TellAsync(topStats, gameEvent.Owner.Manager.CancellationToken);
|
||||
|
@ -15,9 +15,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
public class ViewStatsCommand : Command
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly StatManager _statManager;
|
||||
|
||||
public ViewStatsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||
IDatabaseContextFactory contextFactory, StatManager statManager) : base(config, translationLookup)
|
||||
{
|
||||
Name = "stats";
|
||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_VIEW_DESC"];
|
||||
@ -34,6 +35,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
};
|
||||
|
||||
_contextFactory = contextFactory;
|
||||
_statManager = statManager;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
@ -53,12 +55,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
|
||||
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
|
||||
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
|
||||
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} (Color::Accent)#{performanceRanking}/{totalRankedPlayers}";
|
||||
@ -87,7 +89,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
// getting self stats
|
||||
else
|
||||
{
|
||||
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Origin.ClientId, serverId);
|
||||
var performanceRanking = await _statManager.GetClientOverallRanking(E.Origin.ClientId, serverId);
|
||||
var performanceRankingString = performanceRanking == 0
|
||||
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} (Color::Accent)#{performanceRanking}/{totalRankedPlayers}";
|
||||
@ -131,4 +133,4 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Data.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
@ -21,26 +22,26 @@ namespace Stats.Config
|
||||
public WeaponNameParserConfiguration[] WeaponNameParserConfigurations { get; set; } = {
|
||||
new()
|
||||
{
|
||||
Game = Server.Game.IW3,
|
||||
Game = Reference.Game.IW3,
|
||||
WeaponSuffix = "mp",
|
||||
Delimiters = new[] {'_'}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Game = Server.Game.IW4,
|
||||
Game = Reference.Game.IW4,
|
||||
WeaponSuffix = "mp",
|
||||
Delimiters = new[] {'_'}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Game = Server.Game.IW5,
|
||||
Game = Reference.Game.IW5,
|
||||
WeaponSuffix = "mp",
|
||||
WeaponPrefix = "iw5",
|
||||
Delimiters = new[] {'_'}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Game = Server.Game.T6,
|
||||
Game = Reference.Game.T6,
|
||||
WeaponSuffix = "mp",
|
||||
Delimiters = new[] {'_', '+'}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
using SharedLibraryCore;
|
||||
using Data.Models;
|
||||
|
||||
namespace Stats.Config
|
||||
{
|
||||
public class WeaponNameParserConfiguration
|
||||
{
|
||||
public Server.Game Game { get; set; }
|
||||
public Reference.Game Game { get; set; }
|
||||
public char[] Delimiters { get; set; }
|
||||
public string WeaponSuffix { get; set; }
|
||||
public string WeaponPrefix { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,111 +2,101 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||
using SharedLibraryCore.Events.Game;
|
||||
using EventGeneratorCallback = System.ValueTuple<string, string,
|
||||
System.Func<string, SharedLibraryCore.Interfaces.IEventParserConfiguration,
|
||||
SharedLibraryCore.GameEvent,
|
||||
SharedLibraryCore.GameEvent>>;
|
||||
SharedLibraryCore.GameEvent,
|
||||
SharedLibraryCore.GameEvent>>;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Events
|
||||
{
|
||||
public class Script : IRegisterEvent
|
||||
{
|
||||
private const string EVENT_SCRIPTKILL = "ScriptKill";
|
||||
private const string EVENT_SCRIPTDAMAGE = "ScriptDamage";
|
||||
private const string EVENT_JOINTEAM = "JoinTeam";
|
||||
private const string EventScriptKill = "ScriptKill";
|
||||
private const string EventScriptDamage = "ScriptDamage";
|
||||
|
||||
/// <summary>
|
||||
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private EventGeneratorCallback ScriptKill()
|
||||
private static EventGeneratorCallback ScriptKill()
|
||||
{
|
||||
return (EVENT_SCRIPTKILL, EVENT_SCRIPTKILL, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
||||
{
|
||||
string[] lineSplit = eventLine.Split(";");
|
||||
return (EventScriptKill, EventScriptKill,
|
||||
(eventLine, config, autoEvent) =>
|
||||
{
|
||||
var lineSplit = eventLine.Split(";");
|
||||
|
||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||
{
|
||||
return autoEvent;
|
||||
}
|
||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||
{
|
||||
return autoEvent;
|
||||
}
|
||||
|
||||
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
var originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
var targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
|
||||
autoEvent.Type = GameEvent.EventType.ScriptKill;
|
||||
autoEvent.Origin = new EFClient() { NetworkId = originId };
|
||||
autoEvent.Target = new EFClient() { NetworkId = targetId };
|
||||
autoEvent.RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target;
|
||||
autoEvent.GameTime = autoEvent.GameTime;
|
||||
var anticheatEvent = new AntiCheatDamageEvent
|
||||
{
|
||||
ScriptData = eventLine,
|
||||
Type = GameEvent.EventType.ScriptKill,
|
||||
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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private EventGeneratorCallback ScriptDamage()
|
||||
public EventGeneratorCallback ScriptDamage()
|
||||
{
|
||||
// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
|
||||
return (EVENT_SCRIPTDAMAGE, EVENT_SCRIPTDAMAGE, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
||||
{
|
||||
string[] lineSplit = eventLine.Split(";");
|
||||
return (EventScriptDamage, EventScriptDamage,
|
||||
(eventLine, config, autoEvent) =>
|
||||
{
|
||||
var lineSplit = eventLine.Split(";");
|
||||
|
||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||
{
|
||||
return autoEvent;
|
||||
}
|
||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||
{
|
||||
return autoEvent;
|
||||
}
|
||||
|
||||
long originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
long targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
var originId = lineSplit[1].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
var targetId = lineSplit[2].ConvertGuidToLong(config.GuidNumberStyle, 1);
|
||||
|
||||
autoEvent.Type = GameEvent.EventType.ScriptDamage;
|
||||
autoEvent.Origin = new EFClient() { NetworkId = originId };
|
||||
autoEvent.Target = new EFClient() { NetworkId = targetId };
|
||||
autoEvent.RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target;
|
||||
var anticheatEvent = new AntiCheatDamageEvent
|
||||
{
|
||||
ScriptData = eventLine,
|
||||
Type = GameEvent.EventType.ScriptDamage,
|
||||
Origin = new EFClient { NetworkId = originId },
|
||||
Target = new EFClient { NetworkId = targetId },
|
||||
RequiredEntity =
|
||||
GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
|
||||
GameTime = autoEvent.GameTime
|
||||
};
|
||||
|
||||
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)
|
||||
return (EVENT_JOINTEAM, EVENT_JOINTEAM, (string eventLine, IEventParserConfiguration config, GameEvent autoEvent) =>
|
||||
{
|
||||
string[] lineSplit = eventLine.Split(";");
|
||||
|
||||
if (lineSplit[1].IsBotGuid() || lineSplit[2].IsBotGuid())
|
||||
{
|
||||
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;
|
||||
}
|
||||
);
|
||||
return anticheatEvent;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public IEnumerable<EventGeneratorCallback> Events =>
|
||||
new[]
|
||||
{
|
||||
ScriptKill(),
|
||||
ScriptDamage(),
|
||||
JoinTeam()
|
||||
ScriptDamage()
|
||||
};
|
||||
}
|
||||
|
||||
public class AntiCheatDamageEvent : GameScriptEvent
|
||||
{
|
||||
public bool IsKill { get; init; }
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using Data.Models;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Client.Stats;
|
||||
using IW4MAdmin.Plugins.Stats;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Dtos;
|
||||
@ -114,7 +113,7 @@ namespace Stats.Helpers
|
||||
All = hitStats,
|
||||
Servers = _manager.GetServers()
|
||||
.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)
|
||||
.ToList(),
|
||||
Aggregate = hitStats.FirstOrDefault(hit =>
|
||||
|
@ -46,7 +46,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1);
|
||||
private readonly IServerDistributionCalculator _serverDistributionCalculator;
|
||||
|
||||
public StatManager(ILogger<StatManager> logger, IManager mgr, IDatabaseContextFactory contextFactory,
|
||||
public StatManager(ILogger<StatManager> logger, IDatabaseContextFactory contextFactory,
|
||||
StatsConfiguration statsConfig,
|
||||
IServerDistributionCalculator serverDistributionCalculator)
|
||||
{
|
||||
@ -360,13 +360,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return finished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a server to the StatManager server pool
|
||||
/// </summary>
|
||||
/// <param name="sv"></param>
|
||||
public void AddServer(Server sv)
|
||||
public async Task EnsureServerAdded(IGameServer gameServer, CancellationToken token)
|
||||
{
|
||||
// insert the server if it does not exist
|
||||
try
|
||||
{
|
||||
if (serverModels == null)
|
||||
@ -374,76 +369,75 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
SetupServerIds();
|
||||
}
|
||||
|
||||
long serverId = GetIdForServer(sv);
|
||||
EFServer server;
|
||||
var serverId = GetIdForServer(gameServer as Server);
|
||||
|
||||
using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||
var serverSet = ctx.Set<EFServer>();
|
||||
// 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
|
||||
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
|
||||
server.EndPoint = sv.ToString();
|
||||
ctx.Update(server);
|
||||
cachedServerModel.EndPoint = gameServer.Id;
|
||||
ctx.Update(cachedServerModel);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// server has never been added before
|
||||
if (server == null)
|
||||
if (cachedServerModel == null)
|
||||
{
|
||||
server = new EFServer()
|
||||
cachedServerModel = new EFServer
|
||||
{
|
||||
Port = sv.Port,
|
||||
EndPoint = sv.ToString(),
|
||||
Port = gameServer.ListenPort,
|
||||
EndPoint = gameServer.Id,
|
||||
ServerId = serverId,
|
||||
GameName = (Reference.Game?)sv.GameName,
|
||||
HostName = sv.Hostname
|
||||
GameName = gameServer.GameCode,
|
||||
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
|
||||
ctx.SaveChanges();
|
||||
await ctx.SaveChangesAsync(token);
|
||||
}
|
||||
|
||||
// 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;
|
||||
ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true;
|
||||
ctx.SaveChanges();
|
||||
cachedServerModel.GameName = gameServer.GameCode;
|
||||
ctx.Entry(cachedServerModel).Property(property => property.GameName).IsModified = true;
|
||||
await ctx.SaveChangesAsync(token);
|
||||
}
|
||||
|
||||
if (server.HostName == null || server.HostName != sv.Hostname)
|
||||
if (cachedServerModel.HostName == null || cachedServerModel.HostName != gameServer.ServerName)
|
||||
{
|
||||
server.HostName = sv.Hostname;
|
||||
ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true;
|
||||
ctx.SaveChanges();
|
||||
cachedServerModel.HostName = gameServer.ServerName;
|
||||
ctx.Entry(cachedServerModel).Property(property => property.HostName).IsModified = true;
|
||||
await ctx.SaveChangesAsync(token);
|
||||
}
|
||||
|
||||
ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true;
|
||||
server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword);
|
||||
ctx.SaveChanges();
|
||||
ctx.Entry(cachedServerModel).Property(property => property.IsPasswordProtected).IsModified = true;
|
||||
cachedServerModel.IsPasswordProtected = !string.IsNullOrEmpty(gameServer.GamePassword);
|
||||
await ctx.SaveChangesAsync(token);
|
||||
|
||||
// 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"]);
|
||||
}
|
||||
}
|
||||
@ -552,7 +546,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
clientStats.SessionScore = 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());
|
||||
|
||||
return clientStats;
|
||||
@ -586,41 +580,42 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// <summary>
|
||||
/// Perform stat updates for disconnecting client
|
||||
/// </summary>
|
||||
/// <param name="pl">Disconnecting client</param>
|
||||
/// <param name="client">Disconnecting client</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <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;
|
||||
}
|
||||
|
||||
var serverId = GetIdForServer(pl.CurrentServer);
|
||||
var serverId = GetIdForServer(client.CurrentServer);
|
||||
var serverStats = _servers[serverId].ServerStatistics;
|
||||
|
||||
// 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
|
||||
if (clientStats != null)
|
||||
{
|
||||
clientStats = UpdateStats(clientStats, pl);
|
||||
clientStats = UpdateStats(clientStats, client);
|
||||
await SaveClientStats(clientStats);
|
||||
if (_config.EnableAdvancedMetrics)
|
||||
{
|
||||
await UpdateHistoricalRanking(pl.ClientId, clientStats, serverId);
|
||||
await UpdateHistoricalRanking(client.ClientId, clientStats, serverId);
|
||||
}
|
||||
|
||||
// increment the total play time
|
||||
serverStats.TotalPlayTime += pl.ConnectionLength;
|
||||
pl.SetAdditionalProperty(CLIENT_STATS_KEY, null);
|
||||
serverStats.TotalPlayTime += client.ConnectionLength;
|
||||
client.SetAdditionalProperty(CLIENT_STATS_KEY, null);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (Plugin.Config.Configuration().StoreClientKills)
|
||||
if (_config.StoreClientKills)
|
||||
{
|
||||
var serverWaiter = _servers[serverId].OnSaving;
|
||||
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)
|
||||
{
|
||||
clientDetection.TrackedHits.Add(hit);
|
||||
@ -857,10 +852,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||
{
|
||||
#pragma warning disable CS0612
|
||||
var serverDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||
var serverDetectionTypes = _config.AnticheatConfiguration.ServerDetectionTypes;
|
||||
#pragma warning restore CS0612
|
||||
var gameDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.GameDetectionTypes;
|
||||
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||
var gameDetectionTypes = _config.AnticheatConfiguration.GameDetectionTypes;
|
||||
var ignoredClients = _config.AnticheatConfiguration.IgnoredClientIds;
|
||||
|
||||
if (ignoredClients.Contains(clientId))
|
||||
{
|
||||
@ -1011,9 +1006,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
victimStats.LastScore = estimatedVictimScore;
|
||||
|
||||
// show encouragement/discouragement
|
||||
var streakMessage = (attackerStats.ClientId != victimStats.ClientId)
|
||||
? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak)
|
||||
: StreakMessage.MessageOnStreak(-1, -1);
|
||||
var streakMessage = attackerStats.ClientId != victimStats.ClientId
|
||||
? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak, _config)
|
||||
: StreakMessage.MessageOnStreak(-1, -1, _config);
|
||||
|
||||
if (streakMessage != string.Empty)
|
||||
{
|
||||
@ -1530,13 +1525,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return serverStats;
|
||||
}
|
||||
|
||||
public void ResetKillstreaks(Server sv)
|
||||
public void ResetKillstreaks(IGameServer gameServer)
|
||||
{
|
||||
foreach (var session in sv.GetClientsAsList()
|
||||
.Select(_client => new
|
||||
foreach (var session in gameServer.ConnectedClients
|
||||
.Select(client => new
|
||||
{
|
||||
stat = _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||
detection = _client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||
stat = client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY),
|
||||
detection = client.GetAdditionalProperty<Detection>(CLIENT_DETECTIONS_KEY)
|
||||
}))
|
||||
{
|
||||
session.stat?.StartNewSession();
|
||||
@ -1563,7 +1558,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
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
|
||||
if (clientId < 1)
|
||||
@ -1571,8 +1567,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
|
||||
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
|
||||
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
context.Set<EFClientMessage>().Add(new EFClientMessage()
|
||||
{
|
||||
ClientId = clientId,
|
||||
Message = message,
|
||||
@ -1581,26 +1577,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
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;
|
||||
try
|
||||
{
|
||||
await waiter.WaitAsync();
|
||||
await waiter.WaitAsync(token);
|
||||
|
||||
await using var ctx = _contextFactory.CreateContext();
|
||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
var serverStatsSet = context.Set<EFServerStatistics>();
|
||||
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
||||
await ctx.SaveChangesAsync();
|
||||
await context.SaveChangesAsync(token);
|
||||
|
||||
foreach (var stats in sv.GetClientsAsList()
|
||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
||||
.Where(_stats => _stats != null))
|
||||
foreach (var stats in gameServer.ConnectedClients
|
||||
.Select(client => client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
||||
.Where(stats => stats != null))
|
||||
{
|
||||
await SaveClientStats(stats);
|
||||
}
|
||||
@ -1608,9 +1604,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
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
|
||||
@ -1627,28 +1623,24 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
_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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
long? serverId;
|
||||
|
||||
serverId = serverModels.FirstOrDefault(_server => _server.ServerId == server.EndPoint ||
|
||||
_server.EndPoint == server.ToString() ||
|
||||
_server.ServerId == id)?.ServerId;
|
||||
#pragma warning disable CS0618
|
||||
var serverId = serverModels.FirstOrDefault(cachedServer => cachedServer.ServerId == gameServer.LegacyEndpoint ||
|
||||
#pragma warning restore CS0618
|
||||
cachedServer.EndPoint == gameServer.ToString() ||
|
||||
cachedServer.ServerId == id)?.ServerId;
|
||||
|
||||
if (!serverId.HasValue)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
return serverId.Value;
|
||||
return serverId ?? id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,17 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Stats.Config;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
namespace IW4MAdmin.Plugins.Stats.Helpers;
|
||||
|
||||
public static class StreakMessage
|
||||
{
|
||||
public class StreakMessage
|
||||
public static string MessageOnStreak(int killStreak, int deathStreak, StatsConfiguration config)
|
||||
{
|
||||
/// <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;
|
||||
var killstreakMessage = config.KillstreakMessages;
|
||||
var deathstreakMessage = config.DeathstreakMessages;
|
||||
|
||||
string message = killstreakMessage?.FirstOrDefault(m => m.Count == killStreak)?.Message;
|
||||
message = message ?? deathstreakMessage?.FirstOrDefault(m => m.Count == deathStreak)?.Message;
|
||||
return message ?? "";
|
||||
}
|
||||
var message = killstreakMessage?.FirstOrDefault(m => m.Count == killStreak)?.Message;
|
||||
message ??= deathstreakMessage?.FirstOrDefault(m => m.Count == deathStreak)?.Message;
|
||||
return message ?? "";
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
Loading…
Reference in New Issue
Block a user