From 1adf3ceb3c8c008bb79f7a8464fce86f63d469ac Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 6 Mar 2018 01:22:19 -0600 Subject: [PATCH] the meats --- .gitignore | 4 + Database/Database.csproj | 90 ------ Database/IW4MAdminDatabase.cs | 38 --- Database/IW4MAdminDatabaseContext.cs | 27 -- Database/Models/Alias.cs | 18 -- Database/Models/Client.cs | 43 --- Database/Models/Penalty.cs | 23 -- Database/Properties/AssemblyInfo.cs | 36 --- Database/packages.config | 6 - IW4MAdmin.sln | 10 +- Plugins/SimpleStats/Cheat/Detection.cs | 285 +++++++++++++++--- Plugins/SimpleStats/Cheat/Thresholds.cs | 22 +- Plugins/SimpleStats/Commands/ResetStats.cs | 3 + Plugins/SimpleStats/Helpers/ServerStats.cs | 9 +- Plugins/SimpleStats/Helpers/StatManager.cs | 182 ++++++++--- .../Helpers/ThreadSafeStatsService.cs | 1 - Plugins/SimpleStats/IW4Info.cs | 4 +- .../SimpleStats/Models/EFClientStatistics.cs | 6 +- .../SimpleStats/Models/EFHitLocationCount.cs | 21 ++ Plugins/SimpleStats/Plugin.cs | 46 ++- Plugins/SimpleStats/StatsPlugin.csproj | 3 +- Plugins/Welcome/Plugin.cs | 2 +- SharedLibrary/App.config | 38 ++- SharedLibrary/Commands/NativeCommands.cs | 104 ++++++- SharedLibrary/Database/DatabaseContext.cs | 11 +- SharedLibrary/Database/Initializer.cs | 42 --- SharedLibrary/Database/Repair.cs | 26 ++ SharedLibrary/Dtos/PenaltyInfo.cs | 2 +- SharedLibrary/Dtos/ProfileMeta.cs | 3 +- SharedLibrary/Dtos/SharedInfo.cs | 8 + SharedLibrary/Interfaces/IManager.cs | 5 +- .../201803030146021_Intial.Designer.cs | 29 ++ .../Migrations/201803030146021_Intial.cs | 16 + .../Migrations/201803030146021_Intial.resx | 126 ++++++++ SharedLibrary/Migrations/Configuration.cs | 52 ++++ SharedLibrary/RCON.cs | 2 +- SharedLibrary/Server.cs | 1 + SharedLibrary/ServerConfiguration.cs | 2 + SharedLibrary/Services/ClientService.cs | 5 + SharedLibrary/SharedLibrary.csproj | 65 ++-- SharedLibrary/Utilities.cs | 46 ++- WebfrontCore/Application/Kayak.cs | 191 ------------ WebfrontCore/Application/Main.cs | 22 +- WebfrontCore/Application/Manager.cs | 19 +- WebfrontCore/Application/Server.cs | 29 +- WebfrontCore/Controllers/BaseController.cs | 25 ++ WebfrontCore/Controllers/ClientController.cs | 22 +- WebfrontCore/Controllers/ConsoleController.cs | 8 +- WebfrontCore/Controllers/HomeController.cs | 2 +- WebfrontCore/Controllers/PenaltyController.cs | 4 +- WebfrontCore/Controllers/ServerController.cs | 5 +- .../PenaltyListViewComponent.cs | 13 +- .../Views/Client/Profile/Index.cshtml | 5 +- WebfrontCore/WebfrontCore.csproj | 13 +- WebfrontCore/app.config | 2 +- WebfrontCore/appsettings.json | 4 +- 56 files changed, 1107 insertions(+), 719 deletions(-) delete mode 100644 Database/Database.csproj delete mode 100644 Database/IW4MAdminDatabase.cs delete mode 100644 Database/IW4MAdminDatabaseContext.cs delete mode 100644 Database/Models/Alias.cs delete mode 100644 Database/Models/Client.cs delete mode 100644 Database/Models/Penalty.cs delete mode 100644 Database/Properties/AssemblyInfo.cs delete mode 100644 Database/packages.config create mode 100644 Plugins/SimpleStats/Models/EFHitLocationCount.cs delete mode 100644 SharedLibrary/Database/Initializer.cs create mode 100644 SharedLibrary/Database/Repair.cs create mode 100644 SharedLibrary/Dtos/SharedInfo.cs create mode 100644 SharedLibrary/Migrations/201803030146021_Intial.Designer.cs create mode 100644 SharedLibrary/Migrations/201803030146021_Intial.cs create mode 100644 SharedLibrary/Migrations/201803030146021_Intial.resx create mode 100644 SharedLibrary/Migrations/Configuration.cs delete mode 100644 WebfrontCore/Application/Kayak.cs create mode 100644 WebfrontCore/Controllers/BaseController.cs diff --git a/.gitignore b/.gitignore index 3eb7bf1ba..a9b235f1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +Detection.cs +DetectionPenaltyResult.cs +Thresholds.cs + # User-specific files *.suo *.user diff --git a/Database/Database.csproj b/Database/Database.csproj deleted file mode 100644 index 17e44be3b..000000000 --- a/Database/Database.csproj +++ /dev/null @@ -1,90 +0,0 @@ - - - - - Debug - AnyCPU - {D076ABC9-DDD6-4E30-9584-E45273950902} - Library - Properties - Database - Database - v4.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\Release-Nightly\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release-Stable\ - - - - ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll - - - - - - ..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll - True - - - - - - - - - - - - - - - - - - - {d51eeceb-438a-47da-870f-7d7b41bc24d6} - SharedLibrary - - - - - - - - - if not exist "$(TargetDir)x86" md "$(TargetDir)x86" - xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86" - if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64" - xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64" - - \ No newline at end of file diff --git a/Database/IW4MAdminDatabase.cs b/Database/IW4MAdminDatabase.cs deleted file mode 100644 index 9147a7576..000000000 --- a/Database/IW4MAdminDatabase.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Database.Models; - -namespace Database -{ - public class IW4MAdminDatabase : SharedLibrary.Interfaces.IDatabase - { - private IW4MAdminDatabaseContext _context; - - public IW4MAdminDatabase() - { - _context = new IW4MAdminDatabaseContext(); - } - - public SharedLibrary.Interfaces.IDatabaseContext GetContext() => _context; - - public async Task AddClient(Client newClient) - { - var client = _context.Clients.Add(newClient); - await _context.SaveChangesAsync(); - return client; - } - - public Client GetClient(int clientID) => _context.Clients.SingleOrDefault(c => c.ClientId == clientID); - public Client GetClient(string networkID) => _context.Clients.SingleOrDefault(c => c.NetworkId == networkID); - public IList GetOwners() => _context.Clients.Where(c => c.Level == SharedLibrary.Player.Permission.Owner).ToList(); - public IList GetPlayers(IList networkIDs) => _context.Clients.Where(c => networkIDs.Contains(c.NetworkId)).Select(c => c.ToPlayer()).ToList(); - public IList GetPenalties (int clientID) => _context.Penalties.Where(p => p.OffenderId == clientID).ToList(); - public IList GetAdmins() => _context.Clients.Where(c => c.Level > SharedLibrary.Player.Permission.Flagged).Select(c => c.ToPlayer()).ToList(); - - - } -} diff --git a/Database/IW4MAdminDatabaseContext.cs b/Database/IW4MAdminDatabaseContext.cs deleted file mode 100644 index e30898b73..000000000 --- a/Database/IW4MAdminDatabaseContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.Entity; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Database.Models; -using System.Data.SqlServerCe; - -namespace Database -{ - public class IW4MAdminDatabaseContext : DbContext, SharedLibrary.Interfaces.IDatabaseContext - { - public DbSet Clients { get; set; } - public DbSet Aliases { get; set; } - public DbSet Penalties { get; set; } - - public IW4MAdminDatabaseContext() : base("DefaultConnection") { } - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - var instance = System.Data.Entity.SqlServerCompact.SqlCeProviderServices.Instance; - // todo custom load DBSets from plugins - // https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/ - base.OnModelCreating(modelBuilder); - } - } -} diff --git a/Database/Models/Alias.cs b/Database/Models/Alias.cs deleted file mode 100644 index e670deb8d..000000000 --- a/Database/Models/Alias.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Database.Models -{ - public class Alias - { - [Key] - public int AliasId { get; set; } - public int ClientId { get; set; } - public string Name { get; set; } - public string IP { get; set; } - } -} diff --git a/Database/Models/Client.cs b/Database/Models/Client.cs deleted file mode 100644 index 2e6e10e84..000000000 --- a/Database/Models/Client.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Database.Models -{ - public class Client - { - [Key] - public int ClientId { get; set; } - - [Required] - public string Name { get; set; } - [Required] - public string NetworkId { get; set; } - [Required] - public SharedLibrary.Player.Permission Level { get; set; } - public int Connections { get; set; } - [Required] - public string IPAddress { get; set; } - [Required] - public DateTime LastConnection { get; set; } - public bool Masked { get; set; } - - public SharedLibrary.Player ToPlayer() - { - return new SharedLibrary.Player() - { - Name = Name, - Connections = Connections, - DatabaseID = ClientId, - NetworkID = NetworkId, - Level = Level, - IP = IPAddress, - LastConnection = LastConnection, - Masked = Masked - }; - } - } -} diff --git a/Database/Models/Penalty.cs b/Database/Models/Penalty.cs deleted file mode 100644 index 4b841a6e3..000000000 --- a/Database/Models/Penalty.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Database.Models -{ - public class Penalty - { - [Key] - public int PenaltyId { get; set; } - public int OffenderId { get; set; } - public Client Offender { get; set; } - public int PunisherId { get; set; } - public Client Punisher { get; set; } - public DateTime When { get; set; } - public DateTime Expires { get; set; } - public SharedLibrary.Penalty.Type Type { get; set; } - } -} diff --git a/Database/Properties/AssemblyInfo.cs b/Database/Properties/AssemblyInfo.cs deleted file mode 100644 index 18e5f7e50..000000000 --- a/Database/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Database")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Database")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d076abc9-ddd6-4e30-9584-e45273950902")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Database/packages.config b/Database/packages.config deleted file mode 100644 index 82f88f367..000000000 --- a/Database/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index 21de08eb5..c19ec08f0 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 +VisualStudioVersion = 15.0.27004.2006 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsPlugin", "Plugins\SimpleStats\StatsPlugin.csproj", "{4785AB75-66F3-4391-985D-63A5A049A0FA}" ProjectSection(ProjectDependencies) = postProject @@ -320,16 +320,16 @@ Global {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|Mixed Platforms.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.Build.0 = Release|Any CPU - {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.ActiveCfg = Release|Any CPU - {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.Build.0 = Release|Any CPU + {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.ActiveCfg = Release|x86 + {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.Build.0 = Release|x86 {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.Build.0 = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.ActiveCfg = Release|Any CPU {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.Build.0 = Release|Any CPU - {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.ActiveCfg = Release|Any CPU - {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.Build.0 = Release|Any CPU + {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.ActiveCfg = Release|x86 + {65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Plugins/SimpleStats/Cheat/Detection.cs b/Plugins/SimpleStats/Cheat/Detection.cs index cdbd084b0..15c08f51f 100644 --- a/Plugins/SimpleStats/Cheat/Detection.cs +++ b/Plugins/SimpleStats/Cheat/Detection.cs @@ -1,4 +1,5 @@ using SharedLibrary.Interfaces; +using SharedLibrary.Objects; using StatsPlugin.Models; using System; using System.Collections.Generic; @@ -30,62 +31,276 @@ namespace StatsPlugin.Cheat /// /// kill performed by the player /// true if detection reached thresholds, false otherwise - public bool ProcessKill(EFClientKill kill) + public DetectionPenaltyResult ProcessKill(EFClientKill kill) { - if (kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET) - return false; - - bool thresholdReached = false; + if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && + kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET) || + kill.HitLoc == IW4Info.HitLocation.none) + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Any, + RatioAmount = 0 + }; HitLocationCount[kill.HitLoc]++; Kills++; AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills; - if (Kills > Thresholds.LowSampleMinKills) +#region SESSION_RATIOS + if (Kills >= Thresholds.LowSampleMinKills) { double marginOfError = Thresholds.GetMarginOfError(Kills); // determine what the max headshot percentage can be for current number of kills double lerpAmount = Math.Min(1.0, (Kills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); - double maxHeadshotLerpValue = Thresholds.Lerp( Thresholds.HeadshotRatioThresholdLowSample, Thresholds.HeadshotRatioThresholdHighSample, lerpAmount); + double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError; + double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.0), Thresholds.HeadshotRatioThresholdHighSample(3.0), lerpAmount) + marginOfError; // determine what the max bone percentage can be for current number of kills - double maxBoneRatioLerpValue = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample, Thresholds.BoneRatioThresholdHighSample, lerpAmount); + double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError; + double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError; + // calculate headshot ratio - double headshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills) - marginOfError; + double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills); // calculate maximum bone - double maximumBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max()) - marginOfError; + double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max()); - if (headshotRatio > maxHeadshotLerpValue) + var bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key; + #region HEADSHOT_RATIO + // flag on headshot + if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) { - AboveThresholdCount++; - Log.WriteDebug("**Maximum Headshot Ratio Reached**"); - Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Kills: {Kills}"); - Log.WriteDebug($"**Ratio {headshotRatio}"); - Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValue}"); - var sb = new StringBuilder(); - foreach (var kvp in HitLocationCount) - sb.Append($"HitLocation: {kvp.Key} Count: {kvp.Value}"); - Log.WriteDebug(sb.ToString()); - Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); - thresholdReached = true; + // ban on headshot + if (currentHeadshotRatio > maxHeadshotLerpValueForFlag) + { + AboveThresholdCount++; + Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); + Log.WriteDebug($"ClientId: {kill.AttackerId}"); + Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); + Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Ban, + RatioAmount = currentHeadshotRatio, + Bone = IW4Info.HitLocation.head, + KillCount = Kills + }; + } + else + { + AboveThresholdCount++; + Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); + Log.WriteDebug($"ClientId: {kill.AttackerId}"); + Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); + Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Flag, + RatioAmount = currentHeadshotRatio, + Bone = IW4Info.HitLocation.head, + KillCount = Kills + }; + } } + #endregion - else if (maximumBoneRatio > maxBoneRatioLerpValue) + #region BONE_RATIO + // flag on bone ratio + else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag) { - Log.WriteDebug("**Maximum Bone Ratio Reached**"); - Log.WriteDebug($"ClientId: {kill.AttackerId}"); - Log.WriteDebug($"**Kills: {Kills}"); - Log.WriteDebug($"**Ratio {maximumBoneRatio}"); - Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValue}"); - var sb = new StringBuilder(); - foreach (var kvp in HitLocationCount) - sb.Append($"HitLocation: {kvp.Key} Count: {kvp.Value}"); - Log.WriteDebug(sb.ToString()); - thresholdReached = true; + // ban on bone ratio + if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) + { + Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); + Log.WriteDebug($"ClientId: {kill.AttackerId}"); + Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); + Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Ban, + RatioAmount = currentMaxBoneRatio, + Bone = bone, + KillCount = Kills + }; + } + else + { + Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); + Log.WriteDebug($"ClientId: {kill.AttackerId}"); + Log.WriteDebug($"**Kills: {Kills}"); + Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); + Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Flag, + RatioAmount = currentMaxBoneRatio, + Bone = bone, + KillCount = Kills + }; + } + } + #endregion + } + + #region CHEST_ABDOMEN_RATIO_SESSION + int chestKills = HitLocationCount[IW4Info.HitLocation.torso_upper]; + + if (chestKills >= Thresholds.MediumSampleMinKills) + { + double marginOfError = Thresholds.GetMarginOfError(chestKills); + double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); + // determine max acceptable ratio of chest to abdomen kills + double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(2.25), Thresholds.ChestAbdomenRatioThresholdHighSample(2.25), lerpAmount) + marginOfError; + double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3.25), Thresholds.ChestAbdomenRatioThresholdHighSample(3.25), lerpAmount) + marginOfError; + + double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower]; + + if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) + { + + if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestKills >= Thresholds.MediumSampleMinKills + 30) + { + Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**"); + Log.WriteDebug($"ClientId: {kill.AttackerId}"); + Log.WriteDebug($"**Chest Kills: {chestKills}"); + Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); + Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Ban, + RatioAmount = currentChestAbdomenRatio, + Bone = IW4Info.HitLocation.torso_upper, + KillCount = chestKills + }; + } + else + { + Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); + Log.WriteDebug($"ClientId: {kill.AttackerId}"); + Log.WriteDebug($"**Chest Kills: {chestKills}"); + Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); + Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Flag, + RatioAmount = currentChestAbdomenRatio, + Bone = IW4Info.HitLocation.torso_upper, + KillCount = chestKills + }; + } + } + } + #endregion +#endregion + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Any, + RatioAmount = 0 + }; + } + + public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats) + { + int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.left_arm_upper).HitCount; + + if (totalChestKills >= 250) + { + double marginOfError = Thresholds.GetMarginOfError(totalChestKills); + double lerpAmount = Math.Min(1.0, (totalChestKills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills)); + // determine max acceptable ratio of chest to abdomen kills + double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(2.25), Thresholds.ChestAbdomenRatioThresholdHighSample(2.25), lerpAmount) + marginOfError; + double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), lerpAmount) + marginOfError; + + double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower]; + + if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag) + { + + if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan) + { + Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**"); + Log.WriteDebug($"ClientId: {stats.ClientId}"); + Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); + Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); + Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Ban, + RatioAmount = currentChestAbdomenRatio, + Bone = IW4Info.HitLocation.torso_upper, + KillCount = totalChestKills + }; + } + else + { + Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**"); + Log.WriteDebug($"ClientId: {stats.ClientId}"); + Log.WriteDebug($"**Total Chest Kills: {totalChestKills}"); + Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); + Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); + var sb = new StringBuilder(); + foreach (var kvp in HitLocationCount) + sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); + Log.WriteDebug(sb.ToString()); + // Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}"); + + return new DetectionPenaltyResult() + { + ClientPenalty = Penalty.PenaltyType.Flag, + RatioAmount = currentChestAbdomenRatio, + Bone = IW4Info.HitLocation.torso_upper, + KillCount = totalChestKills + }; + } } } - return thresholdReached; + return new DetectionPenaltyResult() + { + Bone = IW4Info.HitLocation.none, + ClientPenalty = Penalty.PenaltyType.Any + }; } } } diff --git a/Plugins/SimpleStats/Cheat/Thresholds.cs b/Plugins/SimpleStats/Cheat/Thresholds.cs index 8a04c4254..8151a062f 100644 --- a/Plugins/SimpleStats/Cheat/Thresholds.cs +++ b/Plugins/SimpleStats/Cheat/Thresholds.cs @@ -8,26 +8,30 @@ namespace StatsPlugin.Cheat { class Thresholds { - private const double Deviations = 3.33; - - public const double HeadshotRatioThresholdLowSample = HeadshotRatioStandardDeviationLowSample * Deviations + HeadshotRatioMean; - public const double HeadshotRatioThresholdHighSample = HeadshotRatioStandardDeviationHighSample * Deviations + HeadshotRatioMean; + public static double HeadshotRatioThresholdLowSample(double deviations) => HeadshotRatioStandardDeviationLowSample * deviations + HeadshotRatioMean; + public static double HeadshotRatioThresholdHighSample(double deviations) => HeadshotRatioStandardDeviationHighSample * deviations + HeadshotRatioMean; public const double HeadshotRatioStandardDeviationLowSample = 0.1769994181; public const double HeadshotRatioStandardDeviationHighSample = 0.03924263235; - //public const double HeadshotRatioMean = 0.09587712258; public const double HeadshotRatioMean = 0.222; - public const double BoneRatioThresholdLowSample = BoneRatioStandardDeviationLowSample * Deviations + BoneRatioMean; - public const double BoneRatioThresholdHighSample = BoneRatioStandardDeviationHighSample * Deviations + BoneRatioMean; + public static double BoneRatioThresholdLowSample(double deviations) => BoneRatioStandardDeviationLowSample * deviations + BoneRatioMean; + public static double BoneRatioThresholdHighSample(double deviations) => BoneRatioStandardDeviationHighSample * deviations + BoneRatioMean; public const double BoneRatioStandardDeviationLowSample = 0.1324612879; public const double BoneRatioStandardDeviationHighSample = 0.0515753935; - public const double BoneRatioMean = 0.3982907516; + public const double BoneRatioMean = 0.4593110238; + + public static double ChestAbdomenRatioThresholdLowSample(double deviations) => ChestAbdomenStandardDeviationLowSample * deviations + ChestAbdomenRatioMean; + public static double ChestAbdomenRatioThresholdHighSample(double deviations) => ChestAbdomenStandardDeviationHighSample * deviations + ChestAbdomenRatioMean; + public const double ChestAbdomenStandardDeviationLowSample = 0.2859234644; + public const double ChestAbdomenStandardDeviationHighSample = 0.2195212861; + public const double ChestAbdomenRatioMean = 0.3925617500; public const int LowSampleMinKills = 15; + public const int MediumSampleMinKills = 30; public const int HighSampleMinKills = 100; public const double KillTimeThreshold = 0.2; - public static double GetMarginOfError(int numKills) => 1.645 /(2 * Math.Sqrt(numKills)); + public static double GetMarginOfError(int numKills) => 0.98 / Math.Sqrt(numKills); public static double Lerp(double v1, double v2, double amount) { diff --git a/Plugins/SimpleStats/Commands/ResetStats.cs b/Plugins/SimpleStats/Commands/ResetStats.cs index 3671a6796..95ad2506e 100644 --- a/Plugins/SimpleStats/Commands/ResetStats.cs +++ b/Plugins/SimpleStats/Commands/ResetStats.cs @@ -27,6 +27,9 @@ namespace StatsPlugin.Commands stats.SPM = 0; stats.Skill = 0; + // reset the cached version + Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode()); + // fixme: this doesn't work properly when another context exists await svc.SaveChangesAsync(); await E.Origin.Tell("Your stats have been reset"); diff --git a/Plugins/SimpleStats/Helpers/ServerStats.cs b/Plugins/SimpleStats/Helpers/ServerStats.cs index e53e45527..4ac908ec0 100644 --- a/Plugins/SimpleStats/Helpers/ServerStats.cs +++ b/Plugins/SimpleStats/Helpers/ServerStats.cs @@ -2,6 +2,7 @@ using StatsPlugin.Cheat; using StatsPlugin.Models; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,15 +11,15 @@ using System.Threading.Tasks; namespace StatsPlugin.Helpers { class ServerStats { - public Dictionary PlayerStats { get; set; } - public Dictionary PlayerDetections { get; set; } + public ConcurrentDictionary PlayerStats { get; set; } + public ConcurrentDictionary PlayerDetections { get; set; } public EFServerStatistics ServerStatistics { get; private set; } public EFServer Server { get; private set; } public ServerStats(EFServer sv, EFServerStatistics st) { - PlayerStats = new Dictionary(); - PlayerDetections = new Dictionary(); + PlayerStats = new ConcurrentDictionary(); + PlayerDetections = new ConcurrentDictionary(); ServerStatistics = st; Server = sv; } diff --git a/Plugins/SimpleStats/Helpers/StatManager.cs b/Plugins/SimpleStats/Helpers/StatManager.cs index 9ad73b60d..04bfb62c5 100644 --- a/Plugins/SimpleStats/Helpers/StatManager.cs +++ b/Plugins/SimpleStats/Helpers/StatManager.cs @@ -9,6 +9,7 @@ using SharedLibrary.Interfaces; using SharedLibrary.Objects; using SharedLibrary.Services; using StatsPlugin.Models; +using SharedLibrary.Commands; namespace StatsPlugin.Helpers { @@ -75,7 +76,7 @@ namespace StatsPlugin.Helpers catch (Exception e) { - Log.WriteWarning($"Could not add server to ServerStats - {e.Message}"); + Log.WriteError($"Could not add server to ServerStats - {e.Message}"); } } @@ -84,9 +85,17 @@ namespace StatsPlugin.Helpers /// /// Player to add/retrieve stats for /// EFClientStatistic of specified player - public EFClientStatistics AddPlayer(Player pl) + public async Task AddPlayer(Player pl) { + Log.WriteInfo($"Adding {pl} to stats"); int serverId = pl.CurrentServer.GetHashCode(); + + if (!Servers.ContainsKey(serverId)) + { + Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); + return null; + } + var playerStats = Servers[serverId].PlayerStats; var statsSvc = ContextThreads[serverId]; @@ -105,33 +114,50 @@ namespace StatsPlugin.Helpers ServerId = serverId, Skill = 0.0, SPM = 0.0, + HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() + { + Active = true, + HitCount = 0, + Location = hl + }) + .ToList() }; clientStats = statsSvc.ClientStatSvc.Insert(clientStats); + await statsSvc.ClientStatSvc.SaveChangesAsync(); + } + + // migration for previous existing stats + else if (clientStats.HitLocations.Count == 0) + { + clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() + { + Active = true, + HitCount = 0, + Location = hl + }) + .ToList(); + await statsSvc.ClientStatSvc.SaveChangesAsync(); } // set these on connecting clientStats.LastActive = DateTime.UtcNow; clientStats.LastStatCalculation = DateTime.UtcNow; + clientStats.SessionScore = pl.Score; - lock (playerStats) + if (playerStats.ContainsKey(pl.ClientId)) { - if (playerStats.ContainsKey(pl.ClientNumber)) - { - Log.WriteWarning($"Duplicate clientnumber in stats {pl.ClientId} vs {playerStats[pl.ClientNumber].ClientId}"); - playerStats.Remove(pl.ClientNumber); - } - playerStats.Add(pl.ClientNumber, clientStats); + Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId} vs {playerStats[pl.ClientId].ClientId}"); + playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue); } + playerStats.TryAdd(pl.ClientId, clientStats); var detectionStats = Servers[serverId].PlayerDetections; - lock (detectionStats) - { - if (detectionStats.ContainsKey(pl.ClientNumber)) - detectionStats.Remove(pl.ClientNumber); - detectionStats.Add(pl.ClientNumber, new Cheat.Detection(Log)); - } + if (detectionStats.ContainsKey(pl.ClientId)) + detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue); + + detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log)); return clientStats; } @@ -143,19 +169,27 @@ namespace StatsPlugin.Helpers /// public async Task RemovePlayer(Player pl) { + Log.WriteInfo($"Removing {pl} from stats"); + int serverId = pl.CurrentServer.GetHashCode(); var playerStats = Servers[serverId].PlayerStats; var detectionStats = Servers[serverId].PlayerDetections; var serverStats = Servers[serverId].ServerStatistics; var statsSvc = ContextThreads[serverId]; + if (!playerStats.ContainsKey(pl.ClientId)) + { + Log.WriteWarning($"Client disconnecting not in stats {pl}"); + return; + } + // get individual client's stats - var clientStats = playerStats[pl.ClientNumber]; + var clientStats = playerStats[pl.ClientId]; + // sync their score + clientStats.SessionScore = pl.Score; // remove the client from the stats dictionary as they're leaving - lock (playerStats) - playerStats.Remove(pl.ClientNumber); - lock (detectionStats) - detectionStats.Remove(pl.ClientNumber); + playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue); + detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); // sync their stats before they leave UpdateStats(clientStats); @@ -174,16 +208,7 @@ namespace StatsPlugin.Helpers public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type, string damage, string weapon, string killOrigin, string deathOrigin) { - await AddStandardKill(attacker, victim); - - if (victim == null) - { - Log.WriteError($"[AddScriptKill] Victim is null"); - return; - } - var statsSvc = ContextThreads[serverId]; - var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientNumber]; var kill = new EFClientKill() { @@ -200,30 +225,88 @@ namespace StatsPlugin.Helpers Weapon = ParseEnum.Get(weapon, typeof(IW4Info.WeaponName)) }; - playerDetection.ProcessKill(kill); + if (kill.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE && + kill.Damage == 10000) + { + // suicide by switching teams so let's not count it against them + return; + } - return; + await AddStandardKill(attacker, victim); - statsSvc.KillStatsSvc.Insert(kill); - await statsSvc.KillStatsSvc.SaveChangesAsync(); + var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; + var playerStats = Servers[serverId].PlayerStats[attacker.ClientId]; + + // increment their hit count + if (kill.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || + kill.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET) + { + playerStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1; + await statsSvc.ClientStatSvc.SaveChangesAsync(); + } + + //statsSvc.KillStatsSvc.Insert(kill); + //await statsSvc.KillStatsSvc.SaveChangesAsync(); + + async Task executePenalty(Cheat.DetectionPenaltyResult penalty) + { + switch (penalty.ClientPenalty) + { + case Penalty.PenaltyType.Ban: + await attacker.Ban("You appear to be cheating", new Player() { ClientId = 1 }); + break; + case Penalty.PenaltyType.Flag: + if (attacker.Level != Player.Permission.User) + break; + var flagCmd = new CFlag(); + await flagCmd.ExecuteAsync(new Event(Event.GType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player() + { + ClientId = 1, + Level = Player.Permission.Console, + ClientNumber = -1, + CurrentServer = attacker.CurrentServer + }, attacker, attacker.CurrentServer)); + break; + } + } + + await executePenalty(playerDetection.ProcessKill(kill)); + await executePenalty(playerDetection.ProcessTotalRatio(playerStats)); } public async Task AddStandardKill(Player attacker, Player victim) { int serverId = attacker.CurrentServer.GetHashCode(); - var attackerStats = Servers[serverId].PlayerStats[attacker.ClientNumber]; - - if (victim == null) + EFClientStatistics attackerStats = null; + try { - Log.WriteError($"[AddStandardKill] Victim is null"); + attackerStats = Servers[serverId].PlayerStats[attacker.ClientId]; + } + + catch (KeyNotFoundException) + { + Log.WriteError($"[Stats::AddStandardKill] kill attacker ClientId is invalid {attacker.ClientId}-{attacker}"); return; } - var victimStats = Servers[serverId].PlayerStats[victim.ClientNumber]; + EFClientStatistics victimStats = null; + try + { + victimStats = Servers[serverId].PlayerStats[victim.ClientId]; + } + + catch (KeyNotFoundException) + { + Log.WriteError($"[Stats::AddStandardKill] kill victim ClientId is invalid {victim.ClientId}-{victim}"); + return; + } // update the total stats Servers[serverId].ServerStatistics.TotalKills += 1; + attackerStats.SessionScore = attacker.Score; + victimStats.SessionScore = victim.Score; + // calculate for the clients CalculateKill(attackerStats, victimStats); @@ -292,9 +375,7 @@ namespace StatsPlugin.Helpers return clientStats; // calculate the players Score Per Minute for the current session - int currentScore = Manager.GetActiveClients() - .First(c => c.ClientId == clientStats.ClientId) - .Score; + int currentScore = clientStats.SessionScore; double killSPM = currentScore / (timeSinceLastCalc * 60.0); // calculate how much the KDR should weigh @@ -350,6 +431,25 @@ namespace StatsPlugin.Helpers } } + public void ResetKillstreaks(int serverId) + { + var serverStats = Servers[serverId]; + foreach (var stat in serverStats.PlayerStats.Values) + { + stat.KillStreak = 0; + stat.DeathStreak = 0; + } + } + + public void ResetStats(int clientId, int serverId) + { + var stats = Servers[serverId].PlayerStats[clientId]; + stats.Kills = 0; + stats.Deaths = 0; + stats.SPM = 0; + stats.Skill = 0; + } + public async Task AddMessageAsync(int clientId, int serverId, string message) { // the web users can have no account diff --git a/Plugins/SimpleStats/Helpers/ThreadSafeStatsService.cs b/Plugins/SimpleStats/Helpers/ThreadSafeStatsService.cs index d8063b2e2..0115b9217 100644 --- a/Plugins/SimpleStats/Helpers/ThreadSafeStatsService.cs +++ b/Plugins/SimpleStats/Helpers/ThreadSafeStatsService.cs @@ -10,7 +10,6 @@ namespace StatsPlugin.Helpers { public class ThreadSafeStatsService { - public GenericRepository ClientStatSvc { get; private set; } public GenericRepository ServerSvc { get; private set; } public GenericRepository KillStatsSvc { get; private set; } diff --git a/Plugins/SimpleStats/IW4Info.cs b/Plugins/SimpleStats/IW4Info.cs index f5a7cc1ec..0e0240d7a 100644 --- a/Plugins/SimpleStats/IW4Info.cs +++ b/Plugins/SimpleStats/IW4Info.cs @@ -1358,7 +1358,9 @@ namespace StatsPlugin m40a3_mp, peacekeeper_mp, dragunov_mp, - cobra_player_minigun_mp + cobra_player_minigun_mp, + destructible_car, + sentry_minigun_mp } public enum MapName diff --git a/Plugins/SimpleStats/Models/EFClientStatistics.cs b/Plugins/SimpleStats/Models/EFClientStatistics.cs index bd0f87ae0..b243c1e6c 100644 --- a/Plugins/SimpleStats/Models/EFClientStatistics.cs +++ b/Plugins/SimpleStats/Models/EFClientStatistics.cs @@ -24,7 +24,9 @@ namespace StatsPlugin.Models public int Kills { get; set; } [Required] public int Deaths { get; set; } - [Required] + + public virtual ICollection HitLocations { get; set; } + [NotMapped] public double KDR { @@ -51,5 +53,7 @@ namespace StatsPlugin.Models public int LastScore { get; set; } [NotMapped] public DateTime LastActive { get; set; } + [NotMapped] + public int SessionScore { get; set; } } } diff --git a/Plugins/SimpleStats/Models/EFHitLocationCount.cs b/Plugins/SimpleStats/Models/EFHitLocationCount.cs new file mode 100644 index 000000000..a72654861 --- /dev/null +++ b/Plugins/SimpleStats/Models/EFHitLocationCount.cs @@ -0,0 +1,21 @@ +using SharedLibrary.Database.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StatsPlugin.Models +{ + public class EFHitLocationCount : SharedEntity + { + [Key] + public int HitLocationCountId { get; set; } + [Required] + public IW4Info.HitLocation Location { get; set; } + [Required] + public int HitCount { get; set; } + } +} diff --git a/Plugins/SimpleStats/Plugin.cs b/Plugins/SimpleStats/Plugin.cs index 345520f14..97bde99b7 100644 --- a/Plugins/SimpleStats/Plugin.cs +++ b/Plugins/SimpleStats/Plugin.cs @@ -22,7 +22,7 @@ namespace StatsPlugin public string Author => "RaidMax"; - private StatManager Manager; + public static StatManager Manager { get; private set; } private IManager ServerManager; public async Task OnEventAsync(Event E, Server S) @@ -35,19 +35,20 @@ namespace StatsPlugin case Event.GType.Stop: break; case Event.GType.Connect: - Manager.AddPlayer(E.Origin); + await Manager.AddPlayer(E.Origin); break; case Event.GType.Disconnect: await Manager.RemovePlayer(E.Origin); break; case Event.GType.Say: - if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Data.Trim()[0] != '!') + if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Message.Trim()[0] != '!' && E.Origin.ClientId > 1) await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data); break; case Event.GType.MapChange: + Manager.ResetKillstreaks(S.GetHashCode()); + await Manager.Sync(S); break; case Event.GType.MapEnd: - await Manager.Sync(S); break; case Event.GType.Broadcast: break; @@ -92,6 +93,25 @@ namespace StatsPlugin double kdr = Math.Round(kills / (double)deaths, 2); double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Count, 2); + double chestRatio = 0; + double abdomenRatio = 0; + double chestAbdomenRatio = 0; + + if (clientStats.FirstOrDefault()?.HitLocations.Count > 0) + { + chestRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => + c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) / + (double)clientStats.Where(c => c.HitLocations.Count > 0) + .Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2); + + abdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => + c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount) / + (double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2); + + chestAbdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) / + (double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount), 2); + } + return new List() { new ProfileMeta() @@ -113,6 +133,24 @@ namespace StatsPlugin { Key = "Skill", Value = skill + }, + new ProfileMeta() + { + Key = "Chest Ratio", + Value = chestRatio, + Sensitive = true + }, + new ProfileMeta() + { + Key = "Abdomen Ratio", + Value = abdomenRatio, + Sensitive = true + }, + new ProfileMeta() + { + Key = "Chest To Abdomen Ratio", + Value = chestAbdomenRatio, + Sensitive = true } }; } diff --git a/Plugins/SimpleStats/StatsPlugin.csproj b/Plugins/SimpleStats/StatsPlugin.csproj index cebfbf594..4e42a1bd1 100644 --- a/Plugins/SimpleStats/StatsPlugin.csproj +++ b/Plugins/SimpleStats/StatsPlugin.csproj @@ -119,6 +119,7 @@ + @@ -131,6 +132,7 @@ + @@ -146,7 +148,6 @@ {d51eeceb-438a-47da-870f-7d7b41bc24d6} SharedLibrary - False diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index 4b4aca94f..cf6680b9e 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -126,7 +126,7 @@ namespace Welcome_Plugin msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level)); try { - CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup("Plugins/GeoIP.dat"); + CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup($"{Utilities.OperatingDirectory}Plugins{System.IO.Path.DirectorySeparatorChar}GeoIP.dat"); msg = msg.Replace("{{ClientLocation}}", CLT.LookupCountryName(joining.IPAddressString)); } diff --git a/SharedLibrary/App.config b/SharedLibrary/App.config index a6a2b7fa9..6a931919a 100644 --- a/SharedLibrary/App.config +++ b/SharedLibrary/App.config @@ -1,2 +1,36 @@ - - \ No newline at end of file + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SharedLibrary/Commands/NativeCommands.cs b/SharedLibrary/Commands/NativeCommands.cs index 16db947a9..32cb62d34 100644 --- a/SharedLibrary/Commands/NativeCommands.cs +++ b/SharedLibrary/Commands/NativeCommands.cs @@ -11,6 +11,7 @@ using SharedLibrary.Database; using System.Data.Entity; using SharedLibrary.Database.Models; using SharedLibrary.Services; +using SharedLibrary.Exceptions; namespace SharedLibrary.Commands { @@ -170,6 +171,9 @@ namespace SharedLibrary.Commands { String Message = Utilities.RemoveWords(E.Data, 1).Trim(); var length = E.Data.Split(' ')[0].ToLower().ParseTimespan(); + if (length.TotalHours >= 1 && length.TotalHours < 2) + Message = E.Data; + if (E.Origin.Level > E.Target.Level) { @@ -428,8 +432,8 @@ namespace SharedLibrary.Commands if (newPerm > Player.Permission.Banned) { - var ActiveClient = E.Owner.Manager.GetActiveClients().FirstOrDefault(p => p.NetworkId == E.Target.NetworkId); - + var ActiveClient = E.Owner.Manager.GetActiveClients() + .FirstOrDefault(p => p.NetworkId == E.Target.NetworkId); if (ActiveClient != null) { @@ -444,7 +448,6 @@ namespace SharedLibrary.Commands } await E.Origin.Tell($"{E.Target.Name} was successfully promoted!"); - } else @@ -485,17 +488,22 @@ namespace SharedLibrary.Commands public override async Task ExecuteAsync(Event E) { + int numOnline = 0; for (int i = 0; i < E.Owner.Players.Count; i++) { var P = E.Owner.Players[i]; if (P != null && P.Level > Player.Permission.Flagged && !P.Masked) { + numOnline++; if (E.Message[0] == '@') await E.Owner.Broadcast(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name)); else await E.Origin.Tell(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name)); } } + + if (numOnline == 0) + await E.Origin.Tell("No visible administrators online"); } } @@ -519,14 +527,14 @@ namespace SharedLibrary.Commands { if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) { - await E.Owner.Broadcast("Changing to map ^2" + m.Alias); + await E.Owner.Broadcast($"Changing to map ^5{m.Alias}"); Task.Delay(5000).Wait(); await E.Owner.LoadMap(m.Name); return; } } - await E.Owner.Broadcast("Attempting to change to unknown map ^1" + newMap); + await E.Owner.Broadcast($"Attempting to change to unknown map ^5{newMap}"); Task.Delay(5000).Wait(); await E.Owner.LoadMap(newMap); } @@ -927,7 +935,7 @@ namespace SharedLibrary.Commands } }) { } - + public override async Task ExecuteAsync(Event E) { int inactiveDays = 30; @@ -961,7 +969,89 @@ namespace SharedLibrary.Commands inactiveUsers.ForEach(c => c.Level = Player.Permission.User); await context.SaveChangesAsync(); } - await E.Origin.Tell($"Pruned {inactiveUsers.Count} inactive privileged users"); + await E.Origin.Tell($"Pruned {inactiveUsers.Count} inactive privileged users"); + + } + } + + public class CRestartServer : Command + { + public CRestartServer() : base("restartserver", "restart the server", "restart", Player.Permission.Administrator, false) + { + } + + public override async Task ExecuteAsync(Event E) + { + var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x"); + var currentProcess = gameserverProcesses.FirstOrDefault(g => g.GetCommandLine().Contains($"+set net_port {E.Owner.GetPort()}")); + + if (currentProcess == null) + { + await E.Origin.Tell("Could not find running/stalled instance of IW4x"); + } + + else + { + var commandLine = currentProcess.GetCommandLine(); + // attempt to kill it natively + try + { + if (!E.Owner.Throttled) + { + // await E.Owner.ExecuteCommandAsync("quit"); + } + } + + catch (NetworkException) + { + await E.Origin.Tell("Unable to cleanly shutdown server, forcing"); + } + + if (!currentProcess.HasExited) + { + try + { + currentProcess.Kill(); + } + catch (Exception e) + { + await E.Origin.Tell("Could not kill IW4x process"); + E.Owner.Logger.WriteDebug("Unable to kill process"); + E.Owner.Logger.WriteDebug($"Exception: {e.Message}"); + return; + } + + try + { + + System.Diagnostics.Process process = new System.Diagnostics.Process(); + process.StartInfo.UseShellExecute = false; +#if !DEBUG + process.StartInfo.WorkingDirectory = E.Owner.WorkingDirectory; +#else + process.StartInfo.WorkingDirectory = @"C:\Users\User\Desktop\MW2"; +#endif + process.StartInfo.FileName = $"{process.StartInfo.WorkingDirectory}\\iw4x.exe"; + process.StartInfo.Arguments = commandLine.Substring(6); + process.StartInfo.UserName = E.Owner.Config.RestartUsername; + + var pw = new System.Security.SecureString(); + foreach (char c in E.Owner.Config.RestartPassword) + pw.AppendChar(c); + + process.StartInfo.Password = pw; + process.Start(); + } + + catch (Exception e) + { + await E.Origin.Tell("Could not start the IW4x process"); + E.Owner.Logger.WriteDebug("Unable to start process"); + E.Owner.Logger.WriteDebug($"Exception: {e.Message}"); + } + } + } + } } diff --git a/SharedLibrary/Database/DatabaseContext.cs b/SharedLibrary/Database/DatabaseContext.cs index 858eee8f5..cbf16e8a3 100644 --- a/SharedLibrary/Database/DatabaseContext.cs +++ b/SharedLibrary/Database/DatabaseContext.cs @@ -10,6 +10,7 @@ using System.Data.Entity.ModelConfiguration.Conventions; using System.Reflection; using System.Data.Entity.Infrastructure; using System.Data.Entity.SqlServerCompact; +using System.IO; namespace SharedLibrary.Database { @@ -23,7 +24,8 @@ namespace SharedLibrary.Database public DatabaseContext() : base("DefaultConnection") { - System.Data.Entity.Database.SetInitializer(new Initializer()); + System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion()); + //Database.CreateIfNotExists(); Configuration.LazyLoadingEnabled = true; } @@ -50,7 +52,12 @@ namespace SharedLibrary.Database modelBuilder.Conventions.Remove(); // https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/ + //string dir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; +#if !DEBUG foreach (string dllPath in System.IO.Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins")) +#else + foreach (string dllPath in System.IO.Directory.GetFiles(@"C:\Users\User\Desktop\stuff\IW4M-Admin\IW4M-Admin\WebfrontCore\bin\x86\Debug\Plugins")) +#endif { Assembly library; try @@ -64,7 +71,7 @@ namespace SharedLibrary.Database continue; } - foreach(var type in library.ExportedTypes) + foreach (var type in library.ExportedTypes) { if (type.IsClass && type.IsSubclassOf(typeof(SharedEntity))) { diff --git a/SharedLibrary/Database/Initializer.cs b/SharedLibrary/Database/Initializer.cs deleted file mode 100644 index bb0a2dc26..000000000 --- a/SharedLibrary/Database/Initializer.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.Entity; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SharedLibrary.Database -{ - public class Initializer : DropCreateDatabaseIfModelChanges - { - protected override void Seed(DatabaseContext context) - { - var aliasLink = new Models.EFAliasLink(); - - var currentAlias = new Models.EFAlias() - { - Active = true, - DateAdded = DateTime.UtcNow, - IPAddress = 0, - Name = "IW4MAdmin", - Link = aliasLink - }; - - context.Clients.Add(new Models.EFClient() - { - Active = false, - Connections = 0, - FirstConnection = DateTime.UtcNow, - LastConnection = DateTime.UtcNow, - Level = Objects.Player.Permission.Console, - Masked = true, - NetworkId = 0, - AliasLink = aliasLink, - CurrentAlias = currentAlias - }); - - base.Seed(context); - } - - } -} diff --git a/SharedLibrary/Database/Repair.cs b/SharedLibrary/Database/Repair.cs new file mode 100644 index 000000000..3584b4ff8 --- /dev/null +++ b/SharedLibrary/Database/Repair.cs @@ -0,0 +1,26 @@ +using SharedLibrary.Interfaces; +using System; +using System.Data.SqlServerCe; + +namespace SharedLibrary.Database +{ + public class Repair + { + public static void Run(ILogger log) + { + SqlCeEngine engine = new SqlCeEngine(@"Data Source=|DataDirectory|\Database.sdf"); + if (false == engine.Verify()) + { + log.WriteWarning("Database is corrupted."); + try + { + engine.Repair(null, RepairOption.DeleteCorruptedRows); + } + catch (SqlCeException ex) + { + log.WriteError(ex.Message); + } + } + } + } +} \ No newline at end of file diff --git a/SharedLibrary/Dtos/PenaltyInfo.cs b/SharedLibrary/Dtos/PenaltyInfo.cs index 425726047..f90cd98c2 100644 --- a/SharedLibrary/Dtos/PenaltyInfo.cs +++ b/SharedLibrary/Dtos/PenaltyInfo.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace SharedLibrary.Dtos { - public class PenaltyInfo + public class PenaltyInfo : SharedInfo { public string OffenderName { get; set; } public int OffenderId { get; set; } diff --git a/SharedLibrary/Dtos/ProfileMeta.cs b/SharedLibrary/Dtos/ProfileMeta.cs index 8c9a45254..285f3d465 100644 --- a/SharedLibrary/Dtos/ProfileMeta.cs +++ b/SharedLibrary/Dtos/ProfileMeta.cs @@ -7,9 +7,10 @@ using System.Threading.Tasks; namespace SharedLibrary.Dtos { - public class ProfileMeta + public class ProfileMeta : SharedInfo { public DateTime When { get; set; } + public bool Sensitive { get; set; } public string WhenString => Utilities.GetTimePassed(When, false); public string Key { get; set; } public dynamic Value { get; set; } diff --git a/SharedLibrary/Dtos/SharedInfo.cs b/SharedLibrary/Dtos/SharedInfo.cs new file mode 100644 index 000000000..956318ffb --- /dev/null +++ b/SharedLibrary/Dtos/SharedInfo.cs @@ -0,0 +1,8 @@ + +namespace SharedLibrary.Dtos +{ + public class SharedInfo + { + public bool Sensitive { get; set; } + } +} \ No newline at end of file diff --git a/SharedLibrary/Interfaces/IManager.cs b/SharedLibrary/Interfaces/IManager.cs index 99344b486..f0e6aab33 100644 --- a/SharedLibrary/Interfaces/IManager.cs +++ b/SharedLibrary/Interfaces/IManager.cs @@ -2,12 +2,13 @@ using SharedLibrary.Objects; using SharedLibrary.Database.Models; using SharedLibrary.Services; +using System.Threading.Tasks; namespace SharedLibrary.Interfaces { public interface IManager { - void Init(); + Task Init(); void Start(); void Stop(); ILogger GetLogger(); @@ -15,7 +16,7 @@ namespace SharedLibrary.Interfaces IList GetCommands(); IList GetMessageTokens(); IList GetActiveClients(); - ClientService GetClientService(); + ClientService GetClientService(); AliasService GetAliasService(); PenaltyService GetPenaltyService(); } diff --git a/SharedLibrary/Migrations/201803030146021_Intial.Designer.cs b/SharedLibrary/Migrations/201803030146021_Intial.Designer.cs new file mode 100644 index 000000000..f64099275 --- /dev/null +++ b/SharedLibrary/Migrations/201803030146021_Intial.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SharedLibrary.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class Intial : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Intial)); + + string IMigrationMetadata.Id + { + get { return "201803030146021_Intial"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/SharedLibrary/Migrations/201803030146021_Intial.cs b/SharedLibrary/Migrations/201803030146021_Intial.cs new file mode 100644 index 000000000..0fa457969 --- /dev/null +++ b/SharedLibrary/Migrations/201803030146021_Intial.cs @@ -0,0 +1,16 @@ +namespace SharedLibrary.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Intial : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/SharedLibrary/Migrations/201803030146021_Intial.resx b/SharedLibrary/Migrations/201803030146021_Intial.resx new file mode 100644 index 000000000..98122108e --- /dev/null +++ b/SharedLibrary/Migrations/201803030146021_Intial.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/SharedLibrary/Migrations/Configuration.cs b/SharedLibrary/Migrations/Configuration.cs new file mode 100644 index 000000000..a34bbebd5 --- /dev/null +++ b/SharedLibrary/Migrations/Configuration.cs @@ -0,0 +1,52 @@ +namespace SharedLibrary.Migrations +{ + using System; + using System.Data.Entity; + using System.Data.Entity.Migrations; + using System.Linq; + + internal sealed class Configuration : DbMigrationsConfiguration + { + public Configuration() + { + AutomaticMigrationsEnabled = true; + AutomaticMigrationDataLossAllowed = true; + } + + protected override void Seed(SharedLibrary.Database.DatabaseContext context) + { + context.AliasLinks.AddOrUpdate(new SharedLibrary.Database.Models.EFAliasLink() + { + AliasLinkId = 1 + }); + + var currentAlias = new SharedLibrary.Database.Models.EFAlias() + { + AliasId = 1, + Active = true, + DateAdded = DateTime.UtcNow, + IPAddress = 0, + Name = "IW4MAdmin", + LinkId = 1 + }; + + context.Aliases.AddOrUpdate(currentAlias); + + context.Clients.AddOrUpdate(new SharedLibrary.Database.Models.EFClient() + { + ClientId = 1, + Active = false, + Connections = 0, + FirstConnection = DateTime.UtcNow, + LastConnection = DateTime.UtcNow, + Level = Objects.Player.Permission.Console, + Masked = true, + NetworkId = 0, + AliasLinkId = 1, + CurrentAliasId = 1, + }); + + base.Seed(context); + } + } +} diff --git a/SharedLibrary/RCON.cs b/SharedLibrary/RCON.cs index 18c4b9aff..6f2743226 100644 --- a/SharedLibrary/RCON.cs +++ b/SharedLibrary/RCON.cs @@ -66,7 +66,7 @@ namespace SharedLibrary.Network do { ReceiveBuffer = ServerOOBConnection.Receive(ref Endpoint); - QueryResponseString.Append(Encoding.ASCII.GetString(ReceiveBuffer).TrimEnd('\0')); + QueryResponseString.Append(Encoding.UTF7.GetString(ReceiveBuffer).TrimEnd('\0')); } while (ServerOOBConnection.Available > 0 && ServerOOBConnection.Client.Connected); if (QueryResponseString.ToString().Contains("Invalid password")) diff --git a/SharedLibrary/Server.cs b/SharedLibrary/Server.cs index 2b3b5b85a..1a7dcdd72 100644 --- a/SharedLibrary/Server.cs +++ b/SharedLibrary/Server.cs @@ -385,6 +385,7 @@ namespace SharedLibrary public string Password { get; private set; } public bool Throttled { get; protected set; } public bool CustomCallback { get; protected set; } + public string WorkingDirectory { get; protected set; } // Internal protected string IP; diff --git a/SharedLibrary/ServerConfiguration.cs b/SharedLibrary/ServerConfiguration.cs index d5dc59859..4f8ee2626 100644 --- a/SharedLibrary/ServerConfiguration.cs +++ b/SharedLibrary/ServerConfiguration.cs @@ -10,6 +10,8 @@ namespace SharedLibrary public string FtpPrefix; public bool AllowMultipleOwners; public bool AllowTrustedRank; + public string RestartUsername; + public string RestartPassword; public override string Filename() { diff --git a/SharedLibrary/Services/ClientService.cs b/SharedLibrary/Services/ClientService.cs index e49b16eee..75296e56a 100644 --- a/SharedLibrary/Services/ClientService.cs +++ b/SharedLibrary/Services/ClientService.cs @@ -168,6 +168,11 @@ namespace SharedLibrary.Services }; } + else + { + client.CurrentAliasId = entity.CurrentAliasId; + } + // set remaining non-navigation properties that may have been updated client.Level = entity.Level; client.LastConnection = entity.LastConnection; diff --git a/SharedLibrary/SharedLibrary.csproj b/SharedLibrary/SharedLibrary.csproj index 30ff4685e..0350528cd 100644 --- a/SharedLibrary/SharedLibrary.csproj +++ b/SharedLibrary/SharedLibrary.csproj @@ -15,6 +15,21 @@ + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true AnyCPU @@ -118,22 +133,10 @@ MinimumRecommendedRules.ruleset - - ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll - - - ..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll - - - ..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll - True - + @@ -144,19 +147,20 @@ - + + @@ -173,6 +177,11 @@ + + + 201803030146021_Intial.cs + + @@ -197,13 +206,37 @@ + + + 6.2.0 + + + 6.2.0 + 11.0.1 + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + 201803030146021_Intial.cs + + copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\lib" @@ -218,9 +251,7 @@ if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64" xcopy /Y /I /E "$(TargetDir)*" "$(SolutionDir)BUILD\Lib" - if exist "$(SolutionDir)BUILD\Plugins" rmdir /Q /S "$(SolutionDir)BUILD\Plugins" -mkdir "$(SolutionDir)BUILD\Plugins" - + if not exist "$(SolutionDir)BUILD" mkdir "$(SolutionDir)BUILD" if not exist "$(SolutionDir)BUILD\Lib" mkdir "$(SolutionDir)BUILD\Lib" if not exist "$(SolutionDir)BUILD\userraw\scripts" mkdir "$(SolutionDir)BUILD\userraw\scripts" diff --git a/SharedLibrary/Utilities.cs b/SharedLibrary/Utilities.cs index c766a42cd..655eab7fe 100644 --- a/SharedLibrary/Utilities.cs +++ b/SharedLibrary/Utilities.cs @@ -4,11 +4,13 @@ using System.Text; using System.Text.RegularExpressions; using System.Linq; using System.Collections.Generic; +using System.Management; using SharedLibrary.Objects; using static SharedLibrary.Server; using System.Reflection; using System.IO; +using System.Diagnostics; namespace SharedLibrary { @@ -61,14 +63,14 @@ namespace SharedLibrary int cID = -1; int Ping = -1; Int32.TryParse(playerInfo[2], out Ping); - String cName = Utilities.StripColors(responseLine.Substring(46, 18)).Trim(); + String cName = Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(StripColors(responseLine.Substring(46, 18)).Trim()))); long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong(); int.TryParse(playerInfo[0], out cID); var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); int cIP = regex.Value.Split(':')[0].ConvertToIP(); regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]); - Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score}; + Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score }; StatusPlayers.Add(P); } } @@ -96,8 +98,11 @@ namespace SharedLibrary { if (str == null) return ""; - return Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "") + str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", ""); + string str2 = Regex.Match(str, @"(^\/+.*$)|(^.*\/+$)") + .Value .Replace("/", " /"); + return str2.Length > 0 ? str2 : str; } /// @@ -334,11 +339,6 @@ namespace SharedLibrary return "1 hour"; } - public static string EscapeMarkdown(this string markdownString) - { - return markdownString.Replace("<", "\\<").Replace(">", "\\>").Replace("|", "\\|"); - } - public static Player AsPlayer(this Database.Models.EFClient client) { return client == null ? null : new Player() @@ -361,5 +361,35 @@ namespace SharedLibrary CurrentAliasId = client.CurrentAlias.AliasId }; } + + /*https://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c*/ + // Define an extension method for type System.Process that returns the command + // line via WMI. + public static string GetCommandLine(this Process process) + { + string cmdLine = null; + using (var searcher = new ManagementObjectSearcher( + $"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}")) + { + // By definition, the query returns at most 1 match, because the process + // is looked up by ID (which is unique by definition). + var matchEnum = searcher.Get().GetEnumerator(); + if (matchEnum.MoveNext()) // Move to the 1st item. + { + cmdLine = matchEnum.Current["CommandLine"]?.ToString(); + } + } + if (cmdLine == null) + { + // Not having found a command line implies 1 of 2 exceptions, which the + // WMI query masked: + // An "Access denied" exception due to lack of privileges. + // A "Cannot process request because the process () has exited." + // exception due to the process having terminated. + // We provoke the same exception again simply by accessing process.MainModule. + var dummy = process.MainModule; // Provoke exception. + } + return cmdLine; + } } } diff --git a/WebfrontCore/Application/Kayak.cs b/WebfrontCore/Application/Kayak.cs deleted file mode 100644 index cd7eed65a..000000000 --- a/WebfrontCore/Application/Kayak.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Text; -using Kayak.Http; -using Kayak; - - -namespace IW4MAdmin -{ - class Scheduler : ISchedulerDelegate - { - public void OnException(IScheduler scheduler, Exception e) - { - // it looks like there's a library error in - // Kayak.Http.HttpServerTransactionDelegate.OnError - if ((uint)e.HResult == 0x80004003 || (uint)e.InnerException?.HResult == 0x80004003) - return; - - ApplicationManager.GetInstance().Logger.WriteWarning("Web service has encountered an error - " + e.Message); - ApplicationManager.GetInstance().Logger.WriteDebug($"Stack Trace: {e.StackTrace}"); - - if (e.InnerException != null) - { - ApplicationManager.GetInstance().Logger.WriteDebug($"Inner Exception: {e.InnerException.Message}"); - ApplicationManager.GetInstance().Logger.WriteDebug($"Inner Stack Trace: {e.InnerException.StackTrace}"); - } - - } - - public void OnStop(IScheduler scheduler) - { - ApplicationManager.GetInstance().Logger.WriteInfo("Web service has been stopped..."); - } - } - - class Request : IHttpRequestDelegate - { - public void OnRequest(HttpRequestHead request, IDataProducer requestBody, IHttpResponseDelegate response) - { -#if DEBUG - var logger = ApplicationManager.GetInstance().GetLogger(); - logger.WriteDebug($"HTTP request {request.Path}"); - logger.WriteDebug($"QueryString: {request.QueryString}"); - logger.WriteDebug($"IP: {request.IPAddress}"); -#endif - - NameValueCollection querySet = new NameValueCollection(); - - if (request.QueryString != null) - querySet = System.Web.HttpUtility.ParseQueryString(request.QueryString); - - querySet.Set("IP", request.IPAddress); - - try - { - request.Path = String.IsNullOrEmpty(request.Path) ? "/" : request.Path; - SharedLibrary.HttpResponse requestedPage = WebService.GetPage(request.Path, querySet, request.Headers); - - bool binaryContent = requestedPage.BinaryContent != null; - if (requestedPage.content != null && requestedPage.content.GetType() != typeof(string)) -#if !DEBUG - requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content); -#else - requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content, Newtonsoft.Json.Formatting.Indented); -#endif - - string maxAge = requestedPage.contentType == "application/json" ? "0" : "21600"; - var headers = new HttpResponseHead() - { - Status = "200 OK", - Headers = new Dictionary() - { - { "Content-Type", requestedPage.contentType }, - { "Content-Length", binaryContent ? requestedPage.BinaryContent.Length.ToString() : requestedPage.content.ToString().Length.ToString() }, - { "Access-Control-Allow-Origin", "*" }, - { "Cache-Control", $"public,max-age={maxAge}"} - } - }; - - foreach (var key in requestedPage.additionalHeaders.Keys) - headers.Headers.Add(key, requestedPage.additionalHeaders[key]); - if (!binaryContent) - response.OnResponse(headers, new BufferedProducer((string)requestedPage.content)); - else - response.OnResponse(headers, new BufferedProducer(requestedPage.BinaryContent)); - } - - catch (Exception e) - { - if (e.GetType() == typeof(FormatException)) - { - ApplicationManager.GetInstance().Logger.WriteWarning("Request parameter data format was incorrect"); - ApplicationManager.GetInstance().Logger.WriteDebug($"Request Path {request.Path}"); - ApplicationManager.GetInstance().Logger.WriteDebug($"Request Query String {request.QueryString}"); - response.OnResponse(new HttpResponseHead() - { - Status = "400 Bad Request", - Headers = new Dictionary() - { - { "Content-Type", "text/html" }, - { "Content-Length", "0"}, - } - }, new BufferedProducer("")); - } - - else - { - ApplicationManager.GetInstance().Logger.WriteError($"Webfront error during request"); - ApplicationManager.GetInstance().Logger.WriteDebug($"Message: {e.Message}"); - ApplicationManager.GetInstance().Logger.WriteDebug($"Stack Trace: {e.StackTrace}"); - - response.OnResponse(new HttpResponseHead() - { - Status = "500 Internal Server Error", - Headers = new Dictionary() - { - { "Content-Type", "text/html" }, - { "Content-Length", "0"}, - } - }, new BufferedProducer("")); - } - } - } - } - - class BufferedProducer : IDataProducer - { - ArraySegment data; - - public BufferedProducer(string data) : this(data, Encoding.ASCII) { } - public BufferedProducer(string data, Encoding encoding) : this(encoding.GetBytes(data)) { } - public BufferedProducer(byte[] data) : this(new ArraySegment(data)) { } - - public BufferedProducer(ArraySegment data) - { - this.data = data; - } - - public IDisposable Connect(IDataConsumer channel) - { - try - { - channel?.OnData(data, null); - channel?.OnEnd(); - } - - catch (Exception) - { - - } - - return null; - } - } - - class BufferedConsumer : IDataConsumer - { - List> buffer = new List>(); - Action resultCallback; - Action errorCallback; - - public BufferedConsumer(Action resultCallback, Action errorCallback) - { - this.resultCallback = resultCallback; - this.errorCallback = errorCallback; - } - - public bool OnData(ArraySegment data, Action continuation) - { - // this should hopefully clean the non ascii characters out. - buffer?.Add(new ArraySegment(Encoding.ASCII.GetBytes(Encoding.ASCII.GetString(data.ToArray())))); - return false; - } - - public void OnError(Exception error) - { - // errorCallback?.Invoke(error); - } - - public void OnEnd() - { - var str = buffer - .Select(b => Encoding.ASCII.GetString(b.Array, b.Offset, b.Count)) - .Aggregate((result, next) => result + next); - - resultCallback(str); - } - } -} \ No newline at end of file diff --git a/WebfrontCore/Application/Main.cs b/WebfrontCore/Application/Main.cs index 3f20d3740..447fd7fd2 100644 --- a/WebfrontCore/Application/Main.cs +++ b/WebfrontCore/Application/Main.cs @@ -6,11 +6,7 @@ using System.Threading.Tasks; using System.IO; using SharedLibrary.Objects; using System.Reflection; - -#if DEBUG -using SharedLibrary.Database; -#endif - +using System.Linq; namespace IW4MAdmin { @@ -20,7 +16,7 @@ namespace IW4MAdmin public static extern bool AllocConsole(); static public double Version { get; private set; } static public ApplicationManager ServerManager = ApplicationManager.GetInstance(); - public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; + public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; public static void Start() { @@ -29,7 +25,6 @@ namespace IW4MAdmin Version = 1.6; - //double.TryParse(CheckUpdate(), out double latestVersion); Console.WriteLine("====================================================="); Console.WriteLine(" IW4M ADMIN"); Console.WriteLine(" by RaidMax "); @@ -40,13 +35,16 @@ namespace IW4MAdmin { CheckDirectories(); - ServerManager = ApplicationManager.GetInstance(); - ServerManager.Init(); - + Task.Run(async () => + { + ServerManager = ApplicationManager.GetInstance(); + SharedLibrary.Database.Repair.Run(ServerManager.Logger); + await ServerManager.Init(); + ServerManager.Start(); + }); Task.Run(() => { - ServerManager.Start(); String userInput; Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer(); @@ -66,6 +64,8 @@ namespace IW4MAdmin Console.Write('>'); } while (ServerManager.Running); + + Console.WriteLine("Shutdown complete"); }); } diff --git a/WebfrontCore/Application/Manager.cs b/WebfrontCore/Application/Manager.cs index 1c38592e8..c9d9a970a 100644 --- a/WebfrontCore/Application/Manager.cs +++ b/WebfrontCore/Application/Manager.cs @@ -11,8 +11,6 @@ using SharedLibrary.Commands; using SharedLibrary.Helpers; using SharedLibrary.Exceptions; using SharedLibrary.Objects; -using SharedLibrary.Database; -using SharedLibrary.Database.Models; using SharedLibrary.Services; namespace IW4MAdmin @@ -21,6 +19,7 @@ namespace IW4MAdmin { private List _servers; public List Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); + public List AdministratorIPs { get; set; } public ILogger Logger { get; private set; } public bool Running { get; private set; } @@ -34,7 +33,7 @@ namespace IW4MAdmin #if FTP_LOG const int UPDATE_FREQUENCY = 700; #else - const int UPDATE_FREQUENCY = 300; + const int UPDATE_FREQUENCY = 750; #endif private ApplicationManager() @@ -47,6 +46,7 @@ namespace IW4MAdmin ClientSvc = new ClientService(); AliasSvc = new AliasService(); PenaltySvc = new PenaltyService(); + AdministratorIPs = new List(); } public IList GetServers() @@ -64,12 +64,12 @@ namespace IW4MAdmin return Instance ?? (Instance = new ApplicationManager()); } - public void Init() + public async Task Init() { - #region WEBSERVICE - // SharedLibrary.WebService.Init(); - //WebSvc = new WebService(); - //WebSvc.StartScheduler(); + #region DATABASE + AdministratorIPs = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) + .Select(c => c.IPAddress) + .ToList(); #endregion #region PLUGINS @@ -79,7 +79,7 @@ namespace IW4MAdmin { try { - Plugin.OnLoadAsync(this); + await Plugin.OnLoadAsync(this); } catch (Exception e) @@ -174,6 +174,7 @@ namespace IW4MAdmin Commands.Add(new CIP()); Commands.Add(new CMask()); Commands.Add(new CPruneAdmins()); + Commands.Add(new CRestartServer()); foreach (Command C in SharedLibrary.Plugins.PluginImporter.ActiveCommands) Commands.Add(C); diff --git a/WebfrontCore/Application/Server.cs b/WebfrontCore/Application/Server.cs index 58cbc0790..759c8a50a 100644 --- a/WebfrontCore/Application/Server.cs +++ b/WebfrontCore/Application/Server.cs @@ -10,6 +10,8 @@ using SharedLibrary.Network; using SharedLibrary.Interfaces; using SharedLibrary.Objects; using System.Text.RegularExpressions; +using SharedLibrary.Services; +using SharedLibrary.Database.Models; namespace IW4MAdmin { @@ -99,7 +101,7 @@ namespace IW4MAdmin await Manager.GetClientService().Update(client); } - else if (existingAlias.Name == polledPlayer.Name) + else if (existingAlias.Name == polledPlayer.Name) { client.CurrentAlias = existingAlias; client.CurrentAliasId = existingAlias.AliasId; @@ -154,12 +156,13 @@ namespace IW4MAdmin if (cNum >= 0 && Players[cNum] != null) { Player Leaving = Players[cNum]; + Logger.WriteInfo($"Client {Leaving} disconnecting..."); + + await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this)); + Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds; Leaving.LastConnection = DateTime.UtcNow; await Manager.GetClientService().Update(Leaving); - - Logger.WriteInfo($"Client {Leaving} disconnecting..."); - await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this)); Players[cNum] = null; } } @@ -357,7 +360,11 @@ namespace IW4MAdmin async Task PollPlayersAsync() { + var now = DateTime.Now; var CurrentPlayers = await this.GetStatusAsync(); +#if DEBUG + Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms"); +#endif for (int i = 0; i < Players.Count; i++) { @@ -385,9 +392,9 @@ namespace IW4MAdmin override public async Task ProcessUpdatesAsync(CancellationToken cts) { this.cts = cts; -#if DEBUG == false + //#if DEBUG == false try -#endif + //#endif { // first start if (firstRun) @@ -504,7 +511,7 @@ namespace IW4MAdmin } return true; } -#if DEBUG == false + //#if !DEBUG catch (SharedLibrary.Exceptions.NetworkException) { Logger.WriteError($"Could not communicate with {IP}:{Port}"); @@ -518,7 +525,7 @@ namespace IW4MAdmin Logger.WriteDebug("Error Trace: " + E.StackTrace); return false; } -#endif + //#endif } public async Task Initialize() @@ -537,6 +544,7 @@ namespace IW4MAdmin await this.GetDvarAsync("sv_maxclients"); var gametype = await this.GetDvarAsync("g_gametype"); var basepath = await this.GetDvarAsync("fs_basepath"); + WorkingDirectory = basepath.Value; var game = await this.GetDvarAsync("fs_game"); var logfile = await this.GetDvarAsync("g_log"); var logsync = await this.GetDvarAsync("g_logsync"); @@ -705,6 +713,11 @@ namespace IW4MAdmin string mapname = this.GetDvarAsync("mapname").Result.Value; CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map(mapname, mapname); + + // todo: make this more efficient + ((ApplicationManager)(Manager)).AdministratorIPs = (await new GenericRepository().FindAsync(c => c.Level > Player.Permission.Trusted)) + .Select(c => c.IPAddress) + .ToList(); } if (E.Type == Event.GType.MapEnd) diff --git a/WebfrontCore/Controllers/BaseController.cs b/WebfrontCore/Controllers/BaseController.cs new file mode 100644 index 000000000..98e794897 --- /dev/null +++ b/WebfrontCore/Controllers/BaseController.cs @@ -0,0 +1,25 @@ +using IW4MAdmin; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using SharedLibrary; +using SharedLibrary.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebfrontCore.Controllers +{ + public class BaseController : Controller + { + protected ApplicationManager Manager; + protected bool Authorized { get; private set; } + + public override void OnActionExecuting(ActionExecutingContext context) + { + Manager = IW4MAdmin.Program.ServerManager; + Authorized = Manager.AdministratorIPs.Contains(context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()); + base.OnActionExecuting(context); + } + } +} diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index 28a8d65be..f580066cf 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -9,11 +9,11 @@ using System.Threading.Tasks; namespace WebfrontCore.Controllers { - public class ClientController : Controller + public class ClientController : BaseController { public async Task ProfileAsync(int id) { - var client = await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().Get(id); + var client = await Manager.GetClientService().Get(id); var clientDto = new PlayerInfo() { Name = client.Name, @@ -38,11 +38,15 @@ namespace WebfrontCore.Controllers .OrderBy(i => i) .ToList(), }; - - clientDto.Meta.AddRange(await MetaService.GetMeta(client.ClientId)); - clientDto.Meta.AddRange(await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(client.ClientId)); - clientDto.Meta.AddRange(await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(client.ClientId, false)); - clientDto.Meta = clientDto.Meta.OrderByDescending(m => m.When).ToList(); + var meta = await MetaService.GetMeta(client.ClientId); + clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive)); + clientDto.Meta.AddRange(await Manager.GetPenaltyService() + .ReadGetClientPenaltiesAsync(client.ClientId)); + clientDto.Meta.AddRange(await Manager.GetPenaltyService() + .ReadGetClientPenaltiesAsync(client.ClientId, false)); + clientDto.Meta = clientDto.Meta + .OrderByDescending(m => m.When) + .ToList(); ViewBag.Title = clientDto.Name; @@ -51,7 +55,7 @@ namespace WebfrontCore.Controllers public async Task PrivilegedAsync() { - var admins = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetPrivilegedClients()) + var admins = (await Manager.GetClientService().GetPrivilegedClients()) .Where(a => a.Active) .OrderByDescending(a => a.Level); var adminsDict = new Dictionary>(); @@ -74,7 +78,7 @@ namespace WebfrontCore.Controllers public async Task FindAsync(string clientName) { - var clients = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetClientByName(clientName)) + var clients = (await Manager.GetClientService().GetClientByName(clientName)) .OrderByDescending(c => c.LastConnection); var clientsDto = clients.Select(c => new PlayerInfo() { diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index c6b3c42d4..ecad32372 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -9,11 +9,11 @@ using System.Threading.Tasks; namespace WebfrontCore.Controllers { - public class ConsoleController : Controller + public class ConsoleController : BaseController { public IActionResult Index() { - var activeServers = IW4MAdmin.ApplicationManager.GetInstance().Servers.Select(s => new ServerInfo() + var activeServers = Manager.Servers.Select(s => new ServerInfo() { Name = s.Hostname, ID = s.GetHashCode(), @@ -38,10 +38,10 @@ namespace WebfrontCore.Controllers IPAddress = intIP }; #else - var origin = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetUnique(0)).AsPlayer(); + var origin = (await Manager.GetClientService().GetUnique(0)).AsPlayer(); #endif - var server = IW4MAdmin.ApplicationManager.GetInstance().Servers.First(s => s.GetHashCode() == serverId); + var server = Manager.Servers.First(s => s.GetHashCode() == serverId); origin.CurrentServer = server; var remoteEvent = new Event(Event.GType.Say, command, origin, null, server); diff --git a/WebfrontCore/Controllers/HomeController.cs b/WebfrontCore/Controllers/HomeController.cs index b0d5d4416..a7242becc 100644 --- a/WebfrontCore/Controllers/HomeController.cs +++ b/WebfrontCore/Controllers/HomeController.cs @@ -8,7 +8,7 @@ using SharedLibrary.Dtos; namespace WebfrontCore.Controllers { - public class HomeController : Controller + public class HomeController : BaseController { public IActionResult Index() { diff --git a/WebfrontCore/Controllers/PenaltyController.cs b/WebfrontCore/Controllers/PenaltyController.cs index 0a02f8e8c..b8959aa3f 100644 --- a/WebfrontCore/Controllers/PenaltyController.cs +++ b/WebfrontCore/Controllers/PenaltyController.cs @@ -9,9 +9,9 @@ using WebfrontCore.ViewComponents; namespace WebfrontCore.Controllers { - public class PenaltyController : Controller + public class PenaltyController : BaseController { - public IActionResult List() + public IActionResult List() { ViewBag.Title = "Penalty List"; return View(); diff --git a/WebfrontCore/Controllers/ServerController.cs b/WebfrontCore/Controllers/ServerController.cs index d21674445..645453402 100644 --- a/WebfrontCore/Controllers/ServerController.cs +++ b/WebfrontCore/Controllers/ServerController.cs @@ -7,15 +7,14 @@ using System.Threading.Tasks; namespace WebfrontCore.Controllers { - public class ServerController : Controller + public class ServerController : BaseController { - [HttpGet] [ResponseCache(NoStore = true, Duration = 0)] public IActionResult ClientActivity(int id) { - var s = IW4MAdmin.Program.ServerManager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id); + var s = Manager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id); if (s == null) return View("Error", "Invalid server!"); diff --git a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs index 9cbacace5..4c38c1b12 100644 --- a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs +++ b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs @@ -12,6 +12,12 @@ namespace WebfrontCore.ViewComponents { public async Task InvokeAsync(int offset) { + int ip = HttpContext.Connection.RemoteIpAddress + .ToString().ConvertToIP(); + + bool authed = IW4MAdmin.ApplicationManager.GetInstance() + .AdministratorIPs.Contains(ip); + var penalties = await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().GetRecentPenalties(15, offset); var penaltiesDto = penalties.Select(p => new PenaltyInfo() { @@ -23,8 +29,11 @@ namespace WebfrontCore.ViewComponents Offense = p.Offense, Type = p.Type.ToString(), TimePunished = Utilities.GetTimePassed(p.When, false), - TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow) - }).ToList(); + TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow), + Sensitive = p.Type == SharedLibrary.Objects.Penalty.PenaltyType.Flag + }); + + penaltiesDto = authed ? penaltiesDto.ToList() : penaltiesDto.Where(p => !p.Sensitive).ToList(); return View("_List", penaltiesDto); } diff --git a/WebfrontCore/Views/Client/Profile/Index.cshtml b/WebfrontCore/Views/Client/Profile/Index.cshtml index bd5ae00f3..44e1b5dbd 100644 --- a/WebfrontCore/Views/Client/Profile/Index.cshtml +++ b/WebfrontCore/Views/Client/Profile/Index.cshtml @@ -8,7 +8,10 @@
-

@Model.Name

+ @{ + string displayAliasButton = Model.Aliases.Count > 0 ? "" : "display: none;"; + } +

@Model.Name

@{ foreach (string alias in Model.Aliases) diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index f4a2cc8c3..831006c98 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -8,19 +8,22 @@ WebfrontCore AnyCPU;x86 wwwroot\favicon.ico + false x86 + + bin\x86\Debug\ + + - - PreserveNewest @@ -60,12 +63,6 @@ - - - Never - - - diff --git a/WebfrontCore/app.config b/WebfrontCore/app.config index e8b7b597e..6a931919a 100644 --- a/WebfrontCore/app.config +++ b/WebfrontCore/app.config @@ -31,6 +31,6 @@ + connectionString="Data Source=|DataDirectory|\Database.sdf"/> diff --git a/WebfrontCore/appsettings.json b/WebfrontCore/appsettings.json index fa8ce71a9..44ed6f6ef 100644 --- a/WebfrontCore/appsettings.json +++ b/WebfrontCore/appsettings.json @@ -2,9 +2,9 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Debug", + "Default": "Trace", "System": "Information", - "Microsoft": "Information" + "Microsoft": "None" } } }