diff --git a/Admin/Manager.cs b/Admin/Manager.cs index d4b5d9201..958462be9 100644 --- a/Admin/Manager.cs +++ b/Admin/Manager.cs @@ -207,6 +207,12 @@ namespace IW4MAdmin if (Status.RunAverage > 1000 + UPDATE_FREQUENCY) Logger.WriteWarning($"Update task average execution is longer than desired for {(Status.Dependant as Server)} [{Status.RunAverage}ms]"); } + + if (Status.RequestedTask.Status == TaskStatus.Faulted) + { + Logger.WriteWarning($"Update task for {(Status.Dependant as Server)} faulted, restarting"); + Status.Abort(); + } } Thread.Sleep(UPDATE_FREQUENCY); diff --git a/Admin/Server.cs b/Admin/Server.cs index 015b138ad..0baa33750 100644 --- a/Admin/Server.cs +++ b/Admin/Server.cs @@ -380,6 +380,7 @@ namespace IW4MAdmin DateTime playerCountStart = DateTime.Now; DateTime lastCount = DateTime.Now; DateTime tickTime = DateTime.Now; + bool firstRun = true; override public async Task ProcessUpdatesAsync(CancellationToken cts) { @@ -388,6 +389,13 @@ namespace IW4MAdmin try #endif { + // first start + if (firstRun) + { + await ExecuteEvent(new Event(Event.GType.Start, "Server started", null, null, this)); + firstRun = false; + } + if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) return true; @@ -596,7 +604,6 @@ namespace IW4MAdmin LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); #endif Logger.WriteInfo("Log file is " + logPath); - await ExecuteEvent(new Event(Event.GType.Start, "Server started", null, null, this)); #if !DEBUG Broadcast("IW4M Admin is now ^2ONLINE"); #endif @@ -617,13 +624,13 @@ namespace IW4MAdmin else if (E.Type == Event.GType.Script) { - if (E.Origin == E.Target)// suicide/falling + /* if (E.Origin == E.Target)// suicide/falling await ExecuteEvent(new Event(Event.GType.Death, E.Data, E.Target, E.Target, this)); else - { + {*/ await ExecuteEvent(new Event(Event.GType.Kill, E.Data, E.Origin, E.Target, this)); - await ExecuteEvent(new Event(Event.GType.Death, E.Data, E.Target, E.Origin, this)); - } + //await ExecuteEvent(new Event(Event.GType.Death, E.Data, E.Target, E.Origin, this)); + // } } if (E.Type == Event.GType.Say && E.Data.Length >= 2) @@ -806,7 +813,7 @@ namespace IW4MAdmin } else - await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber } \"^1Player Temporarily Banned: ^5{ Reason }\""); + await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber } \"^7Player Temporarily Banned: ^5{ Reason }\""); #if DEBUG await Target.CurrentServer.RemovePlayer(Target.ClientNumber); diff --git a/Admin/WebService.cs b/Admin/WebService.cs index 6d1189391..f9647707c 100644 --- a/Admin/WebService.cs +++ b/Admin/WebService.cs @@ -232,7 +232,7 @@ namespace IW4MAdmin bool authed = querySet["IP"] == "127.0.0.1" || (await (ApplicationManager.GetInstance().GetClientService() as ClientService).GetPrivilegedClients()) - .Where(x => x.IPAddress == querySet["IP"]) + .Where(x => x.IPAddress == querySet["IP"].ConvertToIP()) .Where(x => x.Level > Player.Permission.Trusted).Count() > 0; @@ -384,7 +384,7 @@ namespace IW4MAdmin if (S != null) { // fixme - Func predicate = c => c.IPAddress == querySet["IP"]; + Func predicate = c => c.IPAddress == querySet["IP"].ConvertToIP(); Player admin = (await ApplicationManager.GetInstance().GetClientService().Find(predicate)).FirstOrDefault()?.AsPlayer(); if (admin == null) @@ -752,7 +752,7 @@ namespace IW4MAdmin contentType = GetContentType(), additionalHeaders = new Dictionary() }; - Func predicate = c => c.IPAddress == querySet["IP"] && c.Level > Player.Permission.Trusted; + Func predicate = c => c.IPAddress == querySet["IP"].ConvertToIP() && c.Level > Player.Permission.Trusted; bool authed = (await ApplicationManager.GetInstance().GetClientService().Find(predicate)).Count > 0 || querySet["IP"] == "127.0.0.1"; bool recent = false; @@ -765,7 +765,7 @@ namespace IW4MAdmin else if (querySet["npID"] != null) { - matchedPlayers.Add(await ApplicationManager.GetInstance().GetClientService().GetUnique(querySet["npID"])); + matchedPlayers.Add(await ApplicationManager.GetInstance().GetClientService().GetUnique(querySet["npID"].ConvertLong())); } else if (querySet["name"] != null) @@ -793,11 +793,11 @@ namespace IW4MAdmin PlayerInfo eachPlayer = new PlayerInfo() { - playerIP = pp.IPAddress, + playerIP = pp.IPAddressString, playerID = pp.ClientId, playerLevel = pp.Level.ToString(), playerName = pp.Name, - playernpID = pp.NetworkId, + playernpID = pp.NetworkId.ToString(), forumID = -1, authed = authed, showV2Features = false, @@ -815,7 +815,7 @@ namespace IW4MAdmin if (authed) eachPlayer.playerIPs = pp.AliasLink.Children - .Select(a => a.IPAddress) + .Select(a => a.IPAddress.ConvertIPtoString()) .Distinct() .ToList(); } diff --git a/Admin/lib/SharedLibrary.dll b/Admin/lib/SharedLibrary.dll index 0552f463b..c06a95ac1 100644 Binary files a/Admin/lib/SharedLibrary.dll and b/Admin/lib/SharedLibrary.dll differ diff --git a/Plugins/SimpleStats/Helpers/StatManager.cs b/Plugins/SimpleStats/Helpers/StatManager.cs index fcc305bb3..d1caf6fcf 100644 --- a/Plugins/SimpleStats/Helpers/StatManager.cs +++ b/Plugins/SimpleStats/Helpers/StatManager.cs @@ -15,13 +15,16 @@ namespace StatsPlugin.Helpers { private Dictionary Servers; private Dictionary ContextThreads; + private Dictionary StreakMessages; private ILogger Log; private IManager Manager; + public StatManager(IManager mgr) { Servers = new Dictionary(); ContextThreads = new Dictionary(); + StreakMessages= new Dictionary(); Log = mgr.GetLogger(); Manager = mgr; } @@ -44,6 +47,7 @@ namespace StatsPlugin.Helpers int serverId = sv.GetHashCode(); var statsSvc = new ThreadSafeStatsService(); ContextThreads.Add(serverId, statsSvc); + StreakMessages.Add(serverId, new StreakMessage(sv)); // get the server from the database if it exists, otherwise create and insert a new one var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); @@ -157,7 +161,7 @@ namespace StatsPlugin.Helpers public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type, string damage, string weapon, string killOrigin, string deathOrigin) { - AddStandardKill(attacker, victim); + await AddStandardKill(attacker, victim); var statsSvc = ContextThreads[serverId]; @@ -180,12 +184,19 @@ namespace StatsPlugin.Helpers await statsSvc.KillStatsSvc.SaveChangesAsync(); } - public void AddStandardKill(Player attacker, Player victim) + public async Task AddStandardKill(Player attacker, Player victim) { int serverId = attacker.CurrentServer.GetHashCode(); var attackerStats = Servers[serverId].PlayerStats[attacker.ClientNumber]; // set to access total time played attackerStats.Client = attacker; + + if (victim == null) + { + Log.WriteError($"Stats: Victim is null"); + return; + } + var victimStats = Servers[serverId].PlayerStats[victim.ClientNumber]; // update the total stats @@ -194,6 +205,15 @@ namespace StatsPlugin.Helpers // calculate for the clients CalculateKill(attackerStats, victimStats); + // show encouragement/discouragement + var streakMessageGen = StreakMessages[serverId]; + string streakMessage = (attackerStats.ClientId != victimStats.ClientId) ? + streakMessageGen.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak) : + streakMessageGen.MessageOnStreak(-1, -1); + + if (streakMessage != string.Empty) + await attacker.Tell(streakMessage); + // immediately write changes in debug #if DEBUG var statsSvc = ContextThreads[serverId]; @@ -209,10 +229,16 @@ namespace StatsPlugin.Helpers /// Stats of the victim public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats) { - attackerStats.Kills += 1; - attackerStats.SessionKills += 1; - attackerStats.KillStreak += 1; - attackerStats.DeathStreak = 0; + bool suicide = attackerStats.ClientId == victimStats.ClientId; + + // only update their kills if they didn't kill themselves + if (!suicide) + { + attackerStats.Kills += 1; + attackerStats.SessionKills += 1; + attackerStats.KillStreak += 1; + attackerStats.DeathStreak = 0; + } victimStats.Deaths += 1; victimStats.SessionDeaths += 1; @@ -224,6 +250,8 @@ namespace StatsPlugin.Helpers attackerStats.Client = null; // update after calculation + attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; + victimStats.TimePlayed += (int)(DateTime.UtcNow - victimStats.LastActive).TotalSeconds; attackerStats.LastActive = DateTime.UtcNow; victimStats.LastActive = DateTime.UtcNow; } diff --git a/Plugins/SimpleStats/Helpers/StreakMessage.cs b/Plugins/SimpleStats/Helpers/StreakMessage.cs index 0bc04b9e6..f1a390219 100644 --- a/Plugins/SimpleStats/Helpers/StreakMessage.cs +++ b/Plugins/SimpleStats/Helpers/StreakMessage.cs @@ -1,4 +1,6 @@ -using System; +using SharedLibrary; +using SharedLibrary.Helpers; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,30 +10,55 @@ namespace StatsPlugin.Helpers { public class StreakMessage { - public static string MessageOnStreak(int killStreak, int deathStreak) + private ConfigurationManager config; + + public StreakMessage(Server sv) { - String Message = ""; - switch (killStreak) + config = new ConfigurationManager(sv); + + // initialize default messages + if (config.GetProperty>("KillstreakMessages") == null) { - case 5: - Message = "Great job! You're on a ^55 killstreak!"; - break; - case 10: - Message = "Amazing! ^510 kills ^7without dying!"; - break; + var killstreakMessages = new Dictionary() + { + { -1, "Try not to kill yourself anymore" }, + { 5, "Great job! You're on a ^55 killstreak!" }, + { 10, "Amazing! ^510 kills ^7without dying!" }, + { 25, "You better call in that nuke, ^525 killstreak!" } + }; + config.AddProperty(new KeyValuePair("KillstreakMessages", killstreakMessages)); } - switch (deathStreak) + if (config.GetProperty>("DeathstreakMessages") == null) { - case 5: - Message = "Pick it up soldier, you've died ^55 times ^7in a row..."; - break; - case 10: - Message = "Seriously? ^510 deaths ^7without getting a kill?"; - break; + var deathstreakMessages = new Dictionary() + { + { 5, "Pick it up soldier, you've died ^55 times ^7in a row..." }, + { 10, "Seriously? ^510 deaths ^7without getting a kill?" }, + }; + config.AddProperty(new KeyValuePair("DeathstreakMessages", deathstreakMessages)); } + } - return Message; + /// + /// Get a message from the configuration encouraging or discouraging clients + /// + /// how many kills the client has without dying + /// how many deaths the client has without getting a kill + /// message to send to the client + public string MessageOnStreak(int killStreak, int deathStreak) + { + var killstreakMessage = config.GetProperty>("KillstreakMessages"); + var deathstreakMessage = config.GetProperty>("DeathstreakMessages"); + + string message = ""; + + if (killstreakMessage.ContainsKey(killStreak)) + message =killstreakMessage[killStreak]; + else if (deathstreakMessage.ContainsKey(deathStreak)) + message = deathstreakMessage[deathStreak]; + + return message; } } } diff --git a/Plugins/SimpleStats/IW4Info.cs b/Plugins/SimpleStats/IW4Info.cs index 76fa08823..f5a7cc1ec 100644 --- a/Plugins/SimpleStats/IW4Info.cs +++ b/Plugins/SimpleStats/IW4Info.cs @@ -60,6 +60,7 @@ namespace StatsPlugin public enum WeaponName { + none = 0, defaultweapon_mp = 1, riotshield_mp = 2, beretta_mp = 3, @@ -1333,7 +1334,27 @@ namespace StatsPlugin ak74u_silencer_thermal_mp, ak74u_silencer_xmags_mp, ak74u_thermal_xmags_mp, + m16_fmj_thermal_mp, + m16_fmj_xmags_mp, + m16_gl_heartbeat_mp, + m16_gl_reflex_mp, + m16_gl_silencer_mp, + m16_gl_thermal_mp, + m16_gl_xmags_mp, m16_reflex_silencer_mp, + m16_heartbeat_reflex_mp, + m16_heartbeat_shotgun_mp, + m16_heartbeat_silencer_mp, + m16_heartbeat_thermal_mp, + m16_heartbeat_xmags_mp, + m16_reflex_shotgun_mp, + m16_reflex_xmags_mp, + m16_shotgun_silencer_mp, + m16_shotgun_thermal_mp, + m16_shotgun_xmags_mp, + m16_silencer_thermal_mp, + m16_silencer_xmags_mp, + m16_thermal_xmags_mp, m40a3_mp, peacekeeper_mp, dragunov_mp, diff --git a/Plugins/SimpleStats/Models/EFClientStatistics.cs b/Plugins/SimpleStats/Models/EFClientStatistics.cs index bf264e562..bd0f87ae0 100644 --- a/Plugins/SimpleStats/Models/EFClientStatistics.cs +++ b/Plugins/SimpleStats/Models/EFClientStatistics.cs @@ -28,12 +28,14 @@ namespace StatsPlugin.Models [NotMapped] public double KDR { - get => Deaths == 0 ? Kills : Math.Round((float)Kills / (float)Deaths, 2); + get => Deaths == 0 ? Kills : Math.Round(Kills / (double)Deaths, 2); } [Required] public double SPM { get; set; } [Required] public double Skill { get; set; } + [Required] + public int TimePlayed { get; set; } [NotMapped] public int SessionKills { get; set; } diff --git a/Plugins/SimpleStats/Pages/ClientMessages.cs b/Plugins/SimpleStats/Pages/ClientMessages.cs index cd23b3791..d5f441c6d 100644 --- a/Plugins/SimpleStats/Pages/ClientMessages.cs +++ b/Plugins/SimpleStats/Pages/ClientMessages.cs @@ -10,6 +10,8 @@ namespace StatsPlugin.Pages { public class ClientMessages : HTMLPage { + public ClientMessages() : base(false) { } + public override string GetContent(NameValueCollection querySet, IDictionary headers) { StringBuilder S = new StringBuilder(); diff --git a/Plugins/SimpleStats/Pages/LiveStats.cs b/Plugins/SimpleStats/Pages/LiveStats.cs index aee30f0d1..ab074e3ab 100644 --- a/Plugins/SimpleStats/Pages/LiveStats.cs +++ b/Plugins/SimpleStats/Pages/LiveStats.cs @@ -10,6 +10,8 @@ namespace StatsPlugin.Pages { public class LiveStats : HTMLPage { + public LiveStats() : base(false) { } + public override string GetContent(NameValueCollection querySet, IDictionary headers) { StringBuilder S = new StringBuilder(); diff --git a/Plugins/SimpleStats/Plugin.cs b/Plugins/SimpleStats/Plugin.cs index bc6ff0999..c9ec151f1 100644 --- a/Plugins/SimpleStats/Plugin.cs +++ b/Plugins/SimpleStats/Plugin.cs @@ -71,6 +71,8 @@ namespace StatsPlugin string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; if (killInfo.Length >= 9 && killInfo[0].Contains("ScriptKill")) await Manager.AddScriptKill(E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4]); + else + await Manager.AddStandardKill(E.Origin, E.Target); break; case Event.GType.Death: break; @@ -84,14 +86,14 @@ namespace StatsPlugin { var serverStats = new GenericRepository(); return serverStats.Find(s => s.Active) - .Sum(c => c.TotalKills).ToString(); + .Sum(c => c.TotalKills).ToString("#,##0"); } string totalPlayTime() { var serverStats = new GenericRepository(); - return serverStats.GetQuery(s => s.Active) - .Sum(c => c.TotalPlayTime).ToString(); + return Math.Ceiling((serverStats.GetQuery(s => s.Active) + .Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0"); } manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", totalKills)); diff --git a/Plugins/SimpleStats/_Plugin.cs b/Plugins/SimpleStats/_Plugin.cs index 59cce204b..0fb7aa670 100644 --- a/Plugins/SimpleStats/_Plugin.cs +++ b/Plugins/SimpleStats/_Plugin.cs @@ -66,7 +66,7 @@ namespace StatsPlugin ManagerInstance.GetMessageTokens().Add(new MessageToken("TOTALPLAYTIME", GetTotalPlaytime)); ClientStatsSvc = new SharedLibrary.Services.GenericService(); - ServerSvc = new SharedLibrary.Services.GenericService() + ServerSvc = new SharedLibrary.Services.GenericService(); ChatDB = new ChatDatabase("Database/ChatHistory.rm", ManagerInstance.GetLogger()); diff --git a/Plugins/Tests/Plugin.cs b/Plugins/Tests/Plugin.cs index 1bfe2b064..5df15f00d 100644 --- a/Plugins/Tests/Plugin.cs +++ b/Plugins/Tests/Plugin.cs @@ -11,6 +11,7 @@ using SharedLibrary.Interfaces; using SharedLibrary.Helpers; using SharedLibrary.Objects; using System.Text.RegularExpressions; +using StatsPlugin.Models; namespace IW4MAdmin.Plugins { @@ -59,10 +60,11 @@ namespace IW4MAdmin.Plugins public async Task OnLoadAsync(IManager manager) { Interval = DateTime.Now; + var clients = new List(); + var oldClients = new Dictionary(); #region CLIENTS if (File.Exists("import_clients.csv")) { - var clients = new List(); manager.GetLogger().WriteVerbose("Beginning import of existing clients"); var lines = File.ReadAllLines("import_clients.csv").Skip(1); @@ -81,34 +83,38 @@ namespace IW4MAdmin.Plugins return; } - if (fields[1].Contains("0110") || fields[0] == string.Empty || fields[1] == string.Empty || fields[6] == string.Empty) + if (fields[1].Substring(0, 5) == "01100" || fields[0] == string.Empty || fields[1] == string.Empty || fields[6] == string.Empty) continue; if (!Regex.Match(fields[6], @"^\d+\.\d+\.\d+.\d+$").Success) - continue; + fields[6] = "0"; var client = new Player() { + // for link + ClientId = Convert.ToInt32(fields[2]), Name = fields[0], - NetworkId = fields[1], - IPAddress = fields[6], + NetworkId = fields[1].Trim().ConvertLong(), + IPAddress = fields[6].ConvertToIP(), Level = (Player.Permission)Convert.ToInt32(fields[3]), Connections = Convert.ToInt32(fields[5]), LastConnection = DateTime.Parse(fields[7]), }; clients.Add(client); + oldClients.Add(client.ClientId, client); } - - clients = clients - .GroupBy(c => c.NetworkId, (key, c) => c.FirstOrDefault()) - .ToList(); +//#if DO_IMPORT + clients = clients.Distinct().ToList(); clients = clients .GroupBy(c => new { c.Name, c.IPAddress }) .Select(c => c.FirstOrDefault()) .ToList(); + //newClients = clients.ToList(); + //newClients.ForEach(c => c.ClientId = 0); + manager.GetLogger().WriteVerbose($"Read {clients.Count} clients for import"); try @@ -116,10 +122,11 @@ namespace IW4MAdmin.Plugins SharedLibrary.Database.Importer.ImportClients(clients); } - catch(Exception e) + catch (Exception e) { manager.GetLogger().WriteError("Saving imported clients failed"); } +//#endif } #endregion #region PENALTIES @@ -144,20 +151,20 @@ namespace IW4MAdmin.Plugins return; } - if (fields[2].Contains("0110") || fields[2].Contains("0000000") || fields.Any(p => p == string.Empty)) + if (fields[2].Contains("0110") || fields[2].Contains("0000000")) continue; try { - + var expires = DateTime.Parse(fields[6]); var when = DateTime.Parse(fields[5]); var penalty = new Penalty() { Type = (Penalty.PenaltyType)Int32.Parse(fields[0]), - Expires = expires == DateTime.MinValue ? when : expires, - Punisher = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[3]}, - Offender = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[2]}, + Expires = expires == DateTime.MinValue ? when : expires, + Punisher = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[3].ConvertLong() }, + Offender = new SharedLibrary.Database.Models.EFClient() { NetworkId = fields[2].ConvertLong() }, Offense = fields[1], Active = true, When = when, @@ -172,15 +179,133 @@ namespace IW4MAdmin.Plugins manager.GetLogger().WriteVerbose($"Could not import penalty with line {line}"); } } + //#if DO_IMPORT SharedLibrary.Database.Importer.ImportPenalties(penalties); manager.GetLogger().WriteVerbose($"Imported {penalties.Count} penalties"); + //#endif + } + #endregion + #region CHATHISTORY + // load the entire database lol + var cls = manager.GetClientService().Find(c => c.Active).Result; + + if (File.Exists("import_chathistory.csv")) + { + var chatHistory = new List(); + manager.GetLogger().WriteVerbose("Beginning import of existing messages"); + foreach (string line in File.ReadAllLines("import_chathistory.csv").Skip(1)) + { + string comma = Regex.Match(line, "\".*,.*\"").Value.Replace(",", ""); + string[] fields = Regex.Replace(line, "\".*,.*\"", comma).Split(','); + + fields.All(f => + { + f = f.StripColors().Trim(); + return true; + }); + + if (fields.Length != 4) + { + manager.GetLogger().WriteError("Invalid chat history import file... aborting import"); + return; + } + try + { + int cId = Convert.ToInt32(fields[0]); + var linkedClient = oldClients[cId]; + + var newcl = cls.FirstOrDefault(c => c.NetworkId == linkedClient.NetworkId); + if (newcl == null) + newcl = cls.FirstOrDefault(c => c.Name == linkedClient.Name && c.IPAddress == linkedClient.IPAddress); + int newCId = newcl.ClientId; + + var chatMessage = new EFClientMessage() + { + Active = true, + ClientId = newCId, + Message = fields[1], + TimeSent = DateTime.Parse(fields[3]), + ServerId = Math.Abs($"127.0.0.1:{Convert.ToInt32(fields[2]).ToString()}".GetHashCode()) + }; + + chatHistory.Add(chatMessage); + } + + catch (Exception e) + { + manager.GetLogger().WriteVerbose($"Could not import chatmessage with line {line}"); + } + } + manager.GetLogger().WriteVerbose($"Read {chatHistory.Count} messages for import"); + SharedLibrary.Database.Importer.ImportSQLite(chatHistory); + } + #endregion + #region STATS + if (File.Exists("import_stats.csv")) + { + var stats = new List(); + manager.GetLogger().WriteVerbose("Beginning import of existing client stats"); + + var lines = File.ReadAllLines("import_stats.csv").Skip(1); + foreach (string line in lines) + { + string[] fields = line.Split(','); + + if (fields.Length != 9) + { + manager.GetLogger().WriteError("Invalid client import file... aborting import"); + return; + } + + try + { + if (fields[0].Substring(0, 5) == "01100") + continue; + + long id = fields[0].ConvertLong(); + var client = cls.First(c => c.NetworkId == id); + + var time = Convert.ToInt32(fields[8]); + double spm = time < 60 ? 0 : Math.Round(Convert.ToInt32(fields[1]) * 100.0 / time, 3); + if (spm > 1000) + spm = 0; + + var st = new EFClientStatistics() + { + Active = true, + ClientId = client.ClientId, + ServerId = Math.Abs("127.0.0.1:28965".GetHashCode()), + Kills = Convert.ToInt32(fields[1]), + Deaths = Convert.ToInt32(fields[2]), + SPM = spm, + Skill = 0 + }; + stats.Add(st); + } + catch (Exception e) + { + continue; + } + } + + manager.GetLogger().WriteVerbose($"Read {stats.Count} clients stats for import"); + + try + { + SharedLibrary.Database.Importer.ImportSQLite(stats); + } + + catch (Exception e) + { + manager.GetLogger().WriteError("Saving imported stats failed"); + } } #endregion } public async Task OnTickAsync(Server S) { - + return; if ((DateTime.Now - Interval).TotalSeconds > 1) { var rand = new Random(); @@ -188,16 +313,16 @@ namespace IW4MAdmin.Plugins var p = new Player() { Name = $"Test_{index}", - NetworkId = $"_test_{index}", + NetworkId = (long)$"_test_{index}".GetHashCode(), ClientNumber = index, Ping = 1, - IPAddress = $"127.0.0.{index}" + IPAddress = $"127.0.0.{index}".ConvertToIP() }; if (S.Players.ElementAt(index) != null) await S.RemovePlayer(index); - // await S.AddPlayer(p); - + // await S.AddPlayer(p); + Interval = DateTime.Now; if (S.ClientNum > 0) @@ -221,8 +346,8 @@ namespace IW4MAdmin.Plugins eventLine = new string[] { "ScriptKill", - attackerPlayer.NetworkId, - victimPlayer.NetworkId, + attackerPlayer.NetworkId.ToString(), + victimPlayer.NetworkId.ToString(), new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(), new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(), rand.Next(50, 105).ToString(), @@ -237,11 +362,11 @@ namespace IW4MAdmin.Plugins eventLine = new string[] { "K", - victimPlayer.NetworkId, + victimPlayer.NetworkId.ToString(), victimPlayer.ClientNumber.ToString(), rand.Next(0, 1) == 0 ? "allies" : "axis", victimPlayer.Name, - attackerPlayer.NetworkId, + attackerPlayer.NetworkId.ToString(), attackerPlayer.ClientNumber.ToString(), rand.Next(0, 1) == 0 ? "allies" : "axis", attackerPlayer.Name.ToString(), diff --git a/Plugins/VoteMap/Plugin.cs b/Plugins/VoteMap/Plugin.cs index e47c32425..83606c413 100644 --- a/Plugins/VoteMap/Plugin.cs +++ b/Plugins/VoteMap/Plugin.cs @@ -40,7 +40,7 @@ namespace Votemap_Plugin // we only want to allow a vote during a vote session if (voting.voteInSession) { - if (voting.ClientHasVoted(E.Origin.NetworkId)) + if (voting.ClientHasVoted(E.Origin.NetworkId.ToString())) await E.Origin.Tell("You have already voted. Use ^5!vc ^7to ^5cancel ^7your vote"); else { @@ -51,7 +51,7 @@ namespace Votemap_Plugin await E.Origin.Tell("^1" + E.Data + " is not a recognized map"); else { - voting.CastClientVote(E.Origin.NetworkId, votedMap); + voting.CastClientVote(E.Origin.NetworkId.ToString(), votedMap); await E.Origin.Tell("You voted for ^5" + votedMap.Alias); } } @@ -72,9 +72,9 @@ namespace Votemap_Plugin if (voting.voteInSession) { - if (voting.ClientHasVoted(E.Origin.NetworkId)) + if (voting.ClientHasVoted(E.Origin.NetworkId.ToString())) { - voting.CancelClientVote(E.Origin.NetworkId); + voting.CancelClientVote(E.Origin.NetworkId.ToString()); await E.Origin.Tell("Vote cancelled"); } diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index 412214e76..a5307230e 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -95,7 +95,7 @@ namespace Welcome_Plugin try { CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup("Plugins/GeoIP.dat"); - await E.Owner.Broadcast($"^5{newPlayer.Name} ^7hails from ^5{CLT.lookupCountryName(newPlayer.IPAddress)}"); + await E.Owner.Broadcast($"^5{newPlayer.Name} ^7hails from ^5{CLT.lookupCountryName(newPlayer.IPAddressString)}"); } catch (Exception) diff --git a/SharedLibrary/Commands/NativeCommands.cs b/SharedLibrary/Commands/NativeCommands.cs index 4eae89f6c..64dbb12b9 100644 --- a/SharedLibrary/Commands/NativeCommands.cs +++ b/SharedLibrary/Commands/NativeCommands.cs @@ -854,7 +854,7 @@ namespace SharedLibrary.Commands { StringBuilder message = new StringBuilder(); var names = new List(E.Target.AliasLink.Children.Select(a => a.Name)); - var IPs = new List(E.Target.AliasLink.Children.Select(a => a.IPAddress).Distinct()); + var IPs = new List(E.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString()).Distinct()); await E.Target.Tell($"[^3{E.Target}^7]"); diff --git a/SharedLibrary/Database/Importer.cs b/SharedLibrary/Database/Importer.cs index c55650721..47219168b 100644 --- a/SharedLibrary/Database/Importer.cs +++ b/SharedLibrary/Database/Importer.cs @@ -19,6 +19,8 @@ namespace SharedLibrary.Database { context = new DatabaseContext(); context.Configuration.AutoDetectChangesEnabled = false; + context.Configuration.LazyLoadingEnabled = false; + context.Configuration.ProxyCreationEnabled = false; int count = 0; foreach (var entityToInsert in clients) @@ -96,6 +98,8 @@ namespace SharedLibrary.Database { context = new DatabaseContext(); context.Configuration.AutoDetectChangesEnabled = false; + context.Configuration.LazyLoadingEnabled = false; + context.Configuration.ProxyCreationEnabled = false; int count = 0; foreach (var entityToInsert in penalties) @@ -161,5 +165,59 @@ namespace SharedLibrary.Database return context; } + + public static void ImportSQLite(IList SQLiteData) where T : class + { + DatabaseContext context = null; + + try + { + context = new DatabaseContext(); + context.Configuration.AutoDetectChangesEnabled = false; + context.Configuration.LazyLoadingEnabled = false; + context.Configuration.ProxyCreationEnabled = false; + + int count = 0; + foreach (var entityToInsert in SQLiteData) + { + ++count; + context = AddSQLite(context, entityToInsert, count, 100, true); + } + + context.SaveChanges(); + } + finally + { + if (context != null) + context.Dispose(); + } + } + + private static DatabaseContext AddSQLite(DatabaseContext context, T entity, int count, int commitCount, bool recreateContext) where T : class + { + context.Set().Add(entity); + + if (count % commitCount == 0) + { + try + { + context.SaveChanges(); + } + + catch (Exception e) + { + var a = 1; + } + + if (recreateContext) + { + context.Dispose(); + context = new DatabaseContext(); + context.Configuration.AutoDetectChangesEnabled = false; + } + } + return context; + } } } + diff --git a/SharedLibrary/Database/Initializer.cs b/SharedLibrary/Database/Initializer.cs index afffe33cc..bb0a2dc26 100644 --- a/SharedLibrary/Database/Initializer.cs +++ b/SharedLibrary/Database/Initializer.cs @@ -17,7 +17,7 @@ namespace SharedLibrary.Database { Active = true, DateAdded = DateTime.UtcNow, - IPAddress = "0.0.0.0", + IPAddress = 0, Name = "IW4MAdmin", Link = aliasLink }; @@ -30,7 +30,7 @@ namespace SharedLibrary.Database LastConnection = DateTime.UtcNow, Level = Objects.Player.Permission.Console, Masked = true, - NetworkId = "0000000000000000", + NetworkId = 0, AliasLink = aliasLink, CurrentAlias = currentAlias }); diff --git a/SharedLibrary/Database/Models/EFAlias.cs b/SharedLibrary/Database/Models/EFAlias.cs index adcea7341..68108f604 100644 --- a/SharedLibrary/Database/Models/EFAlias.cs +++ b/SharedLibrary/Database/Models/EFAlias.cs @@ -14,10 +14,12 @@ namespace SharedLibrary.Database.Models public virtual EFAliasLink Link { get; set; } // [Index("IX_IPandName", 0, IsUnique = true)] //[MaxLength(24)] + [Required] public string Name { get; set; } // [Index("IX_IPandName", 1, IsUnique = true)] // [MaxLength(24)] - public string IPAddress { get; set; } + [Required] + public int IPAddress { get; set; } [Required] public DateTime DateAdded { get; set; } } diff --git a/SharedLibrary/Database/Models/EFClient.cs b/SharedLibrary/Database/Models/EFClient.cs index 8304cee15..82c120fae 100644 --- a/SharedLibrary/Database/Models/EFClient.cs +++ b/SharedLibrary/Database/Models/EFClient.cs @@ -13,8 +13,7 @@ namespace SharedLibrary.Database.Models [Key] public int ClientId { get; set; } [Index(IsUnique = true)] - public string NetworkId { get; set; } - + public long NetworkId { get; set; } [Required] public int Connections { get; set; } [Required] @@ -44,12 +43,15 @@ namespace SharedLibrary.Database.Models set { } } [NotMapped] - public virtual string IPAddress + public virtual int IPAddress { get { return CurrentAlias.IPAddress; } set { } } + [NotMapped] + public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString(); + public virtual ICollection ReceivedPenalties { get; set; } public virtual ICollection AdministeredPenalties { get; set; } diff --git a/SharedLibrary/Event.cs b/SharedLibrary/Event.cs index 9609d863f..5ad695e07 100644 --- a/SharedLibrary/Event.cs +++ b/SharedLibrary/Event.cs @@ -131,7 +131,7 @@ namespace SharedLibrary if (removeTime.Contains("ScriptKill")) { - return new Event(GType.Script, String.Join(";", line), SV.Players.FirstOrDefault(p => p != null && p.NetworkId == line[1]), SV.Players.FirstOrDefault(p => p != null && p.NetworkId == line[2]), SV); + return new Event(GType.Script, String.Join(";", line), SV.Players.FirstOrDefault(p => p != null && p.NetworkId == line[1].ConvertLong()), SV.Players.FirstOrDefault(p => p != null && p.NetworkId == line[2].ConvertLong()), SV); } if (removeTime.Contains("ExitLevel")) diff --git a/SharedLibrary/Helpers/AsyncStatus.cs b/SharedLibrary/Helpers/AsyncStatus.cs index 6162f85d8..dfebd736c 100644 --- a/SharedLibrary/Helpers/AsyncStatus.cs +++ b/SharedLibrary/Helpers/AsyncStatus.cs @@ -53,5 +53,10 @@ namespace SharedLibrary.Helpers RunAverage = RunAverage + ((DateTime.Now - StartTime).TotalMilliseconds - RunAverage - UpdateFrequency) / TimesRun; StartTime = DateTime.Now; } + + public void Abort() + { + RequestedTask = null; + } } } diff --git a/SharedLibrary/Helpers/ConfigurationManager.cs b/SharedLibrary/Helpers/ConfigurationManager.cs index f361bef3b..d6e303b2b 100644 --- a/SharedLibrary/Helpers/ConfigurationManager.cs +++ b/SharedLibrary/Helpers/ConfigurationManager.cs @@ -6,65 +6,32 @@ namespace SharedLibrary.Helpers { public class ConfigurationManager { - ConcurrentDictionary> ConfigurationSet; - ConcurrentDictionary ConfigSet; - Type PluginType; + ConcurrentDictionary ConfigSet; Server ServerInstance; - public ConfigurationManager(Type PluginType) - { - ConfigurationSet = new ConcurrentDictionary>(); - this.PluginType = PluginType; - } - public ConfigurationManager(Server S) { try { - ConfigSet = Interfaces.Serialize>.Read($"config/Plugins_{S}.cfg"); + ConfigSet = Interfaces.Serialize>.Read($"config/plugins_{S.ToString()}.cfg"); } catch (Exception) { S.Logger.WriteInfo("ConfigurationManager could not deserialize configuration file, so initializing default config set"); - ConfigSet = new ConcurrentDictionary(); + ConfigSet = new ConcurrentDictionary(); } ServerInstance = S; + SaveChanges(); } private void SaveChanges() { - Interfaces.Serialize>.Write($"config/Plugins_{ServerInstance}.cfg", ConfigSet); + Interfaces.Serialize>.Write($"config/plugins_{ServerInstance.ToString()}.cfg", ConfigSet); } - public void AddConfiguration(Server S) - { - /* if (ConfigurationSet.ContainsKey(S.ToString())) - { - S.Logger.WriteWarning($"not adding server configuration for {S} as it already exists"); - return; - }*/ - - try - { - var Config = Interfaces.Serialize>.Read($"config/{PluginType.ToString()}_{S.ToString()}.cfg"); - ConfigurationSet.TryAdd(S.ToString(), Config); - } - - catch (Exceptions.SerializeException) - { - ConfigurationSet.TryAdd(S.ToString(), new Dictionary()); - } - } - - public void AddProperty(Server S, KeyValuePair Property) - { - ConfigurationSet[S.ToString()].Add(Property.Key, Property.Value); - Interfaces.Serialize>.Write($"config/{PluginType.ToString()}_{S.ToString()}.cfg", ConfigurationSet[S.ToString()]); - } - - public void AddProperty(KeyValuePair prop) + public void AddProperty(KeyValuePair prop) { if (!ConfigSet.ContainsKey(prop.Key)) ConfigSet.TryAdd(prop.Key, prop.Value); @@ -72,13 +39,7 @@ namespace SharedLibrary.Helpers SaveChanges(); } - public void UpdateProperty(Server S, KeyValuePair Property) - { - ConfigurationSet[S.ToString()][Property.Key] = Property.Value; - Interfaces.Serialize>.Write($"config/{PluginType.ToString()}_{S.ToString()}.cfg", ConfigurationSet[S.ToString()]); - } - - public void UpdateProperty(KeyValuePair prop) + public void UpdateProperty(KeyValuePair prop) { if (ConfigSet.ContainsKey(prop.Key)) ConfigSet[prop.Key] = prop.Value; @@ -86,21 +47,16 @@ namespace SharedLibrary.Helpers SaveChanges(); } - public IDictionary GetConfiguration(Server S) - { - return ConfigurationSet[S.ToString()]; - } - - public object GetProperty(string prop) + public T GetProperty(string prop) { try { - return ConfigSet[prop]; + return ConfigSet[prop].ToObject(); } catch (Exception) { - return null; + return default(T); } } } diff --git a/SharedLibrary/Interfaces/IEntityService.cs b/SharedLibrary/Interfaces/IEntityService.cs index 3621e957c..625babc19 100644 --- a/SharedLibrary/Interfaces/IEntityService.cs +++ b/SharedLibrary/Interfaces/IEntityService.cs @@ -14,7 +14,7 @@ namespace SharedLibrary.Interfaces Task Delete(T entity); Task Update(T entity); Task Get(int entityID); - Task GetUnique(string entityProperty); + Task GetUnique(long entityProperty); Task> Find(Func expression); } } diff --git a/SharedLibrary/Interfaces/ISerializable.cs b/SharedLibrary/Interfaces/ISerializable.cs index 9a6f4484d..1af45f07b 100644 --- a/SharedLibrary/Interfaces/ISerializable.cs +++ b/SharedLibrary/Interfaces/ISerializable.cs @@ -45,7 +45,7 @@ namespace SharedLibrary.Interfaces { try { - string configText = Newtonsoft.Json.JsonConvert.SerializeObject(data); + string configText = Newtonsoft.Json.JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented); File.WriteAllText(filename, configText); } diff --git a/SharedLibrary/Objects/Player.cs b/SharedLibrary/Objects/Player.cs index 74355b9c4..c677b070a 100644 --- a/SharedLibrary/Objects/Player.cs +++ b/SharedLibrary/Objects/Player.cs @@ -76,8 +76,8 @@ namespace SharedLibrary.Objects [NotMapped] public int Score { get; set; } - private string _ipaddress; - public override string IPAddress + private int _ipaddress; + public override int IPAddress { get { return _ipaddress; } set { _ipaddress = value; } @@ -88,5 +88,15 @@ namespace SharedLibrary.Objects get { return _name; } set { _name = value; } } + + public override bool Equals(object obj) + { + return ((Player)obj).NetworkId == NetworkId; + } + + public override int GetHashCode() + { + return NetworkId.GetHashCode(); + } } } diff --git a/SharedLibrary/Server.cs b/SharedLibrary/Server.cs index 8a6571d01..97aa74a5e 100644 --- a/SharedLibrary/Server.cs +++ b/SharedLibrary/Server.cs @@ -39,7 +39,7 @@ namespace SharedLibrary Reports = new List(); PlayerHistory = new Queue(); ChatHistory = new List(); - Configuration = new ConfigurationManager(this.GetType()); + //Configuration = new ConfigurationManager(this.GetType()); NextMessage = 0; InitializeTokens(); InitializeAutoMessages(); diff --git a/SharedLibrary/Services/AliasService.cs b/SharedLibrary/Services/AliasService.cs index 2c288d00b..979bb1ef8 100644 --- a/SharedLibrary/Services/AliasService.cs +++ b/SharedLibrary/Services/AliasService.cs @@ -73,7 +73,7 @@ namespace SharedLibrary.Services .SingleOrDefaultAsync(e => e.AliasId == entityID); } - public Task GetUnique(string entityProperty) + public Task GetUnique(long entityProperty) { throw new NotImplementedException(); } diff --git a/SharedLibrary/Services/ClientService.cs b/SharedLibrary/Services/ClientService.cs index cdf08f2ad..02c52e182 100644 --- a/SharedLibrary/Services/ClientService.cs +++ b/SharedLibrary/Services/ClientService.cs @@ -102,14 +102,18 @@ namespace SharedLibrary.Services public async Task Get(int entityID) { using (var context = new DatabaseContext()) + { + context.Configuration.LazyLoadingEnabled = false; + context.Configuration.ProxyCreationEnabled = false; return await new DatabaseContext().Clients .AsNoTracking() .Include(c => c.CurrentAlias) .Include(c => c.AliasLink.Children) .SingleOrDefaultAsync(e => e.ClientId == entityID); + } } - public async Task GetUnique(string entityAttribute) + public async Task GetUnique(long entityAttribute) { using (var context = new DatabaseContext()) { @@ -117,7 +121,7 @@ namespace SharedLibrary.Services .AsNoTracking() .Include(c => c.CurrentAlias) .Include(c => c.AliasLink.Children) - .SingleOrDefaultAsync(c => c.NetworkId == entityAttribute); + .SingleOrDefaultAsync(c => c.NetworkId == (long)entityAttribute); } } diff --git a/SharedLibrary/Services/GenericService.cs b/SharedLibrary/Services/GenericService.cs deleted file mode 100644 index eadb780e1..000000000 --- a/SharedLibrary/Services/GenericService.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using SharedLibrary.Database; - -namespace SharedLibrary.Services -{ - public class GenericService : Interfaces.IEntityService - { - public async Task Create(T entity) - { - using (var context = new DatabaseContext()) - { - var dbSet = context.Set(entity.GetType()); - T addedEntity = (T)dbSet.Add(entity); - await context.SaveChangesAsync(); - - return addedEntity; - } - } - - public Task CreateProxy() - { - throw new NotImplementedException(); - } - - public Task Delete(T entity) - { - throw new NotImplementedException(); - } - - public Task> Find(Func expression) - { - throw new NotImplementedException(); - } - - public async Task Get(int entityID) - { - using (var context = new DatabaseContext()) - { - var dbSet = context.Set(typeof(T)); - return (T)(await dbSet.FindAsync(entityID)); - } - } - - public async Task Get(params object[] entityKeys) - { - using (var context = new DatabaseContext()) - { - var dbSet = context.Set(typeof(T)); - return (T)(await dbSet.FindAsync(entityKeys)); - } - } - - public Task GetUnique(string entityProperty) - { - throw new NotImplementedException(); - } - - public Task Update(T entity) - { - throw new NotImplementedException(); - } - } -} diff --git a/SharedLibrary/Services/PenaltyService.cs b/SharedLibrary/Services/PenaltyService.cs index b7b7c76df..777fefb48 100644 --- a/SharedLibrary/Services/PenaltyService.cs +++ b/SharedLibrary/Services/PenaltyService.cs @@ -73,7 +73,7 @@ namespace SharedLibrary.Services throw new NotImplementedException(); } - public Task GetUnique(string entityProperty) + public Task GetUnique(long entityProperty) { throw new NotImplementedException(); } diff --git a/SharedLibrary/SharedLibrary.csproj b/SharedLibrary/SharedLibrary.csproj index dd143beed..7e84e3968 100644 --- a/SharedLibrary/SharedLibrary.csproj +++ b/SharedLibrary/SharedLibrary.csproj @@ -139,7 +139,6 @@ - diff --git a/SharedLibrary/Utilities.cs b/SharedLibrary/Utilities.cs index 96427cf14..d70c7b9cd 100644 --- a/SharedLibrary/Utilities.cs +++ b/SharedLibrary/Utilities.cs @@ -58,10 +58,10 @@ namespace SharedLibrary int Ping = -1; Int32.TryParse(playerInfo[2], out Ping); String cName = Utilities.StripColors(responseLine.Substring(46, 18)).Trim(); - string npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value; + long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong(); int.TryParse(playerInfo[0], out cID); var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); - string cIP = regex.Value.Split(':')[0]; + int cIP = regex.Value.Split(':')[0].ConvertToIP(); regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]); Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score}; @@ -197,6 +197,21 @@ namespace SharedLibrary } } + public static long ConvertLong(this string str) + { + return Int64.Parse(str, System.Globalization.NumberStyles.HexNumber); + } + + public static int ConvertToIP(this string str) + { + return BitConverter.ToInt32(System.Net.IPAddress.Parse(str).GetAddressBytes(), 0); + } + + public static string ConvertIPtoString(this int ip) + { + return new System.Net.IPAddress(BitConverter.GetBytes(ip)).ToString(); + } + public static String DateTimeSQLite(DateTime datetime) { return datetime.ToString("yyyy-MM-dd H:mm:ss");