Compare commits

..

21 Commits

Author SHA1 Message Date
7b75c35c9b fix aliases for real (hopefully)
fix bug with flag not being applied
fix level being set based on IP instead of IP and name
2018-12-31 20:52:19 -06:00
07ec5cf52f update assembly version 2018-12-30 20:52:26 -06:00
cf5ee8765d finish alias fixes
add manual webfront bind url
2018-12-30 20:48:07 -06:00
9494a17997 update prompt utility functions for issue #65
tweaks to alias stuff
fix bug with bots not showing
2018-12-30 18:13:13 -06:00
5f4171ccf4 hopefulyl fix aliasing issue
bans are applied to an account if the accounts are linked but penallty on a different accounts
2018-12-29 12:43:40 -06:00
a10746d5ff Fix small issue with log reading 2018-12-19 19:24:31 -06:00
8dca05a442 Small fixes 2018-12-17 13:45:16 -06:00
8aa0d204f4 Delete stupid 2018-12-16 21:52:11 -06:00
12cf2e8247 Add server version to master api
Add IsEvadedOffense to EFPenalty
Fix remote log reading in not Windows
2018-12-16 21:16:56 -06:00
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
116 changed files with 5913 additions and 1943 deletions

5
.gitignore vendored
View File

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

View File

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

View File

@ -39,12 +39,14 @@ namespace IW4MAdmin.Application.API.Master
{
ClientNum = s.ClientNum,
Game = s.GameName.ToString(),
Version = s.Version,
Gametype = s.Gametype,
Hostname = s.Hostname,
Map = s.CurrentMap.Name,
MaxClientNum = s.MaxClients,
Id = s.GetHashCode(),
Port = (short)s.GetPort()
Id = s.EndPoint,
Port = (short)s.GetPort(),
IPAddress = s.IP
}).ToList()
};

View File

@ -6,7 +6,7 @@
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.2</Version>
<Version>2.2.3.0</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
@ -31,8 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
<AssemblyVersion>2.2.3.0</AssemblyVersion>
<FileVersion>2.2.3.0</FileVersion>
</PropertyGroup>
<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 System.Collections.Generic;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.EventParsers
{
@ -20,7 +19,7 @@ namespace IW4MAdmin.Application.EventParsers
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()
{
@ -41,7 +40,7 @@ namespace IW4MAdmin.Application.EventParsers
.Replace("\x15", "")
.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] == '@')
{
@ -70,8 +69,8 @@ namespace IW4MAdmin.Application.EventParsers
{
if (!server.CustomCallback)
{
var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6));
var target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
var origin = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6));
var target = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
return new GameEvent()
{
@ -86,8 +85,8 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "ScriptKill")
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
return new GameEvent()
{
Type = GameEvent.EventType.ScriptKill,
@ -100,8 +99,8 @@ namespace IW4MAdmin.Application.EventParsers
if (eventType == "ScriptDamage")
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
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)
{
var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong());
var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong());
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
return new GameEvent()
{
@ -143,41 +142,49 @@ namespace IW4MAdmin.Application.EventParsers
{
return new GameEvent()
{
Type = GameEvent.EventType.Join,
Type = GameEvent.EventType.PreConnect,
Data = logLine,
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(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
State = Player.ClientState.Connecting,
State = EFClient.ClientState.Connecting,
CurrentServer = server
}
};
}
}
//if (eventType == "Q")
//{
// var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
// if (regexMatch.Success)
// {
// return new GameEvent()
// {
// Type = GameEvent.EventType.Quit,
// Data = logLine,
// Owner = server,
// Origin = new Player()
// {
// Name = regexMatch.Groups[4].ToString().StripColors(),
// NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
// ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
// State = Player.ClientState.Connecting
// }
// };
// }
//}
if (eventType == "Q")
{
var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.PreDisconnect,
Data = logLine,
Owner = server,
Origin = new EFClient()
{
CurrentAlias = new EFAlias()
{
Active = false,
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"))
{
@ -185,14 +192,8 @@ namespace IW4MAdmin.Application.EventParsers
{
Type = GameEvent.EventType.MapEnd,
Data = lineSplit[0],
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server
};
}
@ -205,14 +206,8 @@ namespace IW4MAdmin.Application.EventParsers
{
Type = GameEvent.EventType.MapChange,
Data = lineSplit[0],
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server,
Extra = dump.DictionaryFromKeyValue()
};
@ -221,19 +216,16 @@ namespace IW4MAdmin.Application.EventParsers
return new GameEvent()
{
Type = GameEvent.EventType.Unknown,
Origin = new Player()
{
ClientId = 1
},
Target = new Player()
{
ClientId = 1
},
Origin = Utilities.IW4MAdminClient(server),
Target = Utilities.IW4MAdminClient(server),
Owner = server
};
}
// 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.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace IW4MAdmin.Application.EventParsers
{
class IW5EventParser : IW4EventParser
{
public override string GetGameDir() => "logs";
public override string GetGameDir()
{
return "logs";
}
public override GameEvent GetEvent(Server server, string logLine)
{
@ -23,21 +22,25 @@ namespace IW4MAdmin.Application.EventParsers
int clientNum = Int32.Parse(lineSplit[2]);
var player = new Player()
var player = new EFClient()
{
NetworkId = lineSplit[1].ConvertLong(),
ClientNumber = clientNum,
Name = lineSplit[3]
CurrentAlias = new EFAlias()
{
Active = false,
Name = lineSplit[3]
}
};
return new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = new Player()
Type = GameEvent.EventType.PreConnect,
Origin = new EFClient()
{
ClientId = 1
},
Target = new Player()
Target = new EFClient()
{
ClientId = 1
},
@ -47,7 +50,9 @@ namespace IW4MAdmin.Application.EventParsers
}
else
{
return base.GetEvent(server, logLine);
}
}
}
}

View File

@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.IO
Server = server;
}
public void PollForChanges()
public async Task PollForChanges()
{
while (!Server.Manager.ShutdownRequested())
{
@ -45,12 +45,12 @@ namespace IW4MAdmin.Application.IO
{
try
{
UpdateLogEvents();
await UpdateLogEvents();
}
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($"StackTrace: {e.StackTrace}");
}
@ -59,7 +59,7 @@ namespace IW4MAdmin.Application.IO
}
}
private void UpdateLogEvents()
private async Task UpdateLogEvents()
{
long fileSize = Reader.Length;
@ -74,11 +74,12 @@ namespace IW4MAdmin.Application.IO
PreviousFileSize = fileSize;
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0);
var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
foreach (var ev in events)
{
Server.Manager.GetEventHandler().AddEvent(ev);
await ev.WaitAsync();
}
PreviousFileSize = fileSize;

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO
{
@ -22,7 +23,7 @@ namespace IW4MAdmin.Application.IO
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
List<string> logLines = new List<string>();
@ -30,6 +31,7 @@ namespace IW4MAdmin.Application.IO
// open the file as a stream
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
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
// the difference should be in the range of a int :P

View File

@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using static SharedLibraryCore.Utilities;
namespace IW4MAdmin.Application.IO
@ -27,20 +28,21 @@ namespace IW4MAdmin.Application.IO
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
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log");
server.Logger.WriteDebug($"Begin reading from http log");
#endif
var events = new List<GameEvent>();
string b64Path = server.LogPath.ToBase64UrlSafeString();
var response = Api.Log(b64Path).Result;
var response = await Api.Log(b64Path);
if (!response.Success)
{
server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})");
return events;
}
// parse each line

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.Database;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using SharedLibraryCore.Localization;
using IW4MAdmin.Application.Migration;
@ -23,7 +22,6 @@ namespace IW4MAdmin.Application
public static void Main(string[] args)
{
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Gray;
@ -39,6 +37,8 @@ namespace IW4MAdmin.Application
try
{
ServerManager = ApplicationManager.GetInstance();
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
loc = Utilities.CurrentLocalization.LocalizationIndex;
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
@ -47,10 +47,6 @@ namespace IW4MAdmin.Application
// todo: move out
ConfigurationMigration.MoveConfigFolder10518(null);
ServerManager = ApplicationManager.GetInstance();
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
ServerManager.Logger.WriteInfo($"Version is {Version}");
var api = API.Master.Endpoint.Get();
@ -112,7 +108,7 @@ namespace IW4MAdmin.Application
var consoleTask = Task.Run(async () =>
{
String userInput;
Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
do
{

View File

@ -1,24 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Reflection;
using IW4MAdmin.Application.API.Master;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events;
using IW4MAdmin.Application.API.Master;
using IW4MAdmin.Application.Migration;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
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
{
@ -26,7 +23,7 @@ namespace IW4MAdmin.Application
{
private List<Server> _servers;
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 bool Running { get; private set; }
public bool IsInitialized { get; private set; }
@ -49,7 +46,7 @@ namespace IW4MAdmin.Application
ManualResetEventSlim OnQuit;
readonly IPageList PageList;
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()
{
@ -60,7 +57,6 @@ namespace IW4MAdmin.Application
ClientSvc = new ClientService();
AliasSvc = new AliasService();
PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>();
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim();
@ -85,80 +81,11 @@ namespace IW4MAdmin.Application
try
{
// if the origin client is not in an authorized state (detected by RCon) don't execute the event
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"]);
}
await newEvent.Owner.ExecuteEvent(newEvent);
// offload it to the player to keep
newEvent.Origin.DelayedEvents.Enqueue(newEvent);
}
// 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);
}
}
}
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
#if DEBUG
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
@ -192,7 +119,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug(ex.GetExceptionInfo());
}
skip:
skip:
// tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set();
@ -216,7 +143,7 @@ namespace IW4MAdmin.Application
public async Task UpdateServerStates()
{
// store the server hash code and task for it
var runningUpdateTasks = new Dictionary<int, Task>();
var runningUpdateTasks = new Dictionary<long, Task>();
while (Running)
{
@ -236,16 +163,16 @@ namespace IW4MAdmin.Application
}
// remove the update tasks as they have completd
foreach (int serverId in serverTasksToRemove)
foreach (long serverId in serverTasksToRemove)
{
runningUpdateTasks.Remove(serverId);
}
// select the servers where the tasks have completed
var serverIds = Servers.Select(s => s.GetHashCode()).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
foreach (var server in Servers.Where(s => serverIds.Contains(s.GetHashCode())))
var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
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
{
@ -265,11 +192,7 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif
#if DEBUG
await Task.Delay(10000);
#else
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
#endif
}
// trigger the event processing loop to end
@ -286,36 +209,7 @@ namespace IW4MAdmin.Application
await new ContextSeed(db).Seed();
}
// todo: optimize this (or replace it)
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;
}
}
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
#endregion
#region CONFIG
@ -364,7 +258,9 @@ namespace IW4MAdmin.Application
}
else if (config.Servers.Count == 0)
{
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
@ -390,7 +286,9 @@ namespace IW4MAdmin.Application
#region COMMANDS
if (ClientSvc.GetOwners().Result.Count == 0)
{
Commands.Add(new COwner());
}
Commands.Add(new CQuit());
Commands.Add(new CKick());
@ -431,7 +329,9 @@ namespace IW4MAdmin.Application
Commands.Add(new CNextMap());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{
Commands.Add(C);
}
#endregion
#region INIT
@ -466,7 +366,9 @@ namespace IW4MAdmin.Application
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
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"]})");
}
else if (e.GetType() == typeof(NetworkException))
{
Logger.WriteDebug(e.Message);
@ -565,7 +467,7 @@ namespace IW4MAdmin.Application
Running = false;
}
public ILogger GetLogger(int serverId)
public ILogger GetLogger(long serverId)
{
if (Loggers.ContainsKey(serverId))
{
@ -595,23 +497,59 @@ namespace IW4MAdmin.Application
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 AliasService GetAliasService() => AliasSvc;
public PenaltyService GetPenaltyService() => PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public bool ShutdownRequested() => !Running;
public IEventHandler GetEventHandler() => Handler;
public ClientService GetClientService()
{
return ClientSvc;
}
public AliasService GetAliasService()
{
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()
{
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.Text.RegularExpressions;
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
{
@ -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");
return ClientsFromStatus(response);
@ -72,14 +71,19 @@ namespace IW4MAdmin.Application.RconParsers
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0;
}
public virtual CommandPrefix GetCommandPrefixes() => Prefixes;
private List<Player> ClientsFromStatus(string[] Status)
public virtual CommandPrefix GetCommandPrefixes()
{
List<Player> StatusPlayers = new List<Player>();
return Prefixes;
}
private List<EFClient> ClientsFromStatus(string[] Status)
{
List<EFClient> StatusPlayers = new List<EFClient>();
if (Status.Length < 4)
{
throw new ServerException("Unexpected status response received");
}
int validMatches = 0;
foreach (String S in Status)
@ -104,20 +108,30 @@ namespace IW4MAdmin.Application.RconParsers
long networkId = regex.Groups[4].Value.ConvertLong();
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 client = new EFClient()
{
Name = name,
CurrentAlias = new EFAlias()
{
Name = name
},
NetworkId = networkId,
ClientNumber = clientNumber,
IPAddress = ip == 0 ? int.MinValue : ip,
IPAddress = ip,
Ping = ping,
Score = score,
IsBot = ip == 0,
State = Player.ClientState.Connecting
IsBot = ip == null,
State = EFClient.ClientState.Connecting
};
StatusPlayers.Add(P);
// they've not fully connected yet
if (!client.IsBot && ping == 999)
{
continue;
}
StatusPlayers.Add(client);
}
}

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Database.Models;
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");
return ClientsFromStatus(response);
@ -118,9 +119,9 @@ namespace IW4MAdmin.Application.RconParsers
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)
{
@ -141,11 +142,11 @@ namespace IW4MAdmin.Application.RconParsers
long networkId = 0;//playerInfo[4].ConvertLong();
int.TryParse(playerInfo[0], out clientId);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
int? ipAddress = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
var p = new Player()
var p = new EFClient()
{
Name = name,
NetworkId = networkId,
@ -154,7 +155,7 @@ namespace IW4MAdmin.Application.RconParsers
Ping = Ping,
Score = score,
IsBot = false,
State = Player.ClientState.Connecting
State = EFClient.ClientState.Connecting
};
StatusPlayers.Add(p);

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
using System.Text;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Application.RconParsers
{
@ -18,9 +19,9 @@ namespace IW4MAdmin.Application.RconParsers
{
Tell = "tell {0} {1}",
Say = "say {0}",
Kick = "clientKick {0}",
Ban = "clientKick {0}",
TempBan = "clientKick {0}"
Kick = "clientkick_for_reason {0} \"{1}\"",
Ban = "clientkick_for_reason {0} \"{1}\"",
TempBan = "clientkick_for_reason {0} \"{1}\""
};
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");
return ClientsFromStatus(response);
@ -73,9 +74,9 @@ namespace IW4MAdmin.Application.RconParsers
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)
{
@ -96,9 +97,9 @@ namespace IW4MAdmin.Application.RconParsers
#if DEBUG
Ping = 1;
#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+");
var p = new Player()
var p = new EFClient()
{
Name = name,
NetworkId = networkId,
@ -106,8 +107,8 @@ namespace IW4MAdmin.Application.RconParsers
IPAddress = ipAddress,
Ping = Ping,
Score = 0,
State = Player.ClientState.Connecting,
IsBot = networkId == 0
State = EFClient.ClientState.Connecting,
IsBot = ipAddress == null
};
if (p.IsBot)

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>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
<OutputPath>bin\Prerelease\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="DiscordWebhook.py" />
</ItemGroup>
@ -42,7 +47,6 @@
</Interpreter>
</ItemGroup>
<ItemGroup>
<Content Include="config.dev.json" />
<Content Include="config.json">
<Publish>True</Publish>
</Content>
@ -50,7 +54,7 @@
<Publish>True</Publish>
</Content>
</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
Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. -->
@ -59,7 +63,7 @@
</Target>
<Target Name="AfterBuild">
</Target>
<ProjectExtensions>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>

View File

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

View File

@ -5,26 +5,27 @@ import time
class LogReader(object):
def __init__(self):
self.log_file_sizes = {}
# (if the file changes more than this, ignore ) - 1 MB
self.max_file_size_change = 1000000
# (if the file changes more than this, ignore ) - 0.125 MB
self.max_file_size_change = 125000
# (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):
# prevent traversing directories
if re.search('r^.+\.\.\\.+$', path):
return False
# 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
# set the initialze size to the current file size
file_size = 0
if path not in self.log_file_sizes:
self.log_file_sizes[path] = {
'length' : self.file_length(path),
'read': time.time()
}
return ''
return True
# grab the previous values
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
# 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:
return ''
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
return True
new_log_info = self.get_file_lines(path, file_size_difference)
return new_log_info

View File

@ -9,9 +9,11 @@ class LogResource(Resource):
if log_info is False:
print('could not read log file ' + path)
empty_read = (log_info == False) or (log_info == True)
return {
'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
}

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_restful import Api
from .log_resource import LogResource
from .restart_resource import RestartResource
app = Flask(__name__)
def init():
api = Api(app)
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
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7
Flask==1.0.2
Flask-JWT==0.3.2
Flask-JWT-Extended==3.8.1
Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
marshmallow==3.0.0b8
pip==9.0.3
pytz==2018.5
setuptools==39.0.1
psutil==5.4.8
pygal==2.4.0
PyJWT==1.4.2
pytz==2018.7
requests==2.20.0
setuptools==40.5.0
six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1

View File

@ -12,4 +12,4 @@ if __name__ == '__main__':
except ValueError:
PORT = 5555
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
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16

View File

@ -17,7 +17,7 @@
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>Master</Name>
<RootNamespace>Master</RootNamespace>
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
<InterpreterId>MSBuild|env_master|$(MSBuildProjectFullPath)</InterpreterId>
<IsWindowsApplication>False</IsWindowsApplication>
<PythonRunWebServerCommand>
</PythonRunWebServerCommand>
@ -111,18 +111,18 @@
<Folder Include="master\templates\" />
</ItemGroup>
<ItemGroup>
<None Include="FolderProfile.pubxml" />
<Content Include="master\config\master.json" />
<Content Include="master\templates\serverlist.html" />
<None Include="Release.pubxml" />
<Content Include="requirements.txt" />
<Content Include="master\templates\index.html" />
<Content Include="master\templates\layout.html" />
</ItemGroup>
<ItemGroup>
<Interpreter Include="dev_env\">
<Id>dev_env</Id>
<Interpreter Include="env_master\">
<Id>env_master</Id>
<Version>3.6</Version>
<Description>dev_env (Python 3.6 (64-bit))</Description>
<Description>env_master (Python 3.6 (64-bit))</Description>
<InterpreterPath>Scripts\python.exe</InterpreterPath>
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>

View File

@ -1,8 +1,9 @@
class ServerModel(object):
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype, ip):
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype, ip, version):
self.id = id
self.port = port
self.version = version
self.game = game
self.hostname = hostname
self.clientnum = clientnum

View File

@ -23,7 +23,10 @@ class Instance(Resource):
def put(self, id):
try:
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
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json)
except ValidationError as err:
return {'message' : err.messages }, 400
@ -34,7 +37,10 @@ class Instance(Resource):
def post(self):
try:
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
if 'version' not in server:
server['version'] = 'Unknown'
instance = InstanceSchema().load(request.json)
except ValidationError as err:
return {'message' : err.messages }, 400

View File

@ -4,7 +4,7 @@ from master.models.servermodel import ServerModel
class ServerSchema(Schema):
id = fields.Int(
required=True,
validate=validate.Range(1, 2147483647, 'invalid id')
validate=validate.Range(1, 25525525525565535, 'invalid id')
)
ip = fields.Str(
required=True
@ -13,6 +13,10 @@ class ServerSchema(Schema):
required=True,
validate=validate.Range(1, 65535, 'invalid port')
)
version = fields.String(
required=False,
validate=validate.Length(0, 128, 'invalid server version')
)
game = fields.String(
required=True,
validate=validate.Length(1, 5, 'invalid game name')

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
Flask==0.12.2
Flask==1.0.2
Flask-JWT==0.3.2
Flask-JWT-Extended==3.8.1
Flask-RESTful==0.3.6
idna==2.7
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
marshmallow==3.0.0b8
pip==9.0.1
pip==9.0.3
psutil==5.4.8
pygal==2.4.0
PyJWT==1.4.2
pytz==2018.4
setuptools==39.0.1
pytz==2018.7
requests==2.20.0
setuptools==40.5.0
six==1.11.0
timeago==1.0.8
tzlocal==1.5.1
urllib3==1.24
Werkzeug==0.14.1

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -8,7 +9,7 @@ namespace IW4MAdmin.Plugins.Login.Commands
{
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()
{

View File

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

View File

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

View File

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

View File

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

View File

@ -14,8 +14,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public Vector3 LastAngle { 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)
{
if (LastAngle == null)
@ -25,14 +23,15 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double decayFactor = GetDecay(deltaTime);
CurrentStrain *= decayFactor;
#if DEBUG
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
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
if ((distance[0] == 0 && distance[1] == 0) ||
deltaTime == 0 ||
@ -41,14 +40,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
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;
CurrentStrain += newStrain;
if (CurrentStrain > Thresholds.MaxStrainBan)
TimesReachedMaxStrain++;
LastAngle = newAngle;
return CurrentStrain;
}

View File

@ -27,9 +27,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100;
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 const double MaxStrainFlag = 0.36;

View File

@ -8,6 +8,8 @@ using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands
{
@ -15,7 +17,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{
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>()
{
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
@ -34,7 +37,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
join alias in db.Aliases
on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId
where client.Level != Player.Permission.Banned
where client.Level != EFClient.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending
select new
@ -55,7 +58,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
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)
{

View File

@ -1,32 +1,30 @@
using SharedLibraryCore;
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 IW4MAdmin.Plugins.Stats.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Commands
{
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)
{
if (E.Origin.ClientNumber >= 0)
{
int serverId = E.Owner.GetHashCode();
long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
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();
clientStats.Deaths = 0;
@ -38,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
clientStats.EloRating = 200.0;
// 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
await ctx.SaveChangesAsync();

View File

@ -9,15 +9,16 @@ using SharedLibraryCore.Services;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
namespace IW4MAdmin.Plugins.Stats.Commands
{
class TopStats : Command
{
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>()
{
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
@ -34,7 +35,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
where client.Level != Player.Permission.Banned
where client.Level != EFClient.Permission.Banned
where client.LastConnection >= fifteenDaysAgo
orderby stats.Performance descending
select new
@ -67,7 +68,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
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)
{

View File

@ -9,12 +9,14 @@ using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using IW4MAdmin.Plugins.Stats.Helpers;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Plugins.Stats.Commands
{
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()
{
@ -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))
{
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));
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
{
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)));
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 System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IW4MAdmin.Plugins.Stats.Cheat;
using IW4MAdmin.Plugins.Stats.Models;
using IW4MAdmin.Plugins.Stats.Web.Dtos;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Commands;
using IW4MAdmin.Plugins.Stats.Models;
using System.Text.RegularExpressions;
using IW4MAdmin.Plugins.Stats.Web.Dtos;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Services;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Stats.Helpers
{
public class StatManager
{
private ConcurrentDictionary<int, ServerStats> Servers;
private ConcurrentDictionary<long, ServerStats> Servers;
private ILogger Log;
private readonly IManager Manager;
private readonly SemaphoreSlim OnProcessingPenalty;
private readonly SemaphoreSlim OnProcessingSensitive;
public StatManager(IManager mgr)
{
Servers = new ConcurrentDictionary<int, ServerStats>();
Servers = new ConcurrentDictionary<long, ServerStats>();
Log = mgr.GetLogger(0);
Manager = mgr;
OnProcessingPenalty = new SemaphoreSlim(1, 1);
OnProcessingSensitive = new SemaphoreSlim(1, 1);
}
public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId];
public EFClientStatistics GetClientStats(int clientId, 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);
return (r) => r.ServerId == serverId &&
r.When > fifteenDaysAgo &&
r.RatingHistory.Client.Level != Player.Permission.Banned &&
r.RatingHistory.Client.Level != EFClient.Permission.Banned &&
r.Newest &&
r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime;
}
@ -57,7 +57,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary>
/// <param name="clientId">client id of the player</param>
/// <returns></returns>
public async Task<int> GetClientOverallRanking(int clientId)
public static async Task<int> GetClientOverallRanking(int clientId)
{
using (var context = new DatabaseContext(true))
{
@ -191,21 +191,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// insert the server if it does not exist
try
{
int serverId = sv.GetHashCode();
long serverId = GetIdForServer(sv).Result;
EFServer server;
using (var ctx = new DatabaseContext(disableTracking: true))
{
var serverSet = ctx.Set<EFServer>();
// get the server from the database if it exists, otherwise create and insert a new one
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)
{
server = new EFServer()
{
Port = sv.GetPort(),
Active = true,
EndPoint = sv.ToString(),
ServerId = serverId
};
@ -216,7 +231,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
// 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)
{
@ -227,6 +242,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
catch (Exception e)
{
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>
/// <param name="pl">Player to add/retrieve stats for</param>
/// <returns>EFClientStatistic of specified player</returns>
public async Task<EFClientStatistics> AddPlayer(Player pl)
public async Task<EFClientStatistics> AddPlayer(EFClient pl)
{
await OnProcessingSensitive.WaitAsync();
try
{
int serverId = pl.CurrentServer.GetHashCode();
long serverId = await GetIdForServer(pl.CurrentServer);
if (!Servers.ContainsKey(serverId))
{
@ -299,7 +314,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
Log.WriteWarning("Adding new client to stats failed");
}
}
else
@ -348,7 +362,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Log.WriteWarning("Could not add client to detection");
}
Log.WriteInfo($"Adding {pl} to stats");
pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats");
}
return clientStats;
@ -373,21 +387,21 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary>
/// <param name="pl">Disconnecting client</param>
/// <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 detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics;
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
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue1);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2);
detectionStats.TryRemove(pl.ClientId, out Detection removedValue2);
return;
}
@ -396,7 +410,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// remove the client from the stats dictionary as they're leaving
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
clientStats = UpdateStats(clientStats);
@ -408,10 +422,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
// 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]+);(.+);(.+)$";
var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase);
@ -432,7 +446,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// Process stats for kill event
/// </summary>
/// <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 fraction, string visibilityPercentage, string snapAngles)
{
@ -548,7 +562,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
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);
}
@ -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)
{
case Penalty.PenaltyType.Ban:
if (attacker.Level == Player.Permission.Banned)
if (attacker.Level == EFClient.Permission.Banned)
{
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,
AdministeredPenalties = new List<EFPenalty>()
@ -588,16 +617,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
}
},
Level = Player.Permission.Console,
CurrentServer = attacker.CurrentServer
});
Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer,
}, false);
if (clientDetection.Tracker.HasChanges)
{
SaveTrackedSnapshots(clientDetection, ctx);
}
break;
case Penalty.PenaltyType.Flag:
if (attacker.Level != Player.Permission.User)
if (attacker.Level != EFClient.Permission.User)
{
break;
}
@ -606,10 +636,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{(int)penalty.Location}-{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,
Level = Player.Permission.Console,
Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer,
});
@ -677,9 +707,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;
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
@ -709,13 +739,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1;
await Sync(attacker.CurrentServer);
// this happens when the round has changed
if (attackerStats.SessionScore == 0)
{
attackerStats.LastScore = 0;
}
if (victimStats.SessionScore == 0)
{
victimStats.LastScore = 0;
}
attackerStats.SessionScore = attacker.Score;
victimStats.SessionScore = victim.Score;
@ -768,8 +803,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStatsSet.Update(attackerStats);
clientStatsSet.Update(victimStats);
clientStatsSet.Attach(attackerStats).State = EntityState.Modified;
clientStatsSet.Attach(victimStats).State = EntityState.Modified;
await ctx.SaveChangesAsync();
}
}
@ -780,7 +815,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="client">client to update</param>
/// <param name="clientStats">stats of client that is being updated</param>
/// <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;
@ -979,6 +1014,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calulate elo
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1)
{
#region DEPRECATED
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs =>
@ -1002,6 +1038,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/
#endregion
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));
@ -1104,9 +1141,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return clientStats;
}
public EFServerStatistics InitializeServerStats(Server sv)
public EFServerStatistics InitializeServerStats(long serverId)
{
int serverId = sv.GetHashCode();
EFServerStatistics serverStats;
using (var ctx = new DatabaseContext(disableTracking: true))
@ -1116,7 +1152,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (serverStats == null)
{
Log.WriteDebug($"Initializing server stats for {sv}");
Log.WriteDebug($"Initializing server stats for {serverId}");
// server stats have never been generated before
serverStats = new EFServerStatistics()
{
@ -1133,7 +1169,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return serverStats;
}
public void ResetKillstreaks(int serverId)
public void ResetKillstreaks(long serverId)
{
var serverStats = Servers[serverId];
@ -1143,7 +1179,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];
stats.Kills = 0;
@ -1154,11 +1190,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
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
if (clientId < 1)
{
return;
}
using (var ctx = new DatabaseContext(disableTracking: true))
{
@ -1176,19 +1214,64 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task Sync(Server sv)
{
int serverId = sv.GetHashCode();
long serverId = await GetIdForServer(sv);
using (var ctx = new DatabaseContext(disableTracking: true))
{
var serverSet = ctx.Set<EFServer>();
serverSet.Update(Servers[serverId].Server);
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(Servers[serverId].ServerStatistics);
await ctx.SaveChangesAsync();
}
}
public void SetTeamBased(int serverId, bool isTeamBased)
public void SetTeamBased(long serverId, bool 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; }
[ForeignKey("AttackerId")]
public virtual EFClient Attacker { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
public IW4Info.HitLocation HitLoc { get; set; }

View File

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

View File

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

View File

@ -20,7 +20,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int ClientId { get; set; }
[ForeignKey("ClientId"), Column(Order = 0 )]
public EFClient Client { get; set; }
public int ServerId { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId"), Column(Order = 1)]
public EFServer Server { get; set; }
}

View File

@ -13,7 +13,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
[ForeignKey("RatingHistoryId")]
public virtual EFClientRatingHistory RatingHistory { get; set; }
// 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
public virtual EFServer Server { get; set; }
[Required]

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -25,7 +26,7 @@ namespace Tests
[Fact]
public void SetAdditionalPropertyShouldSucceed()
{
var client = new Player();
var client = new EFClient();
int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp);
}
@ -33,7 +34,7 @@ namespace Tests
[Fact]
public void GetAdditionalPropertyShouldSucceed()
{
var client = new Player();
var client = new EFClient();
int newProp = 5;
client.SetAdditionalProperty("NewProp", newProp);
@ -48,11 +49,11 @@ namespace Tests
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");
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();
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,
"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();
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions");
// 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 &&
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");
}
@ -85,11 +86,11 @@ namespace Tests
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");
// 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);
var reportEvent = client.Report("test report", player);
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()}]");
// 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);
Assert.True(!reportEvent.Failed &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
// 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 &&
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");
// 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 &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"duplicate report was applied");
@ -132,42 +133,42 @@ namespace Tests
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");
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();
// succeed
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");
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();
// fail
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();
// fail
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();
// 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();
// 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();
// succeed
@ -182,18 +183,18 @@ namespace Tests
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");
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();
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();
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]
@ -204,13 +205,13 @@ namespace Tests
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");
var tbCommand = new CTempBan();
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,
Data = "5days test tempban",
Type = GameEvent.EventType.Command,
@ -229,13 +230,13 @@ namespace Tests
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");
var banCommand = new CBan();
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,
Data = "test ban",
Type = GameEvent.EventType.Command,
@ -248,8 +249,8 @@ namespace Tests
var unbanCommand = new CUnban();
unbanCommand.ExecuteAsync(new GameEvent()
{
Origin = new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = client.CurrentServer },
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First().AsPlayer(),
Origin = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer },
Target = Manager.GetClientService().Find(c => c.NetworkId == client.NetworkId).Result.First(),
Data = "test unban",
Type = GameEvent.EventType.Command,
Owner = client.CurrentServer

View File

@ -1,5 +1,6 @@
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using System;
@ -53,7 +54,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -78,7 +79,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -113,7 +114,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Connect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,
@ -134,7 +135,7 @@ namespace Tests
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}");
for (int i = clientIndexStart; i < clientNum + clientIndexStart; i++)
@ -142,7 +143,7 @@ namespace Tests
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = new Player()
Origin = new EFClient()
{
Name = $"Player{i}",
NetworkId = i,

View File

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

View File

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

View File

@ -5,13 +5,15 @@ _______
### 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.
### 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
**IW4MAdmin** requires minimal effort to get up and running.
#### 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
1. Install .NET Core Runtime
2. Extract `IW4MAdmin-<version>.zip`
@ -105,6 +107,14 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
`ConnectionString`
* Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite
* 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`
* 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
|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|
|Name |Alias|Description |Requires Target|Syntax |Required Level|
|--------------| -----| --------------------------------------------------------| -----------------| -------------| ---------------|
|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|
|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|
@ -190,24 +200,21 @@ ___
|unflag|uf|Remove flag for client|True|!uf \<player\>|Moderator|
|uptime|up|get current application running time|False|!up |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|
|warnclear|wc|remove all warnings for a client|True|!wc \<player\>|Trusted|
|admins|a|list currently connected privileged clients|False|!a |User|
|getexternalip|ip|view your external IP address|False|!ip |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|
|ping|pi|get client's latency|False|!pi \<optional player\>|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|
|resetstats|rs|reset your stats to factory-new|False|!rs |User|
|rules|r|list server rules|False|!r |User|
|stats|xlrstats|view your stats|False|!xlrstats \<optional player\>|User|
|topstats|ts|view the top 5 players in this server|False|!ts |User|
|setgravatar|sg|set gravatar for webfront profile|False|!sg \<gravatar email\>|User|
|whoami|who|give information about yourself|False|!who |User|
_These commands include all shipped plugin commands._
---
@ -302,6 +309,11 @@ ___
#### VPN Detection [Script Plugin]
- This plugin detects if a client is using a VPN and kicks them if they are
- 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
`Home`
@ -432,9 +444,13 @@ python DiscordWebhook.py
#### 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.
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.
#### 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.
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 Application/Application.csproj -c Prerelease -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\WindowsPrerelease\GameLogServer
dotnet publish WebfrontCore/WebfrontCore.csproj -c Prerelease -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
dotnet publish Application/Application.csproj -c Prerelease -o X:\IW4MAdmin\Publish\WindowsPrerelease /p:PublishProfile=Prerelease
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"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=FolderProfile /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=FolderProfile /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
cd "C:\Projects\IW4M-Admin\DEPLOY\"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=PreRelease /p:DeployOnBuild=true /p:PublishProfileRootFolder=X:\IW4MAdmin\DiscordWebhook\
cd "X:\IW4MAdmin\DEPLOY\"
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 Application/Application.csproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows
dotnet publish GameLogServer/GameLogServer.pyproj -c Release -o C:\Projects\IW4M-Admin\Publish\Windows\GameLogServer
dotnet publish WebfrontCore/WebfrontCore.csproj -c Release -o X:\IW4MAdmin\Publish\Windows
dotnet publish Application/Application.csproj -c Release -o X:\IW4MAdmin\Publish\Windows
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"
msbuild GameLogServer/GameLogServer.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\GameLogServer\
msbuild DiscordWebhook/DiscordWebhook.pyproj /p:PublishProfile=Stable /p:DeployOnBuild=true /p:PublishProfileRootFolder=C:\Projects\IW4M-Admin\DiscordWebhook\
cd "C:\Projects\IW4M-Admin\DEPLOY\"
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=X:\IW4MAdmin\DiscordWebhook\
cd "X:\IW4MAdmin\DEPLOY\"
PowerShell ".\upload_release.ps1"

View File

@ -1,7 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore
@ -14,7 +14,7 @@ namespace SharedLibraryCore
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;
Description = commandDescription;
@ -33,7 +33,7 @@ namespace SharedLibraryCore
public String Alias { get; private set; }
public int RequiredArgumentCount => Arguments.Count(c => c.Required);
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; }
}
}

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
@ -60,7 +61,7 @@ namespace SharedLibraryCore.Commands
var found = await Manager.GetClientService().Get(dbID);
if (found != null)
{
E.Target = found.AsPlayer();
E.Target = found;
E.Target.CurrentServer = E.Owner;
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
{
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));
}
}
List<Player> matchingPlayers;
List<EFClient> matchingPlayers;
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.RegularExpressions;
using System.Threading.Tasks;
using static SharedLibraryCore.RCon.StaticHelpers;
namespace SharedLibraryCore.Commands
{
public class CQuit : Command
{
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)
@ -31,18 +30,32 @@ namespace SharedLibraryCore.Commands
public class COwner : Command
{
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)
{
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"]);
// so setpassword/login works
E.Owner.Manager.GetPrivilegedClients().Add(E.Origin.ClientId, 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
{
@ -54,7 +67,7 @@ namespace SharedLibraryCore.Commands
public class CWarn : Command
{
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()
{
@ -83,7 +96,7 @@ namespace SharedLibraryCore.Commands
public class CWarnClear : Command
{
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()
{
@ -107,7 +120,7 @@ namespace SharedLibraryCore.Commands
public class CKick : Command
{
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()
{
@ -133,7 +146,7 @@ namespace SharedLibraryCore.Commands
public class CSay : Command
{
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()
{
@ -153,7 +166,7 @@ namespace SharedLibraryCore.Commands
public class CTempBan : Command
{
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()
{
@ -183,9 +196,17 @@ namespace SharedLibraryCore.Commands
string tempbanReason = match.Groups[2].ToString();
var length = match.Groups[1].ToString().ParseTimespan();
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}");
if (length > E.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
{
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 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()
{
@ -210,7 +231,7 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var _ = !(await E.Target.Ban(E.Data, E.Origin).WaitAsync()).Failed ?
var _ = !(await E.Target.Ban(E.Data, E.Origin, false).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"]}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"]} {E.Target.Name}");
}
@ -219,7 +240,7 @@ namespace SharedLibraryCore.Commands
public class CUnban : Command
{
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()
{
@ -252,12 +273,12 @@ namespace SharedLibraryCore.Commands
public class CWhoAmI : Command
{
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)
{
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);
return Task.CompletedTask;
@ -267,27 +288,33 @@ namespace SharedLibraryCore.Commands
public class CList : Command
{
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)
{
StringBuilder playerList = new StringBuilder();
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)
{
continue;
}
// todo: fix spacing
// todo: make this better :)
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
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());
count = 0;
@ -312,7 +339,7 @@ namespace SharedLibraryCore.Commands
public class CHelp : Command
{
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()
{
@ -377,7 +404,7 @@ namespace SharedLibraryCore.Commands
public class CFastRestart : Command
{
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)
@ -393,7 +420,7 @@ namespace SharedLibraryCore.Commands
public class CMapRotate : Command
{
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)
@ -410,7 +437,7 @@ namespace SharedLibraryCore.Commands
public class CSetLevel : Command
{
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()
{
@ -433,17 +460,17 @@ namespace SharedLibraryCore.Commands
return;
}
Player.Permission oldPerm = E.Target.Level;
Player.Permission newPerm = Utilities.MatchPermission(E.Data);
EFClient.Permission oldPerm = E.Target.Level;
EFClient.Permission newPerm = Utilities.MatchPermission(E.Data);
if (newPerm == Player.Permission.Owner &&
if (newPerm == EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_OWNER"]);
return;
}
if (E.Origin.Level < Player.Permission.Owner &&
if (E.Origin.Level < EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy)
{
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 (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()));
return;
}
}
else if (newPerm > Player.Permission.Banned)
else if (newPerm > EFClient.Permission.Banned)
{
var ActiveClient = E.Owner.Manager.GetActiveClients()
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
@ -468,7 +495,11 @@ namespace SharedLibraryCore.Commands
{
ActiveClient.Level = newPerm;
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
@ -477,17 +508,6 @@ namespace SharedLibraryCore.Commands
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()
{
Origin = E.Origin,
@ -503,7 +523,9 @@ namespace SharedLibraryCore.Commands
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
@ -516,7 +538,7 @@ namespace SharedLibraryCore.Commands
public class CUsage : Command
{
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)
@ -529,7 +551,7 @@ namespace SharedLibraryCore.Commands
public class CUptime : Command
{
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)
@ -544,13 +566,13 @@ namespace SharedLibraryCore.Commands
public class CListAdmins : Command
{
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)
{
var onlineAdmins = S.GetPlayersAsList()
.Where(p => p.Level > Player.Permission.Flagged)
var onlineAdmins = S.GetClientsAsList()
.Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => !p.Masked)
.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 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()
{
@ -606,7 +628,7 @@ namespace SharedLibraryCore.Commands
public class CFindPlayer : Command
{
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()
{
@ -625,7 +647,7 @@ namespace SharedLibraryCore.Commands
}
IList<EFClient> db_players = (await (E.Owner.Manager.GetClientService() as ClientService)
.GetClientByName(E.Data))
.FindClientsByIdentifier(E.Data))
.OrderByDescending(p => p.LastConnection)
.ToList();
@ -650,7 +672,7 @@ namespace SharedLibraryCore.Commands
public class CListRules : Command
{
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)
@ -668,7 +690,9 @@ namespace SharedLibraryCore.Commands
var rules = new List<string>();
rules.AddRange(E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules);
if (E.Owner.ServerConfig.Rules != null)
{
rules.AddRange(E.Owner.ServerConfig.Rules);
}
foreach (string r in rules)
{
@ -683,7 +707,7 @@ namespace SharedLibraryCore.Commands
public class CPrivateMessage : Command
{
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()
{
@ -710,7 +734,7 @@ namespace SharedLibraryCore.Commands
public class CFlag : Command
{
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()
{
@ -752,7 +776,7 @@ namespace SharedLibraryCore.Commands
public class CUnflag : Command
{
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()
{
@ -790,7 +814,7 @@ namespace SharedLibraryCore.Commands
public class CReport : Command
{
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()
{
@ -861,7 +885,7 @@ namespace SharedLibraryCore.Commands
public class CListReports : Command
{
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()
{
@ -898,7 +922,7 @@ namespace SharedLibraryCore.Commands
public class CMask : Command
{
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)
@ -921,7 +945,7 @@ namespace SharedLibraryCore.Commands
public class CListBanInfo : Command
{
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()
{
@ -935,7 +959,8 @@ namespace SharedLibraryCore.Commands
{
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)
{
@ -943,7 +968,7 @@ namespace SharedLibraryCore.Commands
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"];
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 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()
{
@ -987,7 +1012,7 @@ namespace SharedLibraryCore.Commands
public class CExecuteRCON : Command
{
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()
{
@ -1001,16 +1026,21 @@ namespace SharedLibraryCore.Commands
{
var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
foreach (string S in Response)
{
E.Origin.Tell(S.StripColors());
}
if (Response.Length == 0)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_SUCCESS"]);
}
}
}
public class CPlugins : Command
{
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)
@ -1027,7 +1057,7 @@ namespace SharedLibraryCore.Commands
public class CIP : Command
{
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)
@ -1039,7 +1069,7 @@ namespace SharedLibraryCore.Commands
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()
{
@ -1059,7 +1089,9 @@ namespace SharedLibraryCore.Commands
{
inactiveDays = Int32.Parse(E.Data);
if (inactiveDays < 1)
{
throw new FormatException();
}
}
}
@ -1076,10 +1108,10 @@ namespace SharedLibraryCore.Commands
{
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
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)
.ToListAsync();
inactiveUsers.ForEach(c => c.Level = Player.Permission.User);
inactiveUsers.ForEach(c => c.Level = EFClient.Permission.User);
await context.SaveChangesAsync();
}
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 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()
{
@ -1122,77 +1154,89 @@ namespace SharedLibraryCore.Commands
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)
{
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
System.Diagnostics.Process currentProcess = null;
foreach (var p in gameserverProcesses)
if (E.Owner.ServerConfig.ManualLogPath != null)
{
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())
using (var wc = new WebClient())
{
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
{
// attempt to kill it natively
try
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
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
await E.Owner.ExecuteCommandAsync("quit");
#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)
{
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;
}
return;
}
}
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()
{
@ -1207,16 +1251,24 @@ namespace SharedLibraryCore.Commands
if (E.Message.IsBroadcastCommand())
{
if (E.Target == null)
{
E.Owner.Broadcast($"{E.Origin.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Origin.Ping}^7ms");
}
else
{
E.Owner.Broadcast($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
}
else
{
if (E.Target == null)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"]} ^5{E.Origin.Ping}^7ms");
}
else
{
E.Origin.Tell($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
}
return Task.CompletedTask;
@ -1225,7 +1277,7 @@ namespace SharedLibraryCore.Commands
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()
{
@ -1289,7 +1341,7 @@ namespace SharedLibraryCore.Commands
/// </summary>
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)
{
string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value.ToLower();

View File

@ -16,11 +16,15 @@ namespace SharedLibraryCore.Configuration
public string SocialLinkAddress { get; set; }
public string SocialLinkTitle { get; set; }
public string WebfrontBindUrl { get; set; }
public string ManualWebfrontUrl { get; set; }
public string WebfrontUrl => string.IsNullOrEmpty(ManualWebfrontUrl) ? WebfrontBindUrl.Replace("0.0.0.0", "127.0.0.1") : ManualWebfrontUrl;
public string CustomParserEncoding { get; set; }
public string CustomLocale { get; set; }
public string DatabaseProvider { get; set; } = "sqlite";
public string ConnectionString { get; set; }
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 List<ServerConfiguration> Servers { get; set; }
public int AutoMessagePeriod { get; set; }

View File

@ -47,7 +47,7 @@ namespace SharedLibraryCore.Configuration
if (UseIW5MParser)
ManualLogPath = Utilities.PromptString(loc["SETUP_SERVER_MANUALLOG"]);
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(0, 32);
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32);
return this;
}

View File

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

View File

@ -1,17 +1,13 @@
using System;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
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 System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace SharedLibraryCore.Database
{
@ -26,6 +22,7 @@ namespace SharedLibraryCore.Database
static string _ConnectionString;
static string _provider;
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins\";
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
@ -57,7 +54,7 @@ namespace SharedLibraryCore.Database
$"{Path.DirectorySeparatorChar}{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 connection = new SqliteConnection(connectionString);
@ -98,6 +95,9 @@ namespace SharedLibraryCore.Database
.WithMany(p => p.AdministeredPenalties)
.HasForeignKey(c => c.PunisherId)
.OnDelete(DeleteBehavior.Restrict);
entity.Property(p => p.Expires)
.IsRequired(false);
});
modelBuilder.Entity<EFAliasLink>(entity =>
@ -110,6 +110,7 @@ namespace SharedLibraryCore.Database
modelBuilder.Entity<EFAlias>(ent =>
{
ent.Property(a => a.IPAddress).IsRequired(false);
ent.HasIndex(a => a.IPAddress);
ent.Property(a => a.Name).HasMaxLength(24);
ent.HasIndex(a => a.Name);
@ -123,26 +124,15 @@ namespace SharedLibraryCore.Database
// adapted from
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
IEnumerable<string> directoryFiles;
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")))
#if DEBUG
string pluginDir = _migrationPluginDirectory;
#else
foreach (string dllPath in directoryFiles)
string pluginDir = Path.Join(Utilities.OperatingDirectory, "Plugins");
#endif
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
foreach (string dllPath in directoryFiles)
{
Assembly library;
try
@ -160,7 +150,9 @@ namespace SharedLibraryCore.Database
.Select(c => (IModelConfiguration)Activator.CreateInstance(c));
foreach (var configurable in configurations)
{
configurable.Configure(modelBuilder);
}
foreach (var type in library.ExportedTypes)
{

View File

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

View File

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

View File

@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models
{
@ -27,10 +23,12 @@ namespace SharedLibraryCore.Database.Models
[Required]
public DateTime When { get; set; }
[Required]
public DateTime Expires { get; set; }
public DateTime? Expires { get; set; }
[Required]
public string Offense { get; set; }
public string AutomatedOffense { get; set; }
[Required]
public bool IsEvadedOffense { get; set; }
public Objects.Penalty.PenaltyType Type { get; set; }
}
}

View File

@ -1,17 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static SharedLibraryCore.Objects.Player;
namespace SharedLibraryCore.Dtos
namespace SharedLibraryCore.Dtos
{
public class ClientInfo
{
public string Name { get; set; }
public int ClientId { 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>
public class EntityInfo
{
public int Id { get; set; }
public long Id { get; set; }
public string Name { get; set; }
}
}

View File

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

View File

@ -41,7 +41,7 @@ namespace SharedLibraryCore.Events
OwnerEntity = new EntityInfo()
{
Name = E.Owner.Hostname,
Id = E.Owner.GetHashCode()
Id = E.Owner.EndPoint
},
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.Tasks;
using SharedLibraryCore.Objects;
namespace SharedLibraryCore
{
@ -50,11 +50,11 @@ namespace SharedLibraryCore
/// </summary>
Stop,
/// <summary>
/// a client was detecting as connecting via RCon
/// a client was detecting as connecting via log
/// </summary>
Connect,
/// <summary>
/// a client was detecting joining via log
/// a client was detecting joining by RCon
/// </summary>
Join,
/// <summary>
@ -73,6 +73,18 @@ namespace SharedLibraryCore
/// the current map changed
/// </summary>
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
/// <summary>
@ -158,7 +170,10 @@ namespace SharedLibraryCore
}
static long NextEventId;
static long GetNextEventId() => Interlocked.Increment(ref NextEventId);
static long GetNextEventId()
{
return Interlocked.Increment(ref NextEventId);
}
public GameEvent()
{
@ -170,8 +185,8 @@ namespace SharedLibraryCore
public EventType Type;
public string Data; // Data is usually the message sent by player
public string Message;
public Player Origin;
public Player Target;
public EFClient Origin;
public EFClient Target;
public Server Owner;
public Boolean Remote = false;
public object Extra { get; set; }
@ -185,43 +200,13 @@ namespace SharedLibraryCore
/// asynchronously wait for GameEvent to be processed
/// </summary>
/// <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 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);
return Task.Run(() =>
{
OnProcessed.Wait(timeOut);
return this;
});
}
}
}

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

View File

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

View File

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

@ -0,0 +1,694 @@
// <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("20181216214513_AddEvadePenaltyFlag")]
partial class AddEvadePenaltyFlag
{
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<bool>("IsEvadedOffense");
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,183 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddEvadePenaltyFlag : 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,
IsEvadedOffense BOOLEAN NOT NULL
DEFAULT (0),
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;", suppressTransaction: false);
}
else
{
migrationBuilder.AddColumn<bool>(
name: "IsEvadedOffense",
table: "EFPenalties",
nullable: false,
defaultValue: false);
}
}
protected override void Down(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;", suppressTransaction: false);
}
else
{
migrationBuilder.DropColumn(
name: "IsEvadedOffense",
table: "EFPenalties");
}
}
}
}

View File

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

View File

@ -1,13 +1,13 @@
using System;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Objects
namespace SharedLibraryCore.Database.Models
{
public class Player : Database.Models.EFClient
public partial class EFClient
{
public enum ClientState
{
@ -17,11 +17,6 @@ namespace SharedLibraryCore.Objects
/// </summary>
Connecting,
/// <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
/// and validated by the database
/// </summary>
@ -76,18 +71,21 @@ namespace SharedLibraryCore.Objects
Console = 8
}
public Player()
public EFClient()
{
ConnectionTime = DateTime.UtcNow;
ClientNumber = -1;
DelayedEvents = new Queue<GameEvent>();
_additionalProperties = new Dictionary<string, object>
{
{ "_reportCount", 0 }
};
CurrentAlias = new EFAlias();
}
public override string ToString() => $"{Name}::{NetworkId}";
public override string ToString()
{
return $"{Name}::{NetworkId}";
}
/// <summary>
/// send a message directly to the connected client
@ -113,7 +111,7 @@ namespace SharedLibraryCore.Objects
/// </summary>
/// <param name="warnReason">reason for 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()
{
@ -146,7 +144,7 @@ namespace SharedLibraryCore.Objects
/// </summary>
/// <param name="sender">client performing the warn clear</param>
/// <returns></returns>
public GameEvent WarnClear(Player sender)
public GameEvent WarnClear(EFClient sender)
{
var e = new GameEvent()
{
@ -175,7 +173,7 @@ namespace SharedLibraryCore.Objects
/// <param name="reportReason">reason for the report</param>
/// <param name="sender">client performing the report</param>
/// <returns></returns>
public GameEvent Report(string reportReason, Player sender)
public GameEvent Report(string reportReason, EFClient sender)
{
var e = new GameEvent()
{
@ -221,7 +219,7 @@ namespace SharedLibraryCore.Objects
/// <param name="flagReason">reason for flagging</param>
/// <param name="sender">client performing the flag</param>
/// <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()
{
@ -238,14 +236,14 @@ namespace SharedLibraryCore.Objects
e.FailReason = GameEvent.EventFailReason.Permission;
}
else if (this.Level == Player.Permission.Flagged)
else if (this.Level == Permission.Flagged)
{
e.FailReason = GameEvent.EventFailReason.Invalid;
}
else
{
this.Level = Player.Permission.Flagged;
this.Level = Permission.Flagged;
}
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="sender">client performing the unflag</param>
/// <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()
{
@ -275,7 +273,7 @@ namespace SharedLibraryCore.Objects
e.FailReason = GameEvent.EventFailReason.Permission;
}
else if (this.Level != Player.Permission.Flagged)
else if (this.Level != EFClient.Permission.Flagged)
{
e.FailReason = GameEvent.EventFailReason.Invalid;
}
@ -294,7 +292,7 @@ namespace SharedLibraryCore.Objects
/// </summary>
/// <param name="kickReason">reason to kick for</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()
{
@ -322,7 +320,7 @@ namespace SharedLibraryCore.Objects
/// <param name="tempbanReason">reason for the temp ban</param>
/// <param name="banLength">how long the temp ban lasts</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()
{
@ -350,7 +348,7 @@ namespace SharedLibraryCore.Objects
/// </summary>
/// <param name="banReason">reason for 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, bool isEvade)
{
var e = new GameEvent()
{
@ -359,7 +357,8 @@ namespace SharedLibraryCore.Objects
Data = banReason,
Origin = sender,
Target = this,
Owner = sender.CurrentServer
Owner = sender.CurrentServer,
Extra = isEvade
};
// enforce level restrictions
@ -378,7 +377,7 @@ namespace SharedLibraryCore.Objects
/// <param name="unbanReason">reason for the unban</param>
/// <param name="sender">client performing the unban</param>
/// <returns></returns>
public GameEvent Unban(String unbanReason, Player sender)
public GameEvent Unban(String unbanReason, EFClient sender)
{
var e = new GameEvent()
{
@ -400,9 +399,189 @@ namespace SharedLibraryCore.Objects
return e;
}
/// <summary>
/// Handles any client related logic on connection
/// </summary>
public void OnConnect()
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
LastConnection = DateTime.UtcNow;
Connections += 1;
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
// todo: is this broken on T6?
if (CurrentServer.MaxClients - (CurrentServer.GetClientsAsList().Count(_client => !_client.IsPrivileged())) < CurrentServer.ServerConfig.ReservedSlotNumber &&
!this.IsPrivileged() && CurrentServer.GameName != Server.Game.T6M /* HACK: temporary */)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} their spot is reserved");
Kick(loc["SERVER_KICK_SLOT_IS_RESERVED"], Utilities.IW4MAdminClient(CurrentServer));
return;
}
}
public async Task OnDisconnect()
{
State = ClientState.Disconnecting;
TotalConnectionTime += ConnectionLength;
LastConnection = DateTime.UtcNow;
await CurrentServer.Manager.GetClientService().Update(this);
}
public async Task<bool> OnJoin(int? ipAddress)
{
CurrentServer.Logger.WriteDebug($"Start join for {this}::{ipAddress}::{Level.ToString()}");
IPAddress = ipAddress;
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var autoKickClient = Utilities.IW4MAdminClient(CurrentServer);
if (ipAddress != null)
{
await CurrentServer.Manager.GetClientService().UpdateAlias(this);
}
OnConnect();
CurrentServer.Logger.WriteDebug($"OnConnect finished for {this}");
#region CLIENT_BAN
// kick them as their level is banned
if (Level == Permission.Banned)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because they are banned");
var ban = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Expires == null && _penalty.Active);
if (ban == null)
{
// this is from the old system before bans were applied to all accounts
ban = (await CurrentServer.Manager
.GetPenaltyService()
.GetActivePenaltiesAsync(AliasLinkId))
.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Ban);
CurrentServer.Logger.WriteError($"Client {this} is banned, but no penalty exists for their ban");
// hack: re apply the automated offense to the reban
if (ban.AutomatedOffense != null)
{
autoKickClient.AdministeredPenalties?.Add(new EFPenalty()
{
AutomatedOffense = ban.AutomatedOffense
});
}
// this is a reban of the new GUID and IP
Ban($"{ban.Offense}", autoKickClient, false);
return false;
}
Kick($"{loc["SERVER_BAN_PREV"]} {ban?.Offense}", autoKickClient);
return false;
}
var tempBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan && _penalty.Expires > DateTime.UtcNow && _penalty.Active);
// they have an active tempban tied to their GUID
if (tempBan != null)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because they are temporarily banned");
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
return false;
}
#endregion
// we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID)
var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress);
var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban);
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 evade...");
// reban the "evading" guid
if (Level != Permission.Banned)
{
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
});
}
// this is a reban of the new GUID and IP
Ban($"{currentBan.Offense}", autoKickClient, true);
}
else
{
CurrentServer.Logger.WriteError($"Banned client {this} is banned but, no ban penalty was found (2)");
}
return false;
}
else
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = this,
Target = this,
Owner = CurrentServer
};
CurrentServer.Manager.GetEventHandler().AddEvent(e);
return true;
}
}
[NotMapped]
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)
{
if (_additionalProperties.ContainsKey(name))
@ -431,22 +610,10 @@ namespace SharedLibraryCore.Objects
public int Score { get; set; }
[NotMapped]
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]
public ClientState State { get; set; }
[NotMapped]
public Queue<GameEvent> DelayedEvents { get; set; }
[NotMapped]
// this is kinda dirty, but I need localizable level names
public ClientPermission ClientPermission => new ClientPermission()
@ -458,9 +625,12 @@ namespace SharedLibraryCore.Objects
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 System.Collections.Generic;
using System.Text;
using static SharedLibraryCore.Objects.Player;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Objects
{

View File

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

View File

@ -5,7 +5,6 @@ using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -29,7 +28,8 @@ namespace SharedLibraryCore.RCon
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
public IPEndPoint Endpoint { 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)
{
@ -88,42 +88,50 @@ namespace SharedLibraryCore.RCon
byte[] response = null;
retrySend:
connectionState.SendEventArgs.UserToken = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
retrySend:
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
DontFragment = true,
Ttl = 42,
//DontFragment = true,
Ttl = 100,
ExclusiveAddressUse = true,
};
connectionState.OnSentData.Reset();
connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++;
})
{
connectionState.SendEventArgs.UserToken = socket;
connectionState.OnSentData.Reset();
connectionState.OnReceivedData.Reset();
connectionState.ConnectionAttempts++;
#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
try
{
response = await SendPayloadAsync(payload, waitForResponse);
connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0;
}
catch/* (Exception ex)*/
{
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
try
{
// Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend;
response = await SendPayloadAsync(payload, waitForResponse);
if (response.Length == 0 && waitForResponse)
{
throw new Exception();
}
connectionState.OnComplete.Release(1);
connectionState.ConnectionAttempts = 0;
}
connectionState.OnComplete.Release(1);
//Log.WriteDebug(ex.GetExceptionInfo());
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
catch
{
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"))
{

View File

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

View File

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

View File

@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using System.Linq.Expressions;
using SharedLibraryCore.Objects;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Services
{
@ -20,55 +17,30 @@ namespace SharedLibraryCore.Services
{
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()
{
Active = true,
// 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,
Level = Permission.User,
FirstConnection = DateTime.UtcNow,
Connections = 1,
LastConnection = DateTime.UtcNow,
Masked = false,
NetworkId = entity.NetworkId,
AliasLink = aliasLink,
CurrentAlias = existingAlias,
AliasLink = new EFAliasLink()
{
Active = false
},
ReceivedPenalties = new List<EFPenalty>()
};
client.CurrentAlias = new Alias()
{
Name = entity.Name,
Link = client.AliasLink,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
// 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);
@ -78,6 +50,180 @@ namespace SharedLibraryCore.Services
}
}
public async Task UpdateAlias(EFClient entity)
{
// todo: move this out
#if DEBUG == false
if (entity.IsBot)
{
return;
}
#endif
using (var context = new DatabaseContext())
{
string name = entity.Name;
int? ip = entity.IPAddress;
// indicates if someone appears to have played before
bool hasExistingAlias = false;
// get all aliases by IP address and LinkId
var iqAliases = context.Aliases
.Include(a => a.Link)
//.Where(a => a.Link.Active)
.Where(a => (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 && a.IPAddress == ip);
bool exactAliasMatch = existingAlias != null;
// 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 matches are found, create new link
aliasLink = aliasLink ?? new EFAliasLink();
hasExistingAlias = aliases.Count > 0;
// the existing alias matches ip and name, so we can just ignore the temporary one
if (exactAliasMatch)
{
entity.CurrentServer.Logger.WriteDebug($"{entity} has exact alias match");
// they're using the same alias as before, so we need to make sure the current aliases is set to it
if (entity.CurrentAliasId != existingAlias.AliasId)
{
context.Update(entity);
if (existingAlias.AliasId > 0)
{
entity.CurrentAliasId = existingAlias.AliasId;
}
else
{
entity.CurrentServer.Logger.WriteDebug($"Updating alias for {entity} failed");
}
}
if (!entity.CurrentAlias.Active)
{
existingAlias.Active = true;
}
entity.CurrentAlias = existingAlias;
await context.SaveChangesAsync();
}
// theres no exact match, but they've played before with the GUID or IP
else if (hasExistingAlias)
{
// the current link is temporary so we need to update
if (!entity.AliasLink.Active)
{
entity.CurrentServer.Logger.WriteDebug($"{entity} has temporary alias so we are deleting");
foreach (var alias in aliases.Where(_alias => !_alias.Active))
{
context.Update(alias);
if (alias.IPAddress == null && alias.Name == name)
{
context.Entry(alias).State = EntityState.Deleted;
}
else
{
alias.Active = true;
alias.LinkId = aliasLink.AliasLinkId;
}
}
// we want to delete the temporary alias link
context.Entry(entity.AliasLink).State = EntityState.Deleted;
}
context.Update(entity);
entity.AliasLink = aliasLink;
entity.AliasLinkId = aliasLink.AliasLinkId;
entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}");
var newAlias = new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
LinkId = aliasLink.AliasLinkId,
Name = name,
Active = true,
};
context.Aliases.Add(newAlias);
entity.CurrentAlias = newAlias;
await context.SaveChangesAsync();
}
// no record of them playing
else
{
entity.CurrentServer.Logger.WriteDebug($"{entity} has not be seen before");
if (name != entity.CurrentAlias.Name)
{
var newAlias = new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
LinkId = entity.AliasLink.AliasLinkId,
Name = name,
Active = true
};
context.Update(entity);
context.Aliases.Add(newAlias);
entity.CurrentAlias = newAlias;
}
else
{
context.Update(entity.CurrentAlias);
entity.CurrentAlias.Active = true;
entity.CurrentAlias.IPAddress = ip;
entity.CurrentAlias.Link = entity.AliasLink;
}
context.Update(entity.AliasLink);
entity.AliasLink.Active = true;
await context.SaveChangesAsync();
}
var linkIds = aliases.Select(a => a.LinkId);
if (linkIds.Count() > 0 &&
aliases.Count(_alias => _alias.Name == name && _alias.IPAddress == ip) > 0)
{
var highestLevel = await context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId))
.MaxAsync(c => c.Level);
if (entity.Level != highestLevel)
{
// todo: log level changes here
context.Update(entity);
entity.Level = highestLevel;
await context.SaveChangesAsync();
}
}
}
}
public async Task<EFClient> Delete(EFClient entity)
{
using (var context = new DatabaseContext())
@ -131,13 +277,17 @@ namespace SharedLibraryCore.Services
var foundClient = await iqClient.FirstOrDefaultAsync();
if (foundClient == null)
{
return null;
}
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// 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
foreach (var linked in foundClient.LinkedAccounts)
{
foundClient.Client.LinkedAccounts.Add(linked.ClientId, linked.NetworkId);
}
return foundClient.Client;
}
@ -148,6 +298,7 @@ namespace SharedLibraryCore.Services
context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Include(c => c.ReceivedPenalties)
.FirstOrDefault(c => c.NetworkId == networkId)
);
@ -167,7 +318,7 @@ namespace SharedLibraryCore.Services
var client = context.Clients
.Include(c => c.AliasLink)
.Include(c => c.CurrentAlias)
.Single(e => e.ClientId == entity.ClientId);
.First(e => e.ClientId == entity.ClientId);
// if their level has been changed
if (entity.Level != client.Level)
@ -190,7 +341,7 @@ namespace SharedLibraryCore.Services
{
client.CurrentAlias = new EFAlias()
{
Active = true,
Active = entity.CurrentAlias.IPAddress.HasValue ? true : false,
DateAdded = DateTime.UtcNow,
IPAddress = entity.CurrentAlias.IPAddress,
Name = entity.CurrentAlias.Name,
@ -201,6 +352,8 @@ namespace SharedLibraryCore.Services
else
{
client.CurrentAliasId = entity.CurrentAliasId;
client.IPAddress = entity.IPAddress;
client.Name = entity.Name;
}
// set remaining non-navigation properties that may have been updated
@ -218,7 +371,10 @@ namespace SharedLibraryCore.Services
// this is set so future updates don't trigger a new alias add
if (entity.CurrentAlias.AliasId == 0)
{
entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId;
}
return client;
}
}
@ -227,24 +383,28 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFClient>> GetOwners()
{
using (var context = new DatabaseContext())
{
return await context.Clients
.Where(c => c.Level == Player.Permission.Owner)
.Where(c => c.Level == Permission.Owner)
.ToListAsync();
}
}
public async Task<IList<ClientInfo>> GetPrivilegedClients()
public async Task<List<EFClient>> GetPrivilegedClients()
{
using (var context = new DatabaseContext(disableTracking: true))
{
var iqClients = from client in context.Clients
where client.Level >= Player.Permission.Trusted
where client.Level >= Permission.Trusted
where client.Active
select new ClientInfo()
select new EFClient()
{
AliasLinkId = client.AliasLinkId,
CurrentAlias = client.CurrentAlias,
ClientId = client.ClientId,
Name = client.CurrentAlias.Name,
LinkId = client.AliasLinkId,
Level = client.Level
Level = client.Level,
Password = client.Password,
PasswordSalt = client.PasswordSalt
};
#if DEBUG == true
@ -255,27 +415,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>();
}
name = name.ToLower();
identifier = identifier.ToLower();
using (var context = new DatabaseContext(disableTracking: true))
{
int asIP = name.ConvertToIP();
// hack: so IW4MAdmin and bots don't show up in search results
asIP = asIP == 0 ? int.MaxValue : asIP;
long networkId = identifier.ConvertLong();
int? ipAddress = identifier.ConvertToIP();
var iqLinkIds = (from alias in context.Aliases
where asIP != int.MaxValue ? alias.IPAddress == asIP : alias.Name.ToLower().Contains(name)
select alias.LinkId);
where (alias.IPAddress != null && alias.IPAddress == ipAddress) ||
alias.Name.ToLower().Contains(identifier)
select alias.LinkId).Distinct();
var linkIds = iqLinkIds.ToList();
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.AliasLink.Children);
@ -290,20 +453,16 @@ namespace SharedLibraryCore.Services
public async Task<int> GetTotalClientsAsync()
{
using (var context = new DatabaseContext(true))
{
return await context.Clients
.CountAsync();
}
}
public Task<EFClient> CreateProxy()
{
throw new NotImplementedException();
}
public async Task<int> GetTotalPlayTime()
{
using (var context = new DatabaseContext(true))
return await context.Clients.SumAsync(c => c.TotalConnectionTime);
}
#endregion
}
}

View File

@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using Microsoft.EntityFrameworkCore;
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
{
@ -17,41 +18,89 @@ namespace SharedLibraryCore.Services
{
using (var context = new DatabaseContext())
{
// create the actual EFPenalty
EFPenalty addedEntity = new EFPenalty()
{
OffenderId = newEntity.Offender.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = newEntity.When,
AutomatedOffense = newEntity.AutomatedOffense
};
if (addedEntity.Expires == DateTime.MaxValue)
addedEntity.Expires = DateTime.Parse(System.Data.SqlTypes.SqlDateTime.MaxValue.ToString());
context.ChangeTracker.AutoDetectChangesEnabled = true;
context.ChangeTracker.LazyLoadingEnabled = true;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
// make bans propogate to all aliases
if (addedEntity.Type == Objects.Penalty.PenaltyType.Ban)
if (newEntity.Type == Penalty.PenaltyType.Ban)
{
await context.Clients
.Where(c => c.AliasLinkId == addedEntity.LinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.Banned);
.Include(c => c.ReceivedPenalties)
.Where(c => c.AliasLinkId == newEntity.Link.AliasLinkId)
.ForEachAsync(c =>
{
if (c.Level != Permission.Banned)
{
c.Level = Permission.Banned;
c.ReceivedPenalties.Add(new EFPenalty()
{
Active = true,
OffenderId = c.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = c.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
});
}
});
}
// make flags propogate to all aliases
else if (addedEntity.Type == Objects.Penalty.PenaltyType.Flag)
else if (newEntity.Type == Penalty.PenaltyType.Flag)
{
await context.Clients
.Where(c => c.AliasLinkId == addedEntity.LinkId)
.ForEachAsync(c => c.Level = Objects.Player.Permission.Flagged);
.Include(c => c.ReceivedPenalties)
.Where(c => c.AliasLinkId == newEntity.Link.AliasLinkId)
.ForEachAsync(c =>
{
if (c.Level != Permission.Flagged)
{
c.Level = Permission.Flagged;
c.ReceivedPenalties.Add(new EFPenalty()
{
Active = true,
OffenderId = c.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = c.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
});
}
});
}
// we just want to add it to the database
else
{
var penalty = new EFPenalty()
{
Active = true,
OffenderId = newEntity.Offender.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
};
newEntity.Offender.ReceivedPenalties.Add(penalty);
context.Penalties.Add(penalty);
}
context.Penalties.Add(addedEntity);
await context.SaveChangesAsync();
return addedEntity;
return newEntity;
}
}
@ -68,7 +117,6 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFPenalty>> Find(Func<EFPenalty, bool> expression)
{
throw await Task.FromResult(new Exception());
}
public Task<EFPenalty> Get(int entityID)
@ -89,6 +137,7 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFPenalty>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any)
{
using (var context = new DatabaseContext(true))
{
return await context.Penalties
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
@ -97,24 +146,27 @@ namespace SharedLibraryCore.Services
.OrderByDescending(p => p.When)
.Skip(offset)
.Take(count)
.ToListAsync();
.ToListAsync();
}
}
public async Task<IList<EFPenalty>> GetClientPenaltiesAsync(int clientId)
{
using (var context = new DatabaseContext(true))
{
return await context.Penalties
.Where(p => p.OffenderId == clientId)
.Where(p => p.Active)
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
.ToListAsync();
}
}
/// <summary>
/// Get a read-only copy of client penalties
/// </summary>
/// <param name="clientI"></param>
/// <param name="clientId"></param>
/// <param name="victim">Retreive penalties for clients receiving penalties, other wise given</param>
/// <returns></returns>
public async Task<List<ProfileMeta>> ReadGetClientPenaltiesAsync(int clientId, bool victim = true)
@ -148,7 +200,7 @@ namespace SharedLibraryCore.Services
PunisherId = penalty.PunisherId,
Offense = penalty.Offense,
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
},
When = penalty.When,
@ -160,12 +212,15 @@ namespace SharedLibraryCore.Services
{
// todo: why does this have to be done?
if (((PenaltyInfo)p.Value).Type.Length < 2)
{
((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString();
}
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();
}
});
return list;
}
@ -207,7 +262,9 @@ namespace SharedLibraryCore.Services
{
// todo: why does this have to be done?
if (((PenaltyInfo)p.Value).Type.Length < 2)
{
((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString();
}
});
return list;
@ -215,26 +272,41 @@ 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;
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))
{
var iqPenalties = context.Penalties
.Where(p => p.LinkId == linkId ||
p.Link.Children.Any(a => a.IPAddress == ip))
.Where(p => p.Active)
.Where(p => p.Expires > now);
var iqLinkPenalties = context.Penalties
.Where(p => p.LinkId == linkId)
.Where(filter);
var iqIPPenalties = context.Aliases
.Where(a => a.IPAddress != null & a.IPAddress == ip)
.SelectMany(a => a.Link.ReceivedPenalties)
.Where(filter);
#if DEBUG == true
var penaltiesSql = iqPenalties.ToSql();
var penaltiesSql = iqLinkPenalties.ToSql();
var ipPenaltiesSql = iqIPPenalties.ToSql();
#endif
var activePenalties = await iqPenalties.ToListAsync();
var activePenalties = (await iqLinkPenalties.ToListAsync())
.Union(await iqIPPenalties.ToListAsync())
.Distinct();
// 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 +318,7 @@ namespace SharedLibraryCore.Services
var penalties = await context.Penalties
.Include(p => p.Link.Children)
.Where(p => p.LinkId == aliasLinkId)
.Where(p => p.Expires > now)
.Where(p => p.Expires > now || p.Expires == null)
.ToListAsync();
penalties.ForEach(async p =>
@ -259,7 +331,7 @@ namespace SharedLibraryCore.Services
{
await internalContext.Clients
.Where(c => c.AliasLinkId == p.LinkId)
.ForEachAsync(c => c.Level = Player.Permission.User);
.ForEachAsync(c => c.Level = EFClient.Permission.User);
await internalContext.SaveChangesAsync();
}
}

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