From 9d6cbee69cc3746713ebed05f5f351ce3251e73f Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 27 Nov 2018 18:31:48 -0600 Subject: [PATCH] update stats change server id fIx change log server complaining when empty read --- Application/API/Master/ApiServer.cs | 2 +- Application/API/Master/Heartbeat.cs | 2 +- Application/IO/GameLogEventDetection.cs | 2 +- Application/IW4MServer.cs | 46 +- Application/Manager.cs | 14 +- GameLogServer/GameLogServer.pyproj | 2 +- GameLogServer/GameLogServer/log_reader.py | 17 +- GameLogServer/GameLogServer/log_resource.py | 6 +- GameLogServer/requirements.txt | 20 +- GameLogServer/runserver.py | 2 +- Master/master/schema/serverschema.py | 2 +- Plugins/IW4ScriptCommands/Commands/Balance.cs | 6 +- Plugins/IW4ScriptCommands/GscApiController.cs | 2 +- Plugins/IW4ScriptCommands/Plugin.cs | 2 +- Plugins/Stats/Commands/MostPlayed.cs | 4 +- Plugins/Stats/Commands/ResetStats.cs | 21 +- Plugins/Stats/Commands/TopStats.cs | 3 +- Plugins/Stats/Commands/ViewStats.cs | 2 +- Plugins/Stats/Helpers/StatManager.cs | 118 ++- Plugins/Stats/Models/EFClientKill.cs | 2 +- Plugins/Stats/Models/EFClientMessage.cs | 2 +- Plugins/Stats/Models/EFClientStatistics.cs | 2 +- Plugins/Stats/Models/EFHitLocationCount.cs | 2 +- Plugins/Stats/Models/EFRating.cs | 2 +- Plugins/Stats/Models/EFServer.cs | 3 +- Plugins/Stats/Models/EFServerStatistics.cs | 2 +- Plugins/Stats/Models/ModelConfiguration.cs | 5 +- Plugins/Stats/Plugin.cs | 45 +- SharedLibraryCore/Dtos/EntityInfo.cs | 2 +- SharedLibraryCore/Dtos/ServerInfo.cs | 2 +- SharedLibraryCore/Events/EventAPI.cs | 2 +- SharedLibraryCore/Interfaces/IManager.cs | 2 +- .../20181125193243_MakeClientIPNullable.cs | 2 +- ...ntToEFServerUpdateServerIdType.Designer.cs | 692 ++++++++++++++++++ ...AddEndpointToEFServerUpdateServerIdType.cs | 22 + .../DatabaseContextModelSnapshot.cs | 16 +- SharedLibraryCore/Objects/EFClient.cs | 2 +- SharedLibraryCore/Server.cs | 4 +- SharedLibraryCore/SharedLibraryCore.csproj | 6 + WebfrontCore/Controllers/API/APIController.cs | 2 +- WebfrontCore/Controllers/ActionController.cs | 8 +- WebfrontCore/Controllers/ConsoleController.cs | 9 +- WebfrontCore/Controllers/ServerController.cs | 12 +- .../ViewComponents/ServerListViewComponent.cs | 2 +- 44 files changed, 966 insertions(+), 157 deletions(-) create mode 100644 SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.Designer.cs create mode 100644 SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.cs diff --git a/Application/API/Master/ApiServer.cs b/Application/API/Master/ApiServer.cs index bd42066ac..5a4e5a520 100644 --- a/Application/API/Master/ApiServer.cs +++ b/Application/API/Master/ApiServer.cs @@ -8,7 +8,7 @@ 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")] diff --git a/Application/API/Master/Heartbeat.cs b/Application/API/Master/Heartbeat.cs index ae3076970..31dc0351a 100644 --- a/Application/API/Master/Heartbeat.cs +++ b/Application/API/Master/Heartbeat.cs @@ -43,7 +43,7 @@ namespace IW4MAdmin.Application.API.Master Hostname = s.Hostname, Map = s.CurrentMap.Name, MaxClientNum = s.MaxClients, - Id = s.GetHashCode(), + Id = s.EndPoint, Port = (short)s.GetPort(), IPAddress = s.IP }).ToList() diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 7b2e914f0..23376592e 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -50,7 +50,7 @@ namespace IW4MAdmin.Application.IO 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}"); } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index cee278d2a..592df9af4 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -30,32 +30,32 @@ namespace IW4MAdmin { } - public override int GetHashCode() - { - // hack: my laziness - if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28965") - { - return 886229536; - } + //public override int EndPoint + //{ + // // hack: my laziness + // if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28965") + // { + // return 886229536; + // } - if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28960") - { - return 1645744423; - } + // if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28960") + // { + // return 1645744423; + // } - if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28970") - { - return 1645809959; - } + // if ($"{IP}:{Port.ToString()}" == "66.150.121.184:28970") + // { + // return 1645809959; + // } - if (Id == 0) - { - Id = HashCode.Combine(IP, Port); - Id = Id < 0 ? Math.Abs(Id) : Id; - } + // if (Id == 0) + // { + // Id = HashCode.Combine(IP, Port); + // Id = Id < 0 ? Math.Abs(Id) : Id; + // } - return Id; - } + // return Id; + //} override public async Task OnClientConnected(EFClient clientFromLog) { @@ -763,7 +763,7 @@ namespace IW4MAdmin this.MaxClients = maxplayers; this.FSGame = game; this.Gametype = gametype; - this.IP = ip.Value; + this.IP = ip.Value == "localhost" ? ServerConfig.IPAddress : ip.Value; if (logsync.Value == 0 || logfile.Value == string.Empty) { diff --git a/Application/Manager.cs b/Application/Manager.cs index 9386e3e2b..8b252583e 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -46,7 +46,7 @@ namespace IW4MAdmin.Application ManualResetEventSlim OnQuit; readonly IPageList PageList; readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1); - readonly Dictionary Loggers = new Dictionary(); + readonly Dictionary Loggers = new Dictionary(); private ApplicationManager() { @@ -143,7 +143,7 @@ namespace IW4MAdmin.Application public async Task UpdateServerStates() { // store the server hash code and task for it - var runningUpdateTasks = new Dictionary(); + var runningUpdateTasks = new Dictionary(); while (Running) { @@ -163,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 { @@ -467,7 +467,7 @@ namespace IW4MAdmin.Application Running = false; } - public ILogger GetLogger(int serverId) + public ILogger GetLogger(long serverId) { if (Loggers.ContainsKey(serverId)) { diff --git a/GameLogServer/GameLogServer.pyproj b/GameLogServer/GameLogServer.pyproj index d07c347c9..31350450b 100644 --- a/GameLogServer/GameLogServer.pyproj +++ b/GameLogServer/GameLogServer.pyproj @@ -48,8 +48,8 @@ - + diff --git a/GameLogServer/GameLogServer/log_reader.py b/GameLogServer/GameLogServer/log_reader.py index 0d9aee595..171dc3229 100644 --- a/GameLogServer/GameLogServer/log_reader.py +++ b/GameLogServer/GameLogServer/log_reader.py @@ -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 diff --git a/GameLogServer/GameLogServer/log_resource.py b/GameLogServer/GameLogServer/log_resource.py index e7b2fc7de..ce3573233 100644 --- a/GameLogServer/GameLogServer/log_resource.py +++ b/GameLogServer/GameLogServer/log_resource.py @@ -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 } diff --git a/GameLogServer/requirements.txt b/GameLogServer/requirements.txt index 4133f6081..424e53036 100644 --- a/GameLogServer/requirements.txt +++ b/GameLogServer/requirements.txt @@ -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 diff --git a/GameLogServer/runserver.py b/GameLogServer/runserver.py index 4b283439b..19d214657 100644 --- a/GameLogServer/runserver.py +++ b/GameLogServer/runserver.py @@ -12,4 +12,4 @@ if __name__ == '__main__': except ValueError: PORT = 5555 init() - app.run(HOST, PORT, debug=True) + app.run(HOST, PORT, debug=False) diff --git a/Master/master/schema/serverschema.py b/Master/master/schema/serverschema.py index dcfce8d48..24d0dff2e 100644 --- a/Master/master/schema/serverschema.py +++ b/Master/master/schema/serverschema.py @@ -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 diff --git a/Plugins/IW4ScriptCommands/Commands/Balance.cs b/Plugins/IW4ScriptCommands/Commands/Balance.cs index ffae516c0..2e2d2d132 100644 --- a/Plugins/IW4ScriptCommands/Commands/Balance.cs +++ b/Plugins/IW4ScriptCommands/Commands/Balance.cs @@ -26,7 +26,7 @@ namespace IW4ScriptCommands.Commands { 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.GetHashCode()) + Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(server.Clients.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, server.EndPoint) }) .ToList(); @@ -49,8 +49,8 @@ namespace IW4ScriptCommands.Commands var activeClients = _c.Select(c => new TeamAssignment() { Num = c.ClientNumber, - Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.GetHashCode()), - CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.GetHashCode()).Team + 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) diff --git a/Plugins/IW4ScriptCommands/GscApiController.cs b/Plugins/IW4ScriptCommands/GscApiController.cs index 0b02deadc..1ef0aaf35 100644 --- a/Plugins/IW4ScriptCommands/GscApiController.cs +++ b/Plugins/IW4ScriptCommands/GscApiController.cs @@ -42,7 +42,7 @@ namespace WebfrontCore.Controllers.API var client = Manager.GetActiveClients() .FirstOrDefault(c => c.NetworkId == networkId.ConvertLong()); - var server = Manager.GetServers().First(c => c.GetHashCode() == serverId); + var server = Manager.GetServers().First(c => c.EndPoint == serverId); teams = teams ?? string.Empty; diff --git a/Plugins/IW4ScriptCommands/Plugin.cs b/Plugins/IW4ScriptCommands/Plugin.cs index 82ee95a2f..f1964fba5 100644 --- a/Plugins/IW4ScriptCommands/Plugin.cs +++ b/Plugins/IW4ScriptCommands/Plugin.cs @@ -19,7 +19,7 @@ namespace IW4ScriptCommands { if (E.Type == GameEvent.EventType.Start) { - return S.SetDvarAsync("sv_iw4madmin_serverid", S.GetHashCode()); + return S.SetDvarAsync("sv_iw4madmin_serverid", S.EndPoint); } if (E.Type == GameEvent.EventType.Warn) diff --git a/Plugins/Stats/Commands/MostPlayed.cs b/Plugins/Stats/Commands/MostPlayed.cs index b8dcacd1c..f71e25a7a 100644 --- a/Plugins/Stats/Commands/MostPlayed.cs +++ b/Plugins/Stats/Commands/MostPlayed.cs @@ -9,6 +9,7 @@ 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 { @@ -16,7 +17,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands { public static async Task> GetMostPlayed(Server s) { - int serverId = s.GetHashCode(); + long serverId = await StatManager.GetIdForServer(s); + List mostPlayed = new List() { $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--" diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs index d56869807..700713cf1 100644 --- a/Plugins/Stats/Commands/ResetStats.cs +++ b/Plugins/Stats/Commands/ResetStats.cs @@ -1,14 +1,10 @@ -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 { @@ -21,13 +17,14 @@ namespace IW4MAdmin.Plugins.Stats.Commands 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() - .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; @@ -39,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(); diff --git a/Plugins/Stats/Commands/TopStats.cs b/Plugins/Stats/Commands/TopStats.cs index 59ea63ed7..af405186c 100644 --- a/Plugins/Stats/Commands/TopStats.cs +++ b/Plugins/Stats/Commands/TopStats.cs @@ -10,6 +10,7 @@ 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 { @@ -17,7 +18,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands { public static async Task> GetTopStats(Server s) { - int serverId = s.GetHashCode(); + long serverId = await StatManager.GetIdForServer(s); List topStatsText = new List() { $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--" diff --git a/Plugins/Stats/Commands/ViewStats.cs b/Plugins/Stats/Commands/ViewStats.cs index 7a03c83a5..54091a7b8 100644 --- a/Plugins/Stats/Commands/ViewStats.cs +++ b/Plugins/Stats/Commands/ViewStats.cs @@ -43,7 +43,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands } } - int serverId = E.Owner.GetHashCode(); + long serverId = await StatManager.GetIdForServer(E.Owner); using (var ctx = new DatabaseContext(disableTracking: true)) { diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 9d0713a9b..9ce1c9e46 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -21,7 +21,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { public class StatManager { - private ConcurrentDictionary Servers; + private ConcurrentDictionary Servers; private ILogger Log; private readonly IManager Manager; @@ -30,19 +30,19 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public StatManager(IManager mgr) { - Servers = new ConcurrentDictionary(); + Servers = new ConcurrentDictionary(); Log = mgr.GetLogger(0); Manager = mgr; OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingSensitive = new SemaphoreSlim(1, 1); } - public EFClientStatistics GetClientStats(int clientId, int serverId) + public EFClientStatistics GetClientStats(int clientId, long serverId) { return Servers[serverId].PlayerStats[clientId]; } - public static Expression> GetRankingFunc(int? serverId = null) + public static Expression> GetRankingFunc(long? serverId = null) { var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); return (r) => r.ServerId == serverId && @@ -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(); // 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()); } } @@ -241,7 +257,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers try { - int serverId = pl.CurrentServer.GetHashCode(); + long serverId = await GetIdForServer(pl.CurrentServer); if (!Servers.ContainsKey(serverId)) { @@ -298,7 +314,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { Log.WriteWarning("Adding new client to stats failed"); } - } else @@ -374,19 +389,19 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// 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; } @@ -395,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); @@ -407,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); @@ -431,7 +446,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// Process stats for kill event /// /// - public async Task AddScriptHit(bool isDamage, DateTime time, EFClient attacker, EFClient 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) { @@ -581,7 +596,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } } - void ApplyPenalty(Cheat.DetectionPenaltyResult penalty, Cheat.Detection clientDetection, EFClient attacker, DatabaseContext ctx) + void ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx) { switch (penalty.ClientPenalty) { @@ -693,7 +708,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers 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)) @@ -723,6 +738,7 @@ 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) @@ -997,6 +1013,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 => @@ -1020,6 +1037,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)); @@ -1122,9 +1140,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)) @@ -1134,7 +1151,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() { @@ -1151,7 +1168,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return serverStats; } - public void ResetKillstreaks(int serverId) + public void ResetKillstreaks(long serverId) { var serverStats = Servers[serverId]; @@ -1161,7 +1178,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } } - public void ResetStats(int clientId, int serverId) + public void ResetStats(int clientId, long serverId) { var stats = Servers[serverId].PlayerStats[clientId]; stats.Kills = 0; @@ -1172,7 +1189,7 @@ 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) @@ -1196,19 +1213,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(); serverSet.Update(Servers[serverId].Server); + + var serverStatsSet = ctx.Set(); + 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 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().FirstOrDefaultAsync(_server => _server.ServerId == server.EndPoint || + _server.EndPoint == server.ToString() || + _server.ServerId == id))?.ServerId; + } + + if (!serverId.HasValue) + { + return id; + } + + return serverId.Value; + } + } } } diff --git a/Plugins/Stats/Models/EFClientKill.cs b/Plugins/Stats/Models/EFClientKill.cs index 51c0ec76e..a7c523547 100644 --- a/Plugins/Stats/Models/EFClientKill.cs +++ b/Plugins/Stats/Models/EFClientKill.cs @@ -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; } diff --git a/Plugins/Stats/Models/EFClientMessage.cs b/Plugins/Stats/Models/EFClientMessage.cs index ee078f241..ed3baa308 100644 --- a/Plugins/Stats/Models/EFClientMessage.cs +++ b/Plugins/Stats/Models/EFClientMessage.cs @@ -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; } diff --git a/Plugins/Stats/Models/EFClientStatistics.cs b/Plugins/Stats/Models/EFClientStatistics.cs index 0502a7160..46234768e 100644 --- a/Plugins/Stats/Models/EFClientStatistics.cs +++ b/Plugins/Stats/Models/EFClientStatistics.cs @@ -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] diff --git a/Plugins/Stats/Models/EFHitLocationCount.cs b/Plugins/Stats/Models/EFHitLocationCount.cs index e5fea2f78..c1c82f038 100644 --- a/Plugins/Stats/Models/EFHitLocationCount.cs +++ b/Plugins/Stats/Models/EFHitLocationCount.cs @@ -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; } } diff --git a/Plugins/Stats/Models/EFRating.cs b/Plugins/Stats/Models/EFRating.cs index b9a77a2fc..9aae5f4ed 100644 --- a/Plugins/Stats/Models/EFRating.cs +++ b/Plugins/Stats/Models/EFRating.cs @@ -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] diff --git a/Plugins/Stats/Models/EFServer.cs b/Plugins/Stats/Models/EFServer.cs index 5cf670f48..3aa5bc7fc 100644 --- a/Plugins/Stats/Models/EFServer.cs +++ b/Plugins/Stats/Models/EFServer.cs @@ -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; } } } diff --git a/Plugins/Stats/Models/EFServerStatistics.cs b/Plugins/Stats/Models/EFServerStatistics.cs index 10da850f8..c60f59085 100644 --- a/Plugins/Stats/Models/EFServerStatistics.cs +++ b/Plugins/Stats/Models/EFServerStatistics.cs @@ -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; } diff --git a/Plugins/Stats/Models/ModelConfiguration.cs b/Plugins/Stats/Models/ModelConfiguration.cs index c21803c4f..1234e166b 100644 --- a/Plugins/Stats/Models/ModelConfiguration.cs +++ b/Plugins/Stats/Models/ModelConfiguration.cs @@ -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 { diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 3a144f5b0..24affe605 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -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; @@ -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); + } } } } diff --git a/SharedLibraryCore/Dtos/EntityInfo.cs b/SharedLibraryCore/Dtos/EntityInfo.cs index e157a35bd..79b950010 100644 --- a/SharedLibraryCore/Dtos/EntityInfo.cs +++ b/SharedLibraryCore/Dtos/EntityInfo.cs @@ -9,7 +9,7 @@ namespace SharedLibraryCore.Dtos /// public class EntityInfo { - public int Id { get; set; } + public long Id { get; set; } public string Name { get; set; } } } diff --git a/SharedLibraryCore/Dtos/ServerInfo.cs b/SharedLibraryCore/Dtos/ServerInfo.cs index dd27a685a..103432984 100644 --- a/SharedLibraryCore/Dtos/ServerInfo.cs +++ b/SharedLibraryCore/Dtos/ServerInfo.cs @@ -17,7 +17,7 @@ namespace SharedLibraryCore.Dtos public List ChatHistory { get; set; } public List Players { get; set; } public Helpers.PlayerHistory[] PlayerHistory { get; set; } - public int ID { get; set; } + public long ID { get; set; } public bool Online { get; set; } } } diff --git a/SharedLibraryCore/Events/EventAPI.cs b/SharedLibraryCore/Events/EventAPI.cs index 6cb461a84..f834678c3 100644 --- a/SharedLibraryCore/Events/EventAPI.cs +++ b/SharedLibraryCore/Events/EventAPI.cs @@ -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() { diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index 678656985..68bfaa2d0 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -14,7 +14,7 @@ namespace SharedLibraryCore.Interfaces Task Init(); void Start(); void Stop(); - ILogger GetLogger(int serverId); + ILogger GetLogger(long serverId); IList GetServers(); IList GetCommands(); IList GetMessageTokens(); diff --git a/SharedLibraryCore/Migrations/20181125193243_MakeClientIPNullable.cs b/SharedLibraryCore/Migrations/20181125193243_MakeClientIPNullable.cs index 2b504e38b..b5aa58455 100644 --- a/SharedLibraryCore/Migrations/20181125193243_MakeClientIPNullable.cs +++ b/SharedLibraryCore/Migrations/20181125193243_MakeClientIPNullable.cs @@ -60,7 +60,7 @@ CREATE INDEX IX_EFAlias_LinkId ON EFAlias ( ); PRAGMA foreign_keys = 1; - "); + ", suppressTransaction:true); } else { diff --git a/SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.Designer.cs b/SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.Designer.cs new file mode 100644 index 000000000..f4b8aa2ec --- /dev/null +++ b/SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.Designer.cs @@ -0,0 +1,692 @@ +// +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("SnapshotId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("CurrentSessionLength"); + + b.Property("CurrentStrain"); + + b.Property("CurrentViewAngleId"); + + b.Property("Deaths"); + + b.Property("Distance"); + + b.Property("EloRating"); + + b.Property("HitDestinationId"); + + b.Property("HitLocation"); + + b.Property("HitOriginId"); + + b.Property("HitType"); + + b.Property("Hits"); + + b.Property("Kills"); + + b.Property("LastStrainAngleId"); + + b.Property("SessionAngleOffset"); + + b.Property("SessionSPM"); + + b.Property("SessionScore"); + + b.Property("StrainAngleBetween"); + + b.Property("TimeSinceLastEvent"); + + b.Property("WeaponId"); + + b.Property("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("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("Fraction"); + + b.Property("HitLoc"); + + b.Property("IsKill"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("VisibilityPercentage"); + + b.Property("Weapon"); + + b.Property("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("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("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("RatingHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.Property("VisionAverage"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("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("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ActivityAmount"); + + b.Property("Newest"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.Property("RatingHistoryId"); + + b.Property("ServerId"); + + b.Property("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("ServerId"); + + b.Property("Active"); + + b.Property("EndPoint"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("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("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Comment") + .HasMaxLength(128); + + b.Property("CurrentValue"); + + b.Property("OriginEntityId"); + + b.Property("PreviousValue"); + + b.Property("TargetEntityId"); + + b.Property("TimeChanged"); + + b.Property("TypeOfChange"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("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("MetaId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Created"); + + b.Property("Extra"); + + b.Property("Key") + .IsRequired(); + + b.Property("Updated"); + + b.Property("Value") + .IsRequired(); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("EFACSnapshotSnapshotId"); + + b.Property("X"); + + b.Property("Y"); + + b.Property("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 + } + } +} diff --git a/SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.cs b/SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.cs new file mode 100644 index 000000000..e149e5d7e --- /dev/null +++ b/SharedLibraryCore/Migrations/20181127144417_AddEndpointToEFServerUpdateServerIdType.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddEndpointToEFServerUpdateServerIdType : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EndPoint", + table: "EFServers", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EndPoint", + table: "EFServers"); + } + } +} diff --git a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs index 3faa2c828..b2d09f0ab 100644 --- a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs +++ b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs @@ -105,7 +105,7 @@ namespace SharedLibraryCore.Migrations b.Property("Map"); - b.Property("ServerId"); + b.Property("ServerId"); b.Property("VictimId"); @@ -145,7 +145,7 @@ namespace SharedLibraryCore.Migrations b.Property("Message"); - b.Property("ServerId"); + b.Property("ServerId"); b.Property("TimeSent"); @@ -180,7 +180,7 @@ namespace SharedLibraryCore.Migrations { b.Property("ClientId"); - b.Property("ServerId"); + b.Property("ServerId"); b.Property("Active"); @@ -227,7 +227,7 @@ namespace SharedLibraryCore.Migrations b.Property("MaxAngleDistance"); - b.Property("ServerId") + b.Property("ServerId") .HasColumnName("EFClientStatistics_ServerId"); b.HasKey("HitLocationCountId"); @@ -256,7 +256,7 @@ namespace SharedLibraryCore.Migrations b.Property("RatingHistoryId"); - b.Property("ServerId"); + b.Property("ServerId"); b.Property("When"); @@ -277,10 +277,12 @@ namespace SharedLibraryCore.Migrations modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => { - b.Property("ServerId"); + b.Property("ServerId"); b.Property("Active"); + b.Property("EndPoint"); + b.Property("Port"); b.HasKey("ServerId"); @@ -295,7 +297,7 @@ namespace SharedLibraryCore.Migrations b.Property("Active"); - b.Property("ServerId"); + b.Property("ServerId"); b.Property("TotalKills"); diff --git a/SharedLibraryCore/Objects/EFClient.cs b/SharedLibraryCore/Objects/EFClient.cs index 28a21cd69..552f1ce92 100644 --- a/SharedLibraryCore/Objects/EFClient.cs +++ b/SharedLibraryCore/Objects/EFClient.cs @@ -554,7 +554,7 @@ namespace SharedLibraryCore.Database.Models public int Score { get; set; } [NotMapped] public bool IsBot { get; set; } - + [NotMapped] public ClientState State { get; set; } [NotMapped] diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 357449514..137c86959 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -33,7 +33,7 @@ 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); @@ -49,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() { diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index 8613ceda3..aa3d984bf 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -13,6 +13,12 @@ Debug;Release;Prerelease + + + + + + diff --git a/WebfrontCore/Controllers/API/APIController.cs b/WebfrontCore/Controllers/API/APIController.cs index 536491bf9..845243a26 100644 --- a/WebfrontCore/Controllers/API/APIController.cs +++ b/WebfrontCore/Controllers/API/APIController.cs @@ -25,7 +25,7 @@ namespace WebfrontCore.Controllers.API var serverInfo = Manager.GetServers() .Select(server => new { - Id = server.GetHashCode(), + Id = server.EndPoint, Name = server.Hostname, MaxPlayers = server.MaxClients, CurrentPlayers = server.GetClientsAsList().Count, diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index 57b805d38..4a02388cb 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -79,7 +79,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { - serverId = server.GetHashCode(), + serverId = server.EndPoint, command })); } @@ -110,7 +110,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { - serverId = server.GetHashCode(), + serverId = server.EndPoint, command = $"!unban @{targetId} {Reason}" })); } @@ -162,7 +162,7 @@ namespace WebfrontCore.Controllers Values = Enum.GetValues(typeof(Permission)).OfType() .Where(p => p <= Client.Level) .Where(p => p != Permission.Banned) - .Where(p => p != Permission.Flagged) + .Where(p => p != Permission.Flagged) .ToDictionary(p => p.ToString(), p=> p.ToLocalizedLevelName()) }, }, @@ -178,7 +178,7 @@ namespace WebfrontCore.Controllers return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new { - serverId = server.GetHashCode(), + serverId = server.EndPoint, command = $"!setlevel @{targetId} {level}" })); } diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index c8e54f252..45e53d766 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -2,8 +2,6 @@ using SharedLibraryCore; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; -using SharedLibraryCore.Objects; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -17,7 +15,7 @@ namespace WebfrontCore.Controllers var activeServers = Manager.GetServers().Select(s => new ServerInfo() { Name = s.Hostname, - ID = s.GetHashCode(), + ID = s.EndPoint, }); ViewBag.Description = "Use the IW4MAdmin web console to execute commands"; @@ -27,9 +25,10 @@ namespace WebfrontCore.Controllers return View(activeServers); } - public async Task ExecuteAsync(int serverId, string command) + public async Task ExecuteAsync(long serverId, string command) { - var server = Manager.GetServers().First(s => s.GetHashCode() == serverId); + var server = Manager.GetServers().First(s => s.EndPoint == serverId); + var client = new EFClient() { ClientId = Client.ClientId, diff --git a/WebfrontCore/Controllers/ServerController.cs b/WebfrontCore/Controllers/ServerController.cs index 1df1f3233..dac5175d2 100644 --- a/WebfrontCore/Controllers/ServerController.cs +++ b/WebfrontCore/Controllers/ServerController.cs @@ -1,10 +1,7 @@ using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Dtos; -using System; -using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; namespace WebfrontCore.Controllers { @@ -12,16 +9,19 @@ namespace WebfrontCore.Controllers { [HttpGet] [ResponseCache(NoStore = true, Duration = 0)] - public IActionResult ClientActivity(int id) + public IActionResult ClientActivity(long id) { - var s = Manager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id); + var s = Manager.GetServers().FirstOrDefault(s2 => s2.EndPoint == id); + if (s == null) + { return View("Error", "Invalid server!"); + } var serverInfo = new ServerInfo() { Name = s.Hostname, - ID = s.GetHashCode(), + ID = s.EndPoint, Port = s.GetPort(), Map = s.CurrentMap.Alias, ClientCount = s.ClientNum, diff --git a/WebfrontCore/ViewComponents/ServerListViewComponent.cs b/WebfrontCore/ViewComponents/ServerListViewComponent.cs index 9d204b8be..a071514b4 100644 --- a/WebfrontCore/ViewComponents/ServerListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ServerListViewComponent.cs @@ -13,7 +13,7 @@ namespace WebfrontCore.ViewComponents var serverInfo = servers.Select(s => new ServerInfo() { Name = s.Hostname, - ID = s.GetHashCode(), + ID = s.EndPoint, Port = s.GetPort(), Map = s.CurrentMap.Alias, ClientCount = s.ClientNum,