Compare commits

..

10 Commits

Author SHA1 Message Date
305817d00c version 2.2 stable 2018-10-12 21:28:22 -05:00
65cf3566db fix publish for release
fix bug with localization issue crashing app if config is wrong
2018-10-12 18:59:17 -05:00
e91d076b41 curtail lost connection messages from RCon
verify still compatible with T6
fix potential null reference exception during configuration reading/setup
2018-10-10 19:22:08 -05:00
b5e9519f0c update shared guid kick plugin
script plugins reload without error using the correct file share mode when opening
increase socket timeout to 10 seconds
2018-10-09 20:19:06 -05:00
7caee55a7e refactored the welcome plugin to use a web api instead of a hard coded file
removed deprecated file class
don't wait for response when setting dvar/sending command in T6
potential fix for duplicate kick message in JS plugin
2018-10-08 21:15:59 -05:00
b289917319 update project to .net core 2.1.5
got rid of "threadsafe" stats service in stats plugin
2018-10-07 21:34:30 -05:00
c8366a22e5 fixed the vpn detection plugin method signature call
added some fixes for stats/ac
2018-10-06 15:31:05 -05:00
de902a58ac write individual server log files and main log file seperately
log writing is thread safe now
2018-10-06 11:47:14 -05:00
c7547f1ad5 clean up publish folder output to have a less cluttered structure.
add migration class to perform the migration on update
2018-10-05 22:10:39 -05:00
9d946d1bad more stability changes 2018-10-03 21:20:49 -05:00
59 changed files with 814 additions and 884 deletions

1
.gitignore vendored
View File

@ -229,3 +229,4 @@ bootstrap-custom.min.css
/DiscordWebhook/env /DiscordWebhook/env
/DiscordWebhook/config.dev.json /DiscordWebhook/config.dev.json
/GameLogServer/env /GameLogServer/env
launchSettings.json

View File

@ -3,9 +3,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish> <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId> <PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.1.9.2</Version> <Version>2.2</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>
@ -30,6 +31,8 @@
<PropertyGroup> <PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection> <ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation> <TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -76,7 +79,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
@ -91,6 +94,6 @@
</Target> </Target>
<Target Name="PostPublish" AfterTargets="Publish"> <Target Name="PostPublish" AfterTargets="Publish">
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir)" /> <Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(ConfigurationName)" />
</Target> </Target>
</Project> </Project>

View File

@ -1,6 +1,8 @@
set SolutionDir=%1 set SolutionDir=%1
set ProjectDir=%2 set ProjectDir=%2
set TargetDir=%3 set TargetDir=%3
set CurrentConfiguration=%4
SET COPYCMD=/Y
echo Deleting extra language files echo Deleting extra language files
@ -54,9 +56,52 @@ del "%SolutionDir%Publish\Windows\*pdb"
if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config" if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config"
del "%SolutionDir%Publish\WindowsPrerelease\*pdb" del "%SolutionDir%Publish\WindowsPrerelease\*pdb"
echo making start scripts echo setting up library folders
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh" if "%CurrentConfiguration%" == "Prerelease" (
@(echo #!/bin/bash && echo dotnet IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh" echo PR-Config
if not exist "%SolutionDir%Publish\WindowsPrerelease\Configuration" md "%SolutionDir%Publish\WindowsPrerelease\Configuration"
move "%SolutionDir%Publish\WindowsPrerelease\DefaultSettings.json" "%SolutionDir%Publish\WindowsPrerelease\Configuration\"
)
if "%CurrentConfiguration%" == "Release" (
echo R-Config
if not exist "%SolutionDir%Publish\Windows\Configuration" md "%SolutionDir%Publish\Windows\Configuration"
if exist "%SolutionDir%Publish\Windows\DefaultSettings.json" move "%SolutionDir%Publish\Windows\DefaultSettings.json" "%SolutionDir%Publish\Windows\Configuration\DefaultSettings.json"
)
if "%CurrentConfiguration%" == "Prerelease" (
echo PR-LIB
if not exist "%SolutionDir%Publish\WindowsPrerelease\Lib\" md "%SolutionDir%Publish\WindowsPrerelease\Lib\"
move "%SolutionDir%Publish\WindowsPrerelease\*.dll" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
move "%SolutionDir%Publish\WindowsPrerelease\*.json" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
)
if "%CurrentConfiguration%" == "Release" (
echo R-LIB
if not exist "%SolutionDir%Publish\Windows\Lib\" md "%SolutionDir%Publish\Windows\Lib\"
move "%SolutionDir%Publish\Windows\*.dll" "%SolutionDir%Publish\Windows\Lib\"
move "%SolutionDir%Publish\Windows\*.json" "%SolutionDir%Publish\Windows\Lib\"
)
if "%CurrentConfiguration%" == "Prerelease" (
echo PR-RT
move "%SolutionDir%Publish\WindowsPrerelease\runtimes" "%SolutionDir%Publish\WindowsPrerelease\Lib\runtimes"
if exist "%SolutionDir%Publish\WindowsPrerelease\refs" move "%SolutionDir%Publish\WindowsPrerelease\refs" "%SolutionDir%Publish\WindowsPrerelease\Lib\refs"
)
if "%CurrentConfiguration%" == "Release" (
echo R-RT
move "%SolutionDir%Publish\Windows\runtimes" "%SolutionDir%Publish\Windows\Lib\runtimes"
if exist "%SolutionDir%Publish\Windows\refs" move "%SolutionDir%Publish\Windows\refs" "%SolutionDir%Publish\Windows\Lib\refs"
)
echo making start scripts
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
eCHO "%CurrentConfiguration%"

View File

@ -67,7 +67,7 @@ namespace IW4MAdmin.Application.Core
{ {
if (AuthenticatedClients.Values.Count > 18) if (AuthenticatedClients.Values.Count > 18)
{ {
Program.ServerManager.GetLogger().WriteWarning($"auth client count is {AuthenticatedClients.Values.Count}, this is bad"); Program.ServerManager.GetLogger(0).WriteError($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
return AuthenticatedClients.Values.Take(18).ToList(); return AuthenticatedClients.Values.Take(18).ToList();
} }

View File

@ -25,11 +25,6 @@
"Name": "mp_rust" "Name": "mp_rust"
}, },
{
"Alias": "Highrise",
"Name": "mp_highrise"
},
{ {
"Alias": "Terminal", "Alias": "Terminal",
"Name": "mp_terminal" "Name": "mp_terminal"
@ -40,16 +35,6 @@
"Name": "mp_crash" "Name": "mp_crash"
}, },
{
"Alias": "Skidrow",
"Name": "mp_nightshift"
},
{
"Alias": "Quarry",
"Name": "mp_quarry"
},
{ {
"Alias": "Afghan", "Alias": "Afghan",
"Name": "mp_afghan" "Name": "mp_afghan"

View File

@ -55,9 +55,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e) catch (Exception e)
{ {
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line"); server.Logger.WriteWarning("Could not properly parse event line");
Program.ServerManager.GetLogger().WriteDebug(e.Message); server.Logger.WriteDebug(e.Message);
Program.ServerManager.GetLogger().WriteDebug(eventLine); server.Logger.WriteDebug(eventLine);
} }
} }
} }

View File

@ -59,9 +59,9 @@ namespace IW4MAdmin.Application.IO
catch (Exception e) catch (Exception e)
{ {
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line"); server.Logger.WriteWarning("Could not properly parse event line");
Program.ServerManager.GetLogger().WriteDebug(e.Message); server.Logger.WriteDebug(e.Message);
Program.ServerManager.GetLogger().WriteDebug(eventLine); server.Logger.WriteDebug(eventLine);
} }
} }
} }

View File

@ -15,7 +15,7 @@ namespace IW4MAdmin.Application.Localization
public static void Initialize(string customLocale) public static void Initialize(string customLocale)
{ {
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale; string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
string[] localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale}.json"); string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
try try
{ {
@ -33,13 +33,13 @@ namespace IW4MAdmin.Application.Localization
// culture doesn't exist so we just want language // culture doesn't exist so we just want language
if (localizationFiles.Length == 0) if (localizationFiles.Length == 0)
{ {
localizationFiles = Directory.GetFiles("Localization", $"*.{currentLocale.Substring(0, 2)}*.json"); localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale.Substring(0, 2)}*.json");
} }
// language doesn't exist either so defaulting to english // language doesn't exist either so defaulting to english
if (localizationFiles.Length == 0) if (localizationFiles.Length == 0)
{ {
localizationFiles = Directory.GetFiles("Localization", "*.en-US.json"); localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), "*.en-US.json");
} }
// this should never happen unless the localization folder is empty // this should never happen unless the localization folder is empty
@ -59,12 +59,12 @@ namespace IW4MAdmin.Application.Localization
{ {
if (!localizationDict.TryAdd(item.Key, item.Value)) if (!localizationDict.TryAdd(item.Key, item.Value))
{ {
Program.ServerManager.GetLogger().WriteError($"Could not add locale string {item.Key} to localization"); Program.ServerManager.GetLogger(0).WriteError($"Could not add locale string {item.Key} to localization");
} }
} }
} }
string localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json"; string localizationFile = $"{Path.Join(Utilities.OperatingDirectory, "Localization")}{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict) Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
{ {

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -18,18 +19,44 @@ namespace IW4MAdmin.Application
} }
readonly string FileName; readonly string FileName;
readonly object ThreadLock; readonly SemaphoreSlim OnLogWriting;
static readonly short MAX_LOG_FILES = 10;
public Logger(string fn) public Logger(string fn)
{ {
FileName = fn; FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
ThreadLock = new object(); OnLogWriting = new SemaphoreSlim(1, 1);
if (File.Exists(fn)) RotateLogs();
File.Delete(fn); }
/// <summary>
/// rotates logs when log is initialized
/// </summary>
private void RotateLogs()
{
string maxLog = FileName + MAX_LOG_FILES;
if (File.Exists(maxLog))
{
File.Delete(maxLog);
}
for (int i = MAX_LOG_FILES - 1; i >= 0; i--)
{
string logToMove = i == 0 ? FileName : FileName + i;
string movedLogName = FileName + (i + 1);
if (File.Exists(logToMove))
{
File.Move(logToMove, movedLogName);
}
}
} }
void Write(string msg, LogType type) void Write(string msg, LogType type)
{ {
OnLogWriting.Wait();
string stringType = type.ToString(); string stringType = type.ToString();
try try
@ -40,11 +67,10 @@ namespace IW4MAdmin.Application
catch (Exception) { } catch (Exception) { }
string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}"; string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}";
lock (ThreadLock) try
{ {
#if DEBUG #if DEBUG
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively) // lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
Console.WriteLine(LogLine); Console.WriteLine(LogLine);
File.AppendAllText(FileName, LogLine + Environment.NewLine); File.AppendAllText(FileName, LogLine + Environment.NewLine);
#else #else
@ -54,6 +80,14 @@ namespace IW4MAdmin.Application
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}"); File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
#endif #endif
} }
catch (Exception ex)
{
Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else...");
Console.WriteLine(ex.GetExceptionInfo());
}
OnLogWriting.Release(1);
} }
public void WriteVerbose(string msg) public void WriteVerbose(string msg)

View File

@ -10,19 +10,19 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Collections.Generic; using System.Collections.Generic;
using SharedLibraryCore.Localization; using SharedLibraryCore.Localization;
using IW4MAdmin.Application.Migration;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
public class Program public class Program
{ {
static public double Version { get; private set; } static public double Version { get; private set; }
static public ApplicationManager ServerManager = ApplicationManager.GetInstance(); static public ApplicationManager ServerManager;
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim(); private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim();
public static void Main(string[] args) public static void Main(string[] args)
{ {
AppDomain.CurrentDomain.SetData("DataDirectory", OperatingDirectory); AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
@ -39,12 +39,17 @@ namespace IW4MAdmin.Application
try try
{ {
loc = Utilities.CurrentLocalization.LocalizationIndex;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
CheckDirectories(); CheckDirectories();
// do any needed migrations
// todo: move out
ConfigurationMigration.MoveConfigFolder10518(null);
ServerManager = ApplicationManager.GetInstance(); ServerManager = ApplicationManager.GetInstance();
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale); Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
loc = Utilities.CurrentLocalization.LocalizationIndex;
ServerManager.Logger.WriteInfo($"Version is {Version}"); ServerManager.Logger.WriteInfo($"Version is {Version}");
@ -107,7 +112,7 @@ namespace IW4MAdmin.Application
var consoleTask = Task.Run(async () => var consoleTask = Task.Run(async () =>
{ {
String userInput; String userInput;
Player Origin = Utilities.IW4MAdminClient; Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
do do
{ {
@ -124,7 +129,6 @@ namespace IW4MAdmin.Application
if (userInput?.Length > 0) if (userInput?.Length > 0)
{ {
Origin.CurrentServer = ServerManager.Servers[0];
GameEvent E = new GameEvent() GameEvent E = new GameEvent()
{ {
Type = GameEvent.EventType.Command, Type = GameEvent.EventType.Command,
@ -149,7 +153,7 @@ namespace IW4MAdmin.Application
{ {
e = e.InnerException; e = e.InnerException;
} }
Console.WriteLine($"Exception: {e.Message}"); Console.WriteLine(e.Message);
Console.WriteLine(loc["MANAGER_EXIT"]); Console.WriteLine(loc["MANAGER_EXIT"]);
Console.ReadKey(); Console.ReadKey();
return; return;
@ -169,15 +173,25 @@ namespace IW4MAdmin.Application
private static void OnCancelKey(object sender, ConsoleCancelEventArgs e) private static void OnCancelKey(object sender, ConsoleCancelEventArgs e)
{ {
ServerManager.Stop(); ServerManager.Stop();
OnShutdownComplete.Wait(5000); OnShutdownComplete.Wait();
} }
static void CheckDirectories() static void CheckDirectories()
{ {
string curDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins")))
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins"));
}
if (!Directory.Exists($"{curDirectory}Plugins")) if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database")))
Directory.CreateDirectory($"{curDirectory}Plugins"); {
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database"));
}
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log")))
{
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log"));
}
} }
} }
} }

View File

@ -18,6 +18,7 @@ using SharedLibraryCore.Database;
using SharedLibraryCore.Events; using SharedLibraryCore.Events;
using IW4MAdmin.Application.API.Master; using IW4MAdmin.Application.API.Master;
using IW4MAdmin.Application.Migration;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -26,7 +27,7 @@ namespace IW4MAdmin.Application
private List<Server> _servers; private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public Dictionary<int, Player> PrivilegedClients { get; set; } public Dictionary<int, Player> PrivilegedClients { get; set; }
public ILogger Logger { get; private set; } public ILogger Logger => GetLogger(0);
public bool Running { get; private set; } public bool Running { get; private set; }
public bool IsInitialized { get; private set; } public bool IsInitialized { get; private set; }
// define what the delagate function looks like // define what the delagate function looks like
@ -48,10 +49,10 @@ namespace IW4MAdmin.Application
ManualResetEventSlim OnQuit; ManualResetEventSlim OnQuit;
readonly IPageList PageList; readonly IPageList PageList;
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1); readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<int, ILogger> Loggers = new Dictionary<int, ILogger>();
private ApplicationManager() private ApplicationManager()
{ {
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
_servers = new List<Server>(); _servers = new List<Server>();
Commands = new List<Command>(); Commands = new List<Command>();
TaskStatuses = new List<AsyncStatus>(); TaskStatuses = new List<AsyncStatus>();
@ -379,11 +380,10 @@ namespace IW4MAdmin.Application
await Plugin.OnLoadAsync(this); await Plugin.OnLoadAsync(this);
} }
catch (Exception e) catch (Exception ex)
{ {
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}"); Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
Logger.WriteDebug($"Exception: {e.Message}"); Logger.WriteDebug(ex.GetExceptionInfo());
Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
} }
} }
#endregion #endregion
@ -565,9 +565,29 @@ namespace IW4MAdmin.Application
Running = false; Running = false;
} }
public ILogger GetLogger() public ILogger GetLogger(int serverId)
{ {
return Logger; if (Loggers.ContainsKey(serverId))
{
return Loggers[serverId];
}
else
{
Logger newLogger;
if (serverId == 0)
{
newLogger = new Logger("IW4MAdmin-Manager");
}
else
{
newLogger = new Logger($"IW4MAdmin-Server-{serverId}");
}
Loggers.Add(serverId, newLogger);
return newLogger;
}
} }
public IList<MessageToken> GetMessageTokens() public IList<MessageToken> GetMessageTokens()

View File

@ -0,0 +1,60 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.Migration
{
/// <summary>
/// helps facilitate the migration of configs from one version and or location
/// to another
/// </summary>
class ConfigurationMigration
{
/// <summary>
/// moves existing configs from the root folder into a configs folder
/// </summary>
public static void MoveConfigFolder10518(ILogger log)
{
string currentDirectory = Utilities.OperatingDirectory;
// we don't want to do this for migrations or tests where the
// property isn't initialized or it's wrong
if (currentDirectory != null)
{
string configDirectory = Path.Join(currentDirectory, "Configuration");
if (!Directory.Exists(configDirectory))
{
log?.WriteDebug($"Creating directory for configs {configDirectory}");
Directory.CreateDirectory(configDirectory);
}
var configurationFiles = Directory.EnumerateFiles(currentDirectory, "*.json")
.Select(f => f.Split(Path.DirectorySeparatorChar).Last())
.Where(f => f.Count(c => c == '.') == 1);
foreach (var configFile in configurationFiles)
{
log?.WriteDebug($"Moving config file {configFile}");
string destinationPath = Path.Join("Configuration", configFile);
if (!File.Exists(destinationPath))
{
File.Move(configFile, destinationPath);
}
}
if (!File.Exists(Path.Join("Database", "Database.db")) &&
File.Exists("Database.db"))
{
log?.WriteDebug("Moving database file");
File.Move("Database.db", Path.Join("Database", "Database.db"));
}
}
}
}
}

View File

@ -9,8 +9,6 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon; using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using System.Text; using System.Text;
using System.Linq;
using System.Net.Http;
namespace IW4MAdmin.Application.RconParsers namespace IW4MAdmin.Application.RconParsers
{ {

View File

@ -32,6 +32,8 @@ namespace IW4MAdmin
} }
public override int GetHashCode() public override int GetHashCode()
{
if (GameName == Game.IW4)
{ {
// todo: make this better with collisions // todo: make this better with collisions
int id = Math.Abs($"{IP}:{Port.ToString()}".Select(a => (int)a).Sum()); int id = Math.Abs($"{IP}:{Port.ToString()}".Select(a => (int)a).Sum());
@ -50,6 +52,13 @@ namespace IW4MAdmin
return id; return id;
} }
else
{
int id = HashCode.Combine(IP, Port);
return id < 0 ? Math.Abs(id) : id;
}
}
public async Task OnPlayerJoined(Player logClient) public async Task OnPlayerJoined(Player logClient)
{ {
var existingClient = Players[logClient.ClientNumber]; var existingClient = Players[logClient.ClientNumber];
@ -201,12 +210,11 @@ namespace IW4MAdmin
{ {
player.Level = Player.Permission.User; player.Level = Player.Permission.User;
} }
#if DEBUG == false
if (currentBan != null) if (currentBan != null)
{ {
Logger.WriteInfo($"Banned client {player} trying to connect..."); Logger.WriteInfo($"Banned client {player} trying to connect...");
var autoKickClient = Utilities.IW4MAdminClient; var autoKickClient = Utilities.IW4MAdminClient(this);
autoKickClient.CurrentServer = this;
// the player is permanently banned // the player is permanently banned
if (currentBan.Type == Penalty.PenaltyType.Ban) if (currentBan.Type == Penalty.PenaltyType.Ban)
@ -243,7 +251,6 @@ namespace IW4MAdmin
Players[player.ClientNumber] = null; Players[player.ClientNumber] = null;
return false; return false;
} }
#endif
player.State = Player.ClientState.Connected; player.State = Player.ClientState.Connected;
return true; return true;
@ -251,9 +258,9 @@ namespace IW4MAdmin
catch (Exception ex) catch (Exception ex)
{ {
Manager.GetLogger().WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}"); Logger.WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}");
Manager.GetLogger().WriteDebug(ex.Message); Logger.WriteDebug(ex.Message);
Manager.GetLogger().WriteDebug(ex.StackTrace); Logger.WriteDebug(ex.StackTrace);
return false; return false;
} }
} }
@ -335,7 +342,7 @@ namespace IW4MAdmin
(canExecuteCommand || (canExecuteCommand ||
E.Origin?.Level == Player.Permission.Console)) E.Origin?.Level == Player.Permission.Console))
{ {
var _ = (((Command)E.Extra).ExecuteAsync(E)); await (((Command)E.Extra).ExecuteAsync(E));
} }
} }
@ -432,6 +439,11 @@ namespace IW4MAdmin
await Kick(E.Data, E.Target, E.Origin); await Kick(E.Data, E.Target, E.Origin);
} }
else if (E.Type == GameEvent.EventType.Warn)
{
await Warn(E.Data, E.Target, E.Origin);
}
else if (E.Type == GameEvent.EventType.Quit) else if (E.Type == GameEvent.EventType.Quit)
{ {
var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId); var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId);
@ -871,7 +883,7 @@ namespace IW4MAdmin
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}"); Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG #if !DEBUG
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {logPath}"); throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {logPath}");
//#else #else
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value); LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
#endif #endif
} }
@ -888,7 +900,7 @@ namespace IW4MAdmin
#endif #endif
} }
public override async Task Warn(String Reason, Player Target, Player Origin) protected override async Task Warn(String Reason, Player Target, Player Origin)
{ {
// ensure player gets warned if command not performed on them in game // ensure player gets warned if command not performed on them in game
if (Target.ClientNumber < 0) if (Target.ClientNumber < 0)
@ -907,7 +919,7 @@ namespace IW4MAdmin
{ {
if (Target.Warnings >= 4) if (Target.Warnings >= 4)
{ {
Target.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient); Target.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient(this));
return; return;
} }
@ -963,7 +975,6 @@ namespace IW4MAdmin
Offender = Target, Offender = Target,
Offense = Reason, Offense = Reason,
Punisher = Origin, Punisher = Origin,
Active = true,
When = DateTime.UtcNow, When = DateTime.UtcNow,
Link = Target.AliasLink Link = Target.AliasLink
}; };

View File

@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
_customcallbacks.gsc = _customcallbacks.gsc _customcallbacks.gsc = _customcallbacks.gsc
README.md = README.md README.md = README.md
RunPublishPre.cmd = RunPublishPre.cmd RunPublishPre.cmd = RunPublishPre.cmd
RunPublishRelease.cmd = RunPublishRelease.cmd
version.txt = version.txt version.txt = version.txt
EndProjectSection EndProjectSection
EndProject EndProject

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
</PropertyGroup> </PropertyGroup>
@ -17,7 +18,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
@ -21,7 +22,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -25,7 +25,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
public Task OnEventAsync(GameEvent E, Server S) public Task OnEventAsync(GameEvent E, Server S)
{ {
if (!Settings.Configuration().EnableProfanityDeterment) if (!Settings.Configuration().EnableProfanityDeterment)
return Task.CompletedTask; ; return Task.CompletedTask;
if (E.Type == GameEvent.EventType.Connect) if (E.Type == GameEvent.EventType.Connect)
{ {
@ -50,7 +50,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{ {
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player() E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
{ {
ClientId = 1 ClientId = 1,
CurrentServer = E.Owner
}); });
}; };
} }
@ -86,7 +87,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{ {
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player() clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
{ {
ClientId = 1 ClientId = 1,
CurrentServer = E.Owner
}); });
} }
@ -96,7 +98,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player() clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player()
{ {
ClientId = 1 ClientId = 1,
CurrentServer = E.Owner
}); });
} }
} }

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
@ -19,7 +20,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -1,6 +1,6 @@
var plugin = { var plugin = {
author: 'RaidMax', author: 'RaidMax',
version: 1.0, version: 1.1,
name: 'Shared GUID Kicker Plugin', name: 'Shared GUID Kicker Plugin',
onEventAsync: function (gameEvent, server) { onEventAsync: function (gameEvent, server) {
@ -10,8 +10,7 @@ var plugin = {
} }
// connect or join event // connect or join event
if (gameEvent.Type === 3 || if (gameEvent.Type === 3) {
gameEvent.Type === 4) {
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID // this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
if (gameEvent.Origin.NetworkId === -805366929435212061) { if (gameEvent.Origin.NetworkId === -805366929435212061) {
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient); gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);

View File

@ -28,7 +28,6 @@ var plugin = {
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result; var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
var co = re.Content; var co = re.Content;
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result); var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
// todo: does this work as expected now?
co.Dispose(); co.Dispose();
re.Dispose(); re.Dispose();
cl.Dispose(); cl.Dispose();
@ -39,10 +38,7 @@ var plugin = {
if (usingVPN) { if (usingVPN) {
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')'); this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
var library = importNamespace('SharedLibraryCore'); origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
var kickOrigin = new library.Objects.Player();
kickOrigin.ClientId = 1;
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], kickOrigin);
} }
}, },
@ -55,7 +51,7 @@ var plugin = {
onLoadAsync: function (manager) { onLoadAsync: function (manager) {
this.manager = manager; this.manager = manager;
this.logger = manager.GetLogger(); this.logger = manager.GetLogger(0);
}, },
onUnloadAsync: function () { onUnloadAsync: function () {

View File

@ -26,8 +26,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Dictionary<IW4Info.HitLocation, int> HitLocationCount; Dictionary<IW4Info.HitLocation, int> HitLocationCount;
double AngleDifferenceAverage; double AngleDifferenceAverage;
EFClientStatistics ClientStats; EFClientStatistics ClientStats;
DateTime LastHit;
long LastOffset; long LastOffset;
IW4Info.WeaponName LastWeapon;
ILogger Log; ILogger Log;
Strain Strain; Strain Strain;
readonly DateTime ConnectionTime = DateTime.UtcNow; readonly DateTime ConnectionTime = DateTime.UtcNow;
@ -53,16 +53,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0) kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0 ||
// hack: prevents false positives
(LastWeapon != kill.Weapon && (kill.TimeOffset - LastOffset) == 50))
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
ClientPenalty = Penalty.PenaltyType.Any, ClientPenalty = Penalty.PenaltyType.Any,
}; };
DetectionPenaltyResult result = null; DetectionPenaltyResult result = null;
LastWeapon = kill.Weapon;
if (LastHit == DateTime.MinValue)
LastHit = DateTime.UtcNow;
HitLocationCount[kill.HitLoc]++; HitLocationCount[kill.HitLoc]++;
if (!isDamage) if (!isDamage)

View File

@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
@ -17,23 +19,30 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{ {
if (E.Origin.ClientNumber >= 0) if (E.Origin.ClientNumber >= 0)
{ {
var svc = new SharedLibraryCore.Services.GenericRepository<EFClientStatistics>();
int serverId = E.Owner.GetHashCode();
var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First();
stats.Deaths = 0; int serverId = E.Owner.GetHashCode();
stats.Kills = 0;
stats.SPM = 0.0; EFClientStatistics clientStats;
stats.Skill = 0.0; using (var ctx = new DatabaseContext(disableTracking: true))
stats.TimePlayed = 0; {
clientStats = await ctx.Set<EFClientStatistics>()
.Where(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId)
.FirstAsync();
clientStats.Deaths = 0;
clientStats.Kills = 0;
clientStats.SPM = 0.0;
clientStats.Skill = 0.0;
clientStats.TimePlayed = 0;
// todo: make this more dynamic // todo: make this more dynamic
stats.EloRating = 200.0; clientStats.EloRating = 200.0;
// reset the cached version // reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode()); Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
// fixme: this doesn't work properly when another context exists // fixme: this doesn't work properly when another context exists
await svc.SaveChangesAsync(); await ctx.SaveChangesAsync();
}
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
} }

View File

@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
@ -39,20 +41,22 @@ namespace IW4MAdmin.Plugins.Stats.Commands
} }
} }
var clientStats = new GenericRepository<EFClientStatistics>();
int serverId = E.Owner.GetHashCode(); int serverId = E.Owner.GetHashCode();
using (var ctx = new DatabaseContext(disableTracking: true))
{
if (E.Target != null) if (E.Target != null)
{ {
pStats = (await clientStats.FindAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)).First(); pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
} }
else else
{ {
pStats = (await clientStats.FindAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)).First(); pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)));
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}";
} }
}
if (E.Message.IsBroadcastCommand()) if (E.Message.IsBroadcastCommand())
{ {

View File

@ -24,20 +24,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public class StatManager public class StatManager
{ {
private ConcurrentDictionary<int, ServerStats> Servers; private ConcurrentDictionary<int, ServerStats> Servers;
private ConcurrentDictionary<int, ThreadSafeStatsService> ContextThreads;
private ILogger Log; private ILogger Log;
private IManager Manager; private readonly IManager Manager;
private readonly SemaphoreSlim OnProcessingPenalty; private readonly SemaphoreSlim OnProcessingPenalty;
private readonly SemaphoreSlim OnProcessingSensitive;
public StatManager(IManager mgr) public StatManager(IManager mgr)
{ {
Servers = new ConcurrentDictionary<int, ServerStats>(); Servers = new ConcurrentDictionary<int, ServerStats>();
ContextThreads = new ConcurrentDictionary<int, ThreadSafeStatsService>(); Log = mgr.GetLogger(0);
Log = mgr.GetLogger();
Manager = mgr; Manager = mgr;
OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingPenalty = new SemaphoreSlim(1, 1);
OnProcessingSensitive = new SemaphoreSlim(1, 1);
} }
public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId]; public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId];
@ -188,14 +188,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="sv"></param> /// <param name="sv"></param>
public void AddServer(Server sv) public void AddServer(Server sv)
{ {
// insert the server if it does not exist
try try
{ {
int serverId = sv.GetHashCode(); int serverId = sv.GetHashCode();
var statsSvc = new ThreadSafeStatsService(); EFServer server;
ContextThreads.TryAdd(serverId, statsSvc);
using (var ctx = new DatabaseContext(disableTracking: true))
{
var serverSet = ctx.Set<EFServer>();
// get the server from the database if it exists, otherwise create and insert a new one // get the server from the database if it exists, otherwise create and insert a new one
var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); server = serverSet.FirstOrDefault(c => c.ServerId == serverId);
if (server == null) if (server == null)
{ {
server = new EFServer() server = new EFServer()
@ -205,16 +209,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ServerId = serverId ServerId = serverId
}; };
statsSvc.ServerSvc.Insert(server); server = serverSet.Add(server).Entity;
// this doesn't need to be async as it's during initialization
ctx.SaveChanges();
}
} }
// this doesn't need to be async as it's during initialization
statsSvc.ServerSvc.SaveChanges();
// check to see if the stats have ever been initialized // check to see if the stats have ever been initialized
InitializeServerStats(sv); var serverStats = InitializeServerStats(sv);
statsSvc.ServerStatsSvc.SaveChanges();
var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault();
Servers.TryAdd(serverId, new ServerStats(server, serverStats) Servers.TryAdd(serverId, new ServerStats(server, serverStats)
{ {
IsTeamBased = sv.Gametype != "dm" IsTeamBased = sv.Gametype != "dm"
@ -233,9 +236,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="pl">Player to add/retrieve stats for</param> /// <param name="pl">Player to add/retrieve stats for</param>
/// <returns>EFClientStatistic of specified player</returns> /// <returns>EFClientStatistic of specified player</returns>
public async Task<EFClientStatistics> AddPlayer(Player pl) public async Task<EFClientStatistics> AddPlayer(Player pl)
{
await OnProcessingSensitive.WaitAsync();
try
{ {
int serverId = pl.CurrentServer.GetHashCode(); int serverId = pl.CurrentServer.GetHashCode();
if (!Servers.ContainsKey(serverId)) if (!Servers.ContainsKey(serverId))
{ {
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
@ -244,18 +252,24 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var playerStats = Servers[serverId].PlayerStats; var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections; var detectionStats = Servers[serverId].PlayerDetections;
var statsSvc = ContextThreads[serverId];
if (playerStats.ContainsKey(pl.ClientId)) if (playerStats.ContainsKey(pl.ClientId))
{ {
Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}");
return null; return playerStats[pl.ClientId];
} }
// get the client's stats from the database if it exists, otherwise create and attach a new one // get the client's stats from the database if it exists, otherwise create and attach a new one
// if this fails we want to throw an exception // if this fails we want to throw an exception
var clientStatsSvc = statsSvc.ClientStatSvc;
var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault(); EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStats = clientStatsSet
.Include(cl => cl.HitLocations)
.FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId);
if (clientStats == null) if (clientStats == null)
{ {
@ -278,21 +292,38 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}; };
// insert if they've not been added // insert if they've not been added
clientStats = clientStatsSvc.Insert(clientStats); clientStats = clientStatsSet.Add(clientStats).Entity;
await clientStatsSvc.SaveChangesAsync(); await ctx.SaveChangesAsync();
if (!playerStats.TryAdd(clientStats.ClientId, clientStats))
{
Log.WriteWarning("Adding new client to stats failed");
}
}
else
{
if (!playerStats.TryAdd(clientStats.ClientId, clientStats))
{
Log.WriteWarning("Adding pre-existing client to stats failed");
}
} }
// migration for previous existing stats // migration for previous existing stats
if (clientStats.HitLocations.Count == 0) if (clientStats.HitLocations.Count == 0)
{ {
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount() clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
.Select(hl => new EFHitLocationCount()
{ {
Active = true, Active = true,
HitCount = 0, HitCount = 0,
Location = hl Location = hl
}) })
.ToList(); .ToList();
await statsSvc.ClientStatSvc.SaveChangesAsync();
ctx.Update(clientStats);
await ctx.SaveChangesAsync();
} }
// for stats before rating // for stats before rating
@ -312,17 +343,31 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
clientStats.SessionScore = pl.Score; clientStats.SessionScore = pl.Score;
clientStats.LastScore = pl.Score; clientStats.LastScore = pl.Score;
Log.WriteInfo($"Adding {pl} to stats");
if (!playerStats.TryAdd(pl.ClientId, clientStats))
Log.WriteDebug($"Could not add client to stats {pl}");
if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats)))
Log.WriteDebug("Could not add client to detection"); {
Log.WriteWarning("Could not add client to detection");
}
Log.WriteInfo($"Adding {pl} to stats");
}
return clientStats; return clientStats;
} }
catch (Exception ex)
{
Log.WriteWarning("Could not add client to stats");
Log.WriteDebug(ex.GetExceptionInfo());
}
finally
{
OnProcessingSensitive.Release(1);
}
return null;
}
/// <summary> /// <summary>
/// Perform stat updates for disconnecting client /// Perform stat updates for disconnecting client
/// </summary> /// </summary>
@ -336,7 +381,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var playerStats = Servers[serverId].PlayerStats; var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections; var detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics; var serverStats = Servers[serverId].ServerStatistics;
var statsSvc = ContextThreads[serverId];
if (!playerStats.ContainsKey(pl.ClientId)) if (!playerStats.ContainsKey(pl.ClientId))
{ {
@ -355,10 +399,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
// sync their stats before they leave // sync their stats before they leave
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStats = UpdateStats(clientStats); clientStats = UpdateStats(clientStats);
clientStatsSvc.Update(clientStats);
await clientStatsSvc.SaveChangesAsync(); using (var ctx = new DatabaseContext(disableTracking: true))
{
ctx.Update(clientStats);
await ctx.SaveChangesAsync();
}
// increment the total play time // increment the total play time
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds;
@ -389,7 +436,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads, string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads,
string fraction, string visibilityPercentage, string snapAngles) string fraction, string visibilityPercentage, string snapAngles)
{ {
var statsSvc = ContextThreads[serverId];
Vector3 vDeathOrigin = null; Vector3 vDeathOrigin = null;
Vector3 vKillOrigin = null; Vector3 vKillOrigin = null;
Vector3 vViewAngles = null; Vector3 vViewAngles = null;
@ -466,10 +512,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return; return;
} }
// incase the add palyer event get delayed
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
{
await AddPlayer(attacker);
}
var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
var clientStats = Servers[serverId].PlayerStats[attacker.ClientId]; var clientStats = Servers[serverId].PlayerStats[attacker.ClientId];
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStatsSvc.Update(clientStats); using (var ctx = new DatabaseContext(disableTracking: true))
{
ctx.Set<EFClientStatistics>().Update(clientStats);
await ctx.SaveChangesAsync();
}
// increment their hit count // increment their hit count
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
@ -492,17 +548,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (Plugin.Config.Configuration().EnableAntiCheat) if (Plugin.Config.Configuration().EnableAntiCheat)
{ {
await ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx); ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx);
await ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx); ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
} }
await clientStatsSvc.SaveChangesAsync(); ctx.Set<EFHitLocationCount>().UpdateRange(clientStats.HitLocations);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.WriteError("AC ERROR"); Log.WriteError("Could not save hit or AC info");
Log.WriteDebug(ex.GetExceptionInfo()); Log.WriteDebug(ex.GetExceptionInfo());
} }
@ -510,7 +567,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
async Task ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx) void ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx)
{ {
switch (penalty.ClientPenalty) switch (penalty.ClientPenalty)
{ {
@ -530,7 +587,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
} }
} },
Level = Player.Permission.Console,
CurrentServer = attacker.CurrentServer
}); });
if (clientDetection.Tracker.HasChanges) if (clientDetection.Tracker.HasChanges)
{ {
@ -542,30 +601,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
break; break;
} }
var e = new GameEvent()
{ string flagReason = penalty.Type == Cheat.Detection.DetectionType.Bone ?
Data = penalty.Type == Cheat.Detection.DetectionType.Bone ?
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
Origin = new Player()
attacker.Flag(flagReason, new Player()
{ {
ClientId = 1, ClientId = 1,
Level = Player.Permission.Console, Level = Player.Permission.Console,
ClientNumber = -1, CurrentServer = attacker.CurrentServer,
CurrentServer = attacker.CurrentServer });
},
Target = attacker,
Owner = attacker.CurrentServer,
Type = GameEvent.EventType.Flag
};
// because we created an event it must be processed by the manager
// even if it didn't really do anything
Manager.GetEventHandler().AddEvent(e);
await new CFlag().ExecuteAsync(e);
if (clientDetection.Tracker.HasChanges) if (clientDetection.Tracker.HasChanges)
{ {
SaveTrackedSnapshots(clientDetection, ctx); SaveTrackedSnapshots(clientDetection, ctx);
} }
break; break;
} }
} }
@ -712,10 +764,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// todo: do we want to save this immediately? // todo: do we want to save this immediately?
var clientStatsSvc = ContextThreads[serverId].ClientStatSvc; using (var ctx = new DatabaseContext(disableTracking: true))
clientStatsSvc.Update(attackerStats); {
clientStatsSvc.Update(victimStats); var clientStatsSet = ctx.Set<EFClientStatistics>();
await clientStatsSvc.SaveChangesAsync();
clientStatsSet.Update(attackerStats);
clientStatsSet.Update(victimStats);
await ctx.SaveChangesAsync();
}
} }
/// <summary> /// <summary>
@ -1048,40 +1104,44 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return clientStats; return clientStats;
} }
public void InitializeServerStats(Server sv) public EFServerStatistics InitializeServerStats(Server sv)
{ {
int serverId = sv.GetHashCode(); int serverId = sv.GetHashCode();
var statsSvc = ContextThreads[serverId]; EFServerStatistics serverStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
var serverStats = statsSvc.ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault();
if (serverStats == null) if (serverStats == null)
{ {
Log.WriteDebug($"Initializing server stats for {sv}"); Log.WriteDebug($"Initializing server stats for {sv}");
// server stats have never been generated before // server stats have never been generated before
serverStats = new EFServerStatistics() serverStats = new EFServerStatistics()
{ {
Active = true,
ServerId = serverId, ServerId = serverId,
TotalKills = 0, TotalKills = 0,
TotalPlayTime = 0, TotalPlayTime = 0,
}; };
var ieClientStats = statsSvc.ClientStatSvc.Find(cs => cs.ServerId == serverId); serverStats = serverStatsSet.Add(serverStats).Entity;
ctx.SaveChanges();
// set these incase we've imported settings
serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills);
serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result;
statsSvc.ServerStatsSvc.Insert(serverStats);
} }
} }
return serverStats;
}
public void ResetKillstreaks(int serverId) public void ResetKillstreaks(int serverId)
{ {
var serverStats = Servers[serverId]; var serverStats = Servers[serverId];
foreach (var stat in serverStats.PlayerStats.Values) foreach (var stat in serverStats.PlayerStats.Values)
{
stat.StartNewSession(); stat.StartNewSession();
} }
}
public void ResetStats(int clientId, int serverId) public void ResetStats(int clientId, int serverId)
{ {
@ -1100,32 +1160,30 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (clientId < 1) if (clientId < 1)
return; return;
var messageSvc = ContextThreads[serverId].MessageSvc; using (var ctx = new DatabaseContext(disableTracking: true))
messageSvc.Insert(new EFClientMessage() {
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
{ {
Active = true,
ClientId = clientId, ClientId = clientId,
Message = message, Message = message,
ServerId = serverId, ServerId = serverId,
TimeSent = DateTime.UtcNow TimeSent = DateTime.UtcNow
}); });
await messageSvc.SaveChangesAsync();
await ctx.SaveChangesAsync();
}
} }
public async Task Sync(Server sv) public async Task Sync(Server sv)
{ {
int serverId = sv.GetHashCode(); int serverId = sv.GetHashCode();
var statsSvc = ContextThreads[serverId];
// Log.WriteDebug("Syncing stats contexts"); using (var ctx = new DatabaseContext(disableTracking: true))
await statsSvc.ServerStatsSvc.SaveChangesAsync(); {
//await statsSvc.ClientStatSvc.SaveChangesAsync(); var serverSet = ctx.Set<EFServer>();
await statsSvc.KillStatsSvc.SaveChangesAsync(); serverSet.Update(Servers[serverId].Server);
await statsSvc.ServerSvc.SaveChangesAsync(); await ctx.SaveChangesAsync();
}
statsSvc = null;
// this should prevent the gunk from having a long lasting context.
ContextThreads[serverId] = new ThreadSafeStatsService();
} }
public void SetTeamBased(int serverId, bool isTeamBased) public void SetTeamBased(int serverId, bool isTeamBased)

View File

@ -1,38 +0,0 @@
using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Helpers
{
public class ThreadSafeStatsService
{
public GenericRepository<EFClientStatistics> ClientStatSvc
{
get
{
return new GenericRepository<EFClientStatistics>(true);
}
}
public GenericRepository<EFServer> ServerSvc { get; private set; }
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
public GenericRepository<EFClientMessage> MessageSvc
{
get
{
return new GenericRepository<EFClientMessage>();
}
}
public ThreadSafeStatsService()
{
ServerSvc = new GenericRepository<EFServer>();
KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
}
}
}

View File

@ -23,6 +23,5 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ServerId { get; set; } public int ServerId { get; set; }
[ForeignKey("ServerId"), Column(Order = 1)] [ForeignKey("ServerId"), Column(Order = 1)]
public EFServer Server { get; set; } public EFServer Server { get; set; }
} }
} }

View File

@ -13,6 +13,8 @@ using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Config; using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers; using IW4MAdmin.Plugins.Stats.Helpers;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats namespace IW4MAdmin.Plugins.Stats
{ {
@ -122,8 +124,11 @@ namespace IW4MAdmin.Plugins.Stats
// meta data info // meta data info
async Task<List<ProfileMeta>> getStats(int clientId) async Task<List<ProfileMeta>> getStats(int clientId)
{ {
var statsSvc = new GenericRepository<EFClientStatistics>(); IList<EFClientStatistics> clientStats;
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId); using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == clientId).ToListAsync();
}
int kills = clientStats.Sum(c => c.Kills); int kills = clientStats.Sum(c => c.Kills);
int deaths = clientStats.Sum(c => c.Deaths); int deaths = clientStats.Sum(c => c.Deaths);
@ -170,8 +175,14 @@ namespace IW4MAdmin.Plugins.Stats
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId) async Task<List<ProfileMeta>> getAnticheatInfo(int clientId)
{ {
var statsSvc = new GenericRepository<EFClientStatistics>(); IList<EFClientStatistics> clientStats;
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId); using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>()
.Include(c => c.HitLocations)
.Where(c => c.ClientId == clientId)
.ToListAsync();
}
double headRatio = 0; double headRatio = 0;
double chestRatio = 0; double chestRatio = 0;
@ -231,7 +242,8 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta() new ProfileMeta()
{ {
Key = "Hit Offset Average", Key = "Hit Offset Average",
Value = $"{Math.Round(((float)hitOffsetAverage), 4)}°", // todo: make sure this is wrapped somewhere else
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
Sensitive = true Sensitive = true
}, },
new ProfileMeta() new ProfileMeta()
@ -245,19 +257,24 @@ namespace IW4MAdmin.Plugins.Stats
async Task<List<ProfileMeta>> getMessages(int clientId) async Task<List<ProfileMeta>> getMessages(int clientId)
{ {
var messageSvc = new GenericRepository<EFClientMessage>(); List<ProfileMeta> messageMeta;
var messages = await messageSvc.FindAsync(m => m.ClientId == clientId); using (var ctx = new DatabaseContext(disableTracking: true))
var messageMeta = messages.Select(m => new ProfileMeta() {
var messages = ctx.Set<EFClientMessage>().Where(m => m.ClientId == clientId);
messageMeta = await messages.Select(m => new ProfileMeta()
{ {
Key = "EventMessage", Key = "EventMessage",
Value = m.Message, Value = m.Message,
When = m.TimeSent, When = m.TimeSent,
Extra = m.ServerId.ToString() Extra = m.ServerId.ToString()
}).ToList(); }).ToListAsync();
}
messageMeta.Add(new ProfileMeta() messageMeta.Add(new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = messages.Count Value = messageMeta.Count
}); });
return messageMeta; return messageMeta;
@ -274,16 +291,20 @@ namespace IW4MAdmin.Plugins.Stats
string totalKills(Server server) string totalKills(Server server)
{ {
var serverStats = new GenericRepository<EFServerStatistics>(); using (var ctx = new DatabaseContext(disableTracking: true))
return serverStats.Find(s => s.Active) {
.Sum(c => c.TotalKills).ToString("#,##0"); long kills = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalKills);
return kills.ToString("#,##0");
}
} }
string totalPlayTime(Server server) string totalPlayTime(Server server)
{ {
var serverStats = new GenericRepository<EFServerStatistics>(); using (var ctx = new DatabaseContext(disableTracking: true))
return Math.Ceiling((serverStats.GetQuery(s => s.Active) {
.Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0"); long playTime = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalPlayTime);
return (playTime / 3600.0).ToString("#,##0");
}
} }
string topStats(Server s) string topStats(Server s)

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
@ -26,7 +27,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -53,13 +53,15 @@ namespace Tests
Assert.False(client == null, "no client found to warn"); Assert.False(client == null, "no client found to warn");
var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); var warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(TestTimeout); warnEvent.OnProcessed.Wait();
Assert.True(client.Warnings == 1 || Assert.True((client.Warnings == 1 ||
warnEvent.Failed, "warning did not get applied"); warnEvent.Failed) &&
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied");
warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); warnEvent = client.Warn("test warn", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(TestTimeout); warnEvent.OnProcessed.Wait();
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission && Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions"); client.Warnings == 1, "warning was applied without proper permissions");
@ -86,8 +88,17 @@ namespace Tests
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault();
Assert.False(client == null, "no client found to report"); Assert.False(client == null, "no client found to report");
// fail
var player = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer };
player.SetAdditionalProperty("_reportCount", 3);
var reportEvent = client.Report("test report", player);
reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Throttle &
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]");
// succeed // succeed
var reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent.OnProcessed.Wait(TestTimeout); reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(!reportEvent.Failed && Assert.True(!reportEvent.Failed &&

View File

@ -18,7 +18,9 @@ namespace Tests
{ {
File.WriteAllText("test_mp.log", "test_log_file"); File.WriteAllText("test_mp.log", "test_log_file");
Manager = Program.ServerManager; IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
Manager = ApplicationManager.GetInstance();
var config = new ApplicationConfiguration var config = new ApplicationConfiguration
{ {
@ -43,6 +45,7 @@ namespace Tests
Manager.ConfigHandler.Set(config); Manager.ConfigHandler.Set(config);
Manager.Init().Wait(); Manager.Init().Wait();
Task.Run(() => Manager.Start()); Task.Run(() => Manager.Start());
} }

View File

@ -188,6 +188,22 @@ namespace Tests
resetEvent.Wait(5000); resetEvent.Wait(5000);
} }
[Fact]
public void PrintCommands()
{
var sb = new StringBuilder();
sb.AppendLine("|Name |Alias|Description |Requires Target|Syntax |Required Level|");
sb.AppendLine("|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------|");
foreach (var command in Manager.GetCommands().OrderByDescending(c => c.Permission).ThenBy(c => c.Name))
{
sb.AppendLine($"|{command.Name}|{command.Alias}|{command.Description}|{command.RequiresTarget}|{command.Syntax.Substring(8).EscapeMarkdown()}|{command.Permission}|");
}
Assert.True(false, sb.ToString());
}
} }
} }

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
</PropertyGroup> </PropertyGroup>
@ -22,7 +23,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,142 +0,0 @@
/* CountryLookup.cs
*
* Copyright (C) 2008 MaxMind, Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
using SharedLibraryCore;
using System;
using System.IO;
using System.Net;
namespace CountryLookupProj
{
/// <summary>
/// Summary description for CountryLookup.
/// </summary>
public class CountryLookup
{
private FileStream fileInput;
private static long COUNTRY_BEGIN = 16776960;
private static string[] countryCode =
{ "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ",
"EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ",
"LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA",
"RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA","ZM","ME","ZW","A1","A2",
"O1","AX","GG","IM","JE","BL","MF"
};
private static string[] countryName =
{"a third world country","Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles","Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados","Bangladesh","Belgium",
"Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia","Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the","Central African Republic","Congo","Switzerland","Cote D'Ivoire",
"Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica","Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic","Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji","Falkland Islands (Malvinas)",
"Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana","Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala","Guam","Guinea-Bissau","Guyana",
"Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia","Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan","Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis",
"Korea, Democratic People's Republic of","Korea, Republic of","Kuwait","Cayman Islands","Kazakstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania","Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar",
"Marshall Islands","Macedonia","Mali","Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives","Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua","Netherlands",
"Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia","Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau","Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia",
"Solomon Islands","Seychelles","Sudan","Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname","Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo",
"Thailand","Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan","Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines",
"Venezuela","Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa","Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider",
"Other","Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"};
public CountryLookup(string fileName)
{
fileInput = new FileStream(fileName, FileMode.Open, FileAccess.Read);
}
private long AddrToNum(IPAddress addr)
{
long ipnum = 0;
byte[] b = BitConverter.GetBytes((UInt32)addr.ToString().ConvertToIP());
for (int i = 0; i < 4; ++i)
{
long y = b[i];
if (y < 0)
{
y += 256;
}
ipnum += y << ((3 - i) * 8);
}
return ipnum;
}
public string LookupCountryCode(IPAddress addr)
{
return (countryCode[(int)SeekCountry(0, AddrToNum(addr), 31)]);
}
public string LookupCountryName(string str)
{
IPAddress addr;
try
{
addr = IPAddress.Parse(str);
}
catch (FormatException)
{
return "a third world country";
}
return LookupCountryName(addr);
}
public string LookupCountryName(IPAddress addr)
{
return (countryName[(int)SeekCountry(0, AddrToNum(addr), 31)]);
}
private long SeekCountry(long offset, long ipnum, int depth)
{
byte[] buf = new byte[6];
long[] x = new long[2];
fileInput.Seek(6 * offset, 0);
fileInput.Read(buf, 0, 6);
for (int i = 0; i < 2; i++)
{
x[i] = 0;
for (int j = 0; j < 3; j++)
{
int y = buf[i * 3 + j];
if (y < 0)
{
y += 256;
}
x[i] += (y << (j * 8));
}
}
if ((ipnum & (1 << depth)) > 0)
{
if (x[1] >= COUNTRY_BEGIN)
{
return x[1] - COUNTRY_BEGIN;
}
return SeekCountry(x[1], ipnum, depth - 1);
}
else
{
if (x[0] >= COUNTRY_BEGIN)
{
return x[0] - COUNTRY_BEGIN;
}
return SeekCountry(x[0], ipnum, depth - 1);
}
}
}
}

Binary file not shown.

View File

@ -8,6 +8,11 @@ using SharedLibraryCore.Configuration;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using System.Linq; using System.Linq;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using System.Net;
using Newtonsoft.Json.Linq;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Plugins.Welcome namespace IW4MAdmin.Plugins.Welcome
{ {
@ -82,37 +87,66 @@ namespace IW4MAdmin.Plugins.Welcome
{ {
Player newPlayer = E.Origin; Player newPlayer = E.Origin;
if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked) if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked)
E.Owner.Broadcast(ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer)); E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
newPlayer.Tell(ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer)); newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
if (newPlayer.Level == Player.Permission.Flagged) if (newPlayer.Level == Player.Permission.Flagged)
{ {
var penalty = await new GenericRepository<EFPenalty>().FindAsync(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag); string penaltyReason;
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penalty.FirstOrDefault()?.Offense}) has joined!");
using (var ctx = new DatabaseContext(disableTracking: true))
{
penaltyReason = await ctx.Penalties
.Where(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag)
.OrderByDescending(p => p.When)
.Select(p => p.AutomatedOffense ?? p.Offense)
.FirstOrDefaultAsync();
}
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penaltyReason}) has joined!");
} }
else else
E.Owner.Broadcast(ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer)); E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer));
} }
} }
private string ProcessAnnouncement(string msg, Player joining) private async Task<string> ProcessAnnouncement(string msg, Player joining)
{ {
msg = msg.Replace("{{ClientName}}", joining.Name); msg = msg.Replace("{{ClientName}}", joining.Name);
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name)); msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name));
try // this prevents it from trying to evaluate it every message
if (msg.Contains("{{ClientLocation}}"))
{ {
CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup($"{Utilities.OperatingDirectory}Plugins{System.IO.Path.DirectorySeparatorChar}GeoIP.dat"); msg = msg.Replace("{{ClientLocation}}", await GetCountryName(joining.IPAddressString));
msg = msg.Replace("{{ClientLocation}}", CLT.LookupCountryName(joining.IPAddressString));
}
catch (Exception)
{
joining.CurrentServer.Manager.GetLogger().WriteError("Could not open file Plugins\\GeoIP.dat for Welcome Plugin");
} }
msg = msg.Replace("{{TimesConnected}}", TimesConnected(joining)); msg = msg.Replace("{{TimesConnected}}", TimesConnected(joining));
return msg; return msg;
} }
/// <summary>
/// makes a webrequest to determine IP origin
/// </summary>
/// <param name="ip">IP address to get location of</param>
/// <returns></returns>
private async Task<string> GetCountryName(string ip)
{
using (var wc = new WebClient())
{
try
{
string response = await wc.DownloadStringTaskAsync(new Uri($"http://extreme-ip-lookup.com/json/{ip}"));
var responseObj = JObject.Parse(response);
return responseObj["country"].ToString();
}
catch
{
return "a third world country";
}
}
}
} }
} }

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Welcome</PackageId> <PackageId>RaidMax.IW4MAdmin.Plugins.Welcome</PackageId>
@ -19,17 +20,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="MaxMind\GeoIP.dat"> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;&#xD;&#xA;copy &quot;$(ProjectDir)MaxMind\GeoIP.dat&quot; &quot;$(SolutionDir)BUILD\Plugins\GeoIP.dat&quot;" /> <Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;" />
</Target> </Target>
</Project> </Project>

8
RunPublishRelease.cmd Normal file
View File

@ -0,0 +1,8 @@
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish Application/Application.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows\GameLogServer
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
cd "C:\Projects\IW4M-Admin\DEPLOY\"
PowerShell ".\upload_release.ps1"

View File

@ -773,12 +773,12 @@ namespace SharedLibraryCore.Commands
else if (unflagEvent.FailReason == GameEvent.EventFailReason.Invalid) else if (unflagEvent.FailReason == GameEvent.EventFailReason.Invalid)
{ {
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}"); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]);
} }
else else
{ {
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]); E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}");
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -825,6 +825,11 @@ namespace SharedLibraryCore.Commands
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]); commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]);
} }
else if (reportEvent.FailReason == GameEvent.EventFailReason.Throttle)
{
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_TOOMANY"]);
}
else if (reportEvent.Failed) else if (reportEvent.Failed)
{ {
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]); commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]);
@ -1179,6 +1184,8 @@ namespace SharedLibraryCore.Commands
} }
} }
} }
return;
} }
} }
@ -1324,6 +1331,7 @@ namespace SharedLibraryCore.Commands
var nextMapMatch = currentMap.First().Index != lastMap.Index ? var nextMapMatch = currentMap.First().Index != lastMap.Index ?
regexMatches[regexMatches.IndexOf(currentMap.First()) + 1] : regexMatches[regexMatches.IndexOf(currentMap.First()) + 1] :
regexMatches.First(); regexMatches.First();
nextMap = s.Maps.FirstOrDefault(m => m.Name == nextMapMatch.Groups[3].ToString()) ?? nextMap; nextMap = s.Maps.FirstOrDefault(m => m.Name == nextMapMatch.Groups[3].ToString()) ?? nextMap;
string nextGametype = nextMapMatch.Groups[2].ToString().Length == 0 ? string nextGametype = nextMapMatch.Groups[2].ToString().Length == 0 ?
Utilities.GetLocalizedGametype(s.Gametype) : Utilities.GetLocalizedGametype(s.Gametype) :

View File

@ -24,7 +24,6 @@ namespace SharedLibraryCore.Database
public DbSet<EFMeta> EFMeta { get; set; } public DbSet<EFMeta> EFMeta { get; set; }
public DbSet<EFChangeHistory> EFChangeHistory { get; set; } public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
static string _ConnectionString; static string _ConnectionString;
static string _provider; static string _provider;
@ -52,21 +51,17 @@ namespace SharedLibraryCore.Database
{ {
if (string.IsNullOrEmpty(_ConnectionString)) if (string.IsNullOrEmpty(_ConnectionString))
{ {
string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase); string currentPath = Utilities.OperatingDirectory;
// allows the application to find the database file // allows the application to find the database file
currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
$"{Path.DirectorySeparatorChar}{currentPath}" : $"{Path.DirectorySeparatorChar}{currentPath}" :
currentPath; currentPath;
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database.db".Substring(6) }; var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database{Path.DirectorySeparatorChar}Database.db" };
var connectionString = connectionStringBuilder.ToString(); var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString); var connection = new SqliteConnection(connectionString);
//#if DEBUG == true
//optionsBuilder.UseMySql("UserId=root;Password=dev;Host=127.0.0.1;port=3306;Database=IW4MAdmin");
// optionsBuilder.UseNpgsql("UserId=dev;Password=dev;Host=127.0.0.1;port=5432;Database=IW4MAdmin");
//#else
optionsBuilder.UseSqlite(connection); optionsBuilder.UseSqlite(connection);
//#endif
} }
else else
@ -130,15 +125,15 @@ namespace SharedLibraryCore.Database
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/ // https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
IEnumerable<string> directoryFiles; IEnumerable<string> directoryFiles;
string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.0{Path.DirectorySeparatorChar}Plugins"; string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.1{Path.DirectorySeparatorChar}Plugins";
if (!Directory.Exists(pluginDir)) if (!Directory.Exists(pluginDir))
{ {
pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}Plugins"; pluginDir = Path.Join(Environment.CurrentDirectory, "Plugins");
if (!Directory.Exists(pluginDir)) if (!Directory.Exists(pluginDir))
{ {
pluginDir = Utilities.OperatingDirectory; pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
} }
} }

View File

@ -26,7 +26,11 @@ namespace SharedLibraryCore
/// <summary> /// <summary>
/// executing the event would cause an invalid state /// executing the event would cause an invalid state
/// </summary> /// </summary>
Invalid Invalid,
/// <summary>
/// client is doing too much of something
/// </summary>
Throttle
} }
public enum EventType public enum EventType
@ -216,7 +220,8 @@ namespace SharedLibraryCore
{ {
return (queuedEvent.Target != null && queuedEvent.Target.ClientNumber != -1) && return (queuedEvent.Target != null && queuedEvent.Target.ClientNumber != -1) &&
(queuedEvent.Target.State != Player.ClientState.Connected && (queuedEvent.Target.State != Player.ClientState.Connected &&
queuedEvent.Target.NetworkId != 0); queuedEvent.Target.NetworkId != 0 &&
queuedEvent.Origin?.ClientId != 1);
} }
} }
} }

View File

@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Linq;
namespace SharedLibraryCore
{
public class RemoteFile : IFile
{
readonly string Location;
string[] FileCache = new string[0];
public RemoteFile(string location) : base(string.Empty)
{
Location = location;
}
private void Retrieve()
{
using (var cl = new HttpClient())
FileCache = cl.GetStringAsync(Location).Result.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
}
public override long Length()
{
Retrieve();
return FileCache.Sum(l => l.Length);
}
public override Task<string[]> Tail(int lineCount)
{
return Task.FromResult(FileCache);
}
}
public class IFile
{
public IFile(String fileName)
{
if (fileName != string.Empty)
{
Name = fileName;
Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true), Utilities.EncodingType);
sze = Handle.BaseStream.Length;
}
}
public virtual long Length()
{
sze = Handle.BaseStream.Length;
return sze;
}
public void Close()
{
Handle?.Close();
}
public String[] ReadAllLines()
{
return Handle?.ReadToEnd().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
public String GetText()
{
return Handle?.ReadToEnd();
}
public virtual async Task<String[]> Tail(int lineCount)
{
var buffer = new List<string>(lineCount);
string line;
for (int i = 0; i < lineCount; i++)
{
line = await Handle.ReadLineAsync();
if (line == null) return buffer.ToArray();
buffer.Add(line);
}
int lastLine = lineCount - 1; //The index of the last line read from the buffer. Everything > this index was read earlier than everything <= this indes
while (null != (line = await Handle.ReadLineAsync()))
{
lastLine++;
if (lastLine == lineCount) lastLine = 0;
buffer[lastLine] = line;
}
if (lastLine == lineCount - 1) return buffer.ToArray();
var retVal = new string[lineCount];
buffer.CopyTo(lastLine + 1, retVal, 0, lineCount - lastLine - 1);
buffer.CopyTo(0, retVal, lineCount - lastLine - 1, lastLine + 1);
return retVal;
}
//END
private long sze;
private readonly String Name;
private StreamReader Handle;
}
}

View File

@ -25,7 +25,7 @@ namespace SharedLibraryCore.Configuration
public void Build() public void Build()
{ {
ConfigurationRoot = new ConfigurationBuilder() ConfigurationRoot = new ConfigurationBuilder()
.AddJsonFile($"{AppDomain.CurrentDomain.BaseDirectory}{Filename}.json", true) .AddJsonFile(Path.Join(Utilities.OperatingDirectory, "Configuration", $"{Filename}.json"), true)
.Build(); .Build();
_configuration = ConfigurationRoot.Get<T>(); _configuration = ConfigurationRoot.Get<T>();
@ -37,10 +37,7 @@ namespace SharedLibraryCore.Configuration
public Task Save() public Task Save()
{ {
var appConfigJSON = JsonConvert.SerializeObject(_configuration, Formatting.Indented); var appConfigJSON = JsonConvert.SerializeObject(_configuration, Formatting.Indented);
return Task.Factory.StartNew(() => return File.WriteAllTextAsync(Path.Join(Utilities.OperatingDirectory, "Configuration", $"{Filename}.json"), appConfigJSON);
{
File.WriteAllText($"{AppDomain.CurrentDomain.BaseDirectory}{Filename}.json", appConfigJSON);
});
} }
public T Configuration() => _configuration; public T Configuration() => _configuration;

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Interfaces
Task Init(); Task Init();
void Start(); void Start();
void Stop(); void Stop();
ILogger GetLogger(); ILogger GetLogger(int serverId);
IList<Server> GetServers(); IList<Server> GetServers();
IList<Command> GetCommands(); IList<Command> GetCommands();
IList<Helpers.MessageToken> GetMessageTokens(); IList<Helpers.MessageToken> GetMessageTokens();

View File

@ -81,7 +81,10 @@ namespace SharedLibraryCore.Objects
ConnectionTime = DateTime.UtcNow; ConnectionTime = DateTime.UtcNow;
ClientNumber = -1; ClientNumber = -1;
DelayedEvents = new Queue<GameEvent>(); DelayedEvents = new Queue<GameEvent>();
_additionalProperties = new Dictionary<string, object>(); _additionalProperties = new Dictionary<string, object>
{
{ "_reportCount", 0 }
};
} }
public override string ToString() => $"{Name}::{NetworkId}"; public override string ToString() => $"{Name}::{NetworkId}";
@ -116,19 +119,22 @@ namespace SharedLibraryCore.Objects
{ {
Type = GameEvent.EventType.Warn, Type = GameEvent.EventType.Warn,
Message = warnReason, Message = warnReason,
Data = warnReason,
Origin = sender, Origin = sender,
Target = this, Target = this,
Owner = sender.CurrentServer Owner = sender.CurrentServer
}; };
// enforce level restrictions // enforce level restrictions
if (sender.Level <= this.Level) if (this.Level > sender.Level)
{ {
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
return e;
} }
else
{
this.Warnings++; this.Warnings++;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e; return e;
@ -181,6 +187,8 @@ namespace SharedLibraryCore.Objects
Owner = sender.CurrentServer Owner = sender.CurrentServer
}; };
int reportCount = sender.GetAdditionalProperty<int>("_reportCount");
if (this.Level > sender.Level) if (this.Level > sender.Level)
{ {
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
@ -191,12 +199,18 @@ namespace SharedLibraryCore.Objects
e.FailReason = GameEvent.EventFailReason.Invalid; e.FailReason = GameEvent.EventFailReason.Invalid;
} }
else if (reportCount > 2)
{
e.FailReason = GameEvent.EventFailReason.Throttle;
}
else if (CurrentServer.Reports.Count(report => (report.Origin.NetworkId == sender.NetworkId && else if (CurrentServer.Reports.Count(report => (report.Origin.NetworkId == sender.NetworkId &&
report.Target.NetworkId == this.NetworkId)) > 0) report.Target.NetworkId == this.NetworkId)) > 0)
{ {
e.FailReason = GameEvent.EventFailReason.Exception; e.FailReason = GameEvent.EventFailReason.Exception;
} }
sender.SetAdditionalProperty("_reportCount", reportCount + 1);
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e; return e;
} }
@ -325,7 +339,6 @@ namespace SharedLibraryCore.Objects
if (sender.Level <= this.Level) if (sender.Level <= this.Level)
{ {
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
return e;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
@ -353,7 +366,6 @@ namespace SharedLibraryCore.Objects
if (sender.Level <= this.Level) if (sender.Level <= this.Level)
{ {
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
return e;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
@ -391,7 +403,18 @@ namespace SharedLibraryCore.Objects
[NotMapped] [NotMapped]
Dictionary<string, object> _additionalProperties; Dictionary<string, object> _additionalProperties;
public T GetAdditionalProperty<T>(string name) => (T)_additionalProperties[name]; public T GetAdditionalProperty<T>(string name) => (T)_additionalProperties[name];
public void SetAdditionalProperty(string name, object value) => _additionalProperties.Add(name, value); public void SetAdditionalProperty(string name, object value)
{
if (_additionalProperties.ContainsKey(name))
{
_additionalProperties[name] = value;
}
else
{
_additionalProperties.Add(name, value);
}
}
[NotMapped] [NotMapped]
public int ClientNumber { get; set; } public int ClientNumber { get; set; }
[NotMapped] [NotMapped]

View File

@ -34,7 +34,7 @@ namespace SharedLibraryCore.Plugins
if (dllFileNames.Length == 0 && if (dllFileNames.Length == 0 &&
scriptFileNames.Length == 0) scriptFileNames.Length == 0)
{ {
Manager.GetLogger().WriteDebug(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_NOTFOUND"]); Manager.GetLogger(0).WriteDebug(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_NOTFOUND"]);
return true; return true;
} }
@ -43,7 +43,7 @@ namespace SharedLibraryCore.Plugins
{ {
var plugin = new ScriptPlugin(fileName); var plugin = new ScriptPlugin(fileName);
plugin.Initialize(Manager).Wait(); plugin.Initialize(Manager).Wait();
Manager.GetLogger().WriteDebug($"Loaded script plugin \"{ plugin.Name }\" [{plugin.Version}]"); Manager.GetLogger(0).WriteDebug($"Loaded script plugin \"{ plugin.Name }\" [{plugin.Version}]");
ActivePlugins.Add(plugin); ActivePlugins.Add(plugin);
} }
@ -66,7 +66,7 @@ namespace SharedLibraryCore.Plugins
Object commandObject = Activator.CreateInstance(assemblyType); Object commandObject = Activator.CreateInstance(assemblyType);
Command newCommand = (Command)commandObject; Command newCommand = (Command)commandObject;
ActiveCommands.Add(newCommand); ActiveCommands.Add(newCommand);
Manager.GetLogger().WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_REGISTERCMD"]} \"{newCommand.Name}\""); Manager.GetLogger(0).WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_REGISTERCMD"]} \"{newCommand.Name}\"");
LoadedCommands++; LoadedCommands++;
continue; continue;
} }
@ -82,18 +82,18 @@ namespace SharedLibraryCore.Plugins
{ {
ActivePlugins.Add(newNotify); ActivePlugins.Add(newNotify);
PluginAssemblies.Add(Plugin); PluginAssemblies.Add(Plugin);
Manager.GetLogger().WriteDebug($"Loaded plugin \"{ newNotify.Name }\" [{newNotify.Version}]"); Manager.GetLogger(0).WriteDebug($"Loaded plugin \"{ newNotify.Name }\" [{newNotify.Version}]");
} }
} }
catch (Exception E) catch (Exception E)
{ {
Manager.GetLogger().WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Plugin.Location} - {E.Message}"); Manager.GetLogger(0).WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Plugin.Location} - {E.Message}");
} }
} }
} }
} }
Manager.GetLogger().WriteInfo($"Loaded {ActivePlugins.Count} plugins and registered {LoadedCommands} commands."); Manager.GetLogger(0).WriteInfo($"Loaded {ActivePlugins.Count} plugins and registered {LoadedCommands} commands.");
return true; return true;
} }
} }

View File

@ -95,6 +95,7 @@ namespace SharedLibraryCore.RCon
Ttl = 42, Ttl = 42,
ExclusiveAddressUse = true, ExclusiveAddressUse = true,
}; };
connectionState.OnSentData.Reset(); connectionState.OnSentData.Reset();
connectionState.OnReceivedData.Reset(); connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++; connectionState.ConnectionAttempts++;
@ -103,22 +104,22 @@ namespace SharedLibraryCore.RCon
#endif #endif
try try
{ {
response = await SendPayloadAsync(payload); response = await SendPayloadAsync(payload, waitForResponse);
connectionState.OnComplete.Release(1); connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0; connectionState.ConnectionAttempts = 0;
} }
catch (Exception ex) catch/* (Exception ex)*/
{ {
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails) if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{ {
Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); // Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
await Task.Delay(StaticHelpers.FloodProtectionInterval); await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend; goto retrySend;
} }
connectionState.OnComplete.Release(1); connectionState.OnComplete.Release(1);
Log.WriteDebug(ex.GetExceptionInfo()); //Log.WriteDebug(ex.GetExceptionInfo());
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]"); throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
} }
@ -139,7 +140,7 @@ namespace SharedLibraryCore.RCon
return splitResponse; return splitResponse;
} }
private async Task<byte[]> SendPayloadAsync(byte[] payload) private async Task<byte[]> SendPayloadAsync(byte[] payload, bool waitForResponse)
{ {
var connectionState = ActiveQueries[this.Endpoint]; var connectionState = ActiveQueries[this.Endpoint];
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken; var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
@ -171,6 +172,11 @@ namespace SharedLibraryCore.RCon
} }
} }
if (!waitForResponse)
{
return new byte[0];
}
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer); connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
// get our response back // get our response back

View File

@ -39,7 +39,7 @@ namespace SharedLibraryCore.RCon
/// <summary> /// <summary>
/// timeout in seconds to wait for a socket send or receive before giving up /// timeout in seconds to wait for a socket send or receive before giving up
/// </summary> /// </summary>
public static readonly int SocketTimeout = 1000; public static readonly int SocketTimeout = 10000;
/// <summary> /// <summary>
/// interval in milliseconds to wait before sending the next RCon request /// interval in milliseconds to wait before sending the next RCon request
/// </summary> /// </summary>

View File

@ -42,8 +42,8 @@ namespace SharedLibraryCore
} }
catch (Exception ex) catch (Exception ex)
{ {
Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Name}"); Manager.GetLogger(0).WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Name}");
Manager.GetLogger().WriteDebug(ex.Message); Manager.GetLogger(0).WriteDebug(ex.Message);
} }
} }
@ -57,7 +57,16 @@ namespace SharedLibraryCore
} }
Manager = mgr; Manager = mgr;
string script = File.ReadAllText(FileName); string script;
using (var stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var reader = new StreamReader(stream, Encoding.Default))
{
script = await reader.ReadToEndAsync();
}
}
ScriptEngine = new Jint.Engine(cfg => ScriptEngine = new Jint.Engine(cfg =>
cfg.AllowClr(new[] cfg.AllowClr(new[]
{ {
@ -68,7 +77,6 @@ namespace SharedLibraryCore
ScriptEngine.Execute(script); ScriptEngine.Execute(script);
ScriptEngine.SetValue("_localization", Utilities.CurrentLocalization); ScriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient);
dynamic pluginObject = ScriptEngine.GetValue("plugin").ToObject(); dynamic pluginObject = ScriptEngine.GetValue("plugin").ToObject();
this.Author = pluginObject.author; this.Author = pluginObject.author;
@ -87,6 +95,7 @@ namespace SharedLibraryCore
{ {
ScriptEngine.SetValue("_gameEvent", E); ScriptEngine.SetValue("_gameEvent", E);
ScriptEngine.SetValue("_server", S); ScriptEngine.SetValue("_server", S);
ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
return Task.FromResult(ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue()); return Task.FromResult(ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue());
} }
} }

View File

@ -32,7 +32,8 @@ namespace SharedLibraryCore
IP = config.IPAddress; IP = config.IPAddress;
Port = config.Port; Port = config.Port;
Manager = mgr; Manager = mgr;
Logger = Manager.GetLogger(); Logger = Manager.GetLogger(this.GetHashCode());
Logger.WriteInfo(this.ToString());
ServerConfig = config; ServerConfig = config;
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger); RemoteConnection = new RCon.Connection(IP, Port, Password, Logger);
@ -209,7 +210,7 @@ namespace SharedLibraryCore
/// <param name="Origin">The person who banned the target</param> /// <param name="Origin">The person who banned the target</param>
abstract protected Task Ban(String Reason, Player Target, Player Origin); abstract protected Task Ban(String Reason, Player Target, Player Origin);
abstract public Task Warn(String Reason, Player Target, Player Origin); abstract protected Task Warn(String Reason, Player Target, Player Origin);
/// <summary> /// <summary>
/// Unban a player by npID / GUID /// Unban a player by npID / GUID
@ -263,7 +264,7 @@ namespace SharedLibraryCore
public override string ToString() public override string ToString()
{ {
return $"{IP}_{Port}"; return $"{IP}-{Port}";
} }
protected async Task<bool> ScriptLoaded() protected async Task<bool> ScriptLoaded()
@ -320,7 +321,6 @@ namespace SharedLibraryCore
protected int ConnectionErrors; protected int ConnectionErrors;
protected List<string> BroadcastMessages; protected List<string> BroadcastMessages;
protected TimeSpan LastMessage; protected TimeSpan LastMessage;
protected IFile LogFile;
protected DateTime LastPoll; protected DateTime LastPoll;
protected ManualResetEventSlim OnRemoteCommandResponse; protected ManualResetEventSlim OnRemoteCommandResponse;

View File

@ -1,154 +0,0 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace SharedLibraryCore.Services
{
// https://stackoverflow.com/questions/43677906/crud-operations-with-entityframework-using-generic-type
public class GenericRepository<TEntity> where TEntity : class
{
private DatabaseContext _context;
private DbSet<TEntity> _dbSet;
private readonly bool ShouldTrack;
public GenericRepository(bool shouldTrack)
{
this.ShouldTrack = shouldTrack;
}
public GenericRepository() { }
protected DbContext Context
{
get
{
if (_context == null)
{
_context = new DatabaseContext(ShouldTrack);
}
return _context;
}
}
protected DbSet<TEntity> DBSet
{
get
{
if (_dbSet == null)
{
_dbSet = this.Context.Set<TEntity>();
}
return _dbSet;
}
}
public virtual async Task<IList<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return await this.GetQuery(predicate, orderExpression).ToListAsync();
}
public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return this.GetQuery(predicate, orderExpression).AsEnumerable();
}
public virtual IQueryable<TEntity> GetQuery(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
IQueryable<TEntity> qry = this.DBSet;
foreach (var property in this.Context.Model.FindEntityType(typeof(TEntity)).GetNavigations())
qry = qry.Include(property.Name);
if (predicate != null)
qry = qry.Where(predicate);
if (orderExpression != null)
return orderExpression(qry);
return qry;
}
public virtual void Insert<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Add(entity);
}
public virtual TEntity Insert(TEntity entity)
{
return DBSet.Add(entity).Entity;
}
public virtual void Update<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Attach(entity);
this.Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Update(TEntity entity)
{
this.Attach(entity);
this.Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
if (this.Context.Entry(entity).State == EntityState.Detached)
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(TEntity entity)
{
if (this.Context.Entry(entity).State == EntityState.Detached)
this.Attach(entity);
this.DBSet.Remove(entity);
}
public virtual void Delete<T>(object[] id) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
T entity = dbSet.Find(id);
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(object id)
{
TEntity entity = this.DBSet.Find(id);
this.Delete(entity);
}
public virtual void Attach(TEntity entity)
{
if (this.Context.Entry(entity).State == EntityState.Detached)
this.DBSet.Attach(entity);
}
public virtual void SaveChanges()
{
this.Context.SaveChanges();
}
public virtual Task SaveChangesAsync()
{
return this.Context.SaveChangesAsync();
}
}
}

View File

@ -24,7 +24,6 @@ namespace SharedLibraryCore.Services
PunisherId = newEntity.Punisher.ClientId, PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId, LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type, Type = newEntity.Type,
Active = true,
Expires = newEntity.Expires, Expires = newEntity.Expires,
Offense = newEntity.Offense, Offense = newEntity.Offense,
When = newEntity.When, When = newEntity.When,
@ -69,19 +68,7 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFPenalty>> Find(Func<EFPenalty, bool> expression) public async Task<IList<EFPenalty>> Find(Func<EFPenalty, bool> expression)
{ {
throw await Task.FromResult(new Exception()); throw await Task.FromResult(new Exception());
/*
return await Task.FromResult(new List<EFPenalty>());
// fixme: this is so slow!
return await Task.Run(() =>
{
using (var context = new DatabaseContext())
return context.Penalties
.Include(p => p.Offender)
.Include(p => p.Punisher)
.Where(expression)
.Where(p => p.Active)
.ToList();
});*/
} }
public Task<EFPenalty> Get(int entityID) public Task<EFPenalty> Get(int entityID)
@ -225,8 +212,6 @@ namespace SharedLibraryCore.Services
return list; return list;
} }
} }
} }

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId> <PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
@ -12,20 +13,11 @@
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Migrations\20180502195240_Update.cs" />
<Compile Remove="Migrations\20180923025324_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923025702_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030248_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030426_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030528_FixForPostgreSQL.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="2.1.1" />
@ -39,7 +31,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -20,10 +20,20 @@ namespace SharedLibraryCore
{ {
public static class Utilities public static class Utilities
{ {
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; #if DEBUG == true
public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}";
#else
public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}";
#endif
public static Encoding EncodingType; public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary<string, string>()); public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary<string, string>());
public static Player IW4MAdminClient = new Player() { ClientId = 1, Level = Player.Permission.Console }; public static Player IW4MAdminClient(Server server = null) => new Player()
{
ClientId = 1,
State = Player.ClientState.Connected,
Level = Player.Permission.Console,
CurrentServer = server
};
public static string HttpRequest(string location, string header, string headerValue) public static string HttpRequest(string location, string header, string headerValue)
{ {

View File

@ -23,9 +23,9 @@ namespace WebfrontCore.Controllers
public IActionResult Error() public IActionResult Error()
{ {
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>(); var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
Manager.GetLogger().WriteError($"[Webfront] {exceptionFeature.Error.Message}"); Manager.GetLogger(0).WriteError($"[Webfront] {exceptionFeature.Error.Message}");
Manager.GetLogger().WriteDebug(exceptionFeature.Path); Manager.GetLogger(0).WriteDebug(exceptionFeature.Path);
Manager.GetLogger().WriteDebug(exceptionFeature.Error.StackTrace); Manager.GetLogger(0).WriteDebug(exceptionFeature.Error.StackTrace);
ViewBag.Description = Localization["WEBFRONT_ERROR_DESC"]; ViewBag.Description = Localization["WEBFRONT_ERROR_DESC"];
ViewBag.Title = Localization["WEBFRONT_ERROR_TITLE"]; ViewBag.Title = Localization["WEBFRONT_ERROR_TITLE"];

View File

@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
@ -33,8 +35,14 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> PublicAsync() public async Task<IActionResult> PublicAsync()
{ {
var penalties = await (new GenericRepository<EFPenalty>()) IList<EFPenalty> penalties;
.FindAsync(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active);
using (var ctx = new DatabaseContext(disableTracking: true))
{
penalties = await ctx.Penalties
.Where(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active)
.ToListAsync();
}
var penaltiesDto = penalties.Select(p => new PenaltyInfo() var penaltiesDto = penalties.Select(p => new PenaltyInfo()
{ {

View File

@ -30,7 +30,7 @@ namespace WebfrontCore
#if DEBUG #if DEBUG
.UseContentRoot(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\..\", "WebfrontCore"))) .UseContentRoot(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\..\", "WebfrontCore")))
#else #else
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory)
#endif #endif
.UseUrls(Manager.GetApplicationSettings().Configuration().WebfrontBindUrl) .UseUrls(Manager.GetApplicationSettings().Configuration().WebfrontBindUrl)
.UseKestrel() .UseKestrel()

View File

@ -20,7 +20,6 @@ namespace WebfrontCore
public Startup(IHostingEnvironment env) public Startup(IHostingEnvironment env)
{ {
var builder = new ConfigurationBuilder() var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
Configuration = builder.Build(); Configuration = builder.Build();
@ -32,10 +31,10 @@ namespace WebfrontCore
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
// Add framework services. // Add framework services.
var mvcBulder = services.AddMvc(); var mvcBuilder = services.AddMvc();
foreach (var asm in Program.Manager.GetPluginAssemblies()) foreach (var asm in Program.Manager.GetPluginAssemblies())
mvcBulder.AddApplicationPart(asm); mvcBuilder.AddApplicationPart(asm);
services.Configure<RazorViewEngineOptions>(o => services.Configure<RazorViewEngineOptions>(o =>
{ {

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<RazorCompileOnBuild>false</RazorCompileOnBuild> <RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>false</RazorCompileOnPublish> <RazorCompileOnPublish>false</RazorCompileOnPublish>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
@ -63,7 +64,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Update="Microsoft.NETCore.App" /> <PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
<PackageReference Update="Microsoft.AspNetCore" Version="2.1.2" /> <PackageReference Update="Microsoft.AspNetCore" Version="2.1.2" />
</ItemGroup> </ItemGroup>