update readme

add vision average to client stats
other stuff
This commit is contained in:
RaidMax 2018-09-07 22:29:42 -05:00
parent 385879618d
commit ba5b1e19a6
27 changed files with 1750 additions and 192 deletions

View File

@ -9,14 +9,17 @@ namespace IW4MAdmin.Application
{ {
class GameEventHandler : IEventHandler class GameEventHandler : IEventHandler
{ {
private readonly IManager Manager;
static long NextEventId = 1; static long NextEventId = 1;
private readonly SortedList<long, GameEvent> OutOfOrderEvents; readonly IManager Manager;
readonly SortedList<long, GameEvent> OutOfOrderEvents;
readonly SemaphoreSlim IsProcessingEvent;
public GameEventHandler(IManager mgr) public GameEventHandler(IManager mgr)
{ {
Manager = mgr; Manager = mgr;
OutOfOrderEvents = new SortedList<long, GameEvent>(); OutOfOrderEvents = new SortedList<long, GameEvent>();
IsProcessingEvent = new SemaphoreSlim(0);
IsProcessingEvent.Release();
} }
public void AddEvent(GameEvent gameEvent) public void AddEvent(GameEvent gameEvent)
@ -45,11 +48,40 @@ namespace IW4MAdmin.Application
// event occurs // event occurs
if (gameEvent.Id == Interlocked.Read(ref NextEventId)) if (gameEvent.Id == Interlocked.Read(ref NextEventId))
{ {
#if DEBUG == true //#if DEBUG == true
Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed"); // Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed");
#endif // IsProcessingEvent.Wait();
//#else
// if (GameEvent.IsEventTimeSensitive(gameEvent) &&
// !IsProcessingEvent.Wait(30 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
//#endif
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent)); ((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
//if (GameEvent.IsEventTimeSensitive(gameEvent))
//{
// if( !gameEvent.OnProcessed.Wait(30 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EVENT_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
//}
Interlocked.Increment(ref NextEventId); Interlocked.Increment(ref NextEventId);
//#if DEBUG == true
// gameEvent.OnProcessed.Wait();
//#else
// if (GameEvent.IsEventTimeSensitive(gameEvent) &&
// !gameEvent.OnProcessed.Wait(30 * 1000))
// {
// Manager.GetLogger().WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EVENT_TIMEOUT"]} [{gameEvent.Id}, {gameEvent.Type}]");
// }
//#endif
// Interlocked.Increment(ref NextEventId);
// if (GameEvent.IsEventTimeSensitive(gameEvent))
// {
// IsProcessingEvent.Release();
// }
} }
// a "newer" event has been added before and "older" one has been added (due to threads and context switching) // a "newer" event has been added before and "older" one has been added (due to threads and context switching)

View File

@ -88,6 +88,7 @@ namespace IW4MAdmin.Application
// offload it to the player to keep // offload it to the player to keep
newEvent.Origin.DelayedEvents.Enqueue(newEvent); newEvent.Origin.DelayedEvents.Enqueue(newEvent);
newEvent.OnProcessed.Set();
return; return;
} }
@ -97,6 +98,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed"); Logger.WriteDebug($"Delaying target execution of event type {newEvent.Type} for {newEvent.Target} because they are not authed");
// offload it to the player to keep // offload it to the player to keep
newEvent.Target.DelayedEvents.Enqueue(newEvent); newEvent.Target.DelayedEvents.Enqueue(newEvent);
newEvent.OnProcessed.Set();
return; return;
} }
@ -120,7 +122,6 @@ namespace IW4MAdmin.Application
Owner = oldEvent.Owner, Owner = oldEvent.Owner,
Message = oldEvent.Message, Message = oldEvent.Message,
Target = oldEvent.Target, Target = oldEvent.Target,
OnProcessed = oldEvent.OnProcessed,
Remote = oldEvent.Remote Remote = oldEvent.Remote
}; };

View File

@ -257,8 +257,7 @@ namespace IW4MAdmin
if (cNum >= 0 && Players[cNum] != null) if (cNum >= 0 && Players[cNum] != null)
{ {
Player Leaving = Players[cNum]; Player Leaving = Players[cNum];
Logger.WriteInfo($"Client {Leaving}, state {Leaving.State.ToString()} disconnecting...");
// occurs when the player disconnects via log before being authenticated by RCon // occurs when the player disconnects via log before being authenticated by RCon
if (Leaving.State != Player.ClientState.Connected) if (Leaving.State != Player.ClientState.Connected)
{ {
@ -267,8 +266,9 @@ namespace IW4MAdmin
else else
{ {
Logger.WriteInfo($"Client {Leaving} [{Leaving.State.ToString().ToLower()}] disconnecting...");
Leaving.State = Player.ClientState.Disconnecting; Leaving.State = Player.ClientState.Disconnecting;
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds; Leaving.TotalConnectionTime += Leaving.ConnectionLength;
Leaving.LastConnection = DateTime.UtcNow; Leaving.LastConnection = DateTime.UtcNow;
await Manager.GetClientService().Update(Leaving); await Manager.GetClientService().Update(Leaving);
Players[cNum] = null; Players[cNum] = null;
@ -383,27 +383,29 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Quit) else if (E.Type == GameEvent.EventType.Quit)
{ {
//var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId); var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId);
//if (origin != null && if (origin != null &&
// // we only want to forward the event if they are connected. // we only want to forward the event if they are connected.
// origin.State == Player.ClientState.Connected) origin.State == Player.ClientState.Connected &&
//{ // make sure we don't get the disconnect event from every time the game ends
// var e = new GameEvent() origin.ConnectionLength < Manager.GetApplicationSettings().Configuration().RConPollRate)
// { {
// Type = GameEvent.EventType.Disconnect, var e = new GameEvent()
// Origin = origin, {
// Owner = this Type = GameEvent.EventType.Disconnect,
// }; Origin = origin,
Owner = this
};
// Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
//} }
//else if (origin != null && else if (origin != null &&
// origin.State != Player.ClientState.Connected) origin.State != Player.ClientState.Connected)
//{ {
// await RemovePlayer(origin.ClientNumber); await RemovePlayer(origin.ClientNumber);
//} }
} }
else if (E.Type == GameEvent.EventType.Disconnect) else if (E.Type == GameEvent.EventType.Disconnect)
@ -781,12 +783,12 @@ namespace IW4MAdmin
Logger.WriteWarning("Game log file not properly initialized, restarting map..."); Logger.WriteWarning("Game log file not properly initialized, restarting map...");
await this.ExecuteCommandAsync("map_restart"); await this.ExecuteCommandAsync("map_restart");
logfile = await this.GetDvarAsync<string>("g_log"); logfile = await this.GetDvarAsync<string>("g_log");
} }
//CustomCallback = await ScriptLoaded(); //CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir(); string mainPath = EventParser.GetGameDir();
#if DEBUG #if DEBUG
basepath.Value = @"D:\"; // basepath.Value = @"D:\";
#endif #endif
string logPath = string.Empty; string logPath = string.Empty;

View File

@ -7,7 +7,7 @@ class LogResource(Resource):
path = urlsafe_b64decode(path).decode('utf-8') path = urlsafe_b64decode(path).decode('utf-8')
log_info = reader.read_file(path) log_info = reader.read_file(path)
if not log_info: if log_info is False:
print('could not read log file ' + path) print('could not read log file ' + path)
return { return {

View File

@ -42,7 +42,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{ {
foreach (string word in objectionalWords) foreach (string word in objectionalWords)
{ {
containsObjectionalWord |= Regex.IsMatch(E.Origin.Name.ToLower(), word); containsObjectionalWord |= Regex.IsMatch(E.Origin.Name.ToLower(), word, RegexOptions.IgnoreCase);
} }
} }
@ -70,7 +70,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
foreach (string word in objectionalWords) foreach (string word in objectionalWords)
{ {
containsObjectionalWord |= Regex.IsMatch(E.Origin.Name.ToLower(), word); containsObjectionalWord |= Regex.IsMatch(E.Origin.Name.ToLower(), word, RegexOptions.IgnoreCase);
// break out early because there's at least one objectional word // break out early because there's at least one objectional word
if (containsObjectionalWord) if (containsObjectionalWord)

View File

@ -16,6 +16,7 @@ using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using System.Linq.Expressions;
namespace IW4MAdmin.Plugins.Stats.Helpers namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
@ -36,6 +37,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId]; public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId];
public Expression<Func<EFRating, bool>> GetRankingFunc(int? serverId = null)
{
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
return (r) => r.ServerId == serverId &&
r.RatingHistory.Client.LastConnection > fifteenDaysAgo &&
r.RatingHistory.Client.Level != Player.Permission.Banned &&
r.Newest &&
r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime;
}
/// <summary> /// <summary>
/// gets a ranking across all servers for given client id /// gets a ranking across all servers for given client id
/// </summary> /// </summary>
@ -52,17 +64,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Select(r => r.Performance) .Select(r => r.Performance)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); var iqClientRanking = context.Set<EFRating>()
var iqClientRating = (from rating in context.Set<EFRating>() .Where(r => r.RatingHistory.ClientId == clientId)
where rating.RatingHistory.Client.ClientId != clientId .Where(GetRankingFunc());
where rating.ServerId == null
where rating.RatingHistory.Client.LastConnection > fifteenDaysAgo return await iqClientRanking.CountAsync() + 1;
where rating.RatingHistory.Client.Level != Player.Permission.Banned
where rating.Newest
where rating.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime
where rating.Performance > clientPerformance
select rating.Ranking);
return await iqClientRating.CountAsync() + 1;
} }
} }
@ -70,28 +76,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{ {
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); // setup the query for the clients within the given rating range
var iqClientRatings = (from rating in context.Set<EFRating>() var iqClientRatings = (from rating in context.Set<EFRating>()
where rating.ServerId == null .Where(GetRankingFunc())
where rating.RatingHistory.Client.LastConnection > fifteenDaysAgo
where rating.RatingHistory.Client.Level != Player.Permission.Banned
where rating.Newest
where rating.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime
orderby rating.Performance descending
select new select new
{ {
Ratings = rating.RatingHistory.Ratings.Where(r => r.ServerId == null), Ratings = rating.RatingHistory.Ratings.Where(r => r.ServerId == null),
rating.RatingHistory.ClientId, rating.RatingHistory.ClientId,
rating.RatingHistory.Client.CurrentAlias.Name, rating.RatingHistory.Client.CurrentAlias.Name,
rating.RatingHistory.Client.LastConnection, rating.RatingHistory.Client.LastConnection,
rating.RatingHistory.Client.TotalConnectionTime,
rating.Performance, rating.Performance,
}) }).OrderByDescending(c => c.Performance)
.Skip(start) .Skip(start)
.Take(count); .Take(count);
#if DEBUG == true
var clientRatingsSql = iqClientRatings.ToSql();
#endif
// materialized list
var clientRatings = await iqClientRatings.ToListAsync(); var clientRatings = await iqClientRatings.ToListAsync();
// get all the client ids that
var clientIds = clientRatings var clientIds = clientRatings
.GroupBy(r => r.ClientId) .GroupBy(r => r.ClientId)
.Select(r => r.First().ClientId) .Select(r => r.First().ClientId)
@ -99,23 +103,19 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var iqStatsInfo = (from stat in context.Set<EFClientStatistics>() var iqStatsInfo = (from stat in context.Set<EFClientStatistics>()
where clientIds.Contains(stat.ClientId) where clientIds.Contains(stat.ClientId)
group stat by stat.ClientId into s
select new select new
{ {
stat.ClientId, ClientId = s.Key,
stat.Kills, Kills = s.Sum(c => c.Kills),
stat.Deaths, Deaths = s.Sum(c => c.Deaths),
stat.TimePlayed, KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed),
TotalTimePlayed = s.Sum(c => c.TimePlayed)
}); });
#if DEBUG == true
var statList = await iqStatsInfo.ToListAsync(); var statsInfoSql = iqStatsInfo.ToSql();
var topPlayers = statList.GroupBy(s => s.ClientId) #endif
.Select(s => new var topPlayers = await iqStatsInfo.ToListAsync();
{
s.First().ClientId,
Kills = s.Sum(c => c.Kills),
Deaths = s.Sum(c => c.Deaths),
KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed)
});
var clientRatingsDict = clientRatings.ToDictionary(r => r.ClientId); var clientRatingsDict = clientRatings.ToDictionary(r => r.ClientId);
var finished = topPlayers.Select(s => new TopStatsInfo() var finished = topPlayers.Select(s => new TopStatsInfo()
@ -131,7 +131,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
PerformanceHistory = clientRatingsDict[s.ClientId].Ratings.Count() > 1 ? PerformanceHistory = clientRatingsDict[s.ClientId].Ratings.Count() > 1 ?
clientRatingsDict[s.ClientId].Ratings.Select(r => r.Performance).ToList() : clientRatingsDict[s.ClientId].Ratings.Select(r => r.Performance).ToList() :
new List<double>() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance }, new List<double>() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance },
TimePlayed = Math.Round(clientRatingsDict[s.ClientId].TotalConnectionTime / 3600.0, 1).ToString("#,##0"), TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"),
}) })
.OrderByDescending(r => r.Performance) .OrderByDescending(r => r.Performance)
.ToList(); .ToList();
@ -316,6 +316,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// get individual client's stats // get individual client's stats
var clientStats = playerStats[pl.ClientId]; var clientStats = playerStats[pl.ClientId];
#if DEBUG == true
await UpdateStatHistory(pl, clientStats);
#endif
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
@ -622,7 +626,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <returns></returns> /// <returns></returns>
private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats) private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats)
{ {
int currentServerTotalPlaytime = clientStats.TimePlayed + (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds;
// don't update their stat history if they haven't played long
if (currentSessionTime < 60)
{
return;
}
int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime;
using (var ctx = new DatabaseContext()) using (var ctx = new DatabaseContext())
{ {
@ -651,25 +663,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Ratings = new List<EFRating>() Ratings = new List<EFRating>()
}; };
if (clientHistory.RatingHistoryId == 0) #region INDIVIDUAL_SERVER_PERFORMANCE
{
ctx.Add(clientHistory);
}
else
{
ctx.Update(clientHistory);
}
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15); var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
// get the client ranking for the current server // get the client ranking for the current server
int individualClientRanking = await ctx.Set<EFRating>() int individualClientRanking = await ctx.Set<EFRating>()
.Where(c => c.ServerId == clientStats.ServerId) .Where(GetRankingFunc(clientStats.ServerId))
.Where(r => r.RatingHistory.Client.LastConnection > fifteenDaysAgo)
.Where(r => r.RatingHistory.Client.Level != Player.Permission.Banned)
.Where(r => r.ActivityAmount > Plugin.Config.Configuration().TopPlayersMinPlayTime)
.Where(c => c.RatingHistory.ClientId != client.ClientId) .Where(c => c.RatingHistory.ClientId != client.ClientId)
.Where(r => r.Newest)
.Where(c => c.Performance > clientStats.Performance) .Where(c => c.Performance > clientStats.Performance)
.CountAsync() + 1; .CountAsync() + 1;
@ -682,10 +681,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
// set the previous newest to false // set the previous newest to false
var ratingToUnsetNewest = clientHistory.Ratings.LastOrDefault(r => r.ServerId == clientStats.ServerId); var ratingToUnsetNewest = clientHistory.Ratings.LastOrDefault(r => r.ServerId == clientStats.ServerId && r.Newest);
if (ratingToUnsetNewest != null) if (ratingToUnsetNewest != null)
{ {
ctx.Entry(ratingToUnsetNewest).State = EntityState.Modified; ctx.Entry(ratingToUnsetNewest).State = EntityState.Modified;
ctx.Entry(ratingToUnsetNewest).Property(p => p.Newest).IsModified = true;
ratingToUnsetNewest.Newest = false; ratingToUnsetNewest.Newest = false;
} }
@ -698,9 +699,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Newest = true, Newest = true,
ServerId = clientStats.ServerId, ServerId = clientStats.ServerId,
RatingHistoryId = clientHistory.RatingHistoryId, RatingHistoryId = clientHistory.RatingHistoryId,
ActivityAmount = currentServerTotalPlaytime ActivityAmount = currentServerTotalPlaytime,
}); });
#endregion
#region OVERALL_RATING
// get other server stats // get other server stats
var clientStatsList = await iqClientStats.ToListAsync(); var clientStatsList = await iqClientStats.ToListAsync();
@ -719,28 +722,27 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
int overallClientRanking = await ctx.Set<EFRating>() int overallClientRanking = await ctx.Set<EFRating>()
.Where(r => r.ServerId == null) .Where(GetRankingFunc())
.Where(r => r.RatingHistory.ClientId != client.ClientId) .Where(r => r.RatingHistory.ClientId != client.ClientId)
.Where(r => r.RatingHistory.Client.LastConnection > fifteenDaysAgo) .Where(r => r.Performance > performanceAverage)
.Where(r => r.RatingHistory.Client.Level != Player.Permission.Banned) .CountAsync() + 1;
.Where(r => r.ActivityAmount > Plugin.Config.Configuration().TopPlayersMinPlayTime)
.Where(r => r.Newest)
.Where(r => r.Performance > performanceAverage)
.CountAsync() + 1;
// limit max average history to 40 // limit max average history to 40
if (clientHistory.Ratings.Count(r => r.ServerId == null) >= 40) if (clientHistory.Ratings.Count(r => r.ServerId == null) >= 40)
{ {
var ratingToRemove = clientHistory.Ratings.First(r => r.ServerId == null); var ratingToRemove = clientHistory.Ratings.First(r => r.ServerId == null);
ctx.Attach(ratingToRemove);
ctx.Entry(ratingToRemove).State = EntityState.Deleted; ctx.Entry(ratingToRemove).State = EntityState.Deleted;
clientHistory.Ratings.Remove(ratingToRemove); clientHistory.Ratings.Remove(ratingToRemove);
} }
// set the previous average newest to false // set the previous average newest to false
ratingToUnsetNewest = clientHistory.Ratings.LastOrDefault(r => r.ServerId == null); ratingToUnsetNewest = clientHistory.Ratings.LastOrDefault(r => r.ServerId == null && r.Newest);
if (ratingToUnsetNewest != null) if (ratingToUnsetNewest != null)
{ {
ctx.Attach(ratingToUnsetNewest);
ctx.Entry(ratingToUnsetNewest).State = EntityState.Modified; ctx.Entry(ratingToUnsetNewest).State = EntityState.Modified;
ctx.Entry(ratingToUnsetNewest).Property(p => p.Newest).IsModified = true;
ratingToUnsetNewest.Newest = false; ratingToUnsetNewest.Newest = false;
} }
@ -755,16 +757,19 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
RatingHistoryId = clientHistory.RatingHistoryId, RatingHistoryId = clientHistory.RatingHistoryId,
ActivityAmount = clientStatsList.Sum(s => s.TimePlayed) ActivityAmount = clientStatsList.Sum(s => s.TimePlayed)
}); });
#endregion
try if (clientHistory.RatingHistoryId == 0)
{ {
await ctx.SaveChangesAsync(); ctx.Add(clientHistory);
} }
// this can happen when the client disconnects without any stat changes
catch (DbUpdateConcurrencyException)
{
else
{
ctx.Update(clientHistory);
} }
await ctx.SaveChangesAsync();
} }
} }

View File

@ -25,6 +25,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public double EloRating { get; set; } public double EloRating { get; set; }
public virtual ICollection<EFHitLocationCount> HitLocations { get; set; } public virtual ICollection<EFHitLocationCount> HitLocations { get; set; }
public double RollingWeightedKDR { get; set; } public double RollingWeightedKDR { get; set; }
public double VisionAverage { get; set; }
[NotMapped] [NotMapped]
public double Performance public double Performance
{ {

View File

@ -1,9 +1,7 @@
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
namespace IW4MAdmin.Plugins.Stats.Models namespace IW4MAdmin.Plugins.Stats.Models
{ {
@ -27,5 +25,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public bool Newest { get; set; } public bool Newest { get; set; }
[Required] [Required]
public int ActivityAmount { get; set; } public int ActivityAmount { get; set; }
[Required]
public DateTime When { get; set; } = DateTime.UtcNow;
} }
} }

View File

@ -1,15 +1,9 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using SharedLibraryCore; using SharedLibraryCore;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebfrontCore.Controllers; using WebfrontCore.Controllers;
@ -23,7 +17,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_TITLE"]; ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_DESC"]; ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_DESC"];
return View("Index", await Plugin.Manager.GetTopStats(0, 10)); return View("Index", await Plugin.Manager.GetTopStats(0, 50));
} }
[HttpGet] [HttpGet]
@ -78,34 +72,4 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
} }
} }
} }
#if DEBUG == true
public static class IQueryableExtensions
{
private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();
private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");
private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator");
private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");
private static readonly PropertyInfo DatabaseDependenciesField = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies");
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider);
var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
var queryModel = modelGenerator.ParseQuery(query.Expression);
var database = (IDatabase)DataBaseField.GetValue(queryCompiler);
var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database);
var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();
modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
var sql = modelVisitor.Queries.First().ToString();
return sql;
}
}
#endif
} }

View File

@ -10,5 +10,5 @@
<script type="text/javascript" src="~/js/loader.js"></script> <script type="text/javascript" src="~/js/loader.js"></script>
<script type="text/javascript" src="~/js/stats.js"></script> <script type="text/javascript" src="~/js/stats.js"></script>
</environment> </environment>
<script>initLoader('/Stats/GetTopPlayersAsync', '#stats_top_players');</script> <script>initLoader('/Stats/GetTopPlayersAsync', '#stats_top_players', 50);</script>
} }

View File

@ -43,7 +43,7 @@ namespace Tests
public void AddAndRemoveClientsViaJoinShouldSucceed() public void AddAndRemoveClientsViaJoinShouldSucceed()
{ {
var server = Manager.GetServers().First(); var server = Manager.GetServers().First();
var waiters = new Queue<SemaphoreSlim>(); var waiters = new Queue<GameEvent>();
int clientStartIndex = 4; int clientStartIndex = 4;
int clientNum = 10; int clientNum = 10;
@ -63,12 +63,12 @@ namespace Tests
}; };
server.Manager.GetEventHandler().AddEvent(e); server.Manager.GetEventHandler().AddEvent(e);
waiters.Enqueue(e.OnProcessed); waiters.Enqueue(e);
} }
while (waiters.Count > 0) while (waiters.Count > 0)
{ {
waiters.Dequeue().Wait(); waiters.Dequeue().OnProcessed.Wait();
} }
Assert.True(server.ClientNum == clientNum, $"client num does not match added client num [{server.ClientNum}:{clientNum}]"); Assert.True(server.ClientNum == clientNum, $"client num does not match added client num [{server.ClientNum}:{clientNum}]");
@ -88,12 +88,12 @@ namespace Tests
}; };
server.Manager.GetEventHandler().AddEvent(e); server.Manager.GetEventHandler().AddEvent(e);
waiters.Enqueue(e.OnProcessed); waiters.Enqueue(e);
} }
while (waiters.Count > 0) while (waiters.Count > 0)
{ {
waiters.Dequeue().Wait(); waiters.Dequeue().OnProcessed.Wait();
} }
Assert.True(server.ClientNum == 0, "there are still clients connected"); Assert.True(server.ClientNum == 0, "there are still clients connected");
@ -103,7 +103,7 @@ namespace Tests
public void AddAndRemoveClientsViaRconShouldSucceed() public void AddAndRemoveClientsViaRconShouldSucceed()
{ {
var server = Manager.GetServers().First(); var server = Manager.GetServers().First();
var waiters = new Queue<SemaphoreSlim>(); var waiters = new Queue<GameEvent>();
int clientIndexStart = 1; int clientIndexStart = 1;
int clientNum = 8; int clientNum = 8;
@ -126,12 +126,12 @@ namespace Tests
}; };
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
waiters.Enqueue(e.OnProcessed); waiters.Enqueue(e);
} }
while (waiters.Count > 0) while (waiters.Count > 0)
{ {
waiters.Dequeue().Wait(); waiters.Dequeue().OnProcessed.Wait();
} }
int actualClientNum = server.GetPlayersAsList().Count(p => p.State == Player.ClientState.Connected); int actualClientNum = server.GetPlayersAsList().Count(p => p.State == Player.ClientState.Connected);
@ -155,12 +155,12 @@ namespace Tests
}; };
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
waiters.Enqueue(e.OnProcessed); waiters.Enqueue(e);
} }
while (waiters.Count > 0) while (waiters.Count > 0)
{ {
waiters.Dequeue().Wait(); waiters.Dequeue().OnProcessed.Wait();
} }
actualClientNum = server.ClientNum; actualClientNum = server.ClientNum;

103
README.md
View File

@ -5,14 +5,26 @@ _______
### About ### About
**IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/33/plutonium-t6), [Pluto IW5](https://forum.plutonium.pw/category/5/plutonium-iw5), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze. **IW4MAdmin** is an administration tool for [IW4x](https://iw4xcachep26muba.onion.link/), [Pluto T6](https://forum.plutonium.pw/category/33/plutonium-t6), [Pluto IW5](https://forum.plutonium.pw/category/5/plutonium-iw5), and most Call of Duty® dedicated servers. It allows complete control of your server; from changing maps, to banning players, **IW4MAdmin** monitors and records activity on your server(s). With plugin support, extending its functionality is a breeze.
### Download ### Download
Latest binary builds are always available at https://raidmax.org/IW4MAdmin Latest binary builds are always available at https://raidmax.org/IW4MAdmin
---
### Setup ### Setup
**IW4MAdmin** requires minimal configuration to run. There is only one prerequisite. **IW4MAdmin** requires minimal effort to get up and running.
#### Prerequisites
* [.NET Core 2.1 Runtime](https://www.microsoft.com/net/download) *or newer* * [.NET Core 2.1 Runtime](https://www.microsoft.com/net/download) *or newer*
1. Extract `IW4MAdmin-<version>.zip` #### Installation
2. Run `StartIW4MAdmin.cmd` 1. Install .NET Core Runtime
2. Extract `IW4MAdmin-<version>.zip`
#### Launching
1. Run `StartIW4MAdmin.cmd` (Windows)
2. Run `StartIW4MAdmin.sh` (Linux)
2. Configure **IW4MAdmin**
### Updating
1. Extract newer version of **IW4MAdmin** into pre-existing **IW4MAdmin** folder and overwrite existing files
- _Your configuration and database will be saved_
---
### Help ### Help
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3) Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues) If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
___ ___
@ -21,45 +33,58 @@ ___
When **IW4MAdmin** is launched for the _first time_, you will be prompted to setup your configuration. When **IW4MAdmin** is launched for the _first time_, you will be prompted to setup your configuration.
`Enable webfront` `Enable webfront`
* Enables you to monitor and control your server(s) through a web interface [defaults to `http://127.0.0.1:1624`] * Enables you to monitor and control your server(s) through a web interface
* Default &mdash; `http://0.0.0.0:1624`
`Enable multiple owners` `Enable multiple owners`
* Enables more than one client to be promoted to level of `Owner` * Enables more than one client to be promoted to level of `Owner`
* Default &mdash; `false`
`Enable stepped privilege hierarchy` `Enable stepped privilege hierarchy`
* Allows privileged clients to promote other clients to the level below their current level * Allows privileged clients to promote other clients to the level below their current level
* Default &mdash; `false`
`Enable custom say name` `Enable custom say name`
* Shows a prefix to every message send by **IW4MAdmin** -- `[Admin] message` * Shows a prefix to every message send by **IW4MAdmin** -- `[Admin] message`
* _This feature requires you specify a custom say name_ * _This feature requires you specify a custom say name_
* Default &mdash; `false`
`Enable social link` `Enable social link`
* Shows a link to your community's social media/website on the webfront * Shows a link to your community's social media/website on the webfront
* Default &mdash; `false`
`Use Custom Encoding Parser` `Use Custom Encoding Parser`
* Allows alternative encodings to be used for parsing game information and events * Allows alternative encodings to be used for parsing game information and events
* **Russian users should use this and then specify** `windows-1251` **as the encoding string** * **Russian users should use this and then specify** `windows-1251` **as the encoding string**
* Default &mdash; `false`
#### Server Configuration #### Server Configuration
After initial configuration is finished, you will be prompted to configure your servers for **IW4MAdmin**. After initial configuration is finished, you will be prompted to configure your servers for **IW4MAdmin**.
`Enter server IP Address` `Enter server IP Address`
* For almost all scenarios `127.0.0.1` is sufficient * For almost all scenarios `127.0.0.1` is sufficient
* Default &mdash; `n/a`
`Enter server port` `Enter server port`
* The port that your server is listening on (can be obtained via `net_port`) * The port that your server is listening on (can be obtained via `net_port`)
* Default &mdash; `n/a`
`Enter server RCon password` `Enter server RCon password`
* The *\(R\)emote (Con)sole* password set in your server configuration (can be obtained via `rcon_password`) * The *\(R\)emote (Con)sole* password set in your server configuration (can be obtained via `rcon_password`)
* Default &mdash; `n/a`
`Use Pluto T6 parser` `Use Pluto T6 parser`
* Used if setting up a server for Plutonium T6 (BO2) * Used if setting up a server for Plutonium T6 (BO2)
* Default &mdash; `false`
`Use Pluto IW5 parser` `Use Pluto IW5 parser`
* Used if setting a server for Plutonium IW5 (MW3) * Used if setting a server for Plutonium IW5 (MW3)
* Default &mdash; `false`
`Enter number of reserved slots` `Enter number of reserved slots`
* The number of client slots reserver for privileged players (unavailable for regular users to occupy) * The number of client slots reserver for privileged players (unavailable for regular users to occupy)
* Default &mdash; `0`
#### Advanced Configuration #### Advanced Configuration
If you wish to further customize your experience of **IW4MAdmin**, the following configuration file(s) will allow you to changes core options using any text-editor. If you wish to further customize your experience of **IW4MAdmin**, the following configuration file(s) will allow you to changes core options using any text-editor.
@ -70,34 +95,50 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
* Specifies the address and port the webfront will listen on. * Specifies the address and port the webfront will listen on.
* The value can be an [IP Address](https://en.wikipedia.org/wiki/IP_address):port or [Domain Name](https://en.wikipedia.org/wiki/Domain_name):port * The value can be an [IP Address](https://en.wikipedia.org/wiki/IP_address):port or [Domain Name](https://en.wikipedia.org/wiki/Domain_name):port
* Example http://gameserver.com:8080 * Example http://gameserver.com:8080
* Default &mdash; `http://0.0.0.0:1624`
`CustomLocale` `CustomLocale`
* Specifies a [locale name](https://msdn.microsoft.com/en-us/library/39cwe7zf.aspx) to use instead of system default * Specifies a [locale name](https://msdn.microsoft.com/en-us/library/39cwe7zf.aspx) to use instead of system default
* Locale must be from the `Equivalent Locale Name` column * Locale must be from the `Equivalent Locale Name` column
* Default &mdash; `windows-1252`
`ConnectionString` `ConnectionString`
* Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite * Specifies the [connection string](https://www.connectionstrings.com/mysql/) to a MySQL server that is used instead of SQLite
* Default &mdash; `null`
`RConPollRate` `RConPollRate`
* Specifies (in milliseconds) how often to poll each server for updates * Specifies (in milliseconds) how often to poll each server for updates
* Default &mdash; `5000`
`Servers` `Servers`
* Specifies the list of servers **IW4MAdmin** will monitor * Specifies the list of servers **IW4MAdmin** will monitor
* Default &mdash; `[]`
* `IPAddress` * `IPAddress`
* Specifies the IP Address of the particular server * Specifies the IP Address of the particular server
* Default &mdash; `n/a`
* `Port` * `Port`
* Specifies the port of the particular server * Specifies the port of the particular server
* Default &mdash; `n/a`
* `Password` * `Password`
* Specifies the `rcon_password` of the particular server * Specifies the `rcon_password` of the particular server
* Default &mdash; `n/a`
* `ManualLogPath`
* Specifies the log path to be used instead of the automatically generated one
* To use the `GameLogServer`, this should be set to the http address that the `GameLogServer` is listening on
* Example &mdash; http://gamelogserver.com/
* `AutoMessages` * `AutoMessages`
* Specifies the list of messages that are broadcasted to the particular server * Specifies the list of messages that are broadcasted to the particular server
* Default &mdash; `null`
* `Rules` * `Rules`
* Specifies the list of rules that apply to the particular server * Specifies the list of rules that apply to the particular server
* Default &mdash; `null`
* `ReservedSlotNumber` * `ReservedSlotNumber`
* Specifies the number of client slots to reserve for privileged users * Specifies the number of client slots to reserve for privileged users
* Default &mdash; `0`
`AutoMessagePeriod` `AutoMessagePeriod`
* Specifies (in seconds) how often messages should be broadcasted to each server * Specifies (in seconds) how often messages should be broadcasted to each server
* Default &mdash; `60`
`AutoMessages` `AutoMessages`
* Specifies the list of messages that are broadcasted to **all** servers * Specifies the list of messages that are broadcasted to **all** servers
@ -254,11 +295,11 @@ ___
- Profane words and warning message can be specified in `ProfanityDetermentSettings.json` - Profane words and warning message can be specified in `ProfanityDetermentSettings.json`
- If a client's name contains a word listed in the settings, they will immediately be kicked - If a client's name contains a word listed in the settings, they will immediately be kicked
####IW4 Script Commands #### IW4 Script Commands
- This plugin provides additional integration to IW4x - This plugin provides additional integration to IW4x
- In order to take advantage of it, copy the `userraw` folder into your IW4x server directory - In order to take advantage of it, copy the `userraw` folder into your IW4x server directory
####VPN Detection [Script Plugin] #### VPN Detection [Script Plugin]
- This plugin detects if a client is using a VPN and kicks them if they are - This plugin detects if a client is using a VPN and kicks them if they are
- To disable this plugin, delete `Plugins\VPNDetection.js` - To disable this plugin, delete `Plugins\VPNDetection.js`
___ ___
@ -282,6 +323,39 @@ ___
`Web Console` `Web Console`
* Allows logged in privileged users to execute commands as if they are in-game * Allows logged in privileged users to execute commands as if they are in-game
--- ---
### Game Log Server
The game log server provides a way to remotely host your server's log over a http rest api.
This server is useful if you plan on running IW4MAdmin on a different machine than the game server
#### Requirements
- [Python 3.6](https://www.python.org/downloads/) or newer
- The following [PIP](https://pypi.org/project/pip/) packages (provided in `requirements.txt`)
```Flask>=1.0.2
aniso8601>=3.0.2
click>=6.7
Flask-RESTful>=0.3.6
itsdangerous>=0.24
Jinja2>=2.10
MarkupSafe>=1.0
pip>=9.0.3
pytz>=2018.5
setuptools>=39.0.1
six>=1.11.0
Werkzeug>=0.14.1
```
#### Installation
1. With Python 3 installed, open up a terminal/command prompt window in the `GameLogServer` folder and execute:
```console
pip install -r requirements.txt
```
2. Allow TCP port 1625 through firewall
* [Windows Instructions](https://www.tomshardware.com/news/how-to-open-firewall-ports-in-windows-10,36451.html)
* [Linux Instructions (iptables)](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-basic-iptables-firewall-on-centos-6#open-up-ports-for-selected-services)
#### Launching
With Python 3 installed, open a terminal/command prompt window open in the `GameServerLog` folder and execute:
```console
python runserver.py
```
---
### Extending Plugins ### Extending Plugins
#### Code #### Code
IW4Madmin functionality can be extended by writing additional plugins in C#. IW4Madmin functionality can be extended by writing additional plugins in C#.
@ -347,15 +421,20 @@ Example http://127.0.0.1
Example https://discordapp.com/api/webhooks/id/token Example https://discordapp.com/api/webhooks/id/token
- `DiscordWebhookInformationUrl` &mdash; [optional] Discord generated URL to send information to; this includes information such as player messages - `DiscordWebhookInformationUrl` &mdash; [optional] Discord generated URL to send information to; this includes information such as player messages
- `NotifyRoleIds` &mdash; [optional] List of [discord role ids](https://discordhelp.net/role-id) to mention when notification hook is sent - `NotifyRoleIds` &mdash; [optional] List of [discord role ids](https://discordhelp.net/role-id) to mention when notification hook is sent
#### Launching #### Launching
With Python installed, open a terminal/command prompt window open in the `Webhook` folder and execute `python DiscordWebhook.py` With Python installed, open a terminal/command prompt window open in the `Webhook` folder and execute:
```console
python DiscordWebhook.py
```
--- ---
## Misc ### Misc
#### Anti-cheat #### Anti-cheat
This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools. This is an [IW4x](https://iw4xcachep26muba.onion.link/) only feature (wider game support planned), that uses analytics to detect aimbots and aim-assist tools.
To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder. To utilize anti-cheat, enable it during setup **and** copy `_customcallbacks.gsc` from `userraw` into your `IW4x Server\userraw\scripts` folder.
The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x. The anti-cheat feature is a work in progress and as such will be constantly tweaked and may not be 100% accurate, however the goal is to deter as many cheaters as possible from IW4x.
#### Database Storage #### Database Storage
By default, all **IW4MAdmin** information is stored in `Database.db`. Should you need to reset your database, this file can simply be deleted. Additionally, this file should be preserved during updates to retain client information. By default, all **IW4MAdmin** information is stored in `Database.db`.
Should you need to reset your database, this file can simply be deleted.
Additionally, this file should be preserved during updates to retain client information.
Setting the `ConnectionString` property in `IW4MAdminSettings.json` will cause **IW4MAdmin** to attempt to use a MySQL connection for database storage. Setting the `ConnectionString` property in `IW4MAdminSettings.json` will cause **IW4MAdmin** to attempt to use a MySQL connection for database storage.

View File

@ -11,6 +11,7 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.RCon.StaticHelpers;
namespace SharedLibraryCore.Commands namespace SharedLibraryCore.Commands
{ {
@ -312,6 +313,7 @@ namespace SharedLibraryCore.Commands
if (P == null) if (P == null)
continue; continue;
// todo: fix spacing // todo: fix spacing
// todo: make this better :)
if (P.Masked) if (P.Masked)
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(Player.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - Player.Permission.User.ToString().Length)); playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(Player.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(Player.Permission.SeniorAdmin.ToString().Length - Player.Permission.User.ToString().Length));
else else
@ -320,6 +322,7 @@ namespace SharedLibraryCore.Commands
if (count == 2 || E.Owner.GetPlayersAsList().Count == 1) if (count == 2 || E.Owner.GetPlayersAsList().Count == 1)
{ {
await E.Origin.Tell(playerList.ToString()); await E.Origin.Tell(playerList.ToString());
await Task.Delay(FloodProtectionInterval);
count = 0; count = 0;
playerList = new StringBuilder(); playerList = new StringBuilder();
continue; continue;
@ -327,6 +330,11 @@ namespace SharedLibraryCore.Commands
count++; count++;
} }
if (playerList.Length > 0)
{
await E.Origin.Tell(playerList.ToString());
}
} }
} }
@ -357,6 +365,7 @@ namespace SharedLibraryCore.Commands
{ {
await E.Origin.Tell("[^3" + C.Name + "^7] " + C.Description); await E.Origin.Tell("[^3" + C.Name + "^7] " + C.Description);
await E.Origin.Tell(C.Syntax); await E.Origin.Tell(C.Syntax);
await Task.Delay(FloodProtectionInterval);
found = true; found = true;
} }
} }
@ -382,6 +391,7 @@ namespace SharedLibraryCore.Commands
await E.Owner.Broadcast(helpResponse.ToString()); await E.Owner.Broadcast(helpResponse.ToString());
else else
await E.Origin.Tell(helpResponse.ToString()); await E.Origin.Tell(helpResponse.ToString());
await Task.Delay(FloodProtectionInterval);
helpResponse = new StringBuilder(); helpResponse = new StringBuilder();
count = 0; count = 0;
} }
@ -564,10 +574,10 @@ namespace SharedLibraryCore.Commands
{ {
foreach (string line in OnlineAdmins(E.Owner).Split(Environment.NewLine)) foreach (string line in OnlineAdmins(E.Owner).Split(Environment.NewLine))
{ {
if (E.Message[0] == '@') var t = E.Message.IsBroadcastCommand() ? E.Owner.Broadcast(line) : E.Origin.Tell(line);
await E.Owner.Broadcast(line); await t;
else
await E.Origin.Tell(line); await Task.Delay(FloodProtectionInterval);
} }
} }
} }
@ -645,6 +655,7 @@ namespace SharedLibraryCore.Commands
$"[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}" : $"[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}" :
$"({P.AliasLink.Children.First(a => a.Name.ToLower().Contains(E.Data.ToLower())).Name})->[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}"; $"({P.AliasLink.Children.First(a => a.Name.ToLower().Contains(E.Data.ToLower())).Name})->[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor(P.Level, localizedLevel)}^7] - {P.IPAddressString} | last seen {Utilities.GetTimePassed(P.LastConnection)}";
await E.Origin.Tell(msg); await E.Origin.Tell(msg);
await Task.Delay(FloodProtectionInterval);
} }
} }
} }
@ -675,10 +686,9 @@ namespace SharedLibraryCore.Commands
foreach (string r in rules) foreach (string r in rules)
{ {
if (E.Message.IsBroadcastCommand()) var t = E.Message.IsBroadcastCommand() ? E.Owner.Broadcast($"- {r}") : E.Origin.Tell($"- {r}");
await E.Owner.Broadcast($"- {r}"); await t;
else await Task.Delay(FloodProtectionInterval);
await E.Origin.Tell($"- {r}");
} }
} }
} }
@ -927,7 +937,10 @@ namespace SharedLibraryCore.Commands
} }
foreach (Report R in E.Owner.Reports) foreach (Report R in E.Owner.Reports)
{
await E.Origin.Tell(String.Format("^5{0}^7->^1{1}^7: {2}", R.Origin.Name, R.Target.Name, R.Reason)); await E.Origin.Tell(String.Format("^5{0}^7->^1{1}^7: {2}", R.Origin.Name, R.Target.Name, R.Reason));
await Task.Delay(FloodProtectionInterval);
}
} }
} }
@ -1054,6 +1067,7 @@ namespace SharedLibraryCore.Commands
foreach (var P in Plugins.PluginImporter.ActivePlugins) foreach (var P in Plugins.PluginImporter.ActivePlugins)
{ {
await E.Origin.Tell(String.Format("^3{0} ^7[v^3{1}^7] by ^5{2}^7", P.Name, P.Version, P.Author)); await E.Origin.Tell(String.Format("^3{0} ^7[v^3{1}^7] by ^5{2}^7", P.Name, P.Version, P.Author));
await Task.Delay(FloodProtectionInterval);
} }
} }
} }

View File

@ -27,7 +27,9 @@ namespace SharedLibraryCore.Database
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { } public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
public DatabaseContext(bool disableTracking = false) public DatabaseContext() { }
public DatabaseContext(bool disableTracking)
{ {
if (disableTracking) if (disableTracking)
{ {

View File

@ -51,7 +51,7 @@ namespace SharedLibraryCore
public GameEvent() public GameEvent()
{ {
OnProcessed = new ManualResetEventSlim(); OnProcessed = new ManualResetEventSlim(false);
Time = DateTime.UtcNow; Time = DateTime.UtcNow;
Id = GetNextEventId(); Id = GetNextEventId();
} }
@ -105,5 +105,7 @@ namespace SharedLibraryCore
queuedEvent.Target.State != Player.ClientState.Connected && queuedEvent.Target.State != Player.ClientState.Connected &&
queuedEvent.Target.NetworkId != 0; queuedEvent.Target.NetworkId != 0;
} }
public static bool IsEventTimeSensitive(GameEvent gameEvent) => gameEvent.Type == EventType.Connect;
} }
} }

View File

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

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddVision : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<double>(
name: "VisionAverage",
table: "EFClientStatistics",
nullable: false,
defaultValue: 0.0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VisionAverage",
table: "EFClientStatistics");
}
}
}

View File

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

View File

@ -0,0 +1,25 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddWhenToRating : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "When",
table: "EFRating",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "When",
table: "EFRating");
}
}
}

View File

@ -198,6 +198,8 @@ namespace SharedLibraryCore.Migrations
b.Property<int>("TimePlayed"); b.Property<int>("TimePlayed");
b.Property<double>("VisionAverage");
b.HasKey("ClientId", "ServerId"); b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId"); b.HasIndex("ServerId");
@ -254,6 +256,8 @@ namespace SharedLibraryCore.Migrations
b.Property<int?>("ServerId"); b.Property<int?>("ServerId");
b.Property<DateTime>("When");
b.HasKey("RatingId"); b.HasKey("RatingId");
b.HasIndex("RatingHistoryId"); b.HasIndex("RatingHistoryId");
@ -475,6 +479,8 @@ namespace SharedLibraryCore.Migrations
b.HasKey("Vector3Id"); b.HasKey("Vector3Id");
b.HasIndex("EFACSnapshotSnapshotId");
b.ToTable("Vector3"); b.ToTable("Vector3");
}); });

View File

@ -146,6 +146,8 @@ namespace SharedLibraryCore.Objects
[NotMapped] [NotMapped]
public DateTime ConnectionTime { get; set; } public DateTime ConnectionTime { get; set; }
[NotMapped] [NotMapped]
public int ConnectionLength => (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds;
[NotMapped]
public Server CurrentServer { get; set; } public Server CurrentServer { get; set; }
[NotMapped] [NotMapped]
public int Score { get; set; } public int Score { get; set; }

View File

@ -35,7 +35,6 @@ namespace SharedLibraryCore.RCon
ILogger Log; ILogger Log;
int FailedSends; int FailedSends;
int FailedReceives; int FailedReceives;
static DateTime LastQuery;
string response; string response;
ManualResetEvent OnConnected; ManualResetEvent OnConnected;
@ -142,14 +141,14 @@ namespace SharedLibraryCore.RCon
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true) public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true)
{ {
// will this really prevent flooding? //// will this really prevent flooding?
if ((DateTime.Now - LastQuery).TotalMilliseconds < 350) //if ((DateTime.Now - LastQuery).TotalMilliseconds < 350)
{ //{
Thread.Sleep(350); // Thread.Sleep(350);
//await Task.Delay(350); // //await Task.Delay(350);
} //}
LastQuery = DateTime.Now; // LastQuery = DateTime.Now;
OnSent.Reset(); OnSent.Reset();
OnReceived.Reset(); OnReceived.Reset();

View File

@ -4,15 +4,45 @@ namespace SharedLibraryCore.RCon
{ {
public static class StaticHelpers public static class StaticHelpers
{ {
/// <summary>
/// defines the type of RCon query sent to a server
/// </summary>
public enum QueryType public enum QueryType
{ {
/// <summary>
/// retrieve the status of a server
/// does not require RCon password
/// </summary>
GET_STATUS, GET_STATUS,
/// <summary>
/// retrieve the information of a server
/// server responds with key/value pairs
/// RCon password is required
/// </summary>
GET_INFO, GET_INFO,
/// <summary>
/// retrieve the value of a DVAR
/// RCon password is required
/// </summary>
DVAR, DVAR,
/// <summary>
/// execute a command
/// RCon password is required
/// </summary>
COMMAND, COMMAND,
} }
/// <summary>
/// line seperator char included in response from the server
/// </summary>
public static char SeperatorChar = (char)int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier); public static char SeperatorChar = (char)int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier);
/// <summary>
/// timeout in seconds to wait for a socket send or receive before giving up
/// </summary>
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 10); public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 10);
/// <summary>
/// interval in milliseconds to wait before sending the next RCon request
/// </summary>
public static readonly int FloodProtectionInterval = 350;
} }
} }

View File

@ -18,16 +18,16 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0-preview1-35029" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.1" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.2" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" /> <PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -12,6 +12,10 @@ using System.Threading.Tasks;
using System.Globalization; using System.Globalization;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Storage;
namespace SharedLibraryCore namespace SharedLibraryCore
{ {
public static class Utilities public static class Utilities
@ -494,5 +498,34 @@ namespace SharedLibraryCore
var response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO); var response = await server.RemoteConnection.SendQueryAsync(RCon.StaticHelpers.QueryType.GET_INFO);
return response.FirstOrDefault(r => r[0] == '\\')?.DictionaryFromKeyValue(); return response.FirstOrDefault(r => r[0] == '\\')?.DictionaryFromKeyValue();
} }
#if DEBUG == true
private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();
private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");
private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator");
private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");
private static readonly PropertyInfo DatabaseDependenciesField = typeof(Microsoft.EntityFrameworkCore.Storage.Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies");
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider);
var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
var queryModel = modelGenerator.ParseQuery(query.Expression);
var database = (IDatabase)DataBaseField.GetValue(queryCompiler);
var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database);
var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();
modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
var sql = modelVisitor.Queries.First().ToString();
return sql;
}
#endif
} }
} }

View File

@ -41,7 +41,7 @@ namespace WebfrontCore.Controllers.API
player.Ping, player.Ping,
State = player.State.ToString(), State = player.State.ToString(),
player.ClientNumber, player.ClientNumber,
ConnectionTime = Math.Round((DateTime.UtcNow - player.ConnectionTime).TotalSeconds, 0), ConnectionTime = player.ConnectionLength,
Level = player.Level.ToLocalizedLevelName(), Level = player.Level.ToLocalizedLevelName(),
}) })
}); });

View File

@ -43,7 +43,7 @@ if ($(loaderResponseId).length === 1) {
*/ */
$('html').bind('mousewheel DOMMouseScroll', function (e) { $('html').bind('mousewheel DOMMouseScroll', function (e) {
var delta = (e.originalEvent.wheelDelta || -e.originalEvent.detail); var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
if (delta < 0 && !hasScrollBar) { if (delta < 0 && !hasScrollBar) {
loadMoreItems(); loadMoreItems();