Compare commits

..

12 Commits

Author SHA1 Message Date
b77bdbe793 minor fixed 2018-12-03 19:21:13 -06:00
4522992c0e fix remote commands
user clientkick_for_reason for T6 parsers
small bug fixes
2018-12-01 12:17:53 -06:00
9d6cbee69c update stats
change server id
fIx change log server complaining when empty read
2018-11-27 18:31:48 -06:00
abf0609e2e fix only one administrator showing on admins page
fix profanity determent not applying penalties.
2018-11-25 21:11:55 -06:00
5ac8a55c72 fixes for new polling setup
update database model for alias (nullable ip)
heartbeats now send ip to master server
2018-11-25 20:00:36 -06:00
9bdd7d1b8a More work modifying client stuff 2018-11-07 20:30:11 -06:00
ed83c4c011 started work on getting the restart functionality in the gamelogserver
fix bug with unbanned players still showing as banned via lock icon
move player based stuff into client class
finally renamed Player to EFClient via partial class
don't try to run this build because it's in between stages
2018-11-05 21:01:29 -06:00
d9d548ea18 Small anti-cheat update 2018-10-28 20:47:56 -05:00
1779bf821d more work on skill based team balance.
added on player disconnect to custom callbacks
2018-10-25 08:14:39 -05:00
d50e6c8030 change penalty expiration datetime to null for perm bans
add tempban max time
allow searching for GUID
stats returns ranking as well
fix for promotion/demotion text
2018-10-15 19:51:04 -05:00
a58726d872 add gsc api controller for communicating with gsc
add ignore bots option
fix first localization message not working
2018-10-13 18:51:07 -05:00
dded60a6ef add test to print out all commands 2018-10-12 21:32:30 -05:00
111 changed files with 4714 additions and 1846 deletions

4
.gitignore vendored
View File

@ -229,4 +229,6 @@ bootstrap-custom.min.css
/DiscordWebhook/env /DiscordWebhook/env
/DiscordWebhook/config.dev.json /DiscordWebhook/config.dev.json
/GameLogServer/env /GameLogServer/env
launchSettings.json launchSettings.json
/VpnDetectionPrivate.js
/Plugins/ScriptPlugins/VpnDetectionPrivate.js

View File

@ -8,7 +8,9 @@ namespace IW4MAdmin.Application.API.Master
public class ApiServer public class ApiServer
{ {
[JsonProperty("id")] [JsonProperty("id")]
public int Id { get; set; } public long Id { get; set; }
[JsonProperty("ip")]
public string IPAddress { get; set; }
[JsonProperty("port")] [JsonProperty("port")]
public short Port { get; set; } public short Port { get; set; }
[JsonProperty("gametype")] [JsonProperty("gametype")]

View File

@ -43,8 +43,9 @@ namespace IW4MAdmin.Application.API.Master
Hostname = s.Hostname, Hostname = s.Hostname,
Map = s.CurrentMap.Name, Map = s.CurrentMap.Name,
MaxClientNum = s.MaxClients, MaxClientNum = s.MaxClients,
Id = s.GetHashCode(), Id = s.EndPoint,
Port = (short)s.GetPort() Port = (short)s.GetPort(),
IPAddress = s.IP
}).ToList() }).ToList()
}; };

View File

@ -6,7 +6,7 @@
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish> <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId> <PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.2</Version> <Version>2.2.2.2</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>
@ -31,8 +31,8 @@
<PropertyGroup> <PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection> <ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation> <TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.0.0</AssemblyVersion> <AssemblyVersion>2.2.2.2</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion> <FileVersion>2.2.2.2</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,82 +0,0 @@
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IW4MAdmin.Application.Core
{
class ClientAuthentication : IClientAuthentication
{
private Queue<Player> ClientAuthenticationQueue;
private Dictionary<long, Player> AuthenticatedClients;
public ClientAuthentication()
{
ClientAuthenticationQueue = new Queue<Player>();
AuthenticatedClients = new Dictionary<long, Player>();
}
public void AuthenticateClients(IList<Player> clients)
{
// we need to un-auth all the clients that have disconnected
var clientNetworkIds = clients.Select(c => c.NetworkId);
var clientsToRemove = AuthenticatedClients.Keys.Where(c => !clientNetworkIds.Contains(c));
// remove them
foreach (long Id in clientsToRemove.ToList())
{
AuthenticatedClients.Remove(Id);
}
// loop through the polled clients to see if they've been authenticated yet
foreach (var client in clients)
{
// they've not been authenticated
if (!AuthenticatedClients.TryGetValue(client.NetworkId, out Player value))
{
// authenticate them
client.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(client.NetworkId, client);
}
else
{
// this update their ping
// todo: this seems kinda hacky
value.Ping = client.Ping;
value.Score = client.Score;
}
}
// empty out the queue of clients detected through log
while (ClientAuthenticationQueue.Count > 0)
{
// grab each client that's connected via log
var clientToAuthenticate = ClientAuthenticationQueue.Dequeue();
// if they're not already authed, auth them
if (!AuthenticatedClients.TryGetValue(clientToAuthenticate.NetworkId, out Player value))
{
// authenticate them
clientToAuthenticate.State = Player.ClientState.Authenticated;
AuthenticatedClients.Add(clientToAuthenticate.NetworkId, clientToAuthenticate);
}
}
}
public IList<Player> GetAuthenticatedClients()
{
if (AuthenticatedClients.Values.Count > 18)
{
Program.ServerManager.GetLogger(0).WriteError($"auth client count is {AuthenticatedClients.Values.Count}, this is bad");
return AuthenticatedClients.Values.Take(18).ToList();
}
return AuthenticatedClients.Values.ToList();
}
public void RequestClientAuthentication(Player client)
{
ClientAuthenticationQueue.Enqueue(client);
}
}
}

View File

@ -1,10 +1,9 @@
using System; using SharedLibraryCore;
using System.Collections.Generic; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers namespace IW4MAdmin.Application.EventParsers
{ {
@ -20,7 +19,7 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "JoinTeam") if (eventType == "JoinTeam")
{ {
var origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong()); var origin = server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent() return new GameEvent()
{ {
@ -41,7 +40,7 @@ namespace IW4MAdmin.Application.EventParsers
.Replace("\x15", "") .Replace("\x15", "")
.Trim(); .Trim();
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)); var origin = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
if (message[0] == '!' || message[0] == '@') if (message[0] == '!' || message[0] == '@')
{ {
@ -70,8 +69,8 @@ namespace IW4MAdmin.Application.EventParsers
{ {
if (!server.CustomCallback) if (!server.CustomCallback)
{ {
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)); var origin = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6));
var target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)); var target = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
return new GameEvent() return new GameEvent()
{ {
@ -86,8 +85,8 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "ScriptKill") if (eventType == "ScriptKill")
{ {
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.ScriptKill, Type = GameEvent.EventType.ScriptKill,
@ -100,8 +99,8 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "ScriptDamage") if (eventType == "ScriptDamage")
{ {
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent() return new GameEvent()
{ {
@ -120,8 +119,8 @@ namespace IW4MAdmin.Application.EventParsers
{ {
if (Regex.Match(eventType, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success) if (Regex.Match(eventType, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success)
{ {
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()); var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent() return new GameEvent()
{ {
@ -143,41 +142,49 @@ namespace IW4MAdmin.Application.EventParsers
{ {
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.Join, Type = GameEvent.EventType.PreConnect,
Data = logLine, Data = logLine,
Owner = server, Owner = server,
Origin = new Player() Origin = new EFClient()
{ {
Name = regexMatch.Groups[4].ToString().StripColors(), CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[4].ToString().StripColors(),
},
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(), NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()), ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
State = Player.ClientState.Connecting, State = EFClient.ClientState.Connecting,
CurrentServer = server CurrentServer = server
} }
}; };
} }
} }
//if (eventType == "Q") if (eventType == "Q")
//{ {
// var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$"); var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
// if (regexMatch.Success) if (regexMatch.Success)
// { {
// return new GameEvent() return new GameEvent()
// { {
// Type = GameEvent.EventType.Quit, Type = GameEvent.EventType.PreDisconnect,
// Data = logLine, Data = logLine,
// Owner = server, Owner = server,
// Origin = new Player() Origin = new EFClient()
// { {
// Name = regexMatch.Groups[4].ToString().StripColors(), CurrentAlias = new EFAlias()
// NetworkId = regexMatch.Groups[2].ToString().ConvertLong(), {
// ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()), Active = false,
// State = Player.ClientState.Connecting Name = regexMatch.Groups[4].ToString().StripColors()
// } },
// }; NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
// } ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
//} State = EFClient.ClientState.Disconnecting
}
};
}
}
if (eventType.Contains("ExitLevel")) if (eventType.Contains("ExitLevel"))
{ {
@ -185,14 +192,8 @@ namespace IW4MAdmin.Application.EventParsers
{ {
Type = GameEvent.EventType.MapEnd, Type = GameEvent.EventType.MapEnd,
Data = lineSplit[0], Data = lineSplit[0],
Origin = new Player() Origin = Utilities.IW4MAdminClient(server),
{ Target = Utilities.IW4MAdminClient(server),
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server Owner = server
}; };
} }
@ -205,14 +206,8 @@ namespace IW4MAdmin.Application.EventParsers
{ {
Type = GameEvent.EventType.MapChange, Type = GameEvent.EventType.MapChange,
Data = lineSplit[0], Data = lineSplit[0],
Origin = new Player() Origin = Utilities.IW4MAdminClient(server),
{ Target = Utilities.IW4MAdminClient(server),
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server, Owner = server,
Extra = dump.DictionaryFromKeyValue() Extra = dump.DictionaryFromKeyValue()
}; };
@ -221,19 +216,16 @@ namespace IW4MAdmin.Application.EventParsers
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.Unknown, Type = GameEvent.EventType.Unknown,
Origin = new Player() Origin = Utilities.IW4MAdminClient(server),
{ Target = Utilities.IW4MAdminClient(server),
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Owner = server Owner = server
}; };
} }
// other parsers can derive from this parser so we make it virtual // other parsers can derive from this parser so we make it virtual
public virtual string GetGameDir() => "userraw"; public virtual string GetGameDir()
{
return "userraw";
}
} }
} }

View File

@ -1,17 +1,16 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.EventParsers namespace IW4MAdmin.Application.EventParsers
{ {
class IW5EventParser : IW4EventParser class IW5EventParser : IW4EventParser
{ {
public override string GetGameDir() => "logs"; public override string GetGameDir()
{
return "logs";
}
public override GameEvent GetEvent(Server server, string logLine) public override GameEvent GetEvent(Server server, string logLine)
{ {
@ -23,21 +22,25 @@ namespace IW4MAdmin.Application.EventParsers
int clientNum = Int32.Parse(lineSplit[2]); int clientNum = Int32.Parse(lineSplit[2]);
var player = new Player() var player = new EFClient()
{ {
NetworkId = lineSplit[1].ConvertLong(), NetworkId = lineSplit[1].ConvertLong(),
ClientNumber = clientNum, ClientNumber = clientNum,
Name = lineSplit[3] CurrentAlias = new EFAlias()
{
Active = false,
Name = lineSplit[3]
}
}; };
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.Join, Type = GameEvent.EventType.PreConnect,
Origin = new Player() Origin = new EFClient()
{ {
ClientId = 1 ClientId = 1
}, },
Target = new Player() Target = new EFClient()
{ {
ClientId = 1 ClientId = 1
}, },
@ -47,7 +50,9 @@ namespace IW4MAdmin.Application.EventParsers
} }
else else
{
return base.GetEvent(server, logLine); return base.GetEvent(server, logLine);
}
} }
} }
} }

View File

@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.IO
Server = server; Server = server;
} }
public void PollForChanges() public async Task PollForChanges()
{ {
while (!Server.Manager.ShutdownRequested()) while (!Server.Manager.ShutdownRequested())
{ {
@ -45,12 +45,12 @@ namespace IW4MAdmin.Application.IO
{ {
try try
{ {
UpdateLogEvents(); await UpdateLogEvents();
} }
catch (Exception e) catch (Exception e)
{ {
Server.Logger.WriteWarning($"Failed to update log event for {Server.GetHashCode()}"); Server.Logger.WriteWarning($"Failed to update log event for {Server.EndPoint}");
Server.Logger.WriteDebug($"Exception: {e.Message}"); Server.Logger.WriteDebug($"Exception: {e.Message}");
Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
} }
@ -59,7 +59,7 @@ namespace IW4MAdmin.Application.IO
} }
} }
private void UpdateLogEvents() private async Task UpdateLogEvents()
{ {
long fileSize = Reader.Length; long fileSize = Reader.Length;
@ -74,11 +74,12 @@ namespace IW4MAdmin.Application.IO
PreviousFileSize = fileSize; PreviousFileSize = fileSize;
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0); var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
foreach (var ev in events) foreach (var ev in events)
{ {
Server.Manager.GetEventHandler().AddEvent(ev); Server.Manager.GetEventHandler().AddEvent(ev);
await ev.WaitAsync();
} }
PreviousFileSize = fileSize; PreviousFileSize = fileSize;

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO namespace IW4MAdmin.Application.IO
{ {
@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO
Parser = parser; Parser = parser;
} }
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{ {
// allocate the bytes for the new log lines // allocate the bytes for the new log lines
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();
@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO
// open the file as a stream // open the file as a stream
using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType)) using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType))
{ {
// todo: max async
// take the old start position and go back the number of new characters // take the old start position and go back the number of new characters
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End); rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
// the difference should be in the range of a int :P // the difference should be in the range of a int :P

View File

@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using static SharedLibraryCore.Utilities; using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO namespace IW4MAdmin.Application.IO
@ -27,20 +28,21 @@ namespace IW4MAdmin.Application.IO
public long Length => -1; public long Length => -1;
public int UpdateInterval => 1000; public int UpdateInterval => 350;
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition) public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{ {
#if DEBUG == true #if DEBUG == true
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log"); server.Logger.WriteDebug($"Begin reading from http log");
#endif #endif
var events = new List<GameEvent>(); var events = new List<GameEvent>();
string b64Path = server.LogPath.ToBase64UrlSafeString(); string b64Path = server.LogPath.ToBase64UrlSafeString();
var response = Api.Log(b64Path).Result; var response = await Api.Log(b64Path);
if (!response.Success) if (!response.Success)
{ {
server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})"); server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})");
return events;
} }
// parse each line // parse each line

View File

@ -1,24 +1,22 @@
using System; using IW4MAdmin.Application.EventParsers;
using System.Collections.Generic; using IW4MAdmin.Application.IO;
using System.Threading; using IW4MAdmin.Application.RconParsers;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Localization; using SharedLibraryCore.Localization;
using SharedLibraryCore.Objects;
using IW4MAdmin.Application.RconParsers; using System;
using IW4MAdmin.Application.EventParsers; using System.Collections.Generic;
using IW4MAdmin.Application.IO; using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin namespace IW4MAdmin
{ {
@ -26,268 +24,92 @@ namespace IW4MAdmin
{ {
private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex; private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex;
private GameLogEventDetection LogEvent; private GameLogEventDetection LogEvent;
private DateTime SessionStart;
public int Id { get; private set; }
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg)
{ {
} }
public override int GetHashCode() override public async Task OnClientConnected(EFClient clientFromLog)
{ {
if (GameName == Game.IW4) Logger.WriteDebug($"Client slot #{clientFromLog.ClientNumber} now reserved");
{
// todo: make this better with collisions
int id = Math.Abs($"{IP}:{Port.ToString()}".Select(a => (int)a).Sum());
// hack: this is a nasty fix for get hashcode being changed
switch (id)
{
case 765:
return 886229536;
case 760:
return 1645744423;
case 761:
return 1645809959;
}
return id;
}
else
{
int id = HashCode.Combine(IP, Port);
return id < 0 ? Math.Abs(id) : id;
}
}
public async Task OnPlayerJoined(Player logClient)
{
var existingClient = Players[logClient.ClientNumber];
if (existingClient == null ||
(existingClient.NetworkId != logClient.NetworkId &&
existingClient.State != Player.ClientState.Connected))
{
Logger.WriteDebug($"Log detected {logClient} joining");
Players[logClient.ClientNumber] = logClient;
}
await Task.CompletedTask;
}
override public async Task<bool> AddPlayer(Player polledPlayer)
{
if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
polledPlayer.Ping < 1 ||
polledPlayer.ClientNumber < 0)
{
return false;
}
// set this when they are waiting for authentication
if (Players[polledPlayer.ClientNumber] == null &&
polledPlayer.State == Player.ClientState.Connecting)
{
Players[polledPlayer.ClientNumber] = polledPlayer;
return false;
}
#if !DEBUG
if (polledPlayer.Name.Length < 3)
{
Logger.WriteDebug($"Kicking {polledPlayer} because their name is too short");
string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_MINNAME"]);
await this.ExecuteCommandAsync(formattedKick);
return false;
}
if (Players.FirstOrDefault(p => p != null && p.Name == polledPlayer.Name && p.NetworkId != polledPlayer.NetworkId) != null)
{
Logger.WriteDebug($"Kicking {polledPlayer} because their name is already in use");
string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_NAME_INUSE"]);
await this.ExecuteCommandAsync(formattedKick);
return false;
}
if (polledPlayer.Name == "Unknown Soldier" ||
polledPlayer.Name == "UnknownSoldier" ||
polledPlayer.Name == "CHEATER")
{
Logger.WriteDebug($"Kicking {polledPlayer} because their name is generic");
string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_GENERICNAME"]);
await this.ExecuteCommandAsync(formattedKick);
return false;
}
if (polledPlayer.Name.Where(c => Char.IsControl(c)).Count() > 0)
{
Logger.WriteDebug($"Kicking {polledPlayer} because their name contains control characters");
string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_CONTROLCHARS"]);
await this.ExecuteCommandAsync(formattedKick);
return false;
}
#endif
Logger.WriteDebug($"Client slot #{polledPlayer.ClientNumber} now reserved");
try try
{ {
Player player = null; EFClient client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId);
var client = await Manager.GetClientService().GetUnique(polledPlayer.NetworkId);
// first time client is connecting to server // first time client is connecting to server
if (client == null) if (client == null)
{ {
Logger.WriteDebug($"Client {polledPlayer} first time connecting"); Logger.WriteDebug($"Client {clientFromLog} first time connecting");
player = (await Manager.GetClientService().Create(polledPlayer)).AsPlayer(); client = await Manager.GetClientService().Create(clientFromLog);
} }
// client has connected in the past // client has connected in the past
else else
{ {
client.LastConnection = DateTime.UtcNow; // this is only a temporary version until the IPAddress is transmitted
client.Connections += 1; client.CurrentAlias.Active = false;
client.CurrentAlias.Name = clientFromLog.Name;
var existingAlias = client.AliasLink.Children
.FirstOrDefault(a => a.Name == polledPlayer.Name && a.IPAddress == polledPlayer.IPAddress);
if (existingAlias == null)
{
Logger.WriteDebug($"Client {polledPlayer} has connected previously under a different ip/name");
client.CurrentAlias = new EFAlias()
{
IPAddress = polledPlayer.IPAddress,
Name = polledPlayer.Name,
};
// we need to update their new ip and name to the virtual property
client.Name = polledPlayer.Name;
client.IPAddress = polledPlayer.IPAddress;
}
else
{
client.CurrentAlias = existingAlias;
client.CurrentAliasId = existingAlias.AliasId;
client.Name = existingAlias.Name;
client.IPAddress = existingAlias.IPAddress;
}
await Manager.GetClientService().Update(client);
player = client.AsPlayer();
} }
// reserved slots stuff Logger.WriteInfo($"Client {client} connected...");
if ((MaxClients - ClientNum) < ServerConfig.ReservedSlotNumber &&
!player.IsPrivileged())
{
Logger.WriteDebug($"Kicking {polledPlayer} their spot is reserved");
string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_SLOT_IS_RESERVED"]);
await this.ExecuteCommandAsync(formattedKick);
return false;
}
Logger.WriteInfo($"Client {player} connected...");
// Do the player specific stuff // Do the player specific stuff
player.ClientNumber = polledPlayer.ClientNumber; client.ClientNumber = clientFromLog.ClientNumber;
player.IsBot = polledPlayer.IsBot; client.IsBot = clientFromLog.IsBot;
player.Score = polledPlayer.Score; client.Score = clientFromLog.Score;
player.CurrentServer = this; client.Ping = clientFromLog.Ping;
client.CurrentServer = this;
player.DelayedEvents = (Players[player.ClientNumber]?.DelayedEvents) ?? new Queue<GameEvent>(); Clients[client.ClientNumber] = client;
Players[player.ClientNumber] = player;
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId, player.IPAddress); // this only happens if the preconnect event occurred from RCon polling
var currentBan = activePenalties.FirstOrDefault(b => b.Expires > DateTime.UtcNow); if (clientFromLog.IPAddress.HasValue)
var currentAutoFlag = activePenalties.Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1)
.Where(p => p.Active)
.OrderByDescending(p => p.When)
.FirstOrDefault();
// remove their auto flag status after a week
if (player.Level == Player.Permission.Flagged &&
currentAutoFlag != null &&
(DateTime.Now - currentAutoFlag.When).TotalDays > 7)
{ {
player.Level = Player.Permission.User; await client.OnJoin(clientFromLog.IPAddress);
} }
if (currentBan != null) client.OnConnect();
client.State = EFClient.ClientState.Connected;
#if DEBUG == true
Logger.WriteDebug($"End PreConnect for {client}");
#endif
var e = new GameEvent()
{ {
Logger.WriteInfo($"Banned client {player} trying to connect..."); Origin = client,
var autoKickClient = Utilities.IW4MAdminClient(this); Owner = this,
Type = GameEvent.EventType.Connect
};
// the player is permanently banned Manager.GetEventHandler().AddEvent(e);
if (currentBan.Type == Penalty.PenaltyType.Ban)
{
// don't store the kick message
string formattedKick = String.Format(
RconParser.GetCommandPrefixes().Kick,
polledPlayer.ClientNumber,
$"{loc["SERVER_BAN_PREV"]} {currentBan.Offense} ({loc["SERVER_BAN_APPEAL"]} {Website})");
await this.ExecuteCommandAsync(formattedKick);
}
else
{
string formattedKick = String.Format(
RconParser.GetCommandPrefixes().Kick,
polledPlayer.ClientNumber,
$"{loc["SERVER_TB_REMAIN"]} ({(currentBan.Expires - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})");
await this.ExecuteCommandAsync(formattedKick);
}
// reban the "evading" guid
if (player.Level != Player.Permission.Banned && currentBan.Type == Penalty.PenaltyType.Ban)
{
// hack: re apply the automated offense to the reban
if (currentBan.AutomatedOffense != null)
{
autoKickClient.AdministeredPenalties.Add(new EFPenalty() { AutomatedOffense = currentBan.AutomatedOffense });
}
player.Ban($"{currentBan.Offense}", autoKickClient);
}
// they didn't fully connect so empty their slot
Players[player.ClientNumber] = null;
return false;
}
player.State = Player.ClientState.Connected;
return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {polledPlayer.Name}::{polledPlayer.NetworkId}"); Logger.WriteError($"{loc["SERVER_ERROR_ADDPLAYER"]} {clientFromLog}");
Logger.WriteDebug(ex.Message); Logger.WriteError(ex.GetExceptionInfo());
Logger.WriteDebug(ex.StackTrace);
return false;
} }
} }
//Remove player by CLIENT NUMBER override public async Task OnClientDisconnected(EFClient client)
override public async Task RemovePlayer(int cNum)
{ {
if (cNum >= 0 && Players[cNum] != null) Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
await client.OnDisconnect();
Clients[client.ClientNumber] = null;
#if DEBUG == true
Logger.WriteDebug($"End PreDisconnect for {client}");
#endif
var e = new GameEvent()
{ {
Player Leaving = Players[cNum]; Origin = client,
Owner = this,
Type = GameEvent.EventType.Disconnect
};
// occurs when the player disconnects via log before being authenticated by RCon Manager.GetEventHandler().AddEvent(e);
if (Leaving.State != Player.ClientState.Connected)
{
Players[cNum] = null;
}
else
{
Logger.WriteInfo($"Client {Leaving} [{Leaving.State.ToString().ToLower()}] disconnecting...");
Leaving.State = Player.ClientState.Disconnecting;
Leaving.TotalConnectionTime += Leaving.ConnectionLength;
Leaving.LastConnection = DateTime.UtcNow;
await Manager.GetClientService().Update(Leaving);
Players[cNum] = null;
}
}
} }
public override async Task ExecuteEvent(GameEvent E) public override async Task ExecuteEvent(GameEvent E)
@ -340,7 +162,7 @@ namespace IW4MAdmin
if (E.Type == GameEvent.EventType.Command && if (E.Type == GameEvent.EventType.Command &&
E.Extra != null && E.Extra != null &&
(canExecuteCommand || (canExecuteCommand ||
E.Origin?.Level == Player.Permission.Console)) E.Origin?.Level == EFClient.Permission.Console))
{ {
await (((Command)E.Extra).ExecuteAsync(E)); await (((Command)E.Extra).ExecuteAsync(E));
} }
@ -353,35 +175,46 @@ namespace IW4MAdmin
/// <returns></returns> /// <returns></returns>
override protected async Task<bool> ProcessEvent(GameEvent E) override protected async Task<bool> ProcessEvent(GameEvent E)
{ {
if (E.Type == GameEvent.EventType.Connect) if (E.Type == GameEvent.EventType.ChangePermission)
{ {
E.Origin.State = Player.ClientState.Authenticated; if (!E.Target.IsPrivileged())
// add them to the server
if (!await AddPlayer(E.Origin))
{ {
E.Origin.State = Player.ClientState.Connecting; // remove banned or demoted privileged user
Logger.WriteDebug("client didn't pass authentication, so we are discontinuing event"); Manager.GetPrivilegedClients().Remove(E.Target.ClientId);
return false;
} }
// hack: makes the event propgate with the correct info
E.Origin = Players[E.Origin.ClientNumber];
ChatHistory.Add(new ChatInfo() else
{ {
Name = E.Origin?.Name ?? "ERROR!", Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
Message = "CONNECTED",
Time = DateTime.UtcNow
});
if (E.Origin.Level > Player.Permission.Moderator)
{
E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
} }
} }
else if (E.Type == GameEvent.EventType.Join) else if (E.Type == GameEvent.EventType.PreConnect)
{ {
await OnPlayerJoined(E.Origin); if (Clients[E.Origin.ClientNumber] == null)
{
#if DEBUG == true
Logger.WriteDebug($"Begin PreConnect for {E.Origin}");
#endif
await OnClientConnected(E.Origin);
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "CONNECTED",
Time = DateTime.UtcNow
});
if (E.Origin.Level > EFClient.Permission.Moderator)
{
E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
}
}
else
{
return false;
}
} }
else if (E.Type == GameEvent.EventType.Flag) else if (E.Type == GameEvent.EventType.Flag)
@ -446,13 +279,9 @@ namespace IW4MAdmin
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 = GetClientsAsList().FirstOrDefault(_client => _client.NetworkId.Equals(E.Origin));
if (origin != null && if (origin != null)
// we only want to forward the event if they are connected.
origin.State == Player.ClientState.Connected &&
// make sure we don't get the disconnect event from every time the game ends
origin.ConnectionLength < Manager.GetApplicationSettings().Configuration().RConPollRate)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -464,29 +293,52 @@ namespace IW4MAdmin
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
} }
else if (origin != null && else
origin.State != Player.ClientState.Connected)
{ {
await RemovePlayer(origin.ClientNumber); return false;
} }
} }
else if (E.Type == GameEvent.EventType.Disconnect) else if (E.Type == GameEvent.EventType.PreDisconnect)
{ {
ChatHistory.Add(new ChatInfo() if ((DateTime.UtcNow - SessionStart).TotalSeconds < 5)
{ {
Name = E.Origin.Name, Logger.WriteInfo($"Cancelling pre disconnect for {E.Origin} as it occured too close to map end");
Message = "DISCONNECTED", E.FailReason = GameEvent.EventFailReason.Invalid;
Time = DateTime.UtcNow return false;
});
var currentState = E.Origin.State;
await RemovePlayer(E.Origin.ClientNumber);
if (currentState != Player.ClientState.Connected)
{
throw new ServerException("Disconnecting player was not in a connected state");
} }
// predisconnect comes from minimal rcon polled players and minimal log players
// so we need to disconnect the "full" version of the client
var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
if (client != null)
{
#if DEBUG == true
Logger.WriteDebug($"Begin PreDisconnect for {client}");
#endif
ChatHistory.Add(new ChatInfo()
{
Name = client.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
await OnClientDisconnected(client);
}
else
{
return false;
}
}
else if (E.Type == GameEvent.EventType.Update)
{
#if DEBUG == true
Logger.WriteDebug($"Begin Update for {E.Origin}");
#endif
await OnClientUpdate(E.Origin);
} }
if (E.Type == GameEvent.EventType.Say) if (E.Type == GameEvent.EventType.Say)
@ -550,6 +402,7 @@ namespace IW4MAdmin
if (E.Type == GameEvent.EventType.MapEnd) if (E.Type == GameEvent.EventType.MapEnd)
{ {
Logger.WriteInfo("Game ending..."); Logger.WriteInfo("Game ending...");
SessionStart = DateTime.UtcNow;
} }
if (E.Type == GameEvent.EventType.Tell) if (E.Type == GameEvent.EventType.Tell)
@ -559,54 +412,84 @@ namespace IW4MAdmin
if (E.Type == GameEvent.EventType.Broadcast) if (E.Type == GameEvent.EventType.Broadcast)
{ {
#if DEBUG == false
// this is a little ugly but I don't want to change the abstract class // this is a little ugly but I don't want to change the abstract class
if (E.Data != null) if (E.Data != null)
{ {
await E.Owner.ExecuteCommandAsync(E.Data); await E.Owner.ExecuteCommandAsync(E.Data);
} }
#endif
} }
while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2)) lock (ChatHistory)
ChatHistory.RemoveAt(0); {
while (ChatHistory.Count > Math.Ceiling(ClientNum / 2.0))
{
ChatHistory.RemoveAt(0);
}
}
// the last client hasn't fully disconnected yet // the last client hasn't fully disconnected yet
// so there will still be at least 1 client left // so there will still be at least 1 client left
if (ClientNum < 2) if (ClientNum < 2)
{
ChatHistory.Clear(); ChatHistory.Clear();
}
return true; return true;
} }
private Task OnClientUpdate(EFClient origin)
{
var client = Clients[origin.ClientNumber];
if (client != null)
{
client.Ping = origin.Ping;
client.Score = origin.Score;
// update their IP if it hasn't been set yet
if (!client.IPAddress.HasValue)
{
return client.OnJoin(origin.IPAddress);
}
}
return Task.CompletedTask;
}
/// <summary> /// <summary>
/// lists the connecting and disconnecting clients via RCon response /// lists the connecting and disconnecting clients via RCon response
/// array index 0 = connecting clients /// array index 0 = connecting clients
/// array index 1 = disconnecting clients /// array index 1 = disconnecting clients
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
async Task<IList<Player>[]> PollPlayersAsync() async Task<IList<EFClient>[]> PollPlayersAsync()
{ {
#if DEBUG #if DEBUG
var now = DateTime.Now; var now = DateTime.Now;
#endif #endif
var currentClients = GetPlayersAsList(); var currentClients = GetClientsAsList();
var polledClients = await this.GetStatusAsync(); var polledClients = (await this.GetStatusAsync()).AsEnumerable();
if (this.Manager.GetApplicationSettings().Configuration().IgnoreBots)
{
polledClients = polledClients.Where(c => !c.IsBot);
}
#if DEBUG #if DEBUG
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms"); Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
#endif #endif
Throttled = false; Throttled = false;
foreach (var client in polledClients)
{
// todo: move out somehwere
var existingClient = Players[client.ClientNumber] ?? client;
existingClient.Ping = client.Ping;
existingClient.Score = client.Score;
}
var disconnectingClients = currentClients.Except(polledClients); var disconnectingClients = currentClients.Except(polledClients);
var connectingClients = polledClients.Except(currentClients.Where(c => c.State == Player.ClientState.Connected)); var connectingClients = polledClients.Except(currentClients);
var updatedClients = polledClients.Except(connectingClients).Except(disconnectingClients);
return new List<Player>[] { connectingClients.ToList(), disconnectingClients.ToList() }; return new List<EFClient>[]
{
connectingClients.ToList(),
disconnectingClients.ToList(),
updatedClients.ToList()
};
} }
DateTime start = DateTime.Now; DateTime start = DateTime.Now;
@ -617,19 +500,30 @@ namespace IW4MAdmin
{ {
try try
{ {
#region SHUTDOWN
if (Manager.ShutdownRequested()) if (Manager.ShutdownRequested())
{ {
// todo: fix up disconnect foreach (var client in GetClientsAsList())
//for (int i = 0; i < Players.Count; i++) {
// await RemovePlayer(i); var e = new GameEvent()
{
Type = GameEvent.EventType.PreDisconnect,
Origin = client,
Owner = this,
};
Manager.GetEventHandler().AddEvent(e);
await e.WaitAsync();
}
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
{
await plugin.OnUnloadAsync(); await plugin.OnUnloadAsync();
} }
// only check every 2 minutes if the server doesn't seem to be responding return true;
/* if ((DateTime.Now - LastPoll).TotalMinutes < 0.5 && ConnectionErrors >= 1) }
return true;*/ #endregion
try try
{ {
@ -638,14 +532,14 @@ namespace IW4MAdmin
foreach (var disconnectingClient in polledClients[1]) foreach (var disconnectingClient in polledClients[1])
{ {
if (disconnectingClient.State == Player.ClientState.Disconnecting) if (disconnectingClient.State == EFClient.ClientState.Disconnecting)
{ {
continue; continue;
} }
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Disconnect, Type = GameEvent.EventType.PreDisconnect,
Origin = disconnectingClient, Origin = disconnectingClient,
Owner = this Owner = this
}; };
@ -656,22 +550,15 @@ namespace IW4MAdmin
waiterList.Add(e); waiterList.Add(e);
} }
// wait for all the disconnect tasks to finish // wait for all the disconnect tasks to finish
await Task.WhenAll(waiterList.Select(e => e.WaitAsync())); await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000)));
waiterList.Clear(); waiterList.Clear();
// this are our new connecting clients // this are our new connecting clients
foreach (var client in polledClients[0]) foreach (var client in polledClients[0])
{ {
// this prevents duplicate events from being sent to the event api
if (GetPlayersAsList().Count(c => c.NetworkId == client.NetworkId &&
c.State == Player.ClientState.Connected) != 0)
{
continue;
}
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Connect, Type = GameEvent.EventType.PreConnect,
Origin = client, Origin = client,
Owner = this Owner = this
}; };
@ -681,13 +568,31 @@ namespace IW4MAdmin
} }
// wait for all the connect tasks to finish // wait for all the connect tasks to finish
await Task.WhenAll(waiterList.Select(e => e.WaitAsync())); await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000)));
waiterList.Clear();
// these are the clients that have updated
foreach (var client in polledClients[2])
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Update,
Origin = client,
Owner = this
};
Manager.GetEventHandler().AddEvent(e);
waiterList.Add(e);
}
await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000)));
if (ConnectionErrors > 0) if (ConnectionErrors > 0)
{ {
Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}"); Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}");
Throttled = false; Throttled = false;
} }
ConnectionErrors = 0; ConnectionErrors = 0;
LastPoll = DateTime.Now; LastPoll = DateTime.Now;
} }
@ -695,7 +600,7 @@ namespace IW4MAdmin
catch (NetworkException e) catch (NetworkException e)
{ {
ConnectionErrors++; ConnectionErrors++;
if (ConnectionErrors == 1) if (ConnectionErrors == 3)
{ {
Logger.WriteError($"{e.Message} {IP}:{Port}, {loc["SERVER_ERROR_POLLING"]}"); Logger.WriteError($"{e.Message} {IP}:{Port}, {loc["SERVER_ERROR_POLLING"]}");
Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}"); Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}");
@ -707,26 +612,15 @@ namespace IW4MAdmin
LastMessage = DateTime.Now - start; LastMessage = DateTime.Now - start;
lastCount = DateTime.Now; lastCount = DateTime.Now;
// todo: re-enable on tick
/*
if ((DateTime.Now - tickTime).TotalMilliseconds >= 1000)
{
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
{
if (cts.IsCancellationRequested)
break;
await Plugin.OnTickAsync(this);
}
tickTime = DateTime.Now;
}*/
// update the player history // update the player history
if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval)
{ {
while (PlayerHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours while (ClientHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours
PlayerHistory.Dequeue(); {
PlayerHistory.Enqueue(new SharedLibraryCore.Helpers.PlayerHistory(ClientNum)); ClientHistory.Dequeue();
}
ClientHistory.Enqueue(new SharedLibraryCore.Helpers.PlayerHistory(ClientNum));
playerCountStart = DateTime.Now; playerCountStart = DateTime.Now;
} }
@ -776,7 +670,9 @@ namespace IW4MAdmin
new IW3RConParser(); new IW3RConParser();
if (ServerConfig.UseIW5MParser) if (ServerConfig.UseIW5MParser)
{
RconParser = new IW5MRConParser(); RconParser = new IW5MRConParser();
}
var version = await this.GetDvarAsync<string>("version"); var version = await this.GetDvarAsync<string>("version");
GameName = Utilities.GetGame(version.Value); GameName = Utilities.GetGame(version.Value);
@ -787,16 +683,26 @@ namespace IW4MAdmin
RconParser = new IW4RConParser(); RconParser = new IW4RConParser();
} }
else if (GameName == Game.IW5) else if (GameName == Game.IW5)
{
EventParser = new IW5EventParser(); EventParser = new IW5EventParser();
}
else if (GameName == Game.T5M) else if (GameName == Game.T5M)
{
EventParser = new T5MEventParser(); EventParser = new T5MEventParser();
}
else if (GameName == Game.T6M) else if (GameName == Game.T6M)
{
EventParser = new T6MEventParser(); EventParser = new T6MEventParser();
}
else else
{
EventParser = new IW3EventParser(); // this uses the 'main' folder for log paths EventParser = new IW3EventParser(); // this uses the 'main' folder for log paths
}
if (GameName == Game.UKN) if (GameName == Game.UKN)
{
Logger.WriteWarning($"Game name not recognized: {version}"); Logger.WriteWarning($"Game name not recognized: {version}");
}
var infoResponse = await this.GetInfoAsync(); var infoResponse = await this.GetInfoAsync();
// this is normally slow, but I'm only doing it because different games have different prefixes // this is normally slow, but I'm only doing it because different games have different prefixes
@ -820,6 +726,7 @@ namespace IW4MAdmin
infoResponse["fs_game"]; infoResponse["fs_game"];
var logfile = await this.GetDvarAsync<string>("g_log"); var logfile = await this.GetDvarAsync<string>("g_log");
var logsync = await this.GetDvarAsync<int>("g_logsync"); var logsync = await this.GetDvarAsync<int>("g_logsync");
var ip = await this.GetDvarAsync<string>("net_ip");
WorkingDirectory = basepath.Value; WorkingDirectory = basepath.Value;
@ -841,6 +748,8 @@ namespace IW4MAdmin
this.MaxClients = maxplayers; this.MaxClients = maxplayers;
this.FSGame = game; this.FSGame = game;
this.Gametype = gametype; this.Gametype = gametype;
this.IP = ip.Value == "localhost" ? ServerConfig.IPAddress : ip.Value;
if (logsync.Value == 0 || logfile.Value == string.Empty) if (logsync.Value == 0 || logfile.Value == string.Empty)
{ {
// this DVAR isn't set until the a map is loaded // this DVAR isn't set until the a map is loaded
@ -855,7 +764,7 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded(); CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir(); string mainPath = EventParser.GetGameDir();
#if DEBUG #if DEBUG
// basepath.Value = @"D:\"; // basepath.Value = @"D:\";
#endif #endif
string logPath = string.Empty; string logPath = string.Empty;
@ -900,7 +809,7 @@ namespace IW4MAdmin
#endif #endif
} }
protected override async Task Warn(String Reason, Player Target, Player Origin) protected override async Task Warn(String Reason, EFClient Target, EFClient 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)
@ -942,7 +851,7 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().Create(newPenalty); await Manager.GetPenaltyService().Create(newPenalty);
} }
protected override async Task Kick(String Reason, Player Target, Player Origin) protected override async Task Kick(String Reason, EFClient Target, EFClient Origin)
{ {
// ensure player gets kicked if command not performed on them in game // ensure player gets kicked if command not performed on them in game
if (Target.ClientNumber < 0) if (Target.ClientNumber < 0)
@ -965,7 +874,7 @@ namespace IW4MAdmin
#endif #endif
#if DEBUG #if DEBUG
await Target.CurrentServer.RemovePlayer(Target.ClientNumber); await Target.CurrentServer.OnClientDisconnected(Target);
#endif #endif
var newPenalty = new Penalty() var newPenalty = new Penalty()
@ -982,7 +891,7 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().Create(newPenalty); await Manager.GetPenaltyService().Create(newPenalty);
} }
protected override async Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin) protected override async Task TempBan(String Reason, TimeSpan length, EFClient Target, EFClient Origin)
{ {
// ensure player gets banned if command not performed on them in game // ensure player gets banned if command not performed on them in game
if (Target.ClientNumber < 0) if (Target.ClientNumber < 0)
@ -1003,7 +912,7 @@ namespace IW4MAdmin
await Target.CurrentServer.ExecuteCommandAsync(formattedKick); await Target.CurrentServer.ExecuteCommandAsync(formattedKick);
} }
#else #else
await Target.CurrentServer.RemovePlayer(Target.ClientNumber); await Target.CurrentServer.OnClientDisconnected(Target);
#endif #endif
Penalty newPenalty = new Penalty() Penalty newPenalty = new Penalty()
@ -1021,16 +930,16 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().Create(newPenalty); await Manager.GetPenaltyService().Create(newPenalty);
} }
override protected async Task Ban(String Message, Player Target, Player Origin) override protected async Task Ban(String Message, EFClient Target, EFClient Origin)
{ {
// ensure player gets banned if command not performed on them in game // ensure player gets banned if command not performed on them in game
if (Target.ClientNumber < 0) if (Target.ClientNumber < 0)
{ {
Player ingameClient = null; EFClient ingameClient = null;
ingameClient = Manager.GetServers() ingameClient = Manager.GetServers()
.Select(s => s.GetPlayersAsList()) .Select(s => s.GetClientsAsList())
.FirstOrDefault(l => l.FirstOrDefault(c => c.ClientId == Target.ClientId) != null) .FirstOrDefault(l => l.FirstOrDefault(c => c.ClientId == Target?.ClientId) != null)
?.First(c => c.ClientId == Target.ClientId); ?.First(c => c.ClientId == Target.ClientId);
if (ingameClient != null) if (ingameClient != null)
@ -1043,40 +952,38 @@ namespace IW4MAdmin
else else
{ {
// this is set only because they're still in the server. // this is set only because they're still in the server.
Target.Level = Player.Permission.Banned; Target.Level = EFClient.Permission.Banned;
#if !DEBUG #if !DEBUG
string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7"); string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7");
await Target.CurrentServer.ExecuteCommandAsync(formattedString); await Target.CurrentServer.ExecuteCommandAsync(formattedString);
#else #else
await Target.CurrentServer.RemovePlayer(Target.ClientNumber); await Target.CurrentServer.OnClientDisconnected(Target);
#endif #endif
} }
Penalty newPenalty = new Penalty() Penalty newPenalty = new Penalty()
{ {
Type = Penalty.PenaltyType.Ban, Type = Penalty.PenaltyType.Ban,
Expires = DateTime.MaxValue, Expires = null,
Offender = Target, Offender = Target,
Offense = Message, Offense = Message,
Punisher = Origin, Punisher = Origin,
Active = true, Active = true,
When = DateTime.UtcNow, When = DateTime.UtcNow,
Link = Target.AliasLink, Link = Target.AliasLink,
AutomatedOffense = Origin.AdministeredPenalties.FirstOrDefault()?.AutomatedOffense AutomatedOffense = Origin.AdministeredPenalties?.FirstOrDefault()?.AutomatedOffense
}; };
await Manager.GetPenaltyService().Create(newPenalty); await Manager.GetPenaltyService().Create(newPenalty);
// prevent them from logging in again
Manager.GetPrivilegedClients().Remove(Target.ClientId);
} }
override public async Task Unban(string reason, Player Target, Player Origin) override public async Task Unban(string reason, EFClient Target, EFClient Origin)
{ {
var unbanPenalty = new Penalty() var unbanPenalty = new Penalty()
{ {
Type = Penalty.PenaltyType.Unban, Type = Penalty.PenaltyType.Unban,
Expires = DateTime.UtcNow, Expires = null,
Offender = Target, Offender = Target,
Offense = reason, Offense = reason,
Punisher = Origin, Punisher = Origin,

View File

@ -39,6 +39,8 @@ namespace IW4MAdmin.Application
try try
{ {
ServerManager = ApplicationManager.GetInstance();
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
loc = Utilities.CurrentLocalization.LocalizationIndex; loc = Utilities.CurrentLocalization.LocalizationIndex;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
@ -47,10 +49,6 @@ namespace IW4MAdmin.Application
// todo: move out // todo: move out
ConfigurationMigration.MoveConfigFolder10518(null); ConfigurationMigration.MoveConfigFolder10518(null);
ServerManager = ApplicationManager.GetInstance();
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
ServerManager.Logger.WriteInfo($"Version is {Version}"); ServerManager.Logger.WriteInfo($"Version is {Version}");
var api = API.Master.Endpoint.Get(); var api = API.Master.Endpoint.Get();
@ -112,7 +110,7 @@ namespace IW4MAdmin.Application
var consoleTask = Task.Run(async () => var consoleTask = Task.Run(async () =>
{ {
String userInput; String userInput;
Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
do do
{ {

View File

@ -1,24 +1,21 @@
using System; using IW4MAdmin.Application.API.Master;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Reflection;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Commands; using SharedLibraryCore.Commands;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events; using SharedLibraryCore.Events;
using SharedLibraryCore.Exceptions;
using IW4MAdmin.Application.API.Master; using SharedLibraryCore.Helpers;
using IW4MAdmin.Application.Migration; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
@ -26,7 +23,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, EFClient> PrivilegedClients { get; set; }
public ILogger Logger => GetLogger(0); 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; }
@ -49,7 +46,7 @@ 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>(); readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
private ApplicationManager() private ApplicationManager()
{ {
@ -60,7 +57,6 @@ namespace IW4MAdmin.Application
ClientSvc = new ClientService(); ClientSvc = new ClientService();
AliasSvc = new AliasService(); AliasSvc = new AliasService();
PenaltySvc = new PenaltyService(); PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>();
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow; StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim(); OnQuit = new ManualResetEventSlim();
@ -85,80 +81,11 @@ namespace IW4MAdmin.Application
try try
{ {
// if the origin client is not in an authorized state (detected by RCon) don't execute the event await newEvent.Owner.ExecuteEvent(newEvent);
if (GameEvent.ShouldOriginEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying origin execution of event type {newEvent.Type} for {newEvent.Origin} because they are not authed");
if (newEvent.Type == GameEvent.EventType.Command)
{
newEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_DELAYED_EVENT_WAIT"]);
}
// offload it to the player to keep // save the event info to the database
newEvent.Origin.DelayedEvents.Enqueue(newEvent); var changeHistorySvc = new ChangeHistoryService();
} await changeHistorySvc.Add(args.Event);
// if the target client is not in an authorized state (detected by RCon) don't execute the event
else if (GameEvent.ShouldTargetEventBeDelayed(newEvent))
{
Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed");
// offload it to the player to keep
newEvent.Target.DelayedEvents.Enqueue(newEvent);
}
else
{
await newEvent.Owner.ExecuteEvent(newEvent);
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
(//newEvent.Origin?.State == Player.ClientState.Connected ||
newEvent.Type == GameEvent.EventType.Connect))
{
var events = newEvent.Origin.DelayedEvents;
// add the delayed event to the queue
while (events.Count > 0)
{
var oldEvent = events.Dequeue();
var e = new GameEvent()
{
Type = oldEvent.Type,
Origin = newEvent.Origin,
Data = oldEvent.Data,
Extra = oldEvent.Extra,
Owner = oldEvent.Owner,
Message = oldEvent.Message,
Target = oldEvent.Target,
Remote = oldEvent.Remote
};
e.Origin = newEvent.Origin;
// check if the target was assigned
if (e.Target != null)
{
// update the target incase they left or have newer info
e.Target = newEvent.Owner.GetPlayersAsList()
.FirstOrDefault(p => p.NetworkId == e.Target.NetworkId);
// we have to throw out the event because they left
if (e.Target == null)
{
Logger.WriteWarning($"Delayed event for {e.Origin} was ignored because the target has left");
// hack: don't do anything with the event because the target is invalid
e.Type = GameEvent.EventType.Unknown;
}
}
Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing");
this.GetEventHandler().AddEvent(e);
}
}
}
#if DEBUG #if DEBUG
Logger.WriteDebug($"Processed event with id {newEvent.Id}"); Logger.WriteDebug($"Processed event with id {newEvent.Id}");
@ -192,7 +119,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug(ex.GetExceptionInfo()); Logger.WriteDebug(ex.GetExceptionInfo());
} }
skip: skip:
// tell anyone waiting for the output that we're done // tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set(); newEvent.OnProcessed.Set();
@ -216,7 +143,7 @@ namespace IW4MAdmin.Application
public async Task UpdateServerStates() public async Task UpdateServerStates()
{ {
// store the server hash code and task for it // store the server hash code and task for it
var runningUpdateTasks = new Dictionary<int, Task>(); var runningUpdateTasks = new Dictionary<long, Task>();
while (Running) while (Running)
{ {
@ -236,16 +163,16 @@ namespace IW4MAdmin.Application
} }
// remove the update tasks as they have completd // remove the update tasks as they have completd
foreach (int serverId in serverTasksToRemove) foreach (long serverId in serverTasksToRemove)
{ {
runningUpdateTasks.Remove(serverId); runningUpdateTasks.Remove(serverId);
} }
// select the servers where the tasks have completed // select the servers where the tasks have completed
var serverIds = Servers.Select(s => s.GetHashCode()).Except(runningUpdateTasks.Select(r => r.Key)).ToList(); var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
foreach (var server in Servers.Where(s => serverIds.Contains(s.GetHashCode()))) foreach (var server in Servers.Where(s => serverIds.Contains(s.EndPoint)))
{ {
runningUpdateTasks.Add(server.GetHashCode(), Task.Run(async () => runningUpdateTasks.Add(server.EndPoint, Task.Run(async () =>
{ {
try try
{ {
@ -265,11 +192,7 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m); ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks"); Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif #endif
#if DEBUG
await Task.Delay(10000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate); await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
} }
// trigger the event processing loop to end // trigger the event processing loop to end
@ -286,36 +209,7 @@ namespace IW4MAdmin.Application
await new ContextSeed(db).Seed(); await new ContextSeed(db).Seed();
} }
// todo: optimize this (or replace it) PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new
{
c.Password,
c.PasswordSalt,
c.ClientId,
c.Level,
c.Name
});
foreach (var a in ipList)
{
try
{
PrivilegedClients.Add(a.ClientId, new Player()
{
Name = a.Name,
ClientId = a.ClientId,
Level = a.Level,
PasswordSalt = a.PasswordSalt,
Password = a.Password
});
}
catch (ArgumentException)
{
continue;
}
}
#endregion #endregion
#region CONFIG #region CONFIG
@ -364,7 +258,9 @@ namespace IW4MAdmin.Application
} }
else if (config.Servers.Count == 0) else if (config.Servers.Count == 0)
{
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid"); throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252"); Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
@ -390,7 +286,9 @@ namespace IW4MAdmin.Application
#region COMMANDS #region COMMANDS
if (ClientSvc.GetOwners().Result.Count == 0) if (ClientSvc.GetOwners().Result.Count == 0)
{
Commands.Add(new COwner()); Commands.Add(new COwner());
}
Commands.Add(new CQuit()); Commands.Add(new CQuit());
Commands.Add(new CKick()); Commands.Add(new CKick());
@ -431,7 +329,9 @@ namespace IW4MAdmin.Application
Commands.Add(new CNextMap()); Commands.Add(new CNextMap());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands) foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{
Commands.Add(C); Commands.Add(C);
}
#endregion #endregion
#region INIT #region INIT
@ -466,7 +366,9 @@ namespace IW4MAdmin.Application
{ {
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]"); Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
if (e.GetType() == typeof(DvarException)) if (e.GetType() == typeof(DvarException))
{
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})"); Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
}
else if (e.GetType() == typeof(NetworkException)) else if (e.GetType() == typeof(NetworkException))
{ {
Logger.WriteDebug(e.Message); Logger.WriteDebug(e.Message);
@ -565,7 +467,7 @@ namespace IW4MAdmin.Application
Running = false; Running = false;
} }
public ILogger GetLogger(int serverId) public ILogger GetLogger(long serverId)
{ {
if (Loggers.ContainsKey(serverId)) if (Loggers.ContainsKey(serverId))
{ {
@ -595,23 +497,59 @@ namespace IW4MAdmin.Application
return MessageTokens; return MessageTokens;
} }
public IList<Player> GetActiveClients() => _servers.SelectMany(s => s.Players).Where(p => p != null).ToList(); public IList<EFClient> GetActiveClients()
{
return _servers.SelectMany(s => s.Clients).Where(p => p != null).ToList();
}
public ClientService GetClientService() => ClientSvc; public ClientService GetClientService()
public AliasService GetAliasService() => AliasSvc; {
public PenaltyService GetPenaltyService() => PenaltySvc; return ClientSvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler; }
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public bool ShutdownRequested() => !Running; public AliasService GetAliasService()
public IEventHandler GetEventHandler() => Handler; {
return AliasSvc;
}
public PenaltyService GetPenaltyService()
{
return PenaltySvc;
}
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings()
{
return ConfigHandler;
}
public IDictionary<int, EFClient> GetPrivilegedClients()
{
return PrivilegedClients;
}
public bool ShutdownRequested()
{
return !Running;
}
public IEventHandler GetEventHandler()
{
return Handler;
}
public void SetHasEvent() public void SetHasEvent()
{ {
OnQuit.Set(); OnQuit.Set();
} }
public IList<Assembly> GetPluginAssemblies() => SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies; public IList<Assembly> GetPluginAssemblies()
{
return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
}
public IPageList GetPageList() => PageList; public IPageList GetPageList()
{
return PageList;
}
} }
} }

View File

@ -1,14 +1,13 @@
using System; using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
namespace IW4MAdmin.Application.RconParsers namespace IW4MAdmin.Application.RconParsers
{ {
@ -61,7 +60,7 @@ namespace IW4MAdmin.Application.RconParsers
}; };
} }
public async Task<List<Player>> GetStatusAsync(Connection connection) public async Task<List<EFClient>> GetStatusAsync(Connection connection)
{ {
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response); return ClientsFromStatus(response);
@ -72,14 +71,19 @@ namespace IW4MAdmin.Application.RconParsers
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0; return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0;
} }
public virtual CommandPrefix GetCommandPrefixes() => Prefixes; public virtual CommandPrefix GetCommandPrefixes()
private List<Player> ClientsFromStatus(string[] Status)
{ {
List<Player> StatusPlayers = new List<Player>(); return Prefixes;
}
private List<EFClient> ClientsFromStatus(string[] Status)
{
List<EFClient> StatusPlayers = new List<EFClient>();
if (Status.Length < 4) if (Status.Length < 4)
{
throw new ServerException("Unexpected status response received"); throw new ServerException("Unexpected status response received");
}
int validMatches = 0; int validMatches = 0;
foreach (String S in Status) foreach (String S in Status)
@ -102,20 +106,28 @@ namespace IW4MAdmin.Application.RconParsers
ping = int.Parse(regex.Groups[3].Value); ping = int.Parse(regex.Groups[3].Value);
} }
else
{
continue;
}
long networkId = regex.Groups[4].Value.ConvertLong(); long networkId = regex.Groups[4].Value.ConvertLong();
string name = regex.Groups[5].Value.StripColors().Trim(); string name = regex.Groups[5].Value.StripColors().Trim();
int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP(); int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
Player P = new Player() var P = new EFClient()
{ {
Name = name, CurrentAlias = new EFAlias()
{
Name = name
},
NetworkId = networkId, NetworkId = networkId,
ClientNumber = clientNumber, ClientNumber = clientNumber,
IPAddress = ip == 0 ? int.MinValue : ip, IPAddress = ip == 0 ? int.MinValue : ip,
Ping = ping, Ping = ping,
Score = score, Score = score,
IsBot = ip == 0, IsBot = ip == 0,
State = Player.ClientState.Connecting State = EFClient.ClientState.Connecting
}; };
StatusPlayers.Add(P); StatusPlayers.Add(P);
} }

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon; using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Application.RconParsers namespace IW4MAdmin.Application.RconParsers
{ {
@ -105,7 +106,7 @@ namespace IW4MAdmin.Application.RconParsers
}; };
} }
public async Task<List<Player>> GetStatusAsync(Connection connection) public async Task<List<EFClient>> GetStatusAsync(Connection connection)
{ {
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response); return ClientsFromStatus(response);
@ -118,9 +119,9 @@ namespace IW4MAdmin.Application.RconParsers
return true; return true;
} }
private List<Player> ClientsFromStatus(string[] status) private List<EFClient> ClientsFromStatus(string[] status)
{ {
List<Player> StatusPlayers = new List<Player>(); List<EFClient> StatusPlayers = new List<EFClient>();
foreach (string statusLine in status) foreach (string statusLine in status)
{ {
@ -145,7 +146,7 @@ namespace IW4MAdmin.Application.RconParsers
regex = Regex.Match(responseLine, @" +(\d+ +){3}"); regex = Regex.Match(responseLine, @" +(\d+ +){3}");
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]); int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
var p = new Player() var p = new EFClient()
{ {
Name = name, Name = name,
NetworkId = networkId, NetworkId = networkId,
@ -154,7 +155,7 @@ namespace IW4MAdmin.Application.RconParsers
Ping = Ping, Ping = Ping,
Score = score, Score = score,
IsBot = false, IsBot = false,
State = Player.ClientState.Connecting State = EFClient.ClientState.Connecting
}; };
StatusPlayers.Add(p); StatusPlayers.Add(p);

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon; using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using System.Text; using System.Text;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Application.RconParsers namespace IW4MAdmin.Application.RconParsers
{ {
@ -18,9 +19,9 @@ namespace IW4MAdmin.Application.RconParsers
{ {
Tell = "tell {0} {1}", Tell = "tell {0} {1}",
Say = "say {0}", Say = "say {0}",
Kick = "clientKick {0}", Kick = "clientkick_for_reason {0} \"{1}\"",
Ban = "clientKick {0}", Ban = "clientkick_for_reason {0} \"{1}\"",
TempBan = "clientKick {0}" TempBan = "clientkick_for_reason {0} \"{1}\""
}; };
public CommandPrefix GetCommandPrefixes() => Prefixes; public CommandPrefix GetCommandPrefixes() => Prefixes;
@ -60,7 +61,7 @@ namespace IW4MAdmin.Application.RconParsers
}; };
} }
public async Task<List<Player>> GetStatusAsync(Connection connection) public async Task<List<EFClient>> GetStatusAsync(Connection connection)
{ {
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status"); string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
return ClientsFromStatus(response); return ClientsFromStatus(response);
@ -73,9 +74,9 @@ namespace IW4MAdmin.Application.RconParsers
return true; return true;
} }
private List<Player> ClientsFromStatus(string[] status) private List<EFClient> ClientsFromStatus(string[] status)
{ {
List<Player> StatusPlayers = new List<Player>(); List<EFClient> StatusPlayers = new List<EFClient>();
foreach (string statusLine in status) foreach (string statusLine in status)
{ {
@ -98,7 +99,7 @@ namespace IW4MAdmin.Application.RconParsers
#endif #endif
int ipAddress = regex.Value.Split(':')[0].ConvertToIP(); int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
var p = new Player() var p = new EFClient()
{ {
Name = name, Name = name,
NetworkId = networkId, NetworkId = networkId,
@ -106,7 +107,7 @@ namespace IW4MAdmin.Application.RconParsers
IPAddress = ipAddress, IPAddress = ipAddress,
Ping = Ping, Ping = Ping,
Score = 0, Score = 0,
State = Player.ClientState.Connecting, State = EFClient.ClientState.Connecting,
IsBot = networkId == 0 IsBot = networkId == 0
}; };

View File

@ -0,0 +1,31 @@
var plugin = {
author: 'RaidMax',
version: 1.1,
name: 'Shared GUID Kicker Plugin',
onEventAsync: function (gameEvent, server) {
// make sure we only check for IW4(x)
if (server.GameName !== 2) {
return false;
}
// connect or join event
if (gameEvent.Type === 3) {
// 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) {
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
}
}
},
onLoadAsync: function (manager) {
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -0,0 +1,62 @@
var plugin = {
author: 'RaidMax',
version: 1.0,
name: 'VPN Detection Plugin',
manager: null,
logger: null,
vpnExceptionIds: [],
checkForVpn: function (origin) {
var exempt = false;
// prevent players that are exempt from being kicked
this.vpnExceptionIds.forEach(function (id) {
if (id === origin.ClientId) {
exempt = true;
return false;
}
});
if (exempt) {
return;
}
var usingVPN = false;
try {
var cl = new System.Net.Http.HttpClient();
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
var co = re.Content;
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
co.Dispose();
re.Dispose();
cl.Dispose();
usingVPN = parsedJSON.success && parsedJSON.proxy;
} catch (e) {
this.logger.WriteError(e.message);
}
if (usingVPN) {
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
}
},
onEventAsync: function (gameEvent, server) {
// connect event
if (gameEvent.Type === 3) {
this.checkForVpn(gameEvent.Origin);
}
},
onLoadAsync: function (manager) {
this.manager = manager;
this.logger = manager.GetLogger(0);
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -27,6 +27,11 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DiscordWebhook.py" /> <Compile Include="DiscordWebhook.py" />
</ItemGroup> </ItemGroup>
@ -42,7 +47,6 @@
</Interpreter> </Interpreter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="config.dev.json" />
<Content Include="config.json"> <Content Include="config.json">
<Publish>True</Publish> <Publish>True</Publish>
</Content> </Content>
@ -50,7 +54,7 @@
<Publish>True</Publish> <Publish>True</Publish>
</Content> </Content>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in <!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. --> the BeforeBuild and AfterBuild targets below. -->
@ -59,7 +63,7 @@
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
<ProjectExtensions> <ProjectExtensions>
<VisualStudio> <VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}"> <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties> <WebProjectProperties>

View File

@ -27,10 +27,16 @@
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="GameLogServer\log_reader.py"> <Compile Include="GameLogServer\log_reader.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="GameLogServer\restart_resource.py" />
<Compile Include="GameLogServer\server.py"> <Compile Include="GameLogServer\server.py">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
@ -42,8 +48,8 @@
<Folder Include="GameLogServer\" /> <Folder Include="GameLogServer\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="requirements.txt" /> <Content Include="requirements.txt" />
<None Include="Stable.pubxml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Interpreter Include="env\"> <Interpreter Include="env\">

View File

@ -5,26 +5,27 @@ import time
class LogReader(object): class LogReader(object):
def __init__(self): def __init__(self):
self.log_file_sizes = {} self.log_file_sizes = {}
# (if the file changes more than this, ignore ) - 1 MB # (if the file changes more than this, ignore ) - 0.125 MB
self.max_file_size_change = 1000000 self.max_file_size_change = 125000
# (if the time between checks is greater, ignore ) - 5 minutes # (if the time between checks is greater, ignore ) - 5 minutes
self.max_file_time_change = 1000 self.max_file_time_change = 60
def read_file(self, path): def read_file(self, path):
# prevent traversing directories # prevent traversing directories
if re.search('r^.+\.\.\\.+$', path): if re.search('r^.+\.\.\\.+$', path):
return False return False
# must be a valid log path and log file # must be a valid log path and log file
if not re.search(r'^.+[\\|\/](userraw|mods)[\\|\/].+.log$', path): if not re.search(r'^.+[\\|\/](userraw|mods|main)[\\|\/].+.log$', path):
return False return False
# set the initialze size to the current file size # set the initialze size to the current file size
file_size = 0 file_size = 0
if path not in self.log_file_sizes: if path not in self.log_file_sizes:
self.log_file_sizes[path] = { self.log_file_sizes[path] = {
'length' : self.file_length(path), 'length' : self.file_length(path),
'read': time.time() 'read': time.time()
} }
return '' return True
# grab the previous values # grab the previous values
last_length = self.log_file_sizes[path]['length'] last_length = self.log_file_sizes[path]['length']
@ -50,9 +51,9 @@ class LogReader(object):
# if it's been too long since we read and the amount changed is too great, discard it # if it's been too long since we read and the amount changed is too great, discard it
# todo: do we really want old events? maybe make this an "or" # todo: do we really want old events? maybe make this an "or"
if file_size_difference > self.max_file_size_change and time_difference > self.max_file_time_change: if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
return '' return True
new_log_info = self.get_file_lines(path, file_size_difference) new_log_info = self.get_file_lines(path, file_size_difference)
return new_log_info return new_log_info

View File

@ -9,9 +9,11 @@ class LogResource(Resource):
if log_info is False: if log_info is False:
print('could not read log file ' + path) print('could not read log file ' + path)
empty_read = (log_info == False) or (log_info == True)
return { return {
'success' : log_info is not False, 'success' : log_info is not False,
'length': -1 if log_info is False else len(log_info), 'length': -1 if empty_read else len(log_info),
'data': log_info 'data': log_info
} }

View File

@ -0,0 +1,29 @@
from flask_restful import Resource
from flask import request
import requests
import os
import subprocess
import re
def get_pid_of_server_windows(port):
process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE)
output = process.communicate()[0]
matches = re.search(' *(UDP) +([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):'+ str(port) + ' +[^\w]*([0-9]+)', output.decode('utf-8'))
if matches is not None:
return matches.group(3)
else:
return 0
class RestartResource(Resource):
def get(self):
try:
response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved')
if response.status_code == 200:
pid = get_pid_of_server_windows(response.json()['port'])
subprocess.check_output("Taskkill /PID %s /F" % pid)
else:
return {}, 400
except Exception as e:
print(e)
return {}, 500
return {}, 200

View File

@ -1,9 +1,11 @@
from flask import Flask from flask import Flask
from flask_restful import Api from flask_restful import Api
from .log_resource import LogResource from .log_resource import LogResource
from .restart_resource import RestartResource
app = Flask(__name__) app = Flask(__name__)
def init(): def init():
api = Api(app) api = Api(app)
api.add_resource(LogResource, '/log/<string:path>') api.add_resource(LogResource, '/log/<string:path>')
api.add_resource(RestartResource, '/restart')

View File

@ -1,12 +1,26 @@
Flask==1.0.2
aniso8601==3.0.2 aniso8601==3.0.2
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7 click==6.7
Flask==1.0.2
Flask-JWT==0.3.2
Flask-JWT-Extended==3.8.1
Flask-RESTful==0.3.6 Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24 itsdangerous==0.24
Jinja2==2.10 Jinja2==2.10
MarkupSafe==1.0 MarkupSafe==1.0
marshmallow==3.0.0b8
pip==9.0.3 pip==9.0.3
pytz==2018.5 psutil==5.4.8
setuptools==39.0.1 pygal==2.4.0
PyJWT==1.4.2
pytz==2018.7
requests==2.20.0
setuptools==40.5.0
six==1.11.0 six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1 Werkzeug==0.14.1

View File

@ -12,4 +12,4 @@ if __name__ == '__main__':
except ValueError: except ValueError:
PORT = 5555 PORT = 5555
init() init()
app.run(HOST, PORT, debug=True) app.run(HOST, PORT, debug=False)

View File

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.16 VisualStudioVersion = 15.0.26730.16

View File

@ -17,7 +17,7 @@
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles> <SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>Master</Name> <Name>Master</Name>
<RootNamespace>Master</RootNamespace> <RootNamespace>Master</RootNamespace>
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId> <InterpreterId>MSBuild|env|X:\IW4MAdmin\GameLogServer\GameLogServer.pyproj</InterpreterId>
<IsWindowsApplication>False</IsWindowsApplication> <IsWindowsApplication>False</IsWindowsApplication>
<PythonRunWebServerCommand> <PythonRunWebServerCommand>
</PythonRunWebServerCommand> </PythonRunWebServerCommand>
@ -111,23 +111,15 @@
<Folder Include="master\templates\" /> <Folder Include="master\templates\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="master\config\master.json" /> <Content Include="master\config\master.json" />
<Content Include="master\templates\serverlist.html" /> <Content Include="master\templates\serverlist.html" />
<None Include="Release.pubxml" />
<Content Include="requirements.txt" /> <Content Include="requirements.txt" />
<Content Include="master\templates\index.html" /> <Content Include="master\templates\index.html" />
<Content Include="master\templates\layout.html" /> <Content Include="master\templates\layout.html" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Interpreter Include="dev_env\"> <InterpreterReference Include="MSBuild|env|X:\IW4MAdmin\GameLogServer\GameLogServer.pyproj" />
<Id>dev_env</Id>
<Version>3.6</Version>
<Description>dev_env (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
<Architecture>X64</Architecture>
</Interpreter>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<!-- Specify pre- and post-build commands in the BeforeBuild and <!-- Specify pre- and post-build commands in the BeforeBuild and

View File

@ -23,7 +23,8 @@ class Instance(Resource):
def put(self, id): def put(self, id):
try: try:
for server in request.json['servers']: for server in request.json['servers']:
server['ip'] = request.remote_addr if 'ip' not in server or server['ip'] == 'localhost':
server['ip'] = request.remote_addr
instance = InstanceSchema().load(request.json) instance = InstanceSchema().load(request.json)
except ValidationError as err: except ValidationError as err:
return {'message' : err.messages }, 400 return {'message' : err.messages }, 400
@ -34,7 +35,8 @@ class Instance(Resource):
def post(self): def post(self):
try: try:
for server in request.json['servers']: for server in request.json['servers']:
server['ip'] = request.remote_addr if 'ip' not in server or server['ip'] == 'localhost':
server['ip'] = request.remote_addr
instance = InstanceSchema().load(request.json) instance = InstanceSchema().load(request.json)
except ValidationError as err: except ValidationError as err:
return {'message' : err.messages }, 400 return {'message' : err.messages }, 400

View File

@ -4,7 +4,7 @@ from master.models.servermodel import ServerModel
class ServerSchema(Schema): class ServerSchema(Schema):
id = fields.Int( id = fields.Int(
required=True, required=True,
validate=validate.Range(1, 2147483647, 'invalid id') validate=validate.Range(1, 25525525525565535, 'invalid id')
) )
ip = fields.Str( ip = fields.Str(
required=True required=True

View File

@ -1,16 +1,26 @@
aniso8601==3.0.0 aniso8601==3.0.2
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7 click==6.7
Flask==0.12.2 Flask==1.0.2
Flask-JWT==0.3.2 Flask-JWT==0.3.2
Flask-JWT-Extended==3.8.1 Flask-JWT-Extended==3.8.1
Flask-RESTful==0.3.6 Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24 itsdangerous==0.24
Jinja2==2.10 Jinja2==2.10
MarkupSafe==1.0 MarkupSafe==1.0
marshmallow==3.0.0b8 marshmallow==3.0.0b8
pip==9.0.1 pip==9.0.3
psutil==5.4.8
pygal==2.4.0
PyJWT==1.4.2 PyJWT==1.4.2
pytz==2018.4 pytz==2018.7
setuptools==39.0.1 requests==2.20.0
setuptools==40.5.0
six==1.11.0 six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1 Werkzeug==0.14.1

View File

@ -1,197 +1,200 @@
//using SharedLibraryCore; using SharedLibraryCore;
//using SharedLibraryCore.Objects; using SharedLibraryCore.Database.Models;
//using System; using SharedLibraryCore.Objects;
//using System.Collections.Generic; using System;
//using System.Linq; using System.Collections.Generic;
//using System.Text; using System.Linq;
//using System.Threading.Tasks; using System.Text;
using System.Threading.Tasks;
//namespace IW4ScriptCommands.Commands namespace IW4ScriptCommands.Commands
//{ {
// class Balance : Command class Balance
// { {
// private class TeamAssignment private class TeamAssignment
// { {
// public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; } public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; }
// public int Num { get; set; } public int Num { get; set; }
// public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; } public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
// } }
// public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
// {
// }
// public override async Task ExecuteAsync(GameEvent E) public static string GetTeamAssignments(EFClient client, bool isDisconnect, Server server, string teamsString = "")
// { {
// string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value; var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Split(','))
.Select(c => new TeamAssignment()
{
CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]),
Num = server.GetClientsAsList().FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0]))?.ClientNumber ?? -1,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(server.Clients.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, server.EndPoint)
})
.ToList();
// var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries) // at least one team is full so we can't balance
// .Select(c => c.Split(',')) if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(server.MaxClients / 2.0)
// .Select(c => new TeamAssignment() || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(server.MaxClients / 2.0))
// { {
// CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]), // E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
// Num = E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong())?.ClientNumber ?? -1, return string.Empty;
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong()).ClientId, E.Owner.GetHashCode()) }
// })
// .ToList();
// // at least one team is full so we can't balance List<string> teamAssignments = new List<string>();
// if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(E.Owner.MaxClients / 2.0)
// || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(E.Owner.MaxClients / 2.0))
// {
// await E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
// return;
// }
// List<string> teamAssignments = new List<string>(); var _c = server.GetClientsAsList();
if (isDisconnect && client != null)
{
_c = _c.Where(c => c.ClientNumber != client.ClientNumber).ToList();
}
// var activeClients = E.Owner.GetPlayersAsList().Select(c => new TeamAssignment() var activeClients = _c.Select(c => new TeamAssignment()
// { {
// Num = c.ClientNumber, Num = c.ClientNumber,
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()), Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint),
// CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint).Team
// }) })
// .Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator) .Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator)
// .Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam) .Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam)
// .OrderByDescending(c => c.Stats.Performance) .OrderByDescending(c => c.Stats.Performance)
// .ToList(); .ToList();
// var alliesTeam = scriptClientTeams var alliesTeam = scriptClientTeams
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies)
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0) .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
// .ToList(); .ToList();
// var axisTeam = scriptClientTeams var axisTeam = scriptClientTeams
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis)
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0) .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
// .ToList(); .ToList();
// while (activeClients.Count() > 0) while (activeClients.Count() > 0)
// { {
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count; int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 - double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0; axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
// if (teamSizeDifference == 0) if (teamSizeDifference == 0)
// { {
// if (performanceDisparity == 0) if (performanceDisparity == 0)
// { {
// alliesTeam.Add(activeClients.First()); alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// axisTeam.Add(activeClients.First()); axisTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// alliesTeam.Add(activeClients.First()); alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// } }
// } }
// else if (teamSizeDifference > 0) else if (teamSizeDifference > 0)
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// axisTeam.Add(activeClients.First()); axisTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// axisTeam.Add(activeClients.Last()); axisTeam.Add(activeClients.Last());
// activeClients.RemoveAt(activeClients.Count - 1); activeClients.RemoveAt(activeClients.Count - 1);
// } }
// } }
// else else
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// alliesTeam.Add(activeClients.First()); alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0); activeClients.RemoveAt(0);
// } }
// else else
// { {
// alliesTeam.Add(activeClients.Last()); alliesTeam.Add(activeClients.Last());
// activeClients.RemoveAt(activeClients.Count - 1); activeClients.RemoveAt(activeClients.Count - 1);
// } }
// } }
// } }
// alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance) alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance)
// .ToList(); .ToList();
// axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance) axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance)
// .ToList(); .ToList();
// while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1) while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1)
// { {
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count; int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 - double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0; axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
// if (teamSizeDifference > 0) if (teamSizeDifference > 0)
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// axisTeam.Add(alliesTeam.First()); axisTeam.Add(alliesTeam.First());
// alliesTeam.RemoveAt(0); alliesTeam.RemoveAt(0);
// } }
// else else
// { {
// axisTeam.Add(alliesTeam.Last()); axisTeam.Add(alliesTeam.Last());
// alliesTeam.RemoveAt(axisTeam.Count - 1); alliesTeam.RemoveAt(axisTeam.Count - 1);
// } }
// } }
// else else
// { {
// if (performanceDisparity > 0) if (performanceDisparity > 0)
// { {
// alliesTeam.Add(axisTeam.Last()); alliesTeam.Add(axisTeam.Last());
// axisTeam.RemoveAt(axisTeam.Count - 1); axisTeam.RemoveAt(axisTeam.Count - 1);
// } }
// else else
// { {
// alliesTeam.Add(axisTeam.First()); alliesTeam.Add(axisTeam.First());
// axisTeam.RemoveAt(0); axisTeam.RemoveAt(0);
// } }
// } }
// } }
// foreach (var assignment in alliesTeam) foreach (var assignment in alliesTeam)
// { {
// teamAssignments.Add($"{assignment.Num},2"); teamAssignments.Add($"{assignment.Num},2");
// assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies; assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies;
// } }
// foreach (var assignment in axisTeam)
// {
// teamAssignments.Add($"{assignment.Num},3");
// assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
// }
// if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 && foreach (var assignment in axisTeam)
// axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0) {
// { teamAssignments.Add($"{assignment.Num},3");
// await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]); assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
// return; }
// }
// if (E.Origin?.Level > Player.Permission.Administrator) //if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
// { // axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
// await E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}"); //{
// await E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}"); // //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
// } // return string.Empty;
//}
// string args = string.Join(",", teamAssignments); //if (E.Origin?.Level > Player.Permission.Administrator)
// await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\""); //{
// await E.Origin.Tell("Balance command sent"); // E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
// } // E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
// } //}
//}
//E.Origin.Tell("Balance command sent");
string args = string.Join(",", teamAssignments);
return args;
}
}
}

View File

@ -0,0 +1,54 @@
using IW4ScriptCommands.Commands;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers.API
{
[Route("api/gsc/[action]")]
public class GscApiController : ApiController
{
[HttpGet("{networkId}")]
public IActionResult ClientInfo(string networkId)
{
var clientInfo = Manager.GetActiveClients()
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
if (clientInfo != null)
{
var sb = new StringBuilder();
sb.AppendLine($"admin={clientInfo.IsPrivileged()}");
sb.AppendLine($"level={(int)clientInfo.Level}");
sb.AppendLine($"levelstring={clientInfo.Level.ToLocalizedLevelName()}");
sb.AppendLine($"connections={clientInfo.Connections}");
sb.AppendLine($"authenticated={clientInfo.GetAdditionalProperty<bool>("IsLoggedIn") == true}");
return Content(sb.ToString());
}
return Content("");
}
[HttpGet("{networkId}")]
public IActionResult GetTeamAssignments(string networkId, int serverId, string teams = "", bool isDisconnect = false)
{
return Unauthorized();
var client = Manager.GetActiveClients()
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
var server = Manager.GetServers().First(c => c.EndPoint == serverId);
teams = teams ?? string.Empty;
string assignments = Balance.GetTeamAssignments(client, isDisconnect, server, teams);
return Content(assignments);
}
}
}

View File

@ -6,6 +6,7 @@
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon /> <ApplicationIcon />
<StartupObject /> <StartupObject />
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -17,15 +17,10 @@ namespace IW4ScriptCommands
public Task OnEventAsync(GameEvent E, Server S) public Task OnEventAsync(GameEvent E, Server S)
{ {
//if (E.Type == GameEvent.EventType.JoinTeam || E.Type == GameEvent.EventType.Disconnect) if (E.Type == GameEvent.EventType.Start)
//{ {
// E.Origin = new SharedLibraryCore.Objects.Player() return S.SetDvarAsync("sv_iw4madmin_serverid", S.EndPoint);
// { }
// ClientId = 1,
// CurrentServer = E.Owner
// };
// return new Commands.Balance().ExecuteAsync(E);
//}
if (E.Type == GameEvent.EventType.Warn) if (E.Type == GameEvent.EventType.Warn)
{ {

View File

@ -1,4 +1,5 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -8,7 +9,7 @@ namespace IW4MAdmin.Plugins.Login.Commands
{ {
public class CLogin : Command public class CLogin : Command
{ {
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", Player.Permission.Trusted, false, new CommandArgument[] public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", EFClient.Permission.Trusted, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {

View File

@ -3,6 +3,7 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
@ -28,6 +29,7 @@ namespace IW4MAdmin.Plugins.Login
if (E.Type == GameEvent.EventType.Connect) if (E.Type == GameEvent.EventType.Connect)
{ {
AuthorizedClients.TryAdd(E.Origin.ClientId, false); AuthorizedClients.TryAdd(E.Origin.ClientId, false);
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
} }
if (E.Type == GameEvent.EventType.Disconnect) if (E.Type == GameEvent.EventType.Disconnect)
@ -37,11 +39,11 @@ namespace IW4MAdmin.Plugins.Login
if (E.Type == GameEvent.EventType.Command) if (E.Type == GameEvent.EventType.Command)
{ {
if (E.Origin.Level < Player.Permission.Moderator || if (E.Origin.Level < EFClient.Permission.Moderator ||
E.Origin.Level == Player.Permission.Console) E.Origin.Level == EFClient.Permission.Console)
return Task.CompletedTask; return Task.CompletedTask;
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out Player client); E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out EFClient client);
if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name && if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name &&
client?.Password == null) client?.Password == null)
@ -51,7 +53,14 @@ namespace IW4MAdmin.Plugins.Login
return Task.CompletedTask; return Task.CompletedTask;
if (!AuthorizedClients[E.Origin.ClientId]) if (!AuthorizedClients[E.Origin.ClientId])
{
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]); throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
}
else
{
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
}
} }
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
@ -48,7 +49,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
if (containsObjectionalWord) if (containsObjectionalWord)
{ {
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player() E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
{ {
ClientId = 1, ClientId = 1,
CurrentServer = E.Owner CurrentServer = E.Owner
@ -85,22 +86,14 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
var clientProfanity = ProfanityCounts[E.Origin.ClientId]; var clientProfanity = ProfanityCounts[E.Origin.ClientId];
if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount) if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount)
{ {
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player() clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
{
ClientId = 1,
CurrentServer = E.Owner
});
} }
else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount) else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount)
{ {
clientProfanity.Infringements++; clientProfanity.Infringements++;
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player() clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
{
ClientId = 1,
CurrentServer = E.Owner
});
} }
} }
} }

View File

@ -1,16 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.ProfanityDeterment namespace IW4MAdmin.Plugins.ProfanityDeterment
{ {
class Tracking class Tracking
{ {
public Player Client { get; private set; } public EFClient Client { get; private set; }
public int Infringements { get; set; } public int Infringements { get; set; }
public Tracking(Player client) public Tracking(EFClient client)
{ {
Client = client; Client = client;
Infringements = 0; Infringements = 0;

View File

@ -1,7 +1,7 @@
using SharedLibraryCore.Helpers; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -20,7 +20,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
}; };
public ChangeTracking<EFACSnapshot> Tracker { get; private set; } public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
public const int QUEUE_COUNT = 10;
public List<EFClientKill> QueuedHits { get; set; }
int Kills; int Kills;
int HitCount; int HitCount;
Dictionary<IW4Info.HitLocation, int> HitLocationCount; Dictionary<IW4Info.HitLocation, int> HitLocationCount;
@ -37,49 +39,55 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log = log; Log = log;
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>(); HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation))) foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
{
HitLocationCount.Add((IW4Info.HitLocation)loc, 0); HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
}
ClientStats = clientStats; ClientStats = clientStats;
Strain = new Strain(); Strain = new Strain();
Tracker = new ChangeTracking<EFACSnapshot>(); Tracker = new ChangeTracking<EFACSnapshot>();
QueuedHits = new List<EFClientKill>();
} }
/// <summary> /// <summary>
/// Analyze kill and see if performed by a cheater /// Analyze kill and see if performed by a cheater
/// </summary> /// </summary>
/// <param name="kill">kill performed by the player</param> /// <param name="hit">kill performed by the player</param>
/// <returns>true if detection reached thresholds, false otherwise</returns> /// <returns>true if detection reached thresholds, false otherwise</returns>
public DetectionPenaltyResult ProcessKill(EFClientKill kill, bool isDamage) public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
{ {
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET && hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) || hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
kill.HitLoc == IW4Info.HitLocation.none || kill.TimeOffset - LastOffset < 0 || hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
// hack: prevents false positives // hack: prevents false positives
(LastWeapon != kill.Weapon && (kill.TimeOffset - LastOffset) == 50)) (LastWeapon != hit.Weapon && (hit.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; LastWeapon = hit.Weapon;
HitLocationCount[hit.HitLoc]++;
HitCount++;
HitLocationCount[kill.HitLoc]++;
if (!isDamage) if (!isDamage)
{ {
Kills++; Kills++;
} }
HitCount++;
#region VIEWANGLES #region VIEWANGLES
if (kill.AnglesList.Count >= 2) if (hit.AnglesList.Count >= 2)
{ {
double realAgainstPredict = Vector3.ViewAngleDistance(kill.AnglesList[0], kill.AnglesList[1], kill.ViewAngles); double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
// LIFETIME // LIFETIME
var hitLoc = ClientStats.HitLocations var hitLoc = ClientStats.HitLocations
.First(hl => hl.Location == kill.HitLoc); .First(hl => hl.Location == hit.HitLoc);
float previousAverage = hitLoc.HitOffsetAverage; float previousAverage = hitLoc.HitOffsetAverage;
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount; double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
@ -88,11 +96,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) && if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) &&
hitLoc.HitCount > 100) hitLoc.HitCount > 100)
{ {
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***"); //Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
Log.WriteDebug($"Lifetime Average = {newAverage}"); //Log.WriteDebug($"Lifetime Average = {newAverage}");
Log.WriteDebug($"Bone = {hitLoc.Location}"); //Log.WriteDebug($"Bone = {hitLoc.Location}");
Log.WriteDebug($"HitCount = {hitLoc.HitCount}"); //Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); //Log.WriteDebug($"ID = {hit.AttackerId}");
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -110,10 +118,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (sessAverage > Thresholds.MaxOffset(HitCount) && if (sessAverage > Thresholds.MaxOffset(HitCount) &&
HitCount > 30) HitCount > 30)
{ {
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***"); //Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {sessAverage}"); //Log.WriteDebug($"Session Average = {sessAverage}");
Log.WriteDebug($"HitCount = {HitCount}"); //Log.WriteDebug($"HitCount = {HitCount}");
Log.WriteDebug($"ID = {kill.AttackerId}"); //Log.WriteDebug($"ID = {hit.AttackerId}");
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -130,8 +138,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
#endif #endif
} }
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.Distance / 0.0254, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset)); double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
LastOffset = kill.TimeOffset; #if DEBUG == true
Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
LastOffset = hit.TimeOffset;
if (currentStrain > ClientStats.MaxStrain) if (currentStrain > ClientStats.MaxStrain)
{ {
@ -139,8 +150,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
// flag // flag
if (currentStrain > Thresholds.MaxStrainFlag && if (currentStrain > Thresholds.MaxStrainFlag)
HitCount >= 10)
{ {
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -153,7 +163,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
// ban // ban
if (currentStrain > Thresholds.MaxStrainBan && if (currentStrain > Thresholds.MaxStrainBan &&
HitCount >= 15) HitCount >= 5)
{ {
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -163,11 +173,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Type = DetectionType.Strain Type = DetectionType.Strain
}; };
} }
#if DEBUG
Log.WriteDebug($"Current Strain: {currentStrain}");
#endif
#endregion #endregion
#region SESSION_RATIOS #region SESSION_RATIOS
@ -197,13 +202,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentHeadshotRatio > maxHeadshotLerpValueForBan) if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
{ {
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -218,13 +226,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
else else
{ {
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}"); Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -247,13 +258,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan) if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**"); Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -268,13 +282,16 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
else else
{ {
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**"); Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**HitCount: {HitCount}"); Log.WriteDebug($"**HitCount: {HitCount}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}"); Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}"); Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) foreach (var kvp in HitLocationCount)
{
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
}
Log.WriteDebug(sb.ToString()); Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
@ -308,15 +325,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30) if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= Thresholds.MediumSampleMinKills + 30)
{ {
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**"); //Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); //Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**Chest Hits: {chestHits}"); //Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) //foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); // sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -329,16 +346,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**"); //Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}"); //Log.WriteDebug($"ClientId: {hit.AttackerId}");
Log.WriteDebug($"**Chest Hits: {chestHits}"); //Log.WriteDebug($"**Chest Hits: {chestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var kvp in HitLocationCount) //foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n"); // sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
result = new DetectionPenaltyResult() result = new DetectionPenaltyResult()
{ {
@ -356,31 +372,31 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Tracker.OnChange(new EFACSnapshot() Tracker.OnChange(new EFACSnapshot()
{ {
When = kill.When, When = hit.When,
ClientId = ClientStats.ClientId, ClientId = ClientStats.ClientId,
SessionAngleOffset = AngleDifferenceAverage, SessionAngleOffset = AngleDifferenceAverage,
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds, CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
CurrentStrain = currentStrain, CurrentStrain = currentStrain,
CurrentViewAngle = kill.ViewAngles, CurrentViewAngle = hit.ViewAngles,
Hits = HitCount, Hits = HitCount,
Kills = Kills, Kills = Kills,
Deaths = ClientStats.SessionDeaths, Deaths = ClientStats.SessionDeaths,
HitDestinationId = kill.DeathOrigin.Vector3Id, HitDestinationId = hit.DeathOrigin.Vector3Id,
HitDestination = kill.DeathOrigin, HitDestination = hit.DeathOrigin,
HitOriginId = kill.KillOrigin.Vector3Id, HitOriginId = hit.KillOrigin.Vector3Id,
HitOrigin = kill.KillOrigin, HitOrigin = hit.KillOrigin,
EloRating = ClientStats.EloRating, EloRating = ClientStats.EloRating,
HitLocation = kill.HitLoc, HitLocation = hit.HitLoc,
LastStrainAngle = Strain.LastAngle, LastStrainAngle = Strain.LastAngle,
PredictedViewAngles = kill.AnglesList, PredictedViewAngles = hit.AnglesList,
// this is in "meters" // this is in "meters"
Distance = kill.Distance, Distance = hit.Distance,
SessionScore = ClientStats.SessionScore, SessionScore = ClientStats.SessionScore,
HitType = kill.DeathType, HitType = hit.DeathType,
SessionSPM = ClientStats.SessionSPM, SessionSPM = ClientStats.SessionSPM,
StrainAngleBetween = Strain.LastDistance, StrainAngleBetween = Strain.LastDistance,
TimeSinceLastEvent = (int)Strain.LastDeltaTime, TimeSinceLastEvent = (int)Strain.LastDeltaTime,
WeaponId = kill.Weapon WeaponId = hit.Weapon
}); });
return result ?? new DetectionPenaltyResult() return result ?? new DetectionPenaltyResult()
@ -409,15 +425,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan) if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
{ {
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**"); //Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {stats.ClientId}"); //Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}"); //Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var location in stats.HitLocations) //foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); // sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {
@ -430,15 +446,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
} }
else else
{ {
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**"); //Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {stats.ClientId}"); //Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Hits: {totalChestHits}"); //Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}"); //Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}"); //Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder(); //var sb = new StringBuilder();
foreach (var location in stats.HitLocations) //foreach (var location in stats.HitLocations)
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n"); // sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
Log.WriteDebug(sb.ToString()); //Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult() return new DetectionPenaltyResult()
{ {

View File

@ -14,8 +14,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public Vector3 LastAngle { get; private set; } public Vector3 LastAngle { get; private set; }
public double LastDeltaTime { get; private set; } public double LastDeltaTime { get; private set; }
public int TimesReachedMaxStrain { get; private set; }
public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime) public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime)
{ {
if (LastAngle == null) if (LastAngle == null)
@ -25,14 +23,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double decayFactor = GetDecay(deltaTime); double decayFactor = GetDecay(deltaTime);
CurrentStrain *= decayFactor; CurrentStrain *= decayFactor;
#if DEBUG
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle); double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
LastDistance = distance[0] + distance[1]; LastDistance = distance[0] + distance[1];
#if DEBUG == true
Console.WriteLine($"Angle Between = {LastDistance}");
Console.WriteLine($"Distance From Target = {killDistance}");
Console.WriteLine($"Time Offset = {deltaTime}");
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
// this happens on first kill // this happens on first kill
if ((distance[0] == 0 && distance[1] == 0) || if ((distance[0] == 0 && distance[1] == 0) ||
deltaTime == 0 || deltaTime == 0 ||
@ -41,14 +40,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return CurrentStrain; return CurrentStrain;
} }
double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime; double newStrain = Math.Pow(LastDistance, 0.99) / deltaTime;
newStrain *= killDistance / 1000.0; newStrain *= killDistance / 1000.0;
CurrentStrain += newStrain; CurrentStrain += newStrain;
if (CurrentStrain > Thresholds.MaxStrainBan)
TimesReachedMaxStrain++;
LastAngle = newAngle; LastAngle = newAngle;
return CurrentStrain; return CurrentStrain;
} }

View File

@ -27,9 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100; public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2; public const double KillTimeThreshold = 0.2;
public const double MaxStrainBan = 1.12; public const double MaxStrainBan = 0.9;
//=exp((MAX(-3.07+(-3.07/sqrt(J20)),-3.07-(-3.07/sqrt(J20))))+(4*(0.869)))
public static double MaxOffset(int sampleSize) => Math.Exp(Math.Max(-3.07 + (-3.07 / Math.Sqrt(sampleSize)), -3.07 - (-3.07 / Math.Sqrt(sampleSize))) + 4 * (0.869)); public static double MaxOffset(int sampleSize) => Math.Exp(Math.Max(-3.07 + (-3.07 / Math.Sqrt(sampleSize)), -3.07 - (-3.07 / Math.Sqrt(sampleSize))) + 4 * (0.869));
public const double MaxStrainFlag = 0.36; public const double MaxStrainFlag = 0.36;

View File

@ -8,6 +8,8 @@ using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using System.Collections.Generic; using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
@ -15,7 +17,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{ {
public static async Task<List<string>> GetMostPlayed(Server s) public static async Task<List<string>> GetMostPlayed(Server s)
{ {
int serverId = s.GetHashCode(); long serverId = await StatManager.GetIdForServer(s);
List<string> mostPlayed = new List<string>() List<string> mostPlayed = new List<string>()
{ {
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--" $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
@ -34,7 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
join alias in db.Aliases join alias in db.Aliases
on client.CurrentAliasId equals alias.AliasId on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId where stats.ServerId == serverId
where client.Level != Player.Permission.Banned where client.Level != EFClient.Permission.Banned
where client.LastConnection >= thirtyDaysAgo where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending orderby stats.Kills descending
select new select new
@ -55,7 +58,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return mostPlayed; return mostPlayed;
} }
public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", Player.Permission.User, false) { } public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {

View File

@ -1,32 +1,30 @@
using SharedLibraryCore; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
public class ResetStats : Command public class ResetStats : Command
{ {
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", Player.Permission.User, false) { } public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
if (E.Origin.ClientNumber >= 0) if (E.Origin.ClientNumber >= 0)
{ {
int serverId = E.Owner.GetHashCode(); long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
EFClientStatistics clientStats; EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
clientStats = await ctx.Set<EFClientStatistics>() clientStats = await ctx.Set<EFClientStatistics>()
.Where(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId) .Where(s => s.ClientId == E.Origin.ClientId)
.Where(s => s.ServerId == serverId)
.FirstAsync(); .FirstAsync();
clientStats.Deaths = 0; clientStats.Deaths = 0;
@ -38,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
clientStats.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, serverId);
// fixme: this doesn't work properly when another context exists // fixme: this doesn't work properly when another context exists
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();

View File

@ -9,15 +9,16 @@ using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using System.Collections.Generic; using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
class TopStats : Command class TopStats : Command
{ {
public static async Task<List<string>> GetTopStats(Server s) public static async Task<List<string>> GetTopStats(Server s)
{ {
int serverId = s.GetHashCode(); long serverId = await StatManager.GetIdForServer(s);
List<string> topStatsText = new List<string>() List<string> topStatsText = new List<string>()
{ {
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--" $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
@ -34,7 +35,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
on client.CurrentAliasId equals alias.AliasId on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId where stats.ServerId == serverId
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
where client.Level != Player.Permission.Banned where client.Level != EFClient.Permission.Banned
where client.LastConnection >= fifteenDaysAgo where client.LastConnection >= fifteenDaysAgo
orderby stats.Performance descending orderby stats.Performance descending
select new select new
@ -67,7 +68,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return topStatsText; return topStatsText;
} }
public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", Player.Permission.User, false) { } public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", EFClient.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {

View File

@ -9,12 +9,14 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using IW4MAdmin.Plugins.Stats.Helpers;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Plugins.Stats.Commands namespace IW4MAdmin.Plugins.Stats.Commands
{ {
public class CViewStats : Command public class CViewStats : Command
{ {
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", Player.Permission.User, false, new CommandArgument[] public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", EFClient.Permission.User, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -41,20 +43,26 @@ namespace IW4MAdmin.Plugins.Stats.Commands
} }
} }
int serverId = E.Owner.GetHashCode(); long serverId = await StatManager.GetIdForServer(E.Owner);
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
if (E.Target != null) if (E.Target != null)
{ {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)); 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()} | {performanceRankingString}";
} }
else else
{ {
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId))); 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()} | {performanceRankingString}";
} }
} }

View File

@ -1,53 +1,53 @@
using System; using IW4MAdmin.Plugins.Stats.Cheat;
using System.Collections.Concurrent; using IW4MAdmin.Plugins.Stats.Models;
using System.Collections.Generic; using IW4MAdmin.Plugins.Stats.Web.Dtos;
using System.Linq; using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using SharedLibraryCore.Commands; using System;
using IW4MAdmin.Plugins.Stats.Models; using System.Collections.Concurrent;
using System.Text.RegularExpressions; using System.Collections.Generic;
using IW4MAdmin.Plugins.Stats.Web.Dtos; using System.Linq;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Services;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Helpers namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
public class StatManager public class StatManager
{ {
private ConcurrentDictionary<int, ServerStats> Servers; private ConcurrentDictionary<long, ServerStats> Servers;
private ILogger Log; private ILogger Log;
private readonly IManager Manager; private readonly IManager Manager;
private readonly SemaphoreSlim OnProcessingPenalty; private readonly SemaphoreSlim OnProcessingPenalty;
private readonly SemaphoreSlim OnProcessingSensitive; private readonly SemaphoreSlim OnProcessingSensitive;
public StatManager(IManager mgr) public StatManager(IManager mgr)
{ {
Servers = new ConcurrentDictionary<int, ServerStats>(); Servers = new ConcurrentDictionary<long, ServerStats>();
Log = mgr.GetLogger(0); Log = mgr.GetLogger(0);
Manager = mgr; Manager = mgr;
OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingPenalty = new SemaphoreSlim(1, 1);
OnProcessingSensitive = 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, long serverId)
{
return Servers[serverId].PlayerStats[clientId];
}
public static Expression<Func<EFRating, bool>> GetRankingFunc(int? serverId = null) public static Expression<Func<EFRating, bool>> GetRankingFunc(long? serverId = null)
{ {
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
return (r) => r.ServerId == serverId && return (r) => r.ServerId == serverId &&
r.When > fifteenDaysAgo && r.When > fifteenDaysAgo &&
r.RatingHistory.Client.Level != Player.Permission.Banned && r.RatingHistory.Client.Level != EFClient.Permission.Banned &&
r.Newest && r.Newest &&
r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime; r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime;
} }
@ -57,7 +57,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="clientId">client id of the player</param> /// <param name="clientId">client id of the player</param>
/// <returns></returns> /// <returns></returns>
public async Task<int> GetClientOverallRanking(int clientId) public static async Task<int> GetClientOverallRanking(int clientId)
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{ {
@ -191,21 +191,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// insert the server if it does not exist // insert the server if it does not exist
try try
{ {
int serverId = sv.GetHashCode(); long serverId = GetIdForServer(sv).Result;
EFServer server; EFServer server;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
var serverSet = ctx.Set<EFServer>(); var serverSet = ctx.Set<EFServer>();
// get the server from the database if it exists, otherwise create and insert a new one // get the server from the database if it exists, otherwise create and insert a new one
server = serverSet.FirstOrDefault(c => c.ServerId == serverId); server = serverSet.FirstOrDefault(s => s.ServerId == serverId);
// the server might be using legacy server id
if (server == null)
{
server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString());
if (server != null)
{
// this provides a way to identify legacy server entries
server.EndPoint = sv.ToString();
ctx.Update(server);
ctx.SaveChanges();
}
}
// server has never been added before
if (server == null) if (server == null)
{ {
server = new EFServer() server = new EFServer()
{ {
Port = sv.GetPort(), Port = sv.GetPort(),
Active = true, EndPoint = sv.ToString(),
ServerId = serverId ServerId = serverId
}; };
@ -216,7 +231,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// check to see if the stats have ever been initialized // check to see if the stats have ever been initialized
var serverStats = InitializeServerStats(sv); var serverStats = InitializeServerStats(server.ServerId);
Servers.TryAdd(serverId, new ServerStats(server, serverStats) Servers.TryAdd(serverId, new ServerStats(server, serverStats)
{ {
@ -227,6 +242,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
catch (Exception e) catch (Exception e)
{ {
Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}");
Log.WriteDebug(e.GetExceptionInfo());
} }
} }
@ -235,14 +251,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <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(EFClient pl)
{ {
await OnProcessingSensitive.WaitAsync(); await OnProcessingSensitive.WaitAsync();
try try
{ {
int serverId = pl.CurrentServer.GetHashCode(); long serverId = await GetIdForServer(pl.CurrentServer);
if (!Servers.ContainsKey(serverId)) if (!Servers.ContainsKey(serverId))
{ {
@ -299,7 +314,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
Log.WriteWarning("Adding new client to stats failed"); Log.WriteWarning("Adding new client to stats failed");
} }
} }
else else
@ -348,7 +362,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Log.WriteWarning("Could not add client to detection"); Log.WriteWarning("Could not add client to detection");
} }
Log.WriteInfo($"Adding {pl} to stats"); pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats");
} }
return clientStats; return clientStats;
@ -373,21 +387,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="pl">Disconnecting client</param> /// <param name="pl">Disconnecting client</param>
/// <returns></returns> /// <returns></returns>
public async Task RemovePlayer(Player pl) public async Task RemovePlayer(EFClient pl)
{ {
Log.WriteInfo($"Removing {pl} from stats"); pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats");
int serverId = pl.CurrentServer.GetHashCode(); long serverId = await GetIdForServer(pl.CurrentServer);
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;
if (!playerStats.ContainsKey(pl.ClientId)) if (!playerStats.ContainsKey(pl.ClientId))
{ {
Log.WriteWarning($"Client disconnecting not in stats {pl}"); pl.CurrentServer.Logger.WriteWarning($"Client disconnecting not in stats {pl}");
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); detectionStats.TryRemove(pl.ClientId, out Detection removedValue2);
return; return;
} }
@ -396,7 +410,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); detectionStats.TryRemove(pl.ClientId, out Detection removedValue4);
// sync their stats before they leave // sync their stats before they leave
clientStats = UpdateStats(clientStats); clientStats = UpdateStats(clientStats);
@ -408,10 +422,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// increment the total play time // increment the total play time
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; serverStats.TotalPlayTime += pl.ConnectionLength;
} }
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, int serverId) public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId)
{ {
string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$";
var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase);
@ -432,7 +446,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// Process stats for kill event /// Process stats for kill event
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task AddScriptHit(bool isDamage, DateTime time, Player attacker, Player victim, int serverId, string map, string hitLoc, string type, public async Task AddScriptHit(bool isDamage, DateTime time, EFClient attacker, EFClient victim, long serverId, string map, string hitLoc, string type,
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)
{ {
@ -548,7 +562,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (Plugin.Config.Configuration().EnableAntiCheat) if (Plugin.Config.Configuration().EnableAntiCheat)
{ {
ApplyPenalty(clientDetection.ProcessKill(hit, isDamage), clientDetection, attacker, ctx); if (clientDetection.QueuedHits.Count > Detection.QUEUE_COUNT)
{
while (clientDetection.QueuedHits.Count > 0)
{
clientDetection.QueuedHits = clientDetection.QueuedHits.OrderBy(_hits => _hits.TimeOffset).ToList();
var oldestHit = clientDetection.QueuedHits.First();
clientDetection.QueuedHits.RemoveAt(0);
ApplyPenalty(clientDetection.ProcessHit(oldestHit, isDamage), clientDetection, attacker, ctx);
}
}
else
{
clientDetection.QueuedHits.Add(hit);
}
ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx); ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
} }
@ -567,16 +596,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
void ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, Player attacker, DatabaseContext ctx) void ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx)
{ {
switch (penalty.ClientPenalty) switch (penalty.ClientPenalty)
{ {
case Penalty.PenaltyType.Ban: case Penalty.PenaltyType.Ban:
if (attacker.Level == Player.Permission.Banned) if (attacker.Level == EFClient.Permission.Banned)
{ {
break; break;
} }
attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new EFClient()
{ {
ClientId = 1, ClientId = 1,
AdministeredPenalties = new List<EFPenalty>() AdministeredPenalties = new List<EFPenalty>()
@ -588,7 +617,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
} }
}, },
Level = Player.Permission.Console, Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer CurrentServer = attacker.CurrentServer
}); });
if (clientDetection.Tracker.HasChanges) if (clientDetection.Tracker.HasChanges)
@ -597,7 +626,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
break; break;
case Penalty.PenaltyType.Flag: case Penalty.PenaltyType.Flag:
if (attacker.Level != Player.Permission.User) if (attacker.Level != EFClient.Permission.User)
{ {
break; break;
} }
@ -606,10 +635,10 @@ 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}";
attacker.Flag(flagReason, new Player() attacker.Flag(flagReason, new EFClient()
{ {
ClientId = 1, ClientId = 1,
Level = Player.Permission.Console, Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer, CurrentServer = attacker.CurrentServer,
}); });
@ -677,9 +706,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
public async Task AddStandardKill(Player attacker, Player victim) public async Task AddStandardKill(EFClient attacker, EFClient victim)
{ {
int serverId = attacker.CurrentServer.GetHashCode(); long serverId = await GetIdForServer(attacker.CurrentServer);
EFClientStatistics attackerStats = null; EFClientStatistics attackerStats = null;
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
@ -709,13 +738,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats // update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1; Servers[serverId].ServerStatistics.TotalKills += 1;
await Sync(attacker.CurrentServer);
// this happens when the round has changed // this happens when the round has changed
if (attackerStats.SessionScore == 0) if (attackerStats.SessionScore == 0)
{
attackerStats.LastScore = 0; attackerStats.LastScore = 0;
}
if (victimStats.SessionScore == 0) if (victimStats.SessionScore == 0)
{
victimStats.LastScore = 0; victimStats.LastScore = 0;
}
attackerStats.SessionScore = attacker.Score; attackerStats.SessionScore = attacker.Score;
victimStats.SessionScore = victim.Score; victimStats.SessionScore = victim.Score;
@ -768,8 +802,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
var clientStatsSet = ctx.Set<EFClientStatistics>(); var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStatsSet.Update(attackerStats); clientStatsSet.Attach(attackerStats).State = EntityState.Modified;
clientStatsSet.Update(victimStats); clientStatsSet.Attach(victimStats).State = EntityState.Modified;
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
} }
@ -780,7 +814,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="client">client to update</param> /// <param name="client">client to update</param>
/// <param name="clientStats">stats of client that is being updated</param> /// <param name="clientStats">stats of client that is being updated</param>
/// <returns></returns> /// <returns></returns>
private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats) private async Task UpdateStatHistory(EFClient client, EFClientStatistics clientStats)
{ {
int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds;
@ -979,6 +1013,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calulate elo // calulate elo
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) if (Servers[attackerStats.ServerId].PlayerStats.Count > 1)
{ {
#region DEPRECATED
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId) .Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs => .Where(cs =>
@ -1002,6 +1037,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ? double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) : validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/ victimStats.EloRating;*/
#endregion
double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating)); double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating));
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
@ -1104,9 +1140,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return clientStats; return clientStats;
} }
public EFServerStatistics InitializeServerStats(Server sv) public EFServerStatistics InitializeServerStats(long serverId)
{ {
int serverId = sv.GetHashCode();
EFServerStatistics serverStats; EFServerStatistics serverStats;
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
@ -1116,7 +1151,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (serverStats == null) if (serverStats == null)
{ {
Log.WriteDebug($"Initializing server stats for {sv}"); Log.WriteDebug($"Initializing server stats for {serverId}");
// server stats have never been generated before // server stats have never been generated before
serverStats = new EFServerStatistics() serverStats = new EFServerStatistics()
{ {
@ -1133,7 +1168,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return serverStats; return serverStats;
} }
public void ResetKillstreaks(int serverId) public void ResetKillstreaks(long serverId)
{ {
var serverStats = Servers[serverId]; var serverStats = Servers[serverId];
@ -1143,7 +1178,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
} }
public void ResetStats(int clientId, int serverId) public void ResetStats(int clientId, long serverId)
{ {
var stats = Servers[serverId].PlayerStats[clientId]; var stats = Servers[serverId].PlayerStats[clientId];
stats.Kills = 0; stats.Kills = 0;
@ -1154,11 +1189,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
stats.EloRating = 200; stats.EloRating = 200;
} }
public async Task AddMessageAsync(int clientId, int serverId, string message) public async Task AddMessageAsync(int clientId, long serverId, string message)
{ {
// the web users can have no account // the web users can have no account
if (clientId < 1) if (clientId < 1)
{
return; return;
}
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
@ -1176,19 +1213,64 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task Sync(Server sv) public async Task Sync(Server sv)
{ {
int serverId = sv.GetHashCode(); long serverId = await GetIdForServer(sv);
using (var ctx = new DatabaseContext(disableTracking: true)) using (var ctx = new DatabaseContext(disableTracking: true))
{ {
var serverSet = ctx.Set<EFServer>(); var serverSet = ctx.Set<EFServer>();
serverSet.Update(Servers[serverId].Server); serverSet.Update(Servers[serverId].Server);
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(Servers[serverId].ServerStatistics);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
} }
public void SetTeamBased(int serverId, bool isTeamBased) public void SetTeamBased(long serverId, bool isTeamBased)
{ {
Servers[serverId].IsTeamBased = isTeamBased; Servers[serverId].IsTeamBased = isTeamBased;
} }
public static async Task<long> GetIdForServer(Server server)
{
// hack: my laziness
if ($"{server.IP}:{server.GetPort().ToString()}" == "66.150.121.184:28965")
{
return 886229536;
}
else if ($"{server.IP}:{server.GetPort().ToString()}" == "66.150.121.184:28960")
{
return 1645744423;
}
else if ($"{server.IP}:{server.GetPort().ToString()}" == "66.150.121.184:28970")
{
return 1645809959;
}
else
{
long id = HashCode.Combine(server.IP, server.GetPort());
id = id < 0 ? Math.Abs(id) : id;
long? serverId;
// todo: cache this eventually, as it shouldn't change
using (var ctx = new DatabaseContext(disableTracking: true))
{
serverId = (await ctx.Set<EFServer>().FirstOrDefaultAsync(_server => _server.ServerId == server.EndPoint ||
_server.EndPoint == server.ToString() ||
_server.ServerId == id))?.ServerId;
}
if (!serverId.HasValue)
{
return id;
}
return serverId.Value;
}
}
} }
} }

View File

@ -18,7 +18,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int AttackerId { get; set; } public int AttackerId { get; set; }
[ForeignKey("AttackerId")] [ForeignKey("AttackerId")]
public virtual EFClient Attacker { get; set; } public virtual EFClient Attacker { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
public IW4Info.HitLocation HitLoc { get; set; } public IW4Info.HitLocation HitLoc { get; set; }

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
[Key] [Key]
public long MessageId { get; set; } public long MessageId { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
public int ClientId { get; set; } public int ClientId { get; set; }

View File

@ -15,7 +15,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; } public int ClientId { get; set; }
[ForeignKey("ClientId")] [ForeignKey("ClientId")]
public virtual EFClient Client { get; set; } public virtual EFClient Client { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
[Required] [Required]

View File

@ -20,7 +20,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; } public int ClientId { get; set; }
[ForeignKey("ClientId"), Column(Order = 0 )] [ForeignKey("ClientId"), Column(Order = 0 )]
public EFClient Client { get; set; } public EFClient Client { get; set; }
public int ServerId { get; set; } public long 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,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
[ForeignKey("RatingHistoryId")] [ForeignKey("RatingHistoryId")]
public virtual EFClientRatingHistory RatingHistory { get; set; } public virtual EFClientRatingHistory RatingHistory { get; set; }
// if null, indicates that the rating is an average rating // if null, indicates that the rating is an average rating
public int? ServerId { get; set; } public long? ServerId { get; set; }
// [ForeignKey("ServerId")] can't make this nullable if this annotation is set // [ForeignKey("ServerId")] can't make this nullable if this annotation is set
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
[Required] [Required]

View File

@ -9,8 +9,9 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
[Key] [Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ServerId { get; set; } public long ServerId { get; set; }
[Required] [Required]
public int Port { get; set; } public int Port { get; set; }
public string EndPoint { get; set; }
} }
} }

View File

@ -8,7 +8,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
{ {
[Key] [Key]
public int StatisticId { get; set; } public int StatisticId { get; set; }
public int ServerId { get; set; } public long ServerId { get; set; }
[ForeignKey("ServerId")] [ForeignKey("ServerId")]
public virtual EFServer Server { get; set; } public virtual EFServer Server { get; set; }
public long TotalKills { get; set; } public long TotalKills { get; set; }

View File

@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore; using IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using IW4MAdmin.Plugins.Stats.Models;
namespace Stats.Models namespace Stats.Models
{ {

View File

@ -1,20 +1,19 @@
using System; using IW4MAdmin.Plugins.Stats.Config;
using System.Collections.Generic; using IW4MAdmin.Plugins.Stats.Helpers;
using System.Linq; using IW4MAdmin.Plugins.Stats.Models;
using System.Threading.Tasks; using Microsoft.EntityFrameworkCore;
using System.Reflection;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Config; using System;
using IW4MAdmin.Plugins.Stats.Helpers; using System.Collections.Generic;
using IW4MAdmin.Plugins.Stats.Models; using System.Linq;
using SharedLibraryCore.Database; using System.Reflection;
using Microsoft.EntityFrameworkCore; using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats namespace IW4MAdmin.Plugins.Stats
{ {
@ -44,16 +43,19 @@ namespace IW4MAdmin.Plugins.Stats
break; break;
case GameEvent.EventType.Disconnect: case GameEvent.EventType.Disconnect:
await Manager.RemovePlayer(E.Origin); await Manager.RemovePlayer(E.Origin);
await Manager.Sync(S);
break; break;
case GameEvent.EventType.Say: case GameEvent.EventType.Say:
if (!string.IsNullOrEmpty(E.Data) && if (!string.IsNullOrEmpty(E.Data) &&
E.Origin.ClientId > 1) E.Origin.ClientId > 1)
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data); {
await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data);
}
break; break;
case GameEvent.EventType.MapChange: case GameEvent.EventType.MapChange:
Manager.SetTeamBased(E.Owner.GetHashCode(), E.Owner.Gametype != "dm"); Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
Manager.ResetKillstreaks(S.GetHashCode()); Manager.ResetKillstreaks(await StatManager.GetIdForServer(E.Owner));
await Manager.Sync(S);
break; break;
case GameEvent.EventType.MapEnd: case GameEvent.EventType.MapEnd:
break; break;
@ -77,7 +79,7 @@ namespace IW4MAdmin.Plugins.Stats
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14) if (killInfo.Length >= 14)
{ {
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
} }
break; break;
@ -90,14 +92,14 @@ namespace IW4MAdmin.Plugins.Stats
case GameEvent.EventType.Damage: case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback) if (!E.Owner.CustomCallback)
{ {
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, E.Owner.GetHashCode()); Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner));
} }
break; break;
case GameEvent.EventType.ScriptDamage: case GameEvent.EventType.ScriptDamage:
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14) if (killInfo.Length >= 14)
{ {
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]); killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
} }
break; break;
@ -143,7 +145,7 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta() new ProfileMeta()
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + await Manager.GetClientOverallRanking(clientId), Value = "#" + await StatManager.GetClientOverallRanking(clientId),
}, },
new ProfileMeta() new ProfileMeta()
{ {
@ -327,12 +329,17 @@ namespace IW4MAdmin.Plugins.Stats
Manager = new StatManager(manager); Manager = new StatManager(manager);
} }
public Task OnTickAsync(Server S) => Task.CompletedTask; public Task OnTickAsync(Server S)
{
return Task.CompletedTask;
}
public async Task OnUnloadAsync() public async Task OnUnloadAsync()
{ {
foreach (var sv in ServerManager.GetServers()) foreach (var sv in ServerManager.GetServers())
{
await Manager.Sync(sv); await Manager.Sync(sv);
}
} }
} }
} }

View File

@ -1,6 +1,7 @@
using IW4MAdmin.Application; using IW4MAdmin.Application;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Commands; using SharedLibraryCore.Commands;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,7 +26,7 @@ namespace Tests
[Fact] [Fact]
public void SetAdditionalPropertyShouldSucceed() public void SetAdditionalPropertyShouldSucceed()
{ {
var client = new Player(); var client = new EFClient();
int newProp = 5; int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp); client.SetAdditionalProperty("NewProp", newProp);
} }
@ -33,7 +34,7 @@ namespace Tests
[Fact] [Fact]
public void GetAdditionalPropertyShouldSucceed() public void GetAdditionalPropertyShouldSucceed()
{ {
var client = new Player(); var client = new EFClient();
int newProp = 5; int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp); client.SetAdditionalProperty("NewProp", newProp);
@ -48,11 +49,11 @@ namespace Tests
Thread.Sleep(100); Thread.Sleep(100);
} }
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
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 EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(); warnEvent.OnProcessed.Wait();
Assert.True((client.Warnings == 1 || Assert.True((client.Warnings == 1 ||
@ -60,19 +61,19 @@ namespace Tests
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1, Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied"); "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 EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(); 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");
// warn clear // warn clear
var warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); var warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission && Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was removed without proper permissions"); client.Warnings == 1, "warning was removed without proper permissions");
warnClearEvent = client.WarnClear(new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared"); Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared");
} }
@ -85,11 +86,11 @@ namespace Tests
Thread.Sleep(100); Thread.Sleep(100);
} }
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to report"); Assert.False(client == null, "no client found to report");
// fail // fail
var player = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }; var player = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer };
player.SetAdditionalProperty("_reportCount", 3); player.SetAdditionalProperty("_reportCount", 3);
var reportEvent = client.Report("test report", player); var reportEvent = client.Report("test report", player);
reportEvent.OnProcessed.Wait(TestTimeout); reportEvent.OnProcessed.Wait(TestTimeout);
@ -98,14 +99,14 @@ namespace Tests
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]"); client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]");
// succeed // succeed
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent.OnProcessed.Wait(TestTimeout); reportEvent.OnProcessed.Wait(TestTimeout);
Assert.True(!reportEvent.Failed && Assert.True(!reportEvent.Failed &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]"); client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
// fail // fail
reportEvent = client.Report("test report", new Player() { ClientId = 1, NetworkId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); reportEvent = client.Report("test report", new EFClient() { ClientId = 1, NetworkId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission && Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1,
@ -118,7 +119,7 @@ namespace Tests
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self"); client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was applied to self");
// fail // fail
reportEvent = client.Report("test report", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception && Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Exception &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied"); client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied");
@ -132,42 +133,42 @@ namespace Tests
Thread.Sleep(100); Thread.Sleep(100);
} }
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to flag"); Assert.False(client == null, "no client found to flag");
var flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); var flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait(); flagEvent.OnProcessed.Wait();
// succeed // succeed
Assert.True(!flagEvent.Failed && Assert.True(!flagEvent.Failed &&
client.Level == Player.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]"); client.Level == EFClient.Permission.Flagged, $"player is not flagged [{flagEvent.FailReason.ToString()}]");
Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied"); Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied");
flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait(); flagEvent.OnProcessed.Wait();
// fail // fail
Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions"); Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions");
flagEvent = client.Flag("test flag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait(); flagEvent.OnProcessed.Wait();
// fail // fail
Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied"); Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied");
var unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); var unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait(); unflagEvent.OnProcessed.Wait();
// fail // fail
Assert.False(client.Level == Player.Permission.User, "user was unflagged without permissions"); Assert.False(client.Level == EFClient.Permission.User, "user was unflagged without permissions");
unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait(); unflagEvent.OnProcessed.Wait();
// succeed // succeed
Assert.True(client.Level == Player.Permission.User, "user was not unflagged"); Assert.True(client.Level == EFClient.Permission.User, "user was not unflagged");
unflagEvent = client.Unflag("test unflag", new Player { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait(); unflagEvent.OnProcessed.Wait();
// succeed // succeed
@ -182,18 +183,18 @@ namespace Tests
Thread.Sleep(100); Thread.Sleep(100);
} }
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to kick"); Assert.False(client == null, "no client found to kick");
var kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Banned, CurrentServer = client.CurrentServer }); var kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait(); kickEvent.OnProcessed.Wait();
Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission"); Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission");
kickEvent = client.Kick("test kick", new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }); kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait(); kickEvent.OnProcessed.Wait();
Assert.True(Manager.Servers.First().GetPlayersAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked"); Assert.True(Manager.Servers.First().GetClientsAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
} }
[Fact] [Fact]
@ -204,13 +205,13 @@ namespace Tests
Thread.Sleep(100); Thread.Sleep(100);
} }
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to tempban"); Assert.False(client == null, "no client found to tempban");
var tbCommand = new CTempBan(); var tbCommand = new CTempBan();
tbCommand.ExecuteAsync(new GameEvent() tbCommand.ExecuteAsync(new GameEvent()
{ {
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }, Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = client, Target = client,
Data = "5days test tempban", Data = "5days test tempban",
Type = GameEvent.EventType.Command, Type = GameEvent.EventType.Command,
@ -229,13 +230,13 @@ namespace Tests
Thread.Sleep(100); Thread.Sleep(100);
} }
var client = Manager.Servers.First().GetPlayersAsList().FirstOrDefault(); var client = Manager.Servers.First().GetClientsAsList().FirstOrDefault();
Assert.False(client == null, "no client found to ban"); Assert.False(client == null, "no client found to ban");
var banCommand = new CBan(); var banCommand = new CBan();
banCommand.ExecuteAsync(new GameEvent() banCommand.ExecuteAsync(new GameEvent()
{ {
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }, Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = client, Target = client,
Data = "test ban", Data = "test ban",
Type = GameEvent.EventType.Command, Type = GameEvent.EventType.Command,
@ -248,8 +249,8 @@ namespace Tests
var unbanCommand = new CUnban(); var unbanCommand = new CUnban();
unbanCommand.ExecuteAsync(new GameEvent() unbanCommand.ExecuteAsync(new GameEvent()
{ {
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer }, Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First().AsPlayer(), Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First(),
Data = "test unban", Data = "test unban",
Type = GameEvent.EventType.Command, Type = GameEvent.EventType.Command,
Owner = client.CurrentServer Owner = client.CurrentServer

View File

@ -1,5 +1,6 @@
using IW4MAdmin.Application; using IW4MAdmin.Application;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using System; using System;
@ -53,7 +54,7 @@ namespace Tests
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Join, Type = GameEvent.EventType.Join,
Origin = new Player() Origin = new EFClient()
{ {
Name = $"Player{i}", Name = $"Player{i}",
NetworkId = i, NetworkId = i,
@ -78,7 +79,7 @@ namespace Tests
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Disconnect, Type = GameEvent.EventType.Disconnect,
Origin = new Player() Origin = new EFClient()
{ {
Name = $"Player{i}", Name = $"Player{i}",
NetworkId = i, NetworkId = i,
@ -113,7 +114,7 @@ namespace Tests
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Connect, Type = GameEvent.EventType.Connect,
Origin = new Player() Origin = new EFClient()
{ {
Name = $"Player{i}", Name = $"Player{i}",
NetworkId = i, NetworkId = i,
@ -134,7 +135,7 @@ namespace Tests
waiters.Dequeue().OnProcessed.Wait(); waiters.Dequeue().OnProcessed.Wait();
} }
int actualClientNum = server.GetPlayersAsList().Count(p => p.State == Player.ClientState.Connected); int actualClientNum = server.GetClientsAsList().Count(p => p.State == EFClient.ClientState.Connected);
Assert.True(actualClientNum == clientNum, $"client connected states don't match [{actualClientNum}:{clientNum}"); Assert.True(actualClientNum == clientNum, $"client connected states don't match [{actualClientNum}:{clientNum}");
for (int i = clientIndexStart; i < clientNum + clientIndexStart; i++) for (int i = clientIndexStart; i < clientNum + clientIndexStart; i++)
@ -142,7 +143,7 @@ namespace Tests
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Disconnect, Type = GameEvent.EventType.Disconnect,
Origin = new Player() Origin = new EFClient()
{ {
Name = $"Player{i}", Name = $"Player{i}",
NetworkId = i, NetworkId = i,

View File

@ -1,5 +1,6 @@
using IW4MAdmin.Application; using IW4MAdmin.Application;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using Xunit; using Xunit;
@ -21,7 +22,7 @@ namespace Tests
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Connect, Type = GameEvent.EventType.Connect,
Origin = new Player() Origin = new EFClient()
{ {
Name = $"Player1", Name = $"Player1",
NetworkId = 1, NetworkId = 1,
@ -33,7 +34,7 @@ namespace Tests
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait(); e.OnProcessed.Wait();
var client = Manager.GetServers()[0].Players[0]; var client = Manager.GetServers()[0].Clients[0];
e = new GameEvent() e = new GameEvent()
{ {

View File

@ -13,12 +13,13 @@ using Microsoft.EntityFrameworkCore;
using System.Net; using System.Net;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using static SharedLibraryCore.Database.Models.EFClient;
namespace IW4MAdmin.Plugins.Welcome namespace IW4MAdmin.Plugins.Welcome
{ {
public class Plugin : IPlugin public class Plugin : IPlugin
{ {
String TimesConnected(Player P) String TimesConnected(EFClient P)
{ {
int connection = P.Connections; int connection = P.Connections;
String Prefix = String.Empty; String Prefix = String.Empty;
@ -83,15 +84,15 @@ namespace IW4MAdmin.Plugins.Welcome
public async Task OnEventAsync(GameEvent E, Server S) public async Task OnEventAsync(GameEvent E, Server S)
{ {
if (E.Type == GameEvent.EventType.Connect) if (E.Type == GameEvent.EventType.Join)
{ {
Player newPlayer = E.Origin; EFClient newPlayer = E.Origin;
if (newPlayer.Level >= Player.Permission.Trusted && !E.Origin.Masked) if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked)
E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer)); E.Owner.Broadcast(await ProcessAnnouncement(Config.Configuration().PrivilegedAnnouncementMessage, newPlayer));
newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer)); newPlayer.Tell(await ProcessAnnouncement(Config.Configuration().UserWelcomeMessage, newPlayer));
if (newPlayer.Level == Player.Permission.Flagged) if (newPlayer.Level == Permission.Flagged)
{ {
string penaltyReason; string penaltyReason;
@ -111,7 +112,7 @@ namespace IW4MAdmin.Plugins.Welcome
} }
} }
private async Task<string> ProcessAnnouncement(string msg, Player joining) private async Task<string> ProcessAnnouncement(string msg, EFClient 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));

View File

@ -5,13 +5,15 @@ _______
### About ### About
**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/33/plutonium-t6), ~~[Pluto IW5](https://forum.plutonium.pw/category/5/plutonium-iw5)~~, and most Call of Duty<74> dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze. **IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/33/plutonium-t6), ~~[Pluto IW5](https://forum.plutonium.pw/category/5/plutonium-iw5)~~, and most Call of Duty<74> dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
### Download ### Download
Latest binary builds are always available at https://raidmax.org/IW4MAdmin Latest binary builds are always available at:
- [RaidMax](https://raidmax.org/IW4MAdmin)
- [GitHub](https://github.com/RaidMax/IW4M-Admin/releases)
--- ---
### Setup ### Setup
**IW4MAdmin** requires minimal effort to get up and running. **IW4MAdmin** requires minimal effort to get up and running.
#### Prerequisites #### Prerequisites
* [.NET Core 2.1 Runtime](https://www.microsoft.com/net/download) *or newer* * [.NET Core 2.1.5 Runtime](https://www.microsoft.com/net/download) *or newer*
#### Installation #### Installation
1. Install .NET Core Runtime 1. Install .NET Core Runtime
2. Extract `IW4MAdmin-<version>.zip` 2. Extract `IW4MAdmin-<version>.zip`
@ -105,6 +107,14 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
`ConnectionString` `ConnectionString`
* Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite * Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite
* Default &mdash; `null` * Default &mdash; `null`
`DatabaseProvider`
* Specifies the database provider **IW4MAdmin** should use
* Possible values &mdash; `sqlite`, `mysql`, `postgresql`
* Default &mdash; `sqlite`
`Ignore Bots`
* Disables bots from being registered by **IW4MAdmin**
`RConPollRate` `RConPollRate`
* Specifies (in milliseconds) how often to poll each server for updates * Specifies (in milliseconds) how often to poll each server for updates
@ -163,9 +173,9 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
___ ___
### Commands ### Commands
|Name |Alias|Description |Requires Target|Syntax |Required Level| |Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ----------------| |--------------| -----| --------------------------------------------------------| -----------------| -------------| ---------------|
|prune|pa|demote any privileged clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner| |prune|pa|demote any trusted clients that have not connected recently (defaults to 30 days)|False|!pa \<optional inactive days\>|Owner|
|quit|q|quit IW4MAdmin|False|!q |Owner| |quit|q|quit IW4MAdmin|False|!q |Owner|
|rcon|rcon|send rcon command to server|False|!rcon \<commands\>|Owner| |rcon|rcon|send rcon command to server|False|!rcon \<commands\>|Owner|
|ban|b|permanently ban a client from the server|True|!b \<player\> \<reason\>|SeniorAdmin| |ban|b|permanently ban a client from the server|True|!b \<player\> \<reason\>|SeniorAdmin|
@ -190,24 +200,21 @@ ___
|unflag|uf|Remove flag for client|True|!uf \<player\>|Moderator| |unflag|uf|Remove flag for client|True|!uf \<player\>|Moderator|
|uptime|up|get current application running time|False|!up |Moderator| |uptime|up|get current application running time|False|!up |Moderator|
|usage|us|get application memory usage|False|!us |Moderator| |usage|us|get application memory usage|False|!us |Moderator|
|balance|bal|balance teams|False|!bal |Trusted|
|login|li|login using password|False|!li \<password\>|Trusted|
|warn|w|warn client for infringing rules|True|!w \<player\> \<reason\>|Trusted| |warn|w|warn client for infringing rules|True|!w \<player\> \<reason\>|Trusted|
|warnclear|wc|remove all warnings for a client|True|!wc \<player\>|Trusted| |warnclear|wc|remove all warnings for a client|True|!wc \<player\>|Trusted|
|admins|a|list currently connected privileged clients|False|!a |User| |admins|a|list currently connected privileged clients|False|!a |User|
|getexternalip|ip|view your external IP address|False|!ip |User| |getexternalip|ip|view your external IP address|False|!ip |User|
|help|h|list all available commands|False|!h \<optional commands\>|User| |help|h|list all available commands|False|!h \<optional commands\>|User|
|mostplayed|mp|view the top 5 dedicated players on the server|False|!mp |User| |nextmap|nm|view next map in rotation|False|!nm |User|
|owner|iamgod|claim ownership of the server|False|!iamgod |User| |owner|iamgod|claim ownership of the server|False|!iamgod |User|
|ping|pi|get client's latency|False|!pi \<optional player\>|User| |ping|pi|get client's latency|False|!pi \<optional player\>|User|
|privatemessage|pm|send message to other client|True|!pm \<player\> \<message\>|User| |privatemessage|pm|send message to other client|True|!pm \<player\> \<message\>|User|
|report|rep|report a client for suspicious behavior|True|!rep \<player\> \<reason\>|User| |report|rep|report a client for suspicious behavior|True|!rep \<player\> \<reason\>|User|
|resetstats|rs|reset your stats to factory-new|False|!rs |User|
|rules|r|list server rules|False|!r |User| |rules|r|list server rules|False|!r |User|
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User| |setgravatar|sg|set gravatar for webfront profile|False|!sg \<gravatar email\>|User|
|topstats|ts|view the top 5 players in this server|False|!ts |User|
|whoami|who|give information about yourself|False|!who |User| |whoami|who|give information about yourself|False|!who |User|
_These commands include all shipped plugin commands._ _These commands include all shipped plugin commands._
--- ---
@ -302,6 +309,11 @@ ___
#### VPN Detection [Script Plugin] #### VPN Detection [Script Plugin]
- This plugin detects if a client is using a VPN and kicks them if they are - This plugin detects if a client is using a VPN and kicks them if they are
- To disable this plugin, delete `Plugins\VPNDetection.js` - To disable this plugin, delete `Plugins\VPNDetection.js`
- Adding ClientIds to `vpnExceptionIds` will prevent a client from being kicked.
#### Shared GUID Kicker [Script Plugin]
- This plugin kicks users using a specific GUID
- GUID `F4D2C30B712AC6E3` on IW4x was packed into a torrent version of the game.
___ ___
### Webfront ### Webfront
`Home` `Home`
@ -432,9 +444,13 @@ python DiscordWebhook.py
#### Anti-cheat #### Anti-cheat
This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools. This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools.
To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder. To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder.
The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x. The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x.
#### Database Storage #### Database Storage
By default, all **IW4MAdmin** information is stored in `Database.db`. By default, all **IW4MAdmin** information is stored in `Database.db`.
Should you need to reset your database, this file can simply be deleted. Should you need to reset your database, this file can simply be deleted.
Additionally, this file should be preserved during updates to retain client information. Additionally, this file should be preserved during updates to retain client information.
Setting the `ConnectionString` property in `IW4MAdminSettings.json` will cause **IW4MAdmin** to attempt to use a MySQL connection for database storage.
Setting the `ConnectionString` and `DatabaseProvider` properties in `IW4MAdminSettings.json`
will allow **IW4MAdmin** to use alternate methods for database storage.

View File

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

View File

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

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
namespace SharedLibraryCore namespace SharedLibraryCore
@ -14,7 +14,7 @@ namespace SharedLibraryCore
public abstract class Command public abstract class Command
{ {
public Command(String commandName, String commandDescription, String commandAlias, Player.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null) public Command(String commandName, String commandDescription, String commandAlias, EFClient.Permission requiredPermission, bool requiresTarget, CommandArgument[] param = null)
{ {
Name = commandName; Name = commandName;
Description = commandDescription; Description = commandDescription;
@ -33,7 +33,7 @@ namespace SharedLibraryCore
public String Alias { get; private set; } public String Alias { get; private set; }
public int RequiredArgumentCount => Arguments.Count(c => c.Required); public int RequiredArgumentCount => Arguments.Count(c => c.Required);
public bool RequiresTarget { get; private set; } public bool RequiresTarget { get; private set; }
public Player.Permission Permission { get; private set; } public EFClient.Permission Permission { get; private set; }
public CommandArgument[] Arguments { get; private set; } public CommandArgument[] Arguments { get; private set; }
} }
} }

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -60,7 +61,7 @@ namespace SharedLibraryCore.Commands
var found = await Manager.GetClientService().Get(dbID); var found = await Manager.GetClientService().Get(dbID);
if (found != null) if (found != null)
{ {
E.Target = found.AsPlayer(); E.Target = found;
E.Target.CurrentServer = E.Owner; E.Target.CurrentServer = E.Owner;
E.Data = String.Join(" ", Args.Skip(1)); E.Data = String.Join(" ", Args.Skip(1));
} }
@ -68,14 +69,14 @@ namespace SharedLibraryCore.Commands
else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num
{ {
if (E.Owner.Players[cNum] != null) if (E.Owner.Clients[cNum] != null)
{ {
E.Target = E.Owner.Players[cNum]; E.Target = E.Owner.Clients[cNum];
E.Data = String.Join(" ", Args.Skip(1)); E.Data = String.Join(" ", Args.Skip(1));
} }
} }
List<Player> matchingPlayers; List<EFClient> matchingPlayers;
if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words) if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words)
{ {

View File

@ -12,14 +12,13 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.RCon.StaticHelpers;
namespace SharedLibraryCore.Commands namespace SharedLibraryCore.Commands
{ {
public class CQuit : Command public class CQuit : Command
{ {
public CQuit() : public CQuit() :
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", Player.Permission.Owner, false) base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", EFClient.Permission.Owner, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
@ -31,18 +30,32 @@ namespace SharedLibraryCore.Commands
public class COwner : Command public class COwner : Command
{ {
public COwner() : public COwner() :
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", Player.Permission.User, false) base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", EFClient.Permission.User, false)
{ } { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0) if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0)
{ {
E.Origin.Level = Player.Permission.Owner; var oldPermission = E.Origin.Level;
E.Origin.Level = EFClient.Permission.Owner;
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_SUCCESS"]); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_SUCCESS"]);
// so setpassword/login works
E.Owner.Manager.GetPrivilegedClients().Add(E.Origin.ClientId, E.Origin);
await E.Owner.Manager.GetClientService().Update(E.Origin); await E.Owner.Manager.GetClientService().Update(E.Origin);
var e = new GameEvent()
{
Type = GameEvent.EventType.ChangePermission,
Origin = E.Origin,
Target = E.Origin,
Owner = E.Owner,
Extra = new Change()
{
PreviousValue = oldPermission.ToString(),
NewValue = E.Origin.Level.ToString()
}
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
} }
else else
{ {
@ -54,7 +67,7 @@ namespace SharedLibraryCore.Commands
public class CWarn : Command public class CWarn : Command
{ {
public CWarn() : public CWarn() :
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", Player.Permission.Trusted, true, new CommandArgument[] base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", EFClient.Permission.Trusted, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -83,7 +96,7 @@ namespace SharedLibraryCore.Commands
public class CWarnClear : Command public class CWarnClear : Command
{ {
public CWarnClear() : public CWarnClear() :
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", Player.Permission.Trusted, true, new CommandArgument[] base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", EFClient.Permission.Trusted, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -107,7 +120,7 @@ namespace SharedLibraryCore.Commands
public class CKick : Command public class CKick : Command
{ {
public CKick() : public CKick() :
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", Player.Permission.Moderator, true, new CommandArgument[] base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", EFClient.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -133,7 +146,7 @@ namespace SharedLibraryCore.Commands
public class CSay : Command public class CSay : Command
{ {
public CSay() : public CSay() :
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", Player.Permission.Moderator, false, new CommandArgument[] base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", EFClient.Permission.Moderator, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -153,7 +166,7 @@ namespace SharedLibraryCore.Commands
public class CTempBan : Command public class CTempBan : Command
{ {
public CTempBan() : public CTempBan() :
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Administrator, true, new CommandArgument[] base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", EFClient.Permission.Administrator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -183,9 +196,17 @@ namespace SharedLibraryCore.Commands
string tempbanReason = match.Groups[2].ToString(); string tempbanReason = match.Groups[2].ToString();
var length = match.Groups[1].ToString().ParseTimespan(); var length = match.Groups[1].ToString().ParseTimespan();
var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ? if (length > E.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") : {
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}"); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL_TOOLONG"]);
}
else
{
var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}");
}
} }
} }
} }
@ -193,7 +214,7 @@ namespace SharedLibraryCore.Commands
public class CBan : Command public class CBan : Command
{ {
public CBan() : public CBan() :
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", Player.Permission.SeniorAdmin, true, new CommandArgument[] base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -219,7 +240,7 @@ namespace SharedLibraryCore.Commands
public class CUnban : Command public class CUnban : Command
{ {
public CUnban() : public CUnban() :
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", Player.Permission.SeniorAdmin, true, new CommandArgument[] base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -252,12 +273,12 @@ namespace SharedLibraryCore.Commands
public class CWhoAmI : Command public class CWhoAmI : Command
{ {
public CWhoAmI() : public CWhoAmI() :
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", Player.Permission.User, false) base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", EFClient.Permission.User, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
{ {
String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString); String You = String.Format("{0} [^3#{1}^7] {2} ^7[^3@{3}^7] ^7[{4}^7] IP: {5}", E.Origin.Name, E.Origin.ClientNumber, E.Origin.NetworkId, E.Origin.ClientId, Utilities.ConvertLevelToColor(E.Origin.Level, E.Origin.ClientPermission.Name), E.Origin.IPAddressString);
E.Origin.Tell(You); E.Origin.Tell(You);
return Task.CompletedTask; return Task.CompletedTask;
@ -267,27 +288,33 @@ namespace SharedLibraryCore.Commands
public class CList : Command public class CList : Command
{ {
public CList() : public CList() :
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", Player.Permission.Moderator, false) base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", EFClient.Permission.Moderator, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
{ {
StringBuilder playerList = new StringBuilder(); StringBuilder playerList = new StringBuilder();
int count = 0; int count = 0;
for (int i = 0; i < E.Owner.Players.Count; i++) for (int i = 0; i < E.Owner.Clients.Count; i++)
{ {
var P = E.Owner.Players[i]; var P = E.Owner.Clients[i];
if (P == null) if (P == null)
{
continue; continue;
}
// todo: fix spacing // todo: fix spacing
// todo: make this better :) // todo: make this better :)
if (P.Masked) if (P.Masked)
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(Player.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - Player.Permission.User.ToString().Length)); {
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor( EFClient.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - EFClient.Permission.User.ToString().Length));
}
else else
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length)); {
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
}
if (count == 2 || E.Owner.GetPlayersAsList().Count == 1) if (count == 2 || E.Owner.GetClientsAsList().Count == 1)
{ {
E.Origin.Tell(playerList.ToString()); E.Origin.Tell(playerList.ToString());
count = 0; count = 0;
@ -312,7 +339,7 @@ namespace SharedLibraryCore.Commands
public class CHelp : Command public class CHelp : Command
{ {
public CHelp() : public CHelp() :
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", Player.Permission.User, false, new CommandArgument[] base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", EFClient.Permission.User, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -377,7 +404,7 @@ namespace SharedLibraryCore.Commands
public class CFastRestart : Command public class CFastRestart : Command
{ {
public CFastRestart() : public CFastRestart() :
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", Player.Permission.Moderator, false) base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", EFClient.Permission.Moderator, false)
{ } { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
@ -393,7 +420,7 @@ namespace SharedLibraryCore.Commands
public class CMapRotate : Command public class CMapRotate : Command
{ {
public CMapRotate() : public CMapRotate() :
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", Player.Permission.Administrator, false) base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", EFClient.Permission.Administrator, false)
{ } { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
@ -410,7 +437,7 @@ namespace SharedLibraryCore.Commands
public class CSetLevel : Command public class CSetLevel : Command
{ {
public CSetLevel() : public CSetLevel() :
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", Player.Permission.Moderator, true, new CommandArgument[] base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", EFClient.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -433,17 +460,17 @@ namespace SharedLibraryCore.Commands
return; return;
} }
Player.Permission oldPerm = E.Target.Level; EFClient.Permission oldPerm = E.Target.Level;
Player.Permission newPerm = Utilities.MatchPermission(E.Data); EFClient.Permission newPerm = Utilities.MatchPermission(E.Data);
if (newPerm == Player.Permission.Owner && if (newPerm == EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners) !E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners)
{ {
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_OWNER"]); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_OWNER"]);
return; return;
} }
if (E.Origin.Level < Player.Permission.Owner && if (E.Origin.Level < EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy) !E.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy)
{ {
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{E.Target.Name}"); E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{E.Target.Name}");
@ -452,14 +479,14 @@ namespace SharedLibraryCore.Commands
if (newPerm >= E.Origin.Level) if (newPerm >= E.Origin.Level)
{ {
if (E.Origin.Level < Player.Permission.Owner) if (E.Origin.Level < EFClient.Permission.Owner)
{ {
E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_LEVELTOOHIGH"], E.Target.Name, (E.Origin.Level - 1).ToString())); E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_LEVELTOOHIGH"], E.Target.Name, (E.Origin.Level - 1).ToString()));
return; return;
} }
} }
else if (newPerm > Player.Permission.Banned) else if (newPerm > EFClient.Permission.Banned)
{ {
var ActiveClient = E.Owner.Manager.GetActiveClients() var ActiveClient = E.Owner.Manager.GetActiveClients()
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId); .FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
@ -468,7 +495,11 @@ namespace SharedLibraryCore.Commands
{ {
ActiveClient.Level = newPerm; ActiveClient.Level = newPerm;
await E.Owner.Manager.GetClientService().Update(ActiveClient); await E.Owner.Manager.GetClientService().Update(ActiveClient);
ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}");
if (newPerm > oldPerm)
{
ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}");
}
} }
else else
@ -477,17 +508,6 @@ namespace SharedLibraryCore.Commands
await E.Owner.Manager.GetClientService().Update(E.Target); await E.Owner.Manager.GetClientService().Update(E.Target);
} }
try
{
E.Owner.Manager.GetPrivilegedClients().Add(E.Target.ClientId, E.Target);
}
catch (Exception)
{
// this updates their privilege level to the webfront claims
E.Owner.Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
}
var e = new GameEvent() var e = new GameEvent()
{ {
Origin = E.Origin, Origin = E.Origin,
@ -503,7 +523,9 @@ namespace SharedLibraryCore.Commands
E.Owner.Manager.GetEventHandler().AddEvent(e); E.Owner.Manager.GetEventHandler().AddEvent(e);
E.Origin.Tell($"{E.Target.Name} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"]}"); var _ = newPerm < oldPerm ?
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DEMOTE_SUCCESS"]} {E.Target.Name}") :
E.Origin.Tell($"{E.Target.Name} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"]}");
} }
else else
@ -516,7 +538,7 @@ namespace SharedLibraryCore.Commands
public class CUsage : Command public class CUsage : Command
{ {
public CUsage() : public CUsage() :
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", Player.Permission.Moderator, false) base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", EFClient.Permission.Moderator, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
@ -529,7 +551,7 @@ namespace SharedLibraryCore.Commands
public class CUptime : Command public class CUptime : Command
{ {
public CUptime() : public CUptime() :
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", Player.Permission.Moderator, false) base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", EFClient.Permission.Moderator, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
@ -544,13 +566,13 @@ namespace SharedLibraryCore.Commands
public class CListAdmins : Command public class CListAdmins : Command
{ {
public CListAdmins() : public CListAdmins() :
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", Player.Permission.User, false) base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", EFClient.Permission.User, false)
{ } { }
public static string OnlineAdmins(Server S) public static string OnlineAdmins(Server S)
{ {
var onlineAdmins = S.GetPlayersAsList() var onlineAdmins = S.GetClientsAsList()
.Where(p => p.Level > Player.Permission.Flagged) .Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => !p.Masked) .Where(p => !p.Masked)
.Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}"); .Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}");
@ -573,7 +595,7 @@ namespace SharedLibraryCore.Commands
public class CLoadMap : Command public class CLoadMap : Command
{ {
public CLoadMap() : public CLoadMap() :
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", Player.Permission.Administrator, false, new CommandArgument[] base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", EFClient.Permission.Administrator, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -606,7 +628,7 @@ namespace SharedLibraryCore.Commands
public class CFindPlayer : Command public class CFindPlayer : Command
{ {
public CFindPlayer() : public CFindPlayer() :
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", Player.Permission.Administrator, false, new CommandArgument[] base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", EFClient.Permission.Administrator, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -625,7 +647,7 @@ namespace SharedLibraryCore.Commands
} }
IList<EFClient> db_players = (await (E.Owner.Manager.GetClientService() as ClientService) IList<EFClient> db_players = (await (E.Owner.Manager.GetClientService() as ClientService)
.GetClientByName(E.Data)) .FindClientsByIdentifier(E.Data))
.OrderByDescending(p => p.LastConnection) .OrderByDescending(p => p.LastConnection)
.ToList(); .ToList();
@ -650,7 +672,7 @@ namespace SharedLibraryCore.Commands
public class CListRules : Command public class CListRules : Command
{ {
public CListRules() : public CListRules() :
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", Player.Permission.User, false) base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", EFClient.Permission.User, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
@ -668,7 +690,9 @@ namespace SharedLibraryCore.Commands
var rules = new List<string>(); var rules = new List<string>();
rules.AddRange(E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules); rules.AddRange(E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules);
if (E.Owner.ServerConfig.Rules != null) if (E.Owner.ServerConfig.Rules != null)
{
rules.AddRange(E.Owner.ServerConfig.Rules); rules.AddRange(E.Owner.ServerConfig.Rules);
}
foreach (string r in rules) foreach (string r in rules)
{ {
@ -683,7 +707,7 @@ namespace SharedLibraryCore.Commands
public class CPrivateMessage : Command public class CPrivateMessage : Command
{ {
public CPrivateMessage() : public CPrivateMessage() :
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", Player.Permission.User, true, new CommandArgument[] base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", EFClient.Permission.User, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -710,7 +734,7 @@ namespace SharedLibraryCore.Commands
public class CFlag : Command public class CFlag : Command
{ {
public CFlag() : public CFlag() :
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", Player.Permission.Moderator, true, new CommandArgument[] base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", EFClient.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -752,7 +776,7 @@ namespace SharedLibraryCore.Commands
public class CUnflag : Command public class CUnflag : Command
{ {
public CUnflag() : public CUnflag() :
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", Player.Permission.Moderator, true, new CommandArgument[] base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", EFClient.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -790,7 +814,7 @@ namespace SharedLibraryCore.Commands
public class CReport : Command public class CReport : Command
{ {
public CReport() : public CReport() :
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", Player.Permission.User, true, new CommandArgument[] base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", EFClient.Permission.User, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -861,7 +885,7 @@ namespace SharedLibraryCore.Commands
public class CListReports : Command public class CListReports : Command
{ {
public CListReports() : public CListReports() :
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", Player.Permission.Moderator, false, new CommandArgument[] base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", EFClient.Permission.Moderator, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -898,7 +922,7 @@ namespace SharedLibraryCore.Commands
public class CMask : Command public class CMask : Command
{ {
public CMask() : public CMask() :
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", Player.Permission.Moderator, false) base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", EFClient.Permission.Moderator, false)
{ } { }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
@ -921,7 +945,7 @@ namespace SharedLibraryCore.Commands
public class CListBanInfo : Command public class CListBanInfo : Command
{ {
public CListBanInfo() : public CListBanInfo() :
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", Player.Permission.Moderator, true, new CommandArgument[] base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", EFClient.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -935,7 +959,8 @@ namespace SharedLibraryCore.Commands
{ {
var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId); var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId);
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick && b.Expires > DateTime.UtcNow); var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick &&
(b.Expires == null || b.Expires > DateTime.UtcNow));
if (penalty == null) if (penalty == null)
{ {
@ -943,7 +968,7 @@ namespace SharedLibraryCore.Commands
return; return;
} }
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires - DateTime.UtcNow).TimeSpanText()} remaining)" : ""; string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"]; string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"];
E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, penalty.Punisher.Name)} {penalty.Punisher.Name} {timeRemaining}"); E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, penalty.Punisher.Name)} {penalty.Punisher.Name} {timeRemaining}");
@ -953,7 +978,7 @@ namespace SharedLibraryCore.Commands
public class CListAlias : Command public class CListAlias : Command
{ {
public CListAlias() : public CListAlias() :
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", Player.Permission.Moderator, true, new CommandArgument[] base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", EFClient.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -987,7 +1012,7 @@ namespace SharedLibraryCore.Commands
public class CExecuteRCON : Command public class CExecuteRCON : Command
{ {
public CExecuteRCON() : public CExecuteRCON() :
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", Player.Permission.Owner, false, new CommandArgument[] base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", EFClient.Permission.Owner, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -1001,16 +1026,21 @@ namespace SharedLibraryCore.Commands
{ {
var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim()); var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
foreach (string S in Response) foreach (string S in Response)
{
E.Origin.Tell(S.StripColors()); E.Origin.Tell(S.StripColors());
}
if (Response.Length == 0) if (Response.Length == 0)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_SUCCESS"]); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_SUCCESS"]);
}
} }
} }
public class CPlugins : Command public class CPlugins : Command
{ {
public CPlugins() : public CPlugins() :
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", Player.Permission.Administrator, false) base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", EFClient.Permission.Administrator, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
@ -1027,7 +1057,7 @@ namespace SharedLibraryCore.Commands
public class CIP : Command public class CIP : Command
{ {
public CIP() : public CIP() :
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", Player.Permission.User, false) base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", EFClient.Permission.User, false)
{ } { }
public override Task ExecuteAsync(GameEvent E) public override Task ExecuteAsync(GameEvent E)
@ -1039,7 +1069,7 @@ namespace SharedLibraryCore.Commands
public class CPruneAdmins : Command public class CPruneAdmins : Command
{ {
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", Player.Permission.Owner, false, new CommandArgument[] public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", EFClient.Permission.Owner, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -1059,7 +1089,9 @@ namespace SharedLibraryCore.Commands
{ {
inactiveDays = Int32.Parse(E.Data); inactiveDays = Int32.Parse(E.Data);
if (inactiveDays < 1) if (inactiveDays < 1)
{
throw new FormatException(); throw new FormatException();
}
} }
} }
@ -1076,10 +1108,10 @@ namespace SharedLibraryCore.Commands
{ {
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays); var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
inactiveUsers = await context.Clients inactiveUsers = await context.Clients
.Where(c => c.Level > Player.Permission.Flagged && c.Level <= Player.Permission.Moderator) .Where(c => c.Level > EFClient.Permission.Flagged && c.Level <= EFClient.Permission.Moderator)
.Where(c => c.LastConnection < lastActive) .Where(c => c.LastConnection < lastActive)
.ToListAsync(); .ToListAsync();
inactiveUsers.ForEach(c => c.Level = Player.Permission.User); inactiveUsers.ForEach(c => c.Level = EFClient.Permission.User);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
E.Origin.Tell($"^5{inactiveUsers.Count} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"]}"); E.Origin.Tell($"^5{inactiveUsers.Count} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"]}");
@ -1088,7 +1120,7 @@ namespace SharedLibraryCore.Commands
public class CSetPassword : Command public class CSetPassword : Command
{ {
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", Player.Permission.Moderator, false, new CommandArgument[] public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", EFClient.Permission.Moderator, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -1122,77 +1154,89 @@ namespace SharedLibraryCore.Commands
public class CKillServer : Command public class CKillServer : Command
{ {
public CKillServer() : base("killserver", "kill the game server", "kill", Player.Permission.Administrator, false) public CKillServer() : base("killserver", "kill the game server", "kill", EFClient.Permission.Administrator, false)
{ {
} }
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x"); if (E.Owner.ServerConfig.ManualLogPath != null)
System.Diagnostics.Process currentProcess = null;
foreach (var p in gameserverProcesses)
{ {
string cmdLine = Utilities.GetCommandLine(p.Id); using (var wc = new WebClient())
var regex = Regex.Match(cmdLine, @".*((?:\+set|\+) net_port) +([0-9]+).*");
if (regex.Success && Int32.Parse(regex.Groups[2].Value) == E.Owner.GetPort())
{ {
currentProcess = p; E.Owner.RestartRequested = true;
var response = await wc.DownloadStringTaskAsync(new Uri($"{E.Owner.ServerConfig.ManualLogPath}/restart"));
} }
} }
if (currentProcess == null)
{
E.Origin.Tell("Could not find running/stalled instance of IW4x");
}
else else
{ {
// attempt to kill it natively var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
try
System.Diagnostics.Process currentProcess = null;
foreach (var p in gameserverProcesses)
{ {
if (!E.Owner.Throttled) string cmdLine = Utilities.GetCommandLine(p.Id);
var regex = Regex.Match(cmdLine, @".*((?:\+set|\+) net_port) +([0-9]+).*");
if (regex.Success && Int32.Parse(regex.Groups[2].Value) == E.Owner.GetPort())
{ {
currentProcess = p;
}
}
if (currentProcess == null)
{
E.Origin.Tell("Could not find running/stalled instance of IW4x");
}
else
{
// attempt to kill it natively
try
{
if (!E.Owner.Throttled)
{
#if !DEBUG #if !DEBUG
await E.Owner.ExecuteCommandAsync("quit"); await E.Owner.ExecuteCommandAsync("quit");
#endif #endif
}
}
catch (Exceptions.NetworkException)
{
E.Origin.Tell("Unable to cleanly shutdown server, forcing");
}
if (!currentProcess.HasExited)
{
try
{
currentProcess.Kill();
E.Origin.Tell("Successfully killed server process");
}
catch (Exception e)
{
E.Origin.Tell("Could not kill server process");
E.Owner.Logger.WriteDebug("Unable to kill process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
return;
}
} }
} }
catch (Exceptions.NetworkException) return;
{
E.Origin.Tell("Unable to cleanly shutdown server, forcing");
}
if (!currentProcess.HasExited)
{
try
{
currentProcess.Kill();
E.Origin.Tell("Successfully killed server process");
}
catch (Exception e)
{
E.Origin.Tell("Could not kill server process");
E.Owner.Logger.WriteDebug("Unable to kill process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
return;
}
}
} }
return;
} }
} }
public class CPing : Command public class CPing : Command
{ {
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", Player.Permission.User, false, new CommandArgument[] public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", EFClient.Permission.User, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -1207,16 +1251,24 @@ namespace SharedLibraryCore.Commands
if (E.Message.IsBroadcastCommand()) if (E.Message.IsBroadcastCommand())
{ {
if (E.Target == null) if (E.Target == null)
{
E.Owner.Broadcast($"{E.Origin.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Origin.Ping}^7ms"); E.Owner.Broadcast($"{E.Origin.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Origin.Ping}^7ms");
}
else else
{
E.Owner.Broadcast($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms"); E.Owner.Broadcast($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
} }
else else
{ {
if (E.Target == null) if (E.Target == null)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"]} ^5{E.Origin.Ping}^7ms"); E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"]} ^5{E.Origin.Ping}^7ms");
}
else else
{
E.Origin.Tell($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms"); E.Origin.Tell($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -1225,7 +1277,7 @@ namespace SharedLibraryCore.Commands
public class CSetGravatar : Command public class CSetGravatar : Command
{ {
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", Player.Permission.User, false, new CommandArgument[] public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", EFClient.Permission.User, false, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -1289,7 +1341,7 @@ namespace SharedLibraryCore.Commands
/// </summary> /// </summary>
public class CNextMap : Command public class CNextMap : Command
{ {
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", Player.Permission.User, false) { } public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", EFClient.Permission.User, false) { }
public static async Task<string> GetNextMap(Server s) public static async Task<string> GetNextMap(Server s)
{ {
string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value.ToLower(); string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value.ToLower();

View File

@ -21,6 +21,8 @@ namespace SharedLibraryCore.Configuration
public string DatabaseProvider { get; set; } = "sqlite"; public string DatabaseProvider { get; set; } = "sqlite";
public string ConnectionString { get; set; } public string ConnectionString { get; set; }
public int RConPollRate { get; set; } = 5000; public int RConPollRate { get; set; } = 5000;
public bool IgnoreBots { get; set; }
public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0);
public string Id { get; set; } public string Id { get; set; }
public List<ServerConfiguration> Servers { get; set; } public List<ServerConfiguration> Servers { get; set; }
public int AutoMessagePeriod { get; set; } public int AutoMessagePeriod { get; set; }

View File

@ -3,6 +3,7 @@ using SharedLibraryCore.Database.Models;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Database namespace SharedLibraryCore.Database
{ {
@ -47,7 +48,7 @@ namespace SharedLibraryCore.Database
Connections = 0, Connections = 0,
FirstConnection = DateTime.UtcNow, FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow, LastConnection = DateTime.UtcNow,
Level = Objects.Player.Permission.Console, Level = Permission.Console,
Masked = true, Masked = true,
NetworkId = 0, NetworkId = 0,
AliasLinkId = 1, AliasLinkId = 1,

View File

@ -1,17 +1,13 @@
using System; using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using System.Reflection;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Data.Sqlite;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace SharedLibraryCore.Database namespace SharedLibraryCore.Database
{ {
@ -26,6 +22,7 @@ namespace SharedLibraryCore.Database
static string _ConnectionString; static string _ConnectionString;
static string _provider; static string _provider;
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins\";
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { } public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
@ -57,7 +54,7 @@ namespace SharedLibraryCore.Database
$"{Path.DirectorySeparatorChar}{currentPath}" : $"{Path.DirectorySeparatorChar}{currentPath}" :
currentPath; currentPath;
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = $"{currentPath}{Path.DirectorySeparatorChar}Database{Path.DirectorySeparatorChar}Database.db" }; var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = Path.Join(currentPath, "Database", "Database.db") };
var connectionString = connectionStringBuilder.ToString(); var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString); var connection = new SqliteConnection(connectionString);
@ -98,6 +95,9 @@ namespace SharedLibraryCore.Database
.WithMany(p => p.AdministeredPenalties) .WithMany(p => p.AdministeredPenalties)
.HasForeignKey(c => c.PunisherId) .HasForeignKey(c => c.PunisherId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
entity.Property(p => p.Expires)
.IsRequired(false);
}); });
modelBuilder.Entity<EFAliasLink>(entity => modelBuilder.Entity<EFAliasLink>(entity =>
@ -110,6 +110,7 @@ namespace SharedLibraryCore.Database
modelBuilder.Entity<EFAlias>(ent => modelBuilder.Entity<EFAlias>(ent =>
{ {
ent.Property(a => a.IPAddress).IsRequired(false);
ent.HasIndex(a => a.IPAddress); ent.HasIndex(a => a.IPAddress);
ent.Property(a => a.Name).HasMaxLength(24); ent.Property(a => a.Name).HasMaxLength(24);
ent.HasIndex(a => a.Name); ent.HasIndex(a => a.Name);
@ -123,26 +124,15 @@ namespace SharedLibraryCore.Database
// adapted from // adapted from
// 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; #if DEBUG
string pluginDir = _migrationPluginDirectory;
string pluginDir = $@"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}Debug{Path.DirectorySeparatorChar}netcoreapp2.1{Path.DirectorySeparatorChar}Plugins";
if (!Directory.Exists(pluginDir))
{
pluginDir = Path.Join(Environment.CurrentDirectory, "Plugins");
if (!Directory.Exists(pluginDir))
{
pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
}
}
directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
#if DEBUG == TRUE
foreach (string dllPath in Directory.GetFiles(@"C:\Projects\IW4M-Admin\Application\bin\Debug\netcoreapp2.1\Plugins").Where(f => f.EndsWith(".dll")))
#else #else
foreach (string dllPath in directoryFiles) string pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
#endif #endif
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
foreach (string dllPath in directoryFiles)
{ {
Assembly library; Assembly library;
try try
@ -160,7 +150,9 @@ namespace SharedLibraryCore.Database
.Select(c => (IModelConfiguration)Activator.CreateInstance(c)); .Select(c => (IModelConfiguration)Activator.CreateInstance(c));
foreach (var configurable in configurations) foreach (var configurable in configurations)
{
configurable.Configure(modelBuilder); configurable.Configure(modelBuilder);
}
foreach (var type in library.ExportedTypes) foreach (var type in library.ExportedTypes)
{ {

View File

@ -16,7 +16,7 @@ namespace SharedLibraryCore.Database.Models
[MaxLength(24)] [MaxLength(24)]
public string Name { get; set; } public string Name { get; set; }
[Required] [Required]
public int IPAddress { get; set; } public int? IPAddress { get; set; }
[Required] [Required]
public DateTime DateAdded { get; set; } public DateTime DateAdded { get; set; }
} }

View File

@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models namespace SharedLibraryCore.Database.Models
{ {
public class EFClient : SharedEntity public partial class EFClient : SharedEntity
{ {
[Key] [Key]
public int ClientId { get; set; } public int ClientId { get; set; }
@ -25,7 +25,7 @@ namespace SharedLibraryCore.Database.Models
[ForeignKey("AliasLinkId")] [ForeignKey("AliasLinkId")]
public virtual EFAliasLink AliasLink { get; set; } public virtual EFAliasLink AliasLink { get; set; }
[Required] [Required]
public Objects.Player.Permission Level { get; set; } public Permission Level { get; set; }
[Required] [Required]
public int CurrentAliasId { get; set; } public int CurrentAliasId { get; set; }
@ -41,27 +41,21 @@ namespace SharedLibraryCore.Database.Models
public virtual string Name public virtual string Name
{ {
get { return CurrentAlias.Name; } get { return CurrentAlias.Name; }
set { } set { CurrentAlias.Name = value; }
} }
[NotMapped] [NotMapped]
public virtual int IPAddress public virtual int? IPAddress
{ {
get { return CurrentAlias.IPAddress; } get { return CurrentAlias.IPAddress; }
set { } set { CurrentAlias.IPAddress = value; }
} }
[NotMapped] [NotMapped]
public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString(); public string IPAddressString => IPAddress.ConvertIPtoString();
[NotMapped] [NotMapped]
public virtual IDictionary<int, long> LinkedAccounts { get; set; } public virtual IDictionary<int, long> LinkedAccounts { get; set; }
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; } public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; } public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
public EFClient()
{
ReceivedPenalties = new List<EFPenalty>();
AdministeredPenalties = new List<EFPenalty>();
}
} }
} }

View File

@ -27,7 +27,7 @@ namespace SharedLibraryCore.Database.Models
[Required] [Required]
public DateTime When { get; set; } public DateTime When { get; set; }
[Required] [Required]
public DateTime Expires { get; set; } public DateTime? Expires { get; set; }
[Required] [Required]
public string Offense { get; set; } public string Offense { get; set; }
public string AutomatedOffense { get; set; } public string AutomatedOffense { get; set; }

View File

@ -1,17 +1,10 @@
using System; namespace SharedLibraryCore.Dtos
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static SharedLibraryCore.Objects.Player;
namespace SharedLibraryCore.Dtos
{ {
public class ClientInfo public class ClientInfo
{ {
public string Name { get; set; } public string Name { get; set; }
public int ClientId { get; set; } public int ClientId { get; set; }
public int LinkId { get; set; } public int LinkId { get; set; }
public Permission Level { get; set; } public Database.Models.EFClient.Permission Level { get; set; }
} }
} }

View File

@ -9,7 +9,7 @@ namespace SharedLibraryCore.Dtos
/// </summary> /// </summary>
public class EntityInfo public class EntityInfo
{ {
public int Id { get; set; } public long Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
} }
} }

View File

@ -17,7 +17,7 @@ namespace SharedLibraryCore.Dtos
public List<ChatInfo> ChatHistory { get; set; } public List<ChatInfo> ChatHistory { get; set; }
public List<PlayerInfo> Players { get; set; } public List<PlayerInfo> Players { get; set; }
public Helpers.PlayerHistory[] PlayerHistory { get; set; } public Helpers.PlayerHistory[] PlayerHistory { get; set; }
public int ID { get; set; } public long ID { get; set; }
public bool Online { get; set; } public bool Online { get; set; }
} }
} }

View File

@ -41,7 +41,7 @@ namespace SharedLibraryCore.Events
OwnerEntity = new EntityInfo() OwnerEntity = new EntityInfo()
{ {
Name = E.Owner.Hostname, Name = E.Owner.Hostname,
Id = E.Owner.GetHashCode() Id = E.Owner.EndPoint
}, },
OriginEntity = E.Origin == null ? null : new EntityInfo() OriginEntity = E.Origin == null ? null : new EntityInfo()
{ {

View File

@ -1,7 +1,7 @@
using System; using SharedLibraryCore.Database.Models;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore namespace SharedLibraryCore
{ {
@ -50,11 +50,11 @@ namespace SharedLibraryCore
/// </summary> /// </summary>
Stop, Stop,
/// <summary> /// <summary>
/// a client was detecting as connecting via RCon /// a client was detecting as connecting via log
/// </summary> /// </summary>
Connect, Connect,
/// <summary> /// <summary>
/// a client was detecting joining via log /// a client was detecting joining by RCon
/// </summary> /// </summary>
Join, Join,
/// <summary> /// <summary>
@ -73,6 +73,18 @@ namespace SharedLibraryCore
/// the current map changed /// the current map changed
/// </summary> /// </summary>
MapChange, MapChange,
/// <summary>
/// a client was detected as starting to connect
/// </summary>
PreConnect,
/// <summary>
/// a client was detecting as starting to disconnect
/// </summary>
PreDisconnect,
/// <summary>
/// a client's information was updated
/// </summary>
Update,
// events "generated" by clients // events "generated" by clients
/// <summary> /// <summary>
@ -158,7 +170,10 @@ namespace SharedLibraryCore
} }
static long NextEventId; static long NextEventId;
static long GetNextEventId() => Interlocked.Increment(ref NextEventId); static long GetNextEventId()
{
return Interlocked.Increment(ref NextEventId);
}
public GameEvent() public GameEvent()
{ {
@ -170,8 +185,8 @@ namespace SharedLibraryCore
public EventType Type; public EventType Type;
public string Data; // Data is usually the message sent by player public string Data; // Data is usually the message sent by player
public string Message; public string Message;
public Player Origin; public EFClient Origin;
public Player Target; public EFClient Target;
public Server Owner; public Server Owner;
public Boolean Remote = false; public Boolean Remote = false;
public object Extra { get; set; } public object Extra { get; set; }
@ -185,43 +200,13 @@ namespace SharedLibraryCore
/// asynchronously wait for GameEvent to be processed /// asynchronously wait for GameEvent to be processed
/// </summary> /// </summary>
/// <returns>waitable task </returns> /// <returns>waitable task </returns>
public Task<GameEvent> WaitAsync(int timeOut = int.MaxValue) => Task.Run(() => public Task<GameEvent> WaitAsync(int timeOut = int.MaxValue)
{ {
OnProcessed.Wait(timeOut); return Task.Run(() =>
return this; {
}); OnProcessed.Wait(timeOut);
return this;
/// <summary> });
/// determine whether an event should be delayed or not
/// applies only to the origin entity
/// </summary>
/// <param name="queuedEvent">event to determine status for</param>
/// <returns>true if event should be delayed, false otherwise</returns>
public static bool ShouldOriginEventBeDelayed(GameEvent queuedEvent)
{
return queuedEvent.Origin != null &&
(queuedEvent.Origin.State != Player.ClientState.Connected &&
// we want to allow join and quit events
queuedEvent.Type != EventType.Connect &&
queuedEvent.Type != EventType.Join &&
queuedEvent.Type != EventType.Quit &&
queuedEvent.Type != EventType.Disconnect &&
// we don't care about unknown events
queuedEvent.Origin.NetworkId != 0);
}
/// <summary>
/// determine whether an event should be delayed or not
/// applies only to the target entity
/// </summary>
/// <param name="queuedEvent">event to determine status for</param>
/// <returns>true if event should be delayed, false otherwise</returns>
public static bool ShouldTargetEventBeDelayed(GameEvent queuedEvent)
{
return (queuedEvent.Target != null && queuedEvent.Target.ClientNumber != -1) &&
(queuedEvent.Target.State != Player.ClientState.Connected &&
queuedEvent.Target.NetworkId != 0 &&
queuedEvent.Origin?.ClientId != 1);
} }
} }
} }

View File

@ -1,27 +0,0 @@
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
public interface IClientAuthentication
{
/// <summary>
/// request authentication when a client join event
/// occurs in the log, as no IP is given
/// </summary>
/// <param name="client">client that has joined from the log</param>
void RequestClientAuthentication(Player client);
/// <summary>
/// get all clients that have been authenticated by the status poll
/// </summary>
/// <returns>list of all authenticated clients</returns>
IList<Player> GetAuthenticatedClients();
/// <summary>
/// authenticate a list of clients from status poll
/// </summary>
/// <param name="clients">list of clients to authenticate</param>
void AuthenticateClients(IList<Player> clients);
}
}

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces namespace SharedLibraryCore.Interfaces
{ {
@ -16,7 +17,7 @@ namespace SharedLibraryCore.Interfaces
/// <param name="fileSizeDiff"></param> /// <param name="fileSizeDiff"></param>
/// <param name="startPosition"></param> /// <param name="startPosition"></param>
/// <returns></returns> /// <returns></returns>
ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition); Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition);
/// <summary> /// <summary>
/// how long the log file is /// how long the log file is
/// </summary> /// </summary>

View File

@ -5,6 +5,7 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using System.Reflection; using System.Reflection;
using SharedLibraryCore.Database.Models;
namespace SharedLibraryCore.Interfaces namespace SharedLibraryCore.Interfaces
{ {
@ -13,16 +14,16 @@ namespace SharedLibraryCore.Interfaces
Task Init(); Task Init();
void Start(); void Start();
void Stop(); void Stop();
ILogger GetLogger(int serverId); ILogger GetLogger(long serverId);
IList<Server> GetServers(); IList<Server> GetServers();
IList<Command> GetCommands(); IList<Command> GetCommands();
IList<Helpers.MessageToken> GetMessageTokens(); IList<Helpers.MessageToken> GetMessageTokens();
IList<Player> GetActiveClients(); IList<EFClient> GetActiveClients();
IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings(); IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings();
ClientService GetClientService(); ClientService GetClientService();
AliasService GetAliasService(); AliasService GetAliasService();
PenaltyService GetPenaltyService(); PenaltyService GetPenaltyService();
IDictionary<int, Player> GetPrivilegedClients(); IDictionary<int, EFClient> GetPrivilegedClients();
/// <summary> /// <summary>
/// Get the event handlers /// Get the event handlers
/// </summary> /// </summary>

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon; using SharedLibraryCore.RCon;
@ -10,7 +11,7 @@ namespace SharedLibraryCore.Interfaces
Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName); Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName);
Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue); Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue);
Task<string[]> ExecuteCommandAsync(Connection connection, string command); Task<string[]> ExecuteCommandAsync(Connection connection, string command);
Task<List<Player>> GetStatusAsync(Connection connection); Task<List<EFClient>> GetStatusAsync(Connection connection);
CommandPrefix GetCommandPrefixes(); CommandPrefix GetCommandPrefixes();
} }
} }

View File

@ -0,0 +1,690 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20181014171848_MakePenaltyExpirationNullable")]
partial class MakePenaltyExpirationNullable
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<int>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<int>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<int>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<int>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<int?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("Performance");
b.HasIndex("Ranking");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<int>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired();
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,105 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class MakePenaltyExpirationNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite")
{
migrationBuilder.Sql(@"PRAGMA foreign_keys = 0;
CREATE TABLE sqlitestudio_temp_table AS SELECT *
FROM EFPenalties;
DROP TABLE EFPenalties;
CREATE TABLE EFPenalties (
PenaltyId INTEGER NOT NULL
CONSTRAINT PK_EFPenalties PRIMARY KEY AUTOINCREMENT,
Active INTEGER NOT NULL,
Expires TEXT,
LinkId INTEGER NOT NULL,
OffenderId INTEGER NOT NULL,
Offense TEXT NOT NULL,
PunisherId INTEGER NOT NULL,
Type INTEGER NOT NULL,
[When] TEXT NOT NULL,
AutomatedOffense TEXT,
CONSTRAINT FK_EFPenalties_EFAliasLinks_LinkId FOREIGN KEY (
LinkId
)
REFERENCES EFAliasLinks (AliasLinkId) ON DELETE CASCADE,
CONSTRAINT FK_EFPenalties_EFClients_OffenderId FOREIGN KEY (
OffenderId
)
REFERENCES EFClients (ClientId) ON DELETE RESTRICT,
CONSTRAINT FK_EFPenalties_EFClients_PunisherId FOREIGN KEY (
PunisherId
)
REFERENCES EFClients (ClientId) ON DELETE RESTRICT
);
INSERT INTO EFPenalties (
PenaltyId,
Active,
Expires,
LinkId,
OffenderId,
Offense,
PunisherId,
Type,
[When],
AutomatedOffense
)
SELECT PenaltyId,
Active,
Expires,
LinkId,
OffenderId,
Offense,
PunisherId,
Type,
""When"",
AutomatedOffense
FROM sqlitestudio_temp_table;
DROP TABLE sqlitestudio_temp_table;
CREATE INDEX IX_EFPenalties_LinkId ON EFPenalties(
""LinkId""
);
CREATE INDEX IX_EFPenalties_OffenderId ON EFPenalties(
""OffenderId""
);
CREATE INDEX IX_EFPenalties_PunisherId ON EFPenalties(
""PunisherId""
);
PRAGMA foreign_keys = 1; ");
}
else
{
migrationBuilder.AlterColumn<DateTime>(
name: "Expires",
table: "EFPenalties",
nullable: true,
oldClrType: typeof(DateTime));
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "Expires",
table: "EFPenalties",
nullable: false,
oldClrType: typeof(DateTime),
oldNullable: true);
}
}
}

View File

@ -0,0 +1,690 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20181125193243_MakeClientIPNullable")]
partial class MakeClientIPNullable
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<int>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<int>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<int>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<int>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<int?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("Performance");
b.HasIndex("Ranking");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<int>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int?>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired();
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,85 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class MakeClientIPNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite")
{
migrationBuilder.Sql(@"PRAGMA foreign_keys = 0;
CREATE TABLE sqlitestudio_temp_table AS SELECT *
FROM EFAlias;
DROP TABLE EFAlias;
CREATE TABLE EFAlias (
AliasId INTEGER NOT NULL
CONSTRAINT PK_EFAlias PRIMARY KEY AUTOINCREMENT,
Active INTEGER NOT NULL,
DateAdded TEXT NOT NULL,
IPAddress INTEGER,
LinkId INTEGER NOT NULL,
Name TEXT NOT NULL,
CONSTRAINT FK_EFAlias_EFAliasLinks_LinkId FOREIGN KEY (
LinkId
)
REFERENCES EFAliasLinks (AliasLinkId) ON DELETE RESTRICT
);
INSERT INTO EFAlias (
AliasId,
Active,
DateAdded,
IPAddress,
LinkId,
Name
)
SELECT AliasId,
Active,
DateAdded,
IPAddress,
LinkId,
Name
FROM sqlitestudio_temp_table;
DROP TABLE sqlitestudio_temp_table;
CREATE INDEX IX_EFAlias_LinkId ON EFAlias (
""LinkId""
);
CREATE INDEX IX_EFAlias_IPAddress ON EFAlias(
""IPAddress""
);
CREATE INDEX IX_EFAlias_Name ON EFAlias(
""Name""
);
PRAGMA foreign_keys = 1;
", suppressTransaction:true);
}
else
{
migrationBuilder.AlterColumn<int>(
name: "IPAddress",
table: "EFAlias",
nullable: true,
oldClrType: typeof(int));
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "IPAddress",
table: "EFAlias",
nullable: false,
oldClrType: typeof(int),
oldNullable: true);
}
}
}

View File

@ -0,0 +1,692 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20181127144417_AddEndpointToEFServerUpdateServerIdType")]
partial class AddEndpointToEFServerUpdateServerIdType
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int>("CurrentViewAngleId");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int>("HitDestinationId");
b.Property<int>("HitLocation");
b.Property<int>("HitOriginId");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int>("LastStrainAngleId");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<long>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<long>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<long>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<long?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId");
b.HasIndex("Performance");
b.HasIndex("Ranking");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId");
b.Property<bool>("Active");
b.Property<string>("EndPoint");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<long>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int?>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<string>("CurrentValue");
b.Property<int>("OriginEntityId");
b.Property<string>("PreviousValue");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired();
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime?>("Expires");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddEndpointToEFServerUpdateServerIdType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "EndPoint",
table: "EFServers",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "EndPoint",
table: "EFServers");
}
}
}

View File

@ -3,7 +3,6 @@ using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations namespace SharedLibraryCore.Migrations
@ -15,9 +14,7 @@ namespace SharedLibraryCore.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
.HasAnnotation("ProductVersion", "2.1.3-rtm-32065")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b => modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{ {
@ -108,7 +105,7 @@ namespace SharedLibraryCore.Migrations
b.Property<int>("Map"); b.Property<int>("Map");
b.Property<int>("ServerId"); b.Property<long>("ServerId");
b.Property<int>("VictimId"); b.Property<int>("VictimId");
@ -148,7 +145,7 @@ namespace SharedLibraryCore.Migrations
b.Property<string>("Message"); b.Property<string>("Message");
b.Property<int>("ServerId"); b.Property<long>("ServerId");
b.Property<DateTime>("TimeSent"); b.Property<DateTime>("TimeSent");
@ -183,7 +180,7 @@ namespace SharedLibraryCore.Migrations
{ {
b.Property<int>("ClientId"); b.Property<int>("ClientId");
b.Property<int>("ServerId"); b.Property<long>("ServerId");
b.Property<bool>("Active"); b.Property<bool>("Active");
@ -230,7 +227,7 @@ namespace SharedLibraryCore.Migrations
b.Property<float>("MaxAngleDistance"); b.Property<float>("MaxAngleDistance");
b.Property<int>("ServerId") b.Property<long>("ServerId")
.HasColumnName("EFClientStatistics_ServerId"); .HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId"); b.HasKey("HitLocationCountId");
@ -259,7 +256,7 @@ namespace SharedLibraryCore.Migrations
b.Property<int>("RatingHistoryId"); b.Property<int>("RatingHistoryId");
b.Property<int?>("ServerId"); b.Property<long?>("ServerId");
b.Property<DateTime>("When"); b.Property<DateTime>("When");
@ -280,10 +277,12 @@ namespace SharedLibraryCore.Migrations
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{ {
b.Property<int>("ServerId"); b.Property<long>("ServerId");
b.Property<bool>("Active"); b.Property<bool>("Active");
b.Property<string>("EndPoint");
b.Property<int>("Port"); b.Property<int>("Port");
b.HasKey("ServerId"); b.HasKey("ServerId");
@ -298,7 +297,7 @@ namespace SharedLibraryCore.Migrations
b.Property<bool>("Active"); b.Property<bool>("Active");
b.Property<int>("ServerId"); b.Property<long>("ServerId");
b.Property<long>("TotalKills"); b.Property<long>("TotalKills");
@ -320,7 +319,7 @@ namespace SharedLibraryCore.Migrations
b.Property<DateTime>("DateAdded"); b.Property<DateTime>("DateAdded");
b.Property<int>("IPAddress"); b.Property<int?>("IPAddress");
b.Property<int>("LinkId"); b.Property<int>("LinkId");
@ -456,7 +455,7 @@ namespace SharedLibraryCore.Migrations
b.Property<string>("AutomatedOffense"); b.Property<string>("AutomatedOffense");
b.Property<DateTime>("Expires"); b.Property<DateTime?>("Expires");
b.Property<int>("LinkId"); b.Property<int>("LinkId");

View File

@ -1,13 +1,13 @@
using System; using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SharedLibraryCore.Objects namespace SharedLibraryCore.Database.Models
{ {
public class Player : Database.Models.EFClient public partial class EFClient
{ {
public enum ClientState public enum ClientState
{ {
@ -17,11 +17,6 @@ namespace SharedLibraryCore.Objects
/// </summary> /// </summary>
Connecting, Connecting,
/// <summary> /// <summary>
/// represents when the client has been parsed by RCon,
/// but has not been validated against the database
/// </summary>
Authenticated,
/// <summary>
/// represents when the client has been authenticated by RCon /// represents when the client has been authenticated by RCon
/// and validated by the database /// and validated by the database
/// </summary> /// </summary>
@ -76,18 +71,21 @@ namespace SharedLibraryCore.Objects
Console = 8 Console = 8
} }
public Player() public EFClient()
{ {
ConnectionTime = DateTime.UtcNow; ConnectionTime = DateTime.UtcNow;
ClientNumber = -1; ClientNumber = -1;
DelayedEvents = new Queue<GameEvent>();
_additionalProperties = new Dictionary<string, object> _additionalProperties = new Dictionary<string, object>
{ {
{ "_reportCount", 0 } { "_reportCount", 0 }
}; };
CurrentAlias = new EFAlias();
} }
public override string ToString() => $"{Name}::{NetworkId}"; public override string ToString()
{
return $"{Name}::{NetworkId}";
}
/// <summary> /// <summary>
/// send a message directly to the connected client /// send a message directly to the connected client
@ -113,7 +111,7 @@ namespace SharedLibraryCore.Objects
/// </summary> /// </summary>
/// <param name="warnReason">reason for warn</param> /// <param name="warnReason">reason for warn</param>
/// <param name="sender">client performing the warn</param> /// <param name="sender">client performing the warn</param>
public GameEvent Warn(String warnReason, Player sender) public GameEvent Warn(String warnReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -146,7 +144,7 @@ namespace SharedLibraryCore.Objects
/// </summary> /// </summary>
/// <param name="sender">client performing the warn clear</param> /// <param name="sender">client performing the warn clear</param>
/// <returns></returns> /// <returns></returns>
public GameEvent WarnClear(Player sender) public GameEvent WarnClear(EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -175,7 +173,7 @@ namespace SharedLibraryCore.Objects
/// <param name="reportReason">reason for the report</param> /// <param name="reportReason">reason for the report</param>
/// <param name="sender">client performing the report</param> /// <param name="sender">client performing the report</param>
/// <returns></returns> /// <returns></returns>
public GameEvent Report(string reportReason, Player sender) public GameEvent Report(string reportReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -221,7 +219,7 @@ namespace SharedLibraryCore.Objects
/// <param name="flagReason">reason for flagging</param> /// <param name="flagReason">reason for flagging</param>
/// <param name="sender">client performing the flag</param> /// <param name="sender">client performing the flag</param>
/// <returns>game event for the flag</returns> /// <returns>game event for the flag</returns>
public GameEvent Flag(string flagReason, Player sender) public GameEvent Flag(string flagReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -238,14 +236,14 @@ namespace SharedLibraryCore.Objects
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
} }
else if (this.Level == Player.Permission.Flagged) else if (this.Level == Permission.Flagged)
{ {
e.FailReason = GameEvent.EventFailReason.Invalid; e.FailReason = GameEvent.EventFailReason.Invalid;
} }
else else
{ {
this.Level = Player.Permission.Flagged; this.Level = Permission.Flagged;
} }
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
@ -258,7 +256,7 @@ namespace SharedLibraryCore.Objects
/// <param name="unflagReason">reason to unflag a player for</param> /// <param name="unflagReason">reason to unflag a player for</param>
/// <param name="sender">client performing the unflag</param> /// <param name="sender">client performing the unflag</param>
/// <returns>game event for the un flug</returns> /// <returns>game event for the un flug</returns>
public GameEvent Unflag(string unflagReason, Player sender) public GameEvent Unflag(string unflagReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -275,7 +273,7 @@ namespace SharedLibraryCore.Objects
e.FailReason = GameEvent.EventFailReason.Permission; e.FailReason = GameEvent.EventFailReason.Permission;
} }
else if (this.Level != Player.Permission.Flagged) else if (this.Level != EFClient.Permission.Flagged)
{ {
e.FailReason = GameEvent.EventFailReason.Invalid; e.FailReason = GameEvent.EventFailReason.Invalid;
} }
@ -294,7 +292,7 @@ namespace SharedLibraryCore.Objects
/// </summary> /// </summary>
/// <param name="kickReason">reason to kick for</param> /// <param name="kickReason">reason to kick for</param>
/// <param name="sender">client performing the kick</param> /// <param name="sender">client performing the kick</param>
public GameEvent Kick(String kickReason, Player sender) public GameEvent Kick(String kickReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -322,7 +320,7 @@ namespace SharedLibraryCore.Objects
/// <param name="tempbanReason">reason for the temp ban</param> /// <param name="tempbanReason">reason for the temp ban</param>
/// <param name="banLength">how long the temp ban lasts</param> /// <param name="banLength">how long the temp ban lasts</param>
/// <param name="sender">client performing the tempban</param> /// <param name="sender">client performing the tempban</param>
public GameEvent TempBan(String tempbanReason, TimeSpan banLength, Player sender) public GameEvent TempBan(String tempbanReason, TimeSpan banLength, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -350,7 +348,7 @@ namespace SharedLibraryCore.Objects
/// </summary> /// </summary>
/// <param name="banReason">reason for the ban</param> /// <param name="banReason">reason for the ban</param>
/// <param name="sender">client performing the ban</param> /// <param name="sender">client performing the ban</param>
public GameEvent Ban(String banReason, Player sender) public GameEvent Ban(String banReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -378,7 +376,7 @@ namespace SharedLibraryCore.Objects
/// <param name="unbanReason">reason for the unban</param> /// <param name="unbanReason">reason for the unban</param>
/// <param name="sender">client performing the unban</param> /// <param name="sender">client performing the unban</param>
/// <returns></returns> /// <returns></returns>
public GameEvent Unban(String unbanReason, Player sender) public GameEvent Unban(String unbanReason, EFClient sender)
{ {
var e = new GameEvent() var e = new GameEvent()
{ {
@ -400,9 +398,135 @@ namespace SharedLibraryCore.Objects
return e; return e;
} }
/// <summary>
/// Handles any client related logic on connection
/// </summary>
public void OnConnect()
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
if (Name.Length < 3)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because their name is too short");
Kick(loc["SERVER_KICK_MINNAME"], Utilities.IW4MAdminClient(CurrentServer));
return;
}
if (Name == "Unknown Soldier" ||
Name == "UnknownSoldier" ||
Name == "CHEATER")
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because their name is generic");
Kick(loc["SERVER_KICK_GENERICNAME"], Utilities.IW4MAdminClient(CurrentServer));
return;
}
if (Name.Where(c => char.IsControl(c)).Count() > 0)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because their name contains control characters");
Kick(loc["SERVER_KICK_CONTROLCHARS"], Utilities.IW4MAdminClient(CurrentServer));
return;
}
// reserved slots stuff
if (CurrentServer.MaxClients - (CurrentServer.GetClientsAsList().Count(_client => !_client.IsPrivileged())) < CurrentServer.ServerConfig.ReservedSlotNumber &&
!this.IsPrivileged())
{
CurrentServer.Logger.WriteDebug($"Kicking {this} their spot is reserved");
Kick(loc["SERVER_KICK_SLOT_IS_RESERVED"], Utilities.IW4MAdminClient(CurrentServer));
return;
}
LastConnection = DateTime.UtcNow;
Connections += 1;
}
public async Task OnDisconnect()
{
State = ClientState.Disconnecting;
TotalConnectionTime += ConnectionLength;
LastConnection = DateTime.UtcNow;
await CurrentServer.Manager.GetClientService().Update(this);
}
public async Task OnJoin(int? ipAddress)
{
IPAddress = ipAddress;
await CurrentServer.Manager.GetClientService().UpdateAlias(this);
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress);
var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban || p.Type == Penalty.PenaltyType.TempBan);
var currentAutoFlag = activePenalties.Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1)
.Where(p => p.Active)
.OrderByDescending(p => p.When)
.FirstOrDefault();
// remove their auto flag status after a week
if (Level == Permission.Flagged &&
currentAutoFlag != null &&
(DateTime.UtcNow - currentAutoFlag.When).TotalDays > 7)
{
Level = Permission.User;
}
if (currentBan != null)
{
CurrentServer.Logger.WriteInfo($"Banned client {this} trying to join...");
var autoKickClient = Utilities.IW4MAdminClient(CurrentServer);
// reban the "evading" guid
if (Level != Permission.Banned &&
currentBan.Type == Penalty.PenaltyType.Ban)
{
CurrentServer.Logger.WriteInfo($"Banned client {this} connected using a new GUID");
// hack: re apply the automated offense to the reban
if (currentBan.AutomatedOffense != null)
{
autoKickClient.AdministeredPenalties?.Add(new EFPenalty()
{
AutomatedOffense = currentBan.AutomatedOffense
});
}
Ban($"{currentBan.Offense}", autoKickClient);
}
// the player is permanently banned
else if (currentBan.Type == Penalty.PenaltyType.Ban)
{
Kick($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense} ({loc["SERVER_BAN_APPEAL"]} {CurrentServer.Website})", autoKickClient);
}
else
{
Kick($"{loc["SERVER_TB_REMAIN"]} ({(currentBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
}
}
else
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = this,
Target = this,
Owner = CurrentServer
};
CurrentServer.Manager.GetEventHandler().AddEvent(e);
}
}
[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)
{
return _additionalProperties.ContainsKey(name) ? (T)_additionalProperties[name] : default(T);
}
public void SetAdditionalProperty(string name, object value) public void SetAdditionalProperty(string name, object value)
{ {
if (_additionalProperties.ContainsKey(name)) if (_additionalProperties.ContainsKey(name))
@ -431,22 +555,10 @@ namespace SharedLibraryCore.Objects
public int Score { get; set; } public int Score { get; set; }
[NotMapped] [NotMapped]
public bool IsBot { get; set; } public bool IsBot { get; set; }
private int _ipaddress;
public override int IPAddress
{
get { return _ipaddress; }
set { _ipaddress = value; }
}
private string _name;
public override string Name
{
get { return _name; }
set { _name = value; }
}
[NotMapped] [NotMapped]
public ClientState State { get; set; } public ClientState State { get; set; }
[NotMapped]
public Queue<GameEvent> DelayedEvents { get; set; }
[NotMapped] [NotMapped]
// this is kinda dirty, but I need localizable level names // this is kinda dirty, but I need localizable level names
public ClientPermission ClientPermission => new ClientPermission() public ClientPermission ClientPermission => new ClientPermission()
@ -458,9 +570,12 @@ namespace SharedLibraryCore.Objects
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return ((Player)obj).NetworkId == this.NetworkId; return ((EFClient)obj).NetworkId == this.NetworkId;
} }
public override int GetHashCode() => (int)NetworkId; public override int GetHashCode()
{
return (int)NetworkId;
}
} }
} }

View File

@ -1,7 +1,4 @@
using System; using static SharedLibraryCore.Database.Models.EFClient;
using System.Collections.Generic;
using System.Text;
using static SharedLibraryCore.Objects.Player;
namespace SharedLibraryCore.Objects namespace SharedLibraryCore.Objects
{ {

View File

@ -1,4 +1,5 @@
using System; using SharedLibraryCore.Database.Models;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -8,8 +9,8 @@ namespace SharedLibraryCore.Objects
{ {
public class Report public class Report
{ {
public Player Target { get; set; } public EFClient Target { get; set; }
public Player Origin { get; set; } public EFClient Origin { get; set; }
public String Reason { get; set; } public string Reason { get; set; }
} }
} }

View File

@ -5,7 +5,6 @@ using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -29,7 +28,8 @@ namespace SharedLibraryCore.RCon
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>(); static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
public IPEndPoint Endpoint { get; private set; } public IPEndPoint Endpoint { get; private set; }
public string RConPassword { get; private set; } public string RConPassword { get; private set; }
ILogger Log;
private readonly ILogger Log;
public Connection(string ipAddress, int port, string password, ILogger log) public Connection(string ipAddress, int port, string password, ILogger log)
{ {
@ -88,42 +88,50 @@ namespace SharedLibraryCore.RCon
byte[] response = null; byte[] response = null;
retrySend: retrySend:
connectionState.SendEventArgs.UserToken = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{ {
DontFragment = true, //DontFragment = true,
Ttl = 42, Ttl = 100,
ExclusiveAddressUse = true, ExclusiveAddressUse = true,
}; })
{
connectionState.OnSentData.Reset(); connectionState.SendEventArgs.UserToken = socket;
connectionState.OnReceivedData.Reset(); connectionState.OnSentData.Reset();
connectionState.ConnectionAttempts++; connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++;
#if DEBUG == true #if DEBUG == true
Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); Log.WriteDebug($"Sending {payload.Length} bytes to [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
#endif #endif
try try
{
response = await SendPayloadAsync(payload, waitForResponse);
connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0;
}
catch/* (Exception ex)*/
{
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{ {
// Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})"); response = await SendPayloadAsync(payload, waitForResponse);
await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend; if (response.Length == 0 && waitForResponse)
{
throw new Exception();
}
connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0;
} }
connectionState.OnComplete.Release(1); catch
//Log.WriteDebug(ex.GetExceptionInfo()); {
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]"); if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{
// Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend;
}
connectionState.OnComplete.Release(1);
//Log.WriteDebug(ex.GetExceptionInfo());
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
}
} }
string responseString = Utilities.EncodingType.GetString(response, 0, response.Length).TrimEnd('\0') + '\n'; string responseString = Utilities.EncodingType.GetString(response, 0, response.Length) + '\n';
if (responseString.Contains("Invalid password")) if (responseString.Contains("Invalid password"))
{ {

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -71,7 +72,7 @@ namespace SharedLibraryCore
cfg.AllowClr(new[] cfg.AllowClr(new[]
{ {
typeof(System.Net.Http.HttpClient).Assembly, typeof(System.Net.Http.HttpClient).Assembly,
typeof(Objects.Player).Assembly, typeof(EFClient).Assembly,
}) })
.CatchClrExceptions()); .CatchClrExceptions());

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Database.Models;
namespace SharedLibraryCore namespace SharedLibraryCore
{ {
@ -32,14 +33,14 @@ namespace SharedLibraryCore
IP = config.IPAddress; IP = config.IPAddress;
Port = config.Port; Port = config.Port;
Manager = mgr; Manager = mgr;
Logger = Manager.GetLogger(this.GetHashCode()); Logger = Manager.GetLogger(this.EndPoint);
Logger.WriteInfo(this.ToString()); Logger.WriteInfo(this.ToString());
ServerConfig = config; ServerConfig = config;
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger); RemoteConnection = new RCon.Connection(IP, Port, Password, Logger);
Players = new List<Player>(new Player[18]); Clients = new List<EFClient>(new EFClient[18]);
Reports = new List<Report>(); Reports = new List<Report>();
PlayerHistory = new Queue<PlayerHistory>(); ClientHistory = new Queue<PlayerHistory>();
ChatHistory = new List<ChatInfo>(); ChatHistory = new List<ChatInfo>();
NextMessage = 0; NextMessage = 0;
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName; CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
@ -48,6 +49,8 @@ namespace SharedLibraryCore
InitializeAutoMessages(); InitializeAutoMessages();
} }
public long EndPoint => Convert.ToInt64($"{IP.Replace(".", "")}{Port}");
//Returns current server IP set by `net_ip` -- *STRING* //Returns current server IP set by `net_ip` -- *STRING*
public String GetIP() public String GetIP()
{ {
@ -61,32 +64,32 @@ namespace SharedLibraryCore
} }
//Returns list of all current players //Returns list of all current players
public List<Player> GetPlayersAsList() public List<EFClient> GetClientsAsList()
{ {
return Players.FindAll(x => x != null); return Clients.FindAll(x => x != null);
} }
/// <summary> /// <summary>
/// Add a player to the server's player list /// Add a player to the server's player list
/// </summary> /// </summary>
/// <param name="P">Player pulled from memory reading</param> /// <param name="P">EFClient pulled from memory reading</param>
/// <returns>True if player added sucessfully, false otherwise</returns> /// <returns>True if player added sucessfully, false otherwise</returns>
abstract public Task<bool> AddPlayer(Player P); abstract public Task OnClientConnected(EFClient P);
/// <summary> /// <summary>
/// Remove player by client number /// Remove player by client number
/// </summary> /// </summary>
/// <param name="cNum">Client ID of player to be removed</param> /// <param name="cNum">Client ID of player to be removed</param>
/// <returns>true if removal succeded, false otherwise</returns> /// <returns>true if removal succeded, false otherwise</returns>
abstract public Task RemovePlayer(int cNum); abstract public Task OnClientDisconnected(EFClient client);
/// <summary> /// <summary>
/// Get a player by name /// Get a player by name
/// </summary> /// </summary>
/// <param name="pName">Player name to search for</param> /// <param name="pName">EFClient name to search for</param>
/// <returns>Matching player if found</returns> /// <returns>Matching player if found</returns>
public List<Player> GetClientByName(String pName) public List<EFClient> GetClientByName(String pName)
{ {
string[] QuoteSplit = pName.Split('"'); string[] QuoteSplit = pName.Split('"');
bool literal = false; bool literal = false;
@ -96,9 +99,9 @@ namespace SharedLibraryCore
literal = true; literal = true;
} }
if (literal) if (literal)
return Players.Where(p => p != null && p.Name.ToLower().Equals(pName.ToLower())).ToList(); return Clients.Where(p => p != null && p.Name.ToLower().Equals(pName.ToLower())).ToList();
return Players.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList(); return Clients.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList();
} }
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask; virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask;
@ -115,21 +118,18 @@ namespace SharedLibraryCore
/// Send a message to all players /// Send a message to all players
/// </summary> /// </summary>
/// <param name="message">Message to be sent to all players</param> /// <param name="message">Message to be sent to all players</param>
public GameEvent Broadcast(string message, Player sender = null) public GameEvent Broadcast(string message, EFClient sender = null)
{ {
#if DEBUG == false
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{message}"); string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{message}");
#else
#if DEBUG == true
Logger.WriteVerbose(message.StripColors()); Logger.WriteVerbose(message.StripColors());
#endif #endif
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.Broadcast, Type = GameEvent.EventType.Broadcast,
#if DEBUG == true
Data = message,
#else
Data = formattedMessage, Data = formattedMessage,
#endif
Owner = this, Owner = this,
Origin = sender, Origin = sender,
}; };
@ -142,19 +142,19 @@ namespace SharedLibraryCore
/// Send a message to a particular players /// Send a message to a particular players
/// </summary> /// </summary>
/// <param name="Message">Message to send</param> /// <param name="Message">Message to send</param>
/// <param name="Target">Player to send message to</param> /// <param name="Target">EFClient to send message to</param>
protected async Task Tell(String Message, Player Target) protected async Task Tell(String Message, EFClient Target)
{ {
#if !DEBUG #if !DEBUG
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Tell, Target.ClientNumber, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{Message}"); string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Tell, Target.ClientNumber, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{Message}");
if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console) if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != EFClient.Permission.Console)
await this.ExecuteCommandAsync(formattedMessage); await this.ExecuteCommandAsync(formattedMessage);
#else #else
Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}"); Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}");
await Task.CompletedTask; await Task.CompletedTask;
#endif #endif
if (Target.Level == Player.Permission.Console) if (Target.Level == EFClient.Permission.Console)
{ {
Console.ForegroundColor = ConsoleColor.Cyan; Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(Message.StripColors()); Console.WriteLine(Message.StripColors());
@ -182,7 +182,7 @@ namespace SharedLibraryCore
/// <param name="message">Message to send out</param> /// <param name="message">Message to send out</param>
public void ToAdmins(String message) public void ToAdmins(String message)
{ {
foreach (var client in GetPlayersAsList().Where(c => c.Level > Player.Permission.Flagged)) foreach (var client in GetClientsAsList().Where(c => c.Level > EFClient.Permission.Flagged))
{ {
client.Tell(message); client.Tell(message);
} }
@ -192,15 +192,15 @@ namespace SharedLibraryCore
/// Kick a player from the server /// Kick a player from the server
/// </summary> /// </summary>
/// <param name="Reason">Reason for kicking</param> /// <param name="Reason">Reason for kicking</param>
/// <param name="Target">Player to kick</param> /// <param name="Target">EFClient to kick</param>
abstract protected Task Kick(String Reason, Player Target, Player Origin); abstract protected Task Kick(String Reason, EFClient Target, EFClient Origin);
/// <summary> /// <summary>
/// Temporarily ban a player ( default 1 hour ) from the server /// Temporarily ban a player ( default 1 hour ) from the server
/// </summary> /// </summary>
/// <param name="Reason">Reason for banning the player</param> /// <param name="Reason">Reason for banning the player</param>
/// <param name="Target">The player to ban</param> /// <param name="Target">The player to ban</param>
abstract protected Task TempBan(String Reason, TimeSpan length, Player Target, Player Origin); abstract protected Task TempBan(String Reason, TimeSpan length, EFClient Target, EFClient Origin);
/// <summary> /// <summary>
/// Perm ban a player from the server /// Perm ban a player from the server
@ -208,9 +208,9 @@ namespace SharedLibraryCore
/// <param name="Reason">The reason for the ban</param> /// <param name="Reason">The reason for the ban</param>
/// <param name="Target">The person to ban</param> /// <param name="Target">The person to ban</param>
/// <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, EFClient Target, EFClient Origin);
abstract protected Task Warn(String Reason, Player Target, Player Origin); abstract protected Task Warn(String Reason, EFClient Target, EFClient Origin);
/// <summary> /// <summary>
/// Unban a player by npID / GUID /// Unban a player by npID / GUID
@ -218,7 +218,7 @@ namespace SharedLibraryCore
/// <param name="npID">npID of the player</param> /// <param name="npID">npID of the player</param>
/// <param name="Target">I don't remember what this is for</param> /// <param name="Target">I don't remember what this is for</param>
/// <returns></returns> /// <returns></returns>
abstract public Task Unban(string reason, Player Target, Player Origin); abstract public Task Unban(string reason, EFClient Target, EFClient Origin);
/// <summary> /// <summary>
/// Change the current searver map /// Change the current searver map
@ -287,7 +287,7 @@ namespace SharedLibraryCore
public List<Map> Maps { get; protected set; } public List<Map> Maps { get; protected set; }
public List<Report> Reports { get; set; } public List<Report> Reports { get; set; }
public List<ChatInfo> ChatHistory { get; protected set; } public List<ChatInfo> ChatHistory { get; protected set; }
public Queue<PlayerHistory> PlayerHistory { get; private set; } public Queue<PlayerHistory> ClientHistory { get; private set; }
public Game GameName { get; protected set; } public Game GameName { get; protected set; }
// Info // Info
@ -299,11 +299,11 @@ namespace SharedLibraryCore
{ {
get get
{ {
return Players.Where(p => p != null).Count(); return Clients.Where(p => p != null).Count();
} }
} }
public int MaxClients { get; protected set; } public int MaxClients { get; protected set; }
public List<Player> Players { get; protected set; } public List<EFClient> Clients { get; protected set; }
public string Password { get; private set; } public string Password { get; private set; }
public bool Throttled { get; protected set; } public bool Throttled { get; protected set; }
public bool CustomCallback { get; protected set; } public bool CustomCallback { get; protected set; }
@ -312,9 +312,10 @@ namespace SharedLibraryCore
public IRConParser RconParser { get; protected set; } public IRConParser RconParser { get; protected set; }
public IEventParser EventParser { get; set; } public IEventParser EventParser { get; set; }
public string LogPath { get; protected set; } public string LogPath { get; protected set; }
public bool RestartRequested { get; set; }
// Internal // Internal
protected string IP; public string IP { get; protected set; }
protected int Port; protected int Port;
protected string FSGame; protected string FSGame;
protected int NextMessage; protected int NextMessage;

View File

@ -1,15 +1,12 @@
using System; using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using System.Linq.Expressions;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using Microsoft.EntityFrameworkCore; using System;
using SharedLibraryCore.Dtos; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Services namespace SharedLibraryCore.Services
{ {
@ -20,55 +17,28 @@ namespace SharedLibraryCore.Services
{ {
using (var context = new DatabaseContext()) using (var context = new DatabaseContext())
{ {
bool hasExistingAlias = false;
// get all aliases by IP
var aliases = await context.Aliases
.Include(a => a.Link)
.Where(a => a.IPAddress == entity.IPAddress)
.ToListAsync();
// see if they have a matching IP + Name but new NetworkId
var existingAlias = aliases.FirstOrDefault(a => a.Name == entity.Name);
// if existing alias matches link them
EFAliasLink aliasLink = existingAlias?.Link;
// if no exact matches find the first IP that matches
aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link;
// if no exact or IP matches, create new link
aliasLink = aliasLink ?? new EFAliasLink()
{
Active = true,
};
// this has to be set here because we can't evalute it properly later
hasExistingAlias = existingAlias != null;
// if no existing alias create new alias
existingAlias = existingAlias ?? new EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
Link = aliasLink,
Name = entity.Name,
};
var client = new EFClient() var client = new EFClient()
{ {
Active = true, Level = Permission.User,
// set the level to the level of the existing client if they have the same IP + Name but new NetworkId
// fixme: issues?
Level = hasExistingAlias ?
(await context.Clients.Where(c => c.AliasLinkId == existingAlias.LinkId)
.OrderByDescending(c => c.Level)
.FirstOrDefaultAsync())?.Level ?? Player.Permission.User :
Player.Permission.User,
FirstConnection = DateTime.UtcNow, FirstConnection = DateTime.UtcNow,
Connections = 1, Connections = 1,
LastConnection = DateTime.UtcNow, LastConnection = DateTime.UtcNow,
Masked = false, Masked = false,
NetworkId = entity.NetworkId, NetworkId = entity.NetworkId,
AliasLink = aliasLink, AliasLink = new EFAliasLink()
CurrentAlias = existingAlias, {
Active = false
},
};
client.CurrentAlias = new Alias()
{
Name = entity.Name,
Link = client.AliasLink,
DateAdded = DateTime.UtcNow,
// the first time a client is created, we may not have their ip,
// so we create a temporary alias
Active = false
}; };
context.Clients.Add(client); context.Clients.Add(client);
@ -78,6 +48,131 @@ namespace SharedLibraryCore.Services
} }
} }
public async Task UpdateAlias(EFClient entity)
{
// todo: move this out
if (entity.IsBot)
{
return;
}
using (var context = new DatabaseContext())
{
context.Attach(entity);
string name = entity.Name;
int? ip = entity.IPAddress;
bool hasExistingAlias = false;
// get all aliases by IP
var iqAliases = context.Aliases
.Include(a => a.Link)
.Where(a => a.Link.Active)
.Where(a => (a.IPAddress != null && a.IPAddress == ip) ||
a.LinkId == entity.AliasLinkId);
#if DEBUG == true
var aliasSql = iqAliases.ToSql();
#endif
var aliases = await iqAliases.ToListAsync();
// see if they have a matching IP + Name but new NetworkId
var existingAlias = aliases.FirstOrDefault(a => a.Name == name);
// if existing alias matches link them
EFAliasLink aliasLink = existingAlias?.Link;
// if no exact matches find the first IP that matches
aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link;
// if no exact or IP matches, create new link
aliasLink = aliasLink ?? new EFAliasLink();
// this has to be set here because we can't evalute it properly later
hasExistingAlias = existingAlias != null;
if (hasExistingAlias && !entity.AliasLink.Active)
{
entity.CurrentServer.Logger.WriteDebug($"Removing temporary alias for ${entity}");
// we want to delete the temporary alias
context.Entry(entity.CurrentAlias).State = EntityState.Deleted;
entity.CurrentAlias = null;
// we want to delete the temporary alias link
context.Entry(entity.AliasLink).State = EntityState.Deleted;
entity.AliasLink = null;
// they have an existing alias so assign it
entity.CurrentAlias = existingAlias;
entity.AliasLink = aliasLink;
await context.SaveChangesAsync();
}
// update the temporary alias to permanent one
else if (!entity.AliasLink.Active)
{
entity.CurrentServer.Logger.WriteDebug($"Linking permanent alias for ${entity}");
// we want to track the current alias and link
var alias = context.Update(entity.CurrentAlias).Entity;
var _aliasLink = context.Update(entity.AliasLink).Entity;
alias.Active = true;
alias.IPAddress = ip;
alias.Name = name;
_aliasLink.Active = true;
existingAlias = alias;
aliasLink = _aliasLink;
await context.SaveChangesAsync();
}
// if no existing alias create new alias
existingAlias = existingAlias ?? new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
Link = aliasLink,
Name = name,
};
if (!hasExistingAlias)
{
entity.CurrentServer.Logger.WriteDebug($"Connecting player does not have an existing alias {entity}");
}
else
{
var linkIds = aliases.Select(a => a.LinkId);
if (linkIds.Count() > 0)
{
var highestLevel = await context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId))
.MaxAsync(c => c.Level);
if (entity.Level != highestLevel)
{
context.Update(entity);
entity.Level = highestLevel;
await context.SaveChangesAsync();
}
}
}
if (entity.CurrentAlias != existingAlias ||
entity.AliasLink != aliasLink)
{
entity.CurrentAlias = existingAlias;
entity.AliasLink = aliasLink;
context.Update(entity);
}
await context.SaveChangesAsync();
}
}
public async Task<EFClient> Delete(EFClient entity) public async Task<EFClient> Delete(EFClient entity)
{ {
using (var context = new DatabaseContext()) using (var context = new DatabaseContext())
@ -131,13 +226,17 @@ namespace SharedLibraryCore.Services
var foundClient = await iqClient.FirstOrDefaultAsync(); var foundClient = await iqClient.FirstOrDefaultAsync();
if (foundClient == null) if (foundClient == null)
{
return null; return null;
}
foundClient.Client.LinkedAccounts = new Dictionary<int, long>(); foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// todo: find out the best way to do this // todo: find out the best way to do this
// I'm doing this here because I don't know the best way to have multiple awaits in the query // I'm doing this here because I don't know the best way to have multiple awaits in the query
foreach (var linked in foundClient.LinkedAccounts) foreach (var linked in foundClient.LinkedAccounts)
{
foundClient.Client.LinkedAccounts.Add(linked.ClientId, linked.NetworkId); foundClient.Client.LinkedAccounts.Add(linked.ClientId, linked.NetworkId);
}
return foundClient.Client; return foundClient.Client;
} }
@ -167,7 +266,7 @@ namespace SharedLibraryCore.Services
var client = context.Clients var client = context.Clients
.Include(c => c.AliasLink) .Include(c => c.AliasLink)
.Include(c => c.CurrentAlias) .Include(c => c.CurrentAlias)
.Single(e => e.ClientId == entity.ClientId); .First(e => e.ClientId == entity.ClientId);
// if their level has been changed // if their level has been changed
if (entity.Level != client.Level) if (entity.Level != client.Level)
@ -190,7 +289,7 @@ namespace SharedLibraryCore.Services
{ {
client.CurrentAlias = new EFAlias() client.CurrentAlias = new EFAlias()
{ {
Active = true, Active = entity.CurrentAlias.IPAddress.HasValue ? true : false,
DateAdded = DateTime.UtcNow, DateAdded = DateTime.UtcNow,
IPAddress = entity.CurrentAlias.IPAddress, IPAddress = entity.CurrentAlias.IPAddress,
Name = entity.CurrentAlias.Name, Name = entity.CurrentAlias.Name,
@ -201,6 +300,8 @@ namespace SharedLibraryCore.Services
else else
{ {
client.CurrentAliasId = entity.CurrentAliasId; client.CurrentAliasId = entity.CurrentAliasId;
client.IPAddress = entity.IPAddress;
client.Name = entity.Name;
} }
// set remaining non-navigation properties that may have been updated // set remaining non-navigation properties that may have been updated
@ -218,7 +319,10 @@ namespace SharedLibraryCore.Services
// this is set so future updates don't trigger a new alias add // this is set so future updates don't trigger a new alias add
if (entity.CurrentAlias.AliasId == 0) if (entity.CurrentAlias.AliasId == 0)
{
entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId; entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId;
}
return client; return client;
} }
} }
@ -227,24 +331,28 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFClient>> GetOwners() public async Task<IList<EFClient>> GetOwners()
{ {
using (var context = new DatabaseContext()) using (var context = new DatabaseContext())
{
return await context.Clients return await context.Clients
.Where(c => c.Level == Player.Permission.Owner) .Where(c => c.Level == Permission.Owner)
.ToListAsync(); .ToListAsync();
}
} }
public async Task<IList<ClientInfo>> GetPrivilegedClients() public async Task<List<EFClient>> GetPrivilegedClients()
{ {
using (var context = new DatabaseContext(disableTracking: true)) using (var context = new DatabaseContext(disableTracking: true))
{ {
var iqClients = from client in context.Clients var iqClients = from client in context.Clients
where client.Level >= Player.Permission.Trusted where client.Level >= Permission.Trusted
where client.Active where client.Active
select new ClientInfo() select new EFClient()
{ {
AliasLinkId = client.AliasLinkId,
CurrentAlias = client.CurrentAlias,
ClientId = client.ClientId, ClientId = client.ClientId,
Name = client.CurrentAlias.Name, Level = client.Level,
LinkId = client.AliasLinkId, Password = client.Password,
Level = client.Level PasswordSalt = client.PasswordSalt
}; };
#if DEBUG == true #if DEBUG == true
@ -255,27 +363,30 @@ namespace SharedLibraryCore.Services
} }
} }
public async Task<IList<EFClient>> GetClientByName(string name) public async Task<IList<EFClient>> FindClientsByIdentifier(string identifier)
{ {
if (name.Length < 3) if (identifier.Length < 3)
{
return new List<EFClient>(); return new List<EFClient>();
}
name = name.ToLower(); identifier = identifier.ToLower();
using (var context = new DatabaseContext(disableTracking: true)) using (var context = new DatabaseContext(disableTracking: true))
{ {
int asIP = name.ConvertToIP(); long networkId = identifier.ConvertLong();
// hack: so IW4MAdmin and bots don't show up in search results int ipAddress = identifier.ConvertToIP();
asIP = asIP == 0 ? int.MaxValue : asIP;
var iqLinkIds = (from alias in context.Aliases var iqLinkIds = (from alias in context.Aliases
where asIP != int.MaxValue ? alias.IPAddress == asIP : alias.Name.ToLower().Contains(name) where alias.IPAddress == ipAddress ||
select alias.LinkId); alias.Name.ToLower().Contains(identifier)
select alias.LinkId).Distinct();
var linkIds = iqLinkIds.ToList(); var linkIds = iqLinkIds.ToList();
var iqClients = context.Clients var iqClients = context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId)) .Where(c => linkIds.Contains(c.AliasLinkId) ||
networkId == c.NetworkId)
.Include(c => c.CurrentAlias) .Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children); .Include(c => c.AliasLink.Children);
@ -290,20 +401,16 @@ namespace SharedLibraryCore.Services
public async Task<int> GetTotalClientsAsync() public async Task<int> GetTotalClientsAsync()
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{
return await context.Clients return await context.Clients
.CountAsync(); .CountAsync();
}
} }
public Task<EFClient> CreateProxy() public Task<EFClient> CreateProxy()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<int> GetTotalPlayTime()
{
using (var context = new DatabaseContext(true))
return await context.Clients.SumAsync(c => c.TotalConnectionTime);
}
#endregion #endregion
} }
} }

View File

@ -1,13 +1,14 @@
using System; using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Services namespace SharedLibraryCore.Services
{ {
@ -30,15 +31,12 @@ namespace SharedLibraryCore.Services
AutomatedOffense = newEntity.AutomatedOffense AutomatedOffense = newEntity.AutomatedOffense
}; };
if (addedEntity.Expires == DateTime.MaxValue)
addedEntity.Expires = DateTime.Parse(System.Data.SqlTypes.SqlDateTime.MaxValue.ToString());
// make bans propogate to all aliases // make bans propogate to all aliases
if (addedEntity.Type == Objects.Penalty.PenaltyType.Ban) if (addedEntity.Type == Objects.Penalty.PenaltyType.Ban)
{ {
await context.Clients await context.Clients
.Where(c => c.AliasLinkId == addedEntity.LinkId) .Where(c => c.AliasLinkId == addedEntity.LinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.Banned); .ForEachAsync(c => c.Level = Permission.Banned);
} }
// make flags propogate to all aliases // make flags propogate to all aliases
@ -46,7 +44,7 @@ namespace SharedLibraryCore.Services
{ {
await context.Clients await context.Clients
.Where(c => c.AliasLinkId == addedEntity.LinkId) .Where(c => c.AliasLinkId == addedEntity.LinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.Flagged); .ForEachAsync(c => c.Level = Permission.Flagged);
} }
context.Penalties.Add(addedEntity); context.Penalties.Add(addedEntity);
@ -89,6 +87,7 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFPenalty>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any) public async Task<IList<EFPenalty>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any)
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{
return await context.Penalties return await context.Penalties
.Include(p => p.Offender.CurrentAlias) .Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias) .Include(p => p.Punisher.CurrentAlias)
@ -98,17 +97,20 @@ namespace SharedLibraryCore.Services
.Skip(offset) .Skip(offset)
.Take(count) .Take(count)
.ToListAsync(); .ToListAsync();
}
} }
public async Task<IList<EFPenalty>> GetClientPenaltiesAsync(int clientId) public async Task<IList<EFPenalty>> GetClientPenaltiesAsync(int clientId)
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{
return await context.Penalties return await context.Penalties
.Where(p => p.OffenderId == clientId) .Where(p => p.OffenderId == clientId)
.Where(p => p.Active) .Where(p => p.Active)
.Include(p => p.Offender.CurrentAlias) .Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias) .Include(p => p.Punisher.CurrentAlias)
.ToListAsync(); .ToListAsync();
}
} }
/// <summary> /// <summary>
@ -148,7 +150,7 @@ namespace SharedLibraryCore.Services
PunisherId = penalty.PunisherId, PunisherId = penalty.PunisherId,
Offense = penalty.Offense, Offense = penalty.Offense,
Type = penalty.Type.ToString(), Type = penalty.Type.ToString(),
TimeRemaining = now > penalty.Expires ? "" : penalty.Expires.ToString(), TimeRemaining = penalty.Expires.HasValue ? (now > penalty.Expires ? "" : penalty.Expires.ToString()) : DateTime.MaxValue.ToString(),
AutomatedOffense = penalty.AutomatedOffense AutomatedOffense = penalty.AutomatedOffense
}, },
When = penalty.When, When = penalty.When,
@ -160,12 +162,15 @@ namespace SharedLibraryCore.Services
{ {
// todo: why does this have to be done? // todo: why does this have to be done?
if (((PenaltyInfo)p.Value).Type.Length < 2) if (((PenaltyInfo)p.Value).Type.Length < 2)
{
((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString(); ((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString();
}
var pi = ((PenaltyInfo)p.Value); var pi = ((PenaltyInfo)p.Value);
if (pi.TimeRemaining.Length > 0) if (pi.TimeRemaining?.Length > 0)
{
pi.TimeRemaining = (DateTime.Parse(((PenaltyInfo)p.Value).TimeRemaining) - now).TimeSpanText(); pi.TimeRemaining = (DateTime.Parse(((PenaltyInfo)p.Value).TimeRemaining) - now).TimeSpanText();
}
}); });
return list; return list;
} }
@ -207,7 +212,9 @@ namespace SharedLibraryCore.Services
{ {
// todo: why does this have to be done? // todo: why does this have to be done?
if (((PenaltyInfo)p.Value).Type.Length < 2) if (((PenaltyInfo)p.Value).Type.Length < 2)
{
((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString(); ((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString();
}
}); });
return list; return list;
@ -215,26 +222,37 @@ namespace SharedLibraryCore.Services
} }
} }
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int ip = 0) public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int? ip = null)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
Expression<Func<EFPenalty, bool>> filter = (p) => new Penalty.PenaltyType[]
{
Penalty.PenaltyType.TempBan,
Penalty.PenaltyType.Ban, Penalty.PenaltyType.Flag
}.Contains(p.Type) &&
p.Active &&
(p.Expires == null || p.Expires > now);
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{ {
var iqPenalties = context.Penalties var iqLinkPenalties = context.Penalties
.Where(p => p.LinkId == linkId || .Where(p => p.LinkId == linkId)
p.Link.Children.Any(a => a.IPAddress == ip)) .Where(filter);
.Where(p => p.Active)
.Where(p => p.Expires > now); var iqIPPenalties = context.Aliases
.Where(a => a.IPAddress == ip)
.SelectMany(a => a.Link.ReceivedPenalties)
.Where(filter);
#if DEBUG == true #if DEBUG == true
var penaltiesSql = iqPenalties.ToSql(); var penaltiesSql = iqLinkPenalties.ToSql();
var ipPenaltiesSql = iqIPPenalties.ToSql();
#endif #endif
var activePenalties = await iqPenalties.ToListAsync(); var activePenalties = (await iqLinkPenalties.ToListAsync()).Union(await iqIPPenalties.ToListAsync());
// this is a bit more performant in memory (ordering) // this is a bit more performant in memory (ordering)
return activePenalties.OrderByDescending(p =>p.When).ToList(); return activePenalties.OrderByDescending(p => p.When).ToList();
} }
} }
@ -246,7 +264,7 @@ namespace SharedLibraryCore.Services
var penalties = await context.Penalties var penalties = await context.Penalties
.Include(p => p.Link.Children) .Include(p => p.Link.Children)
.Where(p => p.LinkId == aliasLinkId) .Where(p => p.LinkId == aliasLinkId)
.Where(p => p.Expires > now) .Where(p => p.Expires > now || p.Expires == null)
.ToListAsync(); .ToListAsync();
penalties.ForEach(async p => penalties.ForEach(async p =>
@ -259,7 +277,7 @@ namespace SharedLibraryCore.Services
{ {
await internalContext.Clients await internalContext.Clients
.Where(c => c.AliasLinkId == p.LinkId) .Where(c => c.AliasLinkId == p.LinkId)
.ForEachAsync(c => c.Level = Player.Permission.User); .ForEachAsync(c => c.Level = EFClient.Permission.User);
await internalContext.SaveChangesAsync(); await internalContext.SaveChangesAsync();
} }
} }

View File

@ -13,6 +13,12 @@
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Migrations\20181126232438_AddEndpointToEFServer.cs" />
<Compile Remove="Migrations\20181126233300_AddEndpointToEFServer.cs" />
<Compile Remove="Migrations\20181127143920_AddEndpointToEFServerUpdateServerIdType.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.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />

View File

@ -1,20 +1,18 @@
using System; using Microsoft.EntityFrameworkCore.Query;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using System.Collections.Generic;
using SharedLibraryCore.Objects;
using static SharedLibraryCore.Server;
using System.Reflection;
using System.IO;
using System.Threading.Tasks;
using System.Globalization;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using SharedLibraryCore.Database.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static SharedLibraryCore.Server;
namespace SharedLibraryCore namespace SharedLibraryCore
{ {
@ -27,13 +25,21 @@ namespace SharedLibraryCore
#endif #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(Server server = null) => new Player() public static EFClient IW4MAdminClient(Server server = null)
{ {
ClientId = 1, return new EFClient()
State = Player.ClientState.Connected, {
Level = Player.Permission.Console, ClientId = 1,
CurrentServer = server State = EFClient.ClientState.Connected,
}; Level = EFClient.Permission.Console,
CurrentServer = server,
CurrentAlias = new EFAlias()
{
Name = "IW4MAdmin"
},
AdministeredPenalties = new List<EFPenalty>()
};
}
public static string HttpRequest(string location, string header, string headerValue) public static string HttpRequest(string location, string header, string headerValue)
{ {
@ -62,7 +68,9 @@ namespace SharedLibraryCore
public static String RemoveWords(this string str, int num) public static String RemoveWords(this string str, int num)
{ {
if (str == null || str.Length == 0) if (str == null || str.Length == 0)
{
return ""; return "";
}
String newStr = String.Empty; String newStr = String.Empty;
String[] tmp = str.Split(' '); String[] tmp = str.Split(' ');
@ -70,7 +78,9 @@ namespace SharedLibraryCore
for (int i = 0; i < tmp.Length; i++) for (int i = 0; i < tmp.Length; i++)
{ {
if (i >= num) if (i >= num)
{
newStr += tmp[i] + ' '; newStr += tmp[i] + ' ';
}
} }
return newStr; return newStr;
@ -98,16 +108,20 @@ namespace SharedLibraryCore
return sb.ToString(); return sb.ToString();
} }
public static Player.Permission MatchPermission(String str) public static EFClient.Permission MatchPermission(String str)
{ {
String lookingFor = str.ToLower(); String lookingFor = str.ToLower();
for (Player.Permission Perm = Player.Permission.User; Perm < Player.Permission.Console; Perm++) for (EFClient.Permission Perm = EFClient.Permission.User; Perm < EFClient.Permission.Console; Perm++)
{
if (lookingFor.Contains(Perm.ToString().ToLower()) if (lookingFor.Contains(Perm.ToString().ToLower())
|| lookingFor.Contains(CurrentLocalization.LocalizationIndex[$"GLOBAL_PERMISSION_{Perm.ToString().ToUpper()}"].ToLower())) || lookingFor.Contains(CurrentLocalization.LocalizationIndex[$"GLOBAL_PERMISSION_{Perm.ToString().ToUpper()}"].ToLower()))
{
return Perm; return Perm;
}
}
return Player.Permission.Banned; return EFClient.Permission.Banned;
} }
/// <summary> /// <summary>
@ -118,7 +132,10 @@ namespace SharedLibraryCore
public static String StripColors(this string str) public static String StripColors(this string str)
{ {
if (str == null) if (str == null)
{
return ""; return "";
}
str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", ""); str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "");
string str2 = Regex.Match(str, @"(^\/+.*$)|(^.*\/+$)") string str2 = Regex.Match(str, @"(^\/+.*$)|(^.*\/+$)")
.Value .Value
@ -131,25 +148,25 @@ namespace SharedLibraryCore
/// </summary> /// </summary>
/// <param name="level">Specified player level</param> /// <param name="level">Specified player level</param>
/// <returns></returns> /// <returns></returns>
public static String ConvertLevelToColor(Player.Permission level, string localizedLevel) public static String ConvertLevelToColor(EFClient.Permission level, string localizedLevel)
{ {
char colorCode = '6'; char colorCode = '6';
// todo: maybe make this game independant? // todo: maybe make this game independant?
switch (level) switch (level)
{ {
case Player.Permission.Banned: case EFClient.Permission.Banned:
colorCode = '1'; colorCode = '1';
break; break;
case Player.Permission.Flagged: case EFClient.Permission.Flagged:
colorCode = '9'; colorCode = '9';
break; break;
case Player.Permission.Owner: case EFClient.Permission.Owner:
colorCode = '5'; colorCode = '5';
break; break;
case Player.Permission.User: case EFClient.Permission.User:
colorCode = '2'; colorCode = '2';
break; break;
case Player.Permission.Trusted: case EFClient.Permission.Trusted:
colorCode = '3'; colorCode = '3';
break; break;
default: default:
@ -159,7 +176,10 @@ namespace SharedLibraryCore
return $"^{colorCode}{localizedLevel ?? level.ToString()}"; return $"^{colorCode}{localizedLevel ?? level.ToString()}";
} }
public static string ToLocalizedLevelName(this Player.Permission perm) => CurrentLocalization.LocalizationIndex[$"GLOBAL_PERMISSION_{perm.ToString().ToUpper()}"]; public static string ToLocalizedLevelName(this EFClient.Permission perm)
{
return CurrentLocalization.LocalizationIndex[$"GLOBAL_PERMISSION_{perm.ToString().ToUpper()}"];
}
public static String ProcessMessageToken(this Server server, IList<Helpers.MessageToken> tokens, String str) public static String ProcessMessageToken(this Server server, IList<Helpers.MessageToken> tokens, String str)
{ {
@ -172,7 +192,9 @@ namespace SharedLibraryCore
var found = tokens.FirstOrDefault(t => t.Name.ToLower() == Identifier.ToLower()); var found = tokens.FirstOrDefault(t => t.Name.ToLower() == Identifier.ToLower());
if (found != null) if (found != null)
{
str = str.Replace(Match, found.Process(server)); str = str.Replace(Match, found.Process(server));
}
} }
return str; return str;
@ -243,24 +265,30 @@ namespace SharedLibraryCore
{ {
str = str.Substring(0, Math.Min(str.Length, 16)); str = str.Substring(0, Math.Min(str.Length, 16));
if (Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id)) if (Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id))
{
return id; return id;
}
var bot = Regex.Match(str, @"bot[0-9]+").Value; var bot = Regex.Match(str, @"bot[0-9]+").Value;
if (!string.IsNullOrEmpty(bot)) if (!string.IsNullOrEmpty(bot))
{
// should set their GUID to the negation of their 1 based index (-1 - -18) // should set their GUID to the negation of their 1 based index (-1 - -18)
return -(Convert.ToInt64(bot.Substring(3)) + 1); return -(Convert.ToInt64(bot.Substring(3)) + 1);
return 0; }
return long.MinValue;
} }
public static int ConvertToIP(this string str) public static int ConvertToIP(this string str)
{ {
System.Net.IPAddress.TryParse(str, out System.Net.IPAddress ip); System.Net.IPAddress.TryParse(str, out System.Net.IPAddress ip);
return ip == null ? 0 : BitConverter.ToInt32(ip.GetAddressBytes(), 0); return ip == null ? int.MaxValue : BitConverter.ToInt32(ip.GetAddressBytes(), 0);
} }
public static string ConvertIPtoString(this int ip) public static string ConvertIPtoString(this int? ip)
{ {
return new System.Net.IPAddress(BitConverter.GetBytes(ip)).ToString(); return !ip.HasValue ? "" : new System.Net.IPAddress(BitConverter.GetBytes(ip.Value)).ToString();
} }
public static String GetTimePassed(DateTime start) public static String GetTimePassed(DateTime start)
@ -280,19 +308,28 @@ namespace SharedLibraryCore
if (Elapsed.TotalMinutes < 120) if (Elapsed.TotalMinutes < 120)
{ {
if (Elapsed.TotalMinutes < 1.5) if (Elapsed.TotalMinutes < 1.5)
{
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}"; return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}";
}
return Math.Round(Elapsed.TotalMinutes, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}"; return Math.Round(Elapsed.TotalMinutes, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}";
} }
if (Elapsed.TotalHours <= 24) if (Elapsed.TotalHours <= 24)
{ {
if (Elapsed.TotalHours < 1.5) if (Elapsed.TotalHours < 1.5)
{
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}"; return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}";
}
return Math.Round(Elapsed.TotalHours, 0) + $" { CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}"; return Math.Round(Elapsed.TotalHours, 0) + $" { CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}";
} }
if (Elapsed.TotalDays <= 90) if (Elapsed.TotalDays <= 90)
{ {
if (Elapsed.TotalDays < 1.5) if (Elapsed.TotalDays < 1.5)
{
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}"; return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}";
}
return Math.Round(Elapsed.TotalDays, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}"; return Math.Round(Elapsed.TotalDays, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}";
} }
if (Elapsed.TotalDays <= 365) if (Elapsed.TotalDays <= 365)
@ -308,19 +345,39 @@ namespace SharedLibraryCore
public static Game GetGame(string gameName) public static Game GetGame(string gameName)
{ {
if (gameName.Contains("IW4")) if (gameName.Contains("IW4"))
{
return Game.IW4; return Game.IW4;
}
if (gameName.Contains("CoD4")) if (gameName.Contains("CoD4"))
{
return Game.IW3; return Game.IW3;
}
if (gameName.Contains("COD_WaW")) if (gameName.Contains("COD_WaW"))
{
return Game.T4; return Game.T4;
}
if (gameName.Contains("COD_T5_S")) if (gameName.Contains("COD_T5_S"))
{
return Game.T5; return Game.T5;
}
if (gameName.Contains("T5M")) if (gameName.Contains("T5M"))
{
return Game.T5M; return Game.T5M;
}
if (gameName.Contains("IW5")) if (gameName.Contains("IW5"))
{
return Game.IW5; return Game.IW5;
}
if (gameName.Contains("COD_T6_S")) if (gameName.Contains("COD_T6_S"))
{
return Game.T6M; return Game.T6M;
}
return Game.UKN; return Game.UKN;
} }
@ -335,7 +392,9 @@ namespace SharedLibraryCore
var expressionMatch = Regex.Match(input, @"([0-9]+)(\w+)"); var expressionMatch = Regex.Match(input, @"([0-9]+)(\w+)");
if (!expressionMatch.Success) // fallback to default tempban length of 1 hour if (!expressionMatch.Success) // fallback to default tempban length of 1 hour
{
return new TimeSpan(1, 0, 0); return new TimeSpan(1, 0, 0);
}
char lengthDenote = expressionMatch.Groups[2].ToString()[0]; char lengthDenote = expressionMatch.Groups[2].ToString()[0];
int length = Int32.Parse(expressionMatch.Groups[1].ToString()); int length = Int32.Parse(expressionMatch.Groups[1].ToString());
@ -375,52 +434,42 @@ namespace SharedLibraryCore
var loc = CurrentLocalization.LocalizationIndex; var loc = CurrentLocalization.LocalizationIndex;
if (span.TotalMinutes < 60) if (span.TotalMinutes < 60)
{
return $"{span.Minutes} {loc["GLOBAL_TIME_MINUTES"]}"; return $"{span.Minutes} {loc["GLOBAL_TIME_MINUTES"]}";
}
else if (span.Hours >= 1 && span.TotalHours < 24) else if (span.Hours >= 1 && span.TotalHours < 24)
{
return $"{span.Hours} {loc["GLOBAL_TIME_HOURS"]}"; return $"{span.Hours} {loc["GLOBAL_TIME_HOURS"]}";
}
else if (span.TotalDays >= 1 && span.TotalDays < 7) else if (span.TotalDays >= 1 && span.TotalDays < 7)
{
return $"{span.Days} {loc["GLOBAL_TIME_DAYS"]}"; return $"{span.Days} {loc["GLOBAL_TIME_DAYS"]}";
}
else if (span.TotalDays >= 7 && span.TotalDays < 90) else if (span.TotalDays >= 7 && span.TotalDays < 90)
{
return $"{Math.Round(span.Days / 7.0, 0)} {loc["GLOBAL_TIME_WEEKS"]}"; return $"{Math.Round(span.Days / 7.0, 0)} {loc["GLOBAL_TIME_WEEKS"]}";
}
else if (span.TotalDays >= 90 && span.TotalDays < 365) else if (span.TotalDays >= 90 && span.TotalDays < 365)
{
return $"{Math.Round(span.Days / 30.0, 0)} {loc["GLOBAL_TIME_MONTHS"]}"; return $"{Math.Round(span.Days / 30.0, 0)} {loc["GLOBAL_TIME_MONTHS"]}";
}
else if (span.TotalDays >= 365 && span.TotalDays < 36500) else if (span.TotalDays >= 365 && span.TotalDays < 36500)
{
return $"{Math.Round(span.Days / 365.0, 0)} {loc["GLOBAL_TIME_YEARS"]}"; return $"{Math.Round(span.Days / 365.0, 0)} {loc["GLOBAL_TIME_YEARS"]}";
}
else if (span.TotalDays >= 36500) else if (span.TotalDays >= 36500)
{
return loc["GLOBAL_TIME_FOREVER"]; return loc["GLOBAL_TIME_FOREVER"];
}
return "unknown"; return "unknown";
} }
public static Player AsPlayer(this Database.Models.EFClient client) public static bool IsPrivileged(this EFClient p)
{ {
return client == null ? null : new Player() return p.Level > EFClient.Permission.User;
{
Active = client.Active,
AliasLink = client.AliasLink,
AliasLinkId = client.AliasLinkId,
ClientId = client.ClientId,
ClientNumber = -1,
FirstConnection = client.FirstConnection,
Connections = client.Connections,
NetworkId = client.NetworkId,
TotalConnectionTime = client.TotalConnectionTime,
Masked = client.Masked,
Name = client.CurrentAlias.Name,
IPAddress = client.CurrentAlias.IPAddress,
Level = client.Level,
LastConnection = client.LastConnection == DateTime.MinValue ? DateTime.UtcNow : client.LastConnection,
CurrentAlias = client.CurrentAlias,
CurrentAliasId = client.CurrentAlias.AliasId,
// todo: make sure this is up to date
IsBot = client.IPAddress == int.MinValue,
Password = client.Password,
PasswordSalt = client.PasswordSalt
};
} }
public static bool IsPrivileged(this Player p) => p.Level > Player.Permission.User;
public static bool PromptBool(string question) public static bool PromptBool(string question)
{ {
Console.Write($"{question}? [y/n]: "); Console.Write($"{question}? [y/n]: ");
@ -472,7 +521,9 @@ namespace SharedLibraryCore
int.TryParse(lineSplit[cIDPos].Trim(), out pID); int.TryParse(lineSplit[cIDPos].Trim(), out pID);
if (pID == -1) // special case similar to mod_suicide if (pID == -1) // special case similar to mod_suicide
{
int.TryParse(lineSplit[2], out pID); int.TryParse(lineSplit[2], out pID);
}
return pID; return pID;
} }
@ -487,7 +538,9 @@ namespace SharedLibraryCore
{ {
dict = new Dictionary<string, string>(); dict = new Dictionary<string, string>();
for (int i = 0; i < values.Length; i += 2) for (int i = 0; i < values.Length; i += 2)
{
dict.Add(values[i], values[i + 1]); dict.Add(values[i], values[i + 1]);
}
} }
return dict; return dict;
@ -516,15 +569,30 @@ namespace SharedLibraryCore
return cmdLine.Length > 1 ? cmdLine[1] : cmdLine[0]; return cmdLine.Length > 1 ? cmdLine[1] : cmdLine[0];
} }
public static string ToBase64UrlSafeString(this string src) => Convert.ToBase64String(src.Select(c => Convert.ToByte(c)).ToArray()).Replace('+', '-').Replace('/', '_'); public static string ToBase64UrlSafeString(this string src)
{
return Convert.ToBase64String(src.Select(c => Convert.ToByte(c)).ToArray()).Replace('+', '-').Replace('/', '_');
}
public static Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName) => server.RconParser.GetDvarAsync<T>(server.RemoteConnection, dvarName); public static Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName)
{
return server.RconParser.GetDvarAsync<T>(server.RemoteConnection, dvarName);
}
public static Task SetDvarAsync(this Server server, string dvarName, object dvarValue) => server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue); public static Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
{
return server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue);
}
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName) => await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName); public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
{
return await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName);
}
public static Task<List<Player>> GetStatusAsync(this Server server) => server.RconParser.GetStatusAsync(server.RemoteConnection); public static Task<List<EFClient>> GetStatusAsync(this Server server)
{
return server.RconParser.GetStatusAsync(server.RemoteConnection);
}
public static async Task<Dictionary<string, string>> GetInfoAsync(this Server server) public static async Task<Dictionary<string, string>> GetInfoAsync(this Server server)
{ {
@ -533,7 +601,10 @@ namespace SharedLibraryCore
{ {
response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO); response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO);
if (response.Length == 2) if (response.Length == 2)
{
break; break;
}
await Task.Delay(RCon.StaticHelpers.FloodProtectionInterval); await Task.Delay(RCon.StaticHelpers.FloodProtectionInterval);
} }
return response.FirstOrDefault(r => r[0] == '\\')?.DictionaryFromKeyValue(); return response.FirstOrDefault(r => r[0] == '\\')?.DictionaryFromKeyValue();
@ -546,7 +617,10 @@ namespace SharedLibraryCore
return double.Parse(version) / 1000.0; return double.Parse(version) / 1000.0;
} }
public static string GetVersionAsString() => Assembly.GetCallingAssembly().GetName().Version.ToString(); public static string GetVersionAsString()
{
return Assembly.GetCallingAssembly().GetName().Version.ToString();
}
#if DEBUG == true #if DEBUG == true

View File

@ -1,16 +1,16 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Events; using SharedLibraryCore.Events;
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers.API namespace WebfrontCore.Controllers.API
{ {
public class ApiController : BaseController public class ApiController : BaseController
{ {
public IActionResult Index() => Ok($"IW4MAdmin API"); public IActionResult Index()
{
return Ok($"IW4MAdmin API");
}
[HttpGet] [HttpGet]
public IActionResult Event(bool shouldConsume = true) public IActionResult Event(bool shouldConsume = true)
@ -25,15 +25,15 @@ namespace WebfrontCore.Controllers.API
var serverInfo = Manager.GetServers() var serverInfo = Manager.GetServers()
.Select(server => new .Select(server => new
{ {
Id = server.GetHashCode(), Id = server.EndPoint,
Name = server.Hostname, Name = server.Hostname,
MaxPlayers = server.MaxClients, MaxPlayers = server.MaxClients,
CurrentPlayers = server.GetPlayersAsList().Count, CurrentPlayers = server.GetClientsAsList().Count,
Map = server.CurrentMap, Map = server.CurrentMap,
GameMode = server.Gametype, GameMode = server.Gametype,
Port = server.GetPort(), Port = server.GetPort(),
Game = server.GameName.ToString(), Game = server.GameName.ToString(),
Players = server.GetPlayersAsList() Players = server.GetClientsAsList()
.Select(player => new .Select(player => new
{ {
player.Name, player.Name,
@ -53,5 +53,23 @@ namespace WebfrontCore.Controllers.API
return Json(serverInfo); return Json(serverInfo);
} }
[HttpGet]
public IActionResult RestartApproved()
{
var serverToRestart = Manager.GetServers().FirstOrDefault(_server => _server.RestartRequested);
if (serverToRestart != null)
{
serverToRestart.RestartRequested = false;
}
return serverToRestart != null ?
(IActionResult)Json(new
{
port = serverToRestart.GetPort()
}) :
Unauthorized();
}
} }
} }

View File

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using WebfrontCore.ViewModels; using WebfrontCore.ViewModels;
using static SharedLibraryCore.Objects.Player; using static SharedLibraryCore.Database.Models.EFClient;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
@ -79,7 +79,7 @@ namespace WebfrontCore.Controllers
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
{ {
serverId = server.GetHashCode(), serverId = server.EndPoint,
command command
})); }));
} }
@ -110,7 +110,7 @@ namespace WebfrontCore.Controllers
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
{ {
serverId = server.GetHashCode(), serverId = server.EndPoint,
command = $"!unban @{targetId} {Reason}" command = $"!unban @{targetId} {Reason}"
})); }));
} }
@ -162,7 +162,7 @@ namespace WebfrontCore.Controllers
Values = Enum.GetValues(typeof(Permission)).OfType<Permission>() Values = Enum.GetValues(typeof(Permission)).OfType<Permission>()
.Where(p => p <= Client.Level) .Where(p => p <= Client.Level)
.Where(p => p != Permission.Banned) .Where(p => p != Permission.Banned)
.Where(p => p != Permission.Flagged) .Where(p => p != Permission.Flagged)
.ToDictionary(p => p.ToString(), p=> p.ToLocalizedLevelName()) .ToDictionary(p => p.ToString(), p=> p.ToLocalizedLevelName())
}, },
}, },
@ -178,7 +178,7 @@ namespace WebfrontCore.Controllers
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
{ {
serverId = server.GetHashCode(), serverId = server.EndPoint,
command = $"!setlevel @{targetId} {level}" command = $"!setlevel @{targetId} {level}"
})); }));
} }

Some files were not shown because too many files have changed in this diff Show More