add most played command

hopefully fixed thread lock?
started work on elo rating
This commit is contained in:
RaidMax 2018-05-15 23:57:37 -05:00
parent 699c19cd4b
commit 4006c09045
20 changed files with 714 additions and 88 deletions

View File

@ -46,7 +46,7 @@ namespace IW4MAdmin.Application.API
FlaggedMessageCount = 0;
E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_REPORT"]).Wait();
E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_REPORT"]).Wait(5000);
Events.Enqueue(new EventInfo(
EventInfo.EventType.ALERT,
EventInfo.EventVersion.IW4MAdmin,

View File

@ -53,3 +53,6 @@ del "%SolutionDir%Publish\Windows\*pdb"
if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config"
del "%SolutionDir%Publish\WindowsPrerelease\*pdb"
echo making start script
@echo dotnet IW4MAdmin.dll > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"

View File

@ -44,7 +44,6 @@ namespace IW4MAdmin.Application
EventApi Api;
GameEventHandler Handler;
ManualResetEventSlim OnEvent;
Timer HeartbeatTimer;
private ApplicationManager()
{
@ -337,66 +336,75 @@ namespace IW4MAdmin.Application
{
var heartbeatState = (HeartbeatState)state;
if (!heartbeatState.Connected)
while (Running)
{
try
if (!heartbeatState.Connected)
{
Heartbeat.Send(this, true).Wait(5000);
heartbeatState.Connected = true;
}
catch (Exception e)
{
heartbeatState.Connected = false;
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
}
}
else
{
try
{
Heartbeat.Send(this).Wait(5000);
}
catch (System.Net.Http.HttpRequestException e)
{
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
}
catch (AggregateException e)
{
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(RestEase.ApiException));
foreach (var ex in exceptions)
try
{
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
Heartbeat.Send(this, true).Wait(5000);
heartbeatState.Connected = true;
}
catch (Exception e)
{
heartbeatState.Connected = false;
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
}
}
else
{
try
{
Heartbeat.Send(this).Wait(5000);
}
catch (System.Net.Http.HttpRequestException e)
{
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
}
catch (AggregateException e)
{
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(RestEase.ApiException));
foreach (var ex in exceptions)
{
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
heartbeatState.Connected = false;
}
}
}
catch (RestEase.ApiException e)
{
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
heartbeatState.Connected = false;
}
}
}
catch (RestEase.ApiException e)
{
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
catch (Exception e)
{
heartbeatState.Connected = false;
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
}
}
}
Task.Delay(30000).Wait();
}
}
public async Task Start()
{
#if !DEBUG
// start heartbeat
HeartbeatTimer = new Timer(SendHeartbeat, new HeartbeatState(), 0, 30000);
#endif
// this needs to be run seperately from the main thread
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
#if !DEBUG
// start heartbeat
Task.Run(() => SendHeartbeat(new HeartbeatState()));
#endif
Task.Run(() => UpdateStatus(null));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
@ -439,7 +447,7 @@ namespace IW4MAdmin.Application
while (Running)
{
// wait for new event to be added
OnEvent.Wait();
OnEvent.Wait(5000);
// todo: sequencially or parallelize?
while ((queuedEvent = Handler.GetNextEvent()) != null)
@ -454,8 +462,6 @@ namespace IW4MAdmin.Application
OnEvent.Reset();
}
#if !DEBUG
HeartbeatTimer.Change(0, Timeout.Infinite);
foreach (var S in _servers)
await S.Broadcast("^1" + Utilities.CurrentLocalization.LocalizationIndex["BROADCAST_OFFLINE"]);
#endif

View File

@ -230,7 +230,9 @@ namespace IW4MAdmin
var e = new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this);
Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.WaitHandle.WaitOne(5000);
// wait until the disconnect event is complete
e.OnProcessed.Wait();
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
Leaving.LastConnection = DateTime.UtcNow;
@ -487,7 +489,7 @@ namespace IW4MAdmin
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin?.Name ?? "ERROR!",
Name = E.Origin.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
@ -505,8 +507,8 @@ namespace IW4MAdmin
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin?.Name ?? "ERROR!",
Message = E.Data,
Name = E.Origin.Name,
Message = E.Data ?? "NULL",
Time = DateTime.UtcNow
});
}

View File

@ -82,7 +82,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
hitLoc.HitOffsetAverage = (float)newAverage;
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset &&
hitLoc.HitCount > 15)
hitLoc.HitCount > 100)
{
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
Log.WriteDebug($"Lifetime Average = {newAverage}");
@ -104,7 +104,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
AngleDifferenceAverage = sessAverage;
if (sessAverage > Thresholds.MaxOffset &&
HitCount > 15)
HitCount > 30)
{
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {sessAverage}");
@ -125,7 +125,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
#endif
}
var currentStrain = Strain.GetStrain(kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
double currentStrain = Strain.GetStrain(isDamage, kill.Damage, kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
currentStrain *= ClientStats.SPM / 179.0;
LastOffset = kill.TimeOffset;
if (currentStrain > ClientStats.MaxStrain)
@ -149,12 +150,26 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
Tracker.ClearChanges();
}
if (Strain.TimesReachedMaxStrain >= 3)
// flag
if (currentStrain > Thresholds.MaxStrainFlag)
{
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
Value = ClientStats.MaxStrain,
Value = currentStrain,
HitCount = HitCount,
Type = DetectionType.Strain
};
}
// ban
if (currentStrain > Thresholds.MaxStrainBan
&& Kills > Thresholds.LowSampleMinKills)
{
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
Value = currentStrain,
HitCount = HitCount,
Type = DetectionType.Strain
};

View File

@ -8,7 +8,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
{
class Strain : ITrackable
{
private static double StrainDecayBase = 0.15;
private const double StrainDecayBase = 0.9;
private double CurrentStrain;
private Vector3 LastAngle;
private double LastDeltaTime;
@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public int TimesReachedMaxStrain { get; private set; }
public double GetStrain(Vector3 newAngle, double deltaTime)
public double GetStrain(bool isDamage, int damage, Vector3 newAngle, double deltaTime)
{
if (LastAngle == null)
LastAngle = newAngle;
@ -26,21 +26,36 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double decayFactor = GetDecay(deltaTime);
CurrentStrain *= decayFactor;
#if DEBUG
Console.WriteLine($"Decay Factor = {decayFactor} ");
#endif
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
LastDistance = distance[0] + distance[1];
// this happens on first kill
if ((distance[0] == 0 && distance[1] == 0) ||
deltaTime == 0 ||
deltaTime == 0 ||
double.IsNaN(CurrentStrain))
{
return CurrentStrain;
}
double newStrain = Math.Pow(distance[0] + distance[1], 0.99) / deltaTime;
if (damage < 100 && isDamage)
{
newStrain *= Math.Pow(damage, 2) / 10000.0;
}
else if (damage > 100)
{
newStrain *= damage / 100.0;
}
CurrentStrain += newStrain;
if (CurrentStrain > Thresholds.MaxStrainFlag)
if (CurrentStrain > Thresholds.MaxStrainBan)
TimesReachedMaxStrain++;
LastAngle = newAngle;
@ -52,6 +67,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return $"Strain - {CurrentStrain}, Angle - {LastAngle}, Delta Time - {LastDeltaTime}, Distance - {LastDistance}";
}
private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, deltaTime / 1000.0);
private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, Math.Pow(2.0, deltaTime / 250.0) / 1000.0);
}
}

View File

@ -27,9 +27,9 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2;
public const double MaxStrainBan = 0.4399;
public const double MaxStrainBan = 2.5;
public const double MaxOffset = 1.2;
public const double MaxStrainFlag = 1;
public const double MaxStrainFlag = 2.0;
public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills);

View File

@ -0,0 +1,75 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using IW4MAdmin.Plugins.Stats.Models;
using SharedLibraryCore.Database;
using System.Collections.Generic;
namespace IW4MAdmin.Plugins.Stats.Commands
{
class MostPlayed : Command
{
public static async Task<List<string>> GetMostPlayed(Server s)
{
int serverId = s.GetHashCode();
List<string> mostPlayed = new List<string>()
{
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
};
using (var db = new DatabaseContext())
{
db.ChangeTracker.AutoDetectChangesEnabled = false;
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
var iqStats = (from stats in db.Set<EFClientStatistics>()
join client in db.Clients
on stats.ClientId equals client.ClientId
join alias in db.Aliases
on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId
where client.Level != Player.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending
select new
{
alias.Name,
client.TotalConnectionTime,
stats.Kills
})
.Take(5);
var iqList = await iqStats.ToListAsync();
mostPlayed.AddRange(iqList.Select(stats =>
$"^3{stats.Name}^7 - ^5{stats.Kills} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"]} | ^5{Utilities.GetTimePassed(DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime), false)} ^7{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"].ToLower()}"));
}
return mostPlayed;
}
public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", Player.Permission.User, false) { }
public override async Task ExecuteAsync(GameEvent E)
{
var topStats = await GetMostPlayed(E.Owner);
if (!E.Message.IsBroadcastCommand())
{
foreach (var stat in topStats)
await E.Origin.Tell(stat);
}
else
{
foreach (var stat in topStats)
await E.Owner.Broadcast(stat);
}
}
}
}

View File

@ -40,10 +40,19 @@ namespace IW4MAdmin.Plugins.Stats.Commands
where client.Level != Player.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
orderby stats.Skill descending
select $"^3{alias.Name}^7 - ^5{stats.KDR} ^7KDR | ^5{stats.Skill} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_SKILL"]}")
select new
{
stats.KDR,
stats.Skill,
stats.EloRating,
alias.Name
})
.Take(5);
topStatsText.AddRange(await iqStats.ToListAsync());
var statsList = (await iqStats.ToListAsync())
.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Skill} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_SKILL"]} | ^5{stats.EloRating} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOPSTATS_RATING"]}");
topStatsText.AddRange(statsList);
}
// no one qualified

View File

@ -53,13 +53,13 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (E.Target != null)
{
pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId).First();
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Skill} ^7{loc["PLUGINS_STATS_TEXT_SKILL"]}";
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Skill} ^7{loc["PLUGINS_STATS_TEXT_SKILL"]} | ^5{pStats.EloRating} ^7{loc["PLUGINS_STATS_COMMANDS_TOPSTATS_RATING"].ToUpper()}";
}
else
{
pStats = pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId).First();
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Skill} ^7{loc["PLUGINS_STATS_TEXT_SKILL"]}";
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Skill} ^7{loc["PLUGINS_STATS_TEXT_SKILL"]} | ^5{pStats.EloRating} ^7{loc["PLUGINS_STATS_COMMANDS_TOPSTATS_RATING"].ToUpper()}";
}
if (E.Message.IsBroadcastCommand())

View File

@ -1,13 +1,6 @@
using SharedLibraryCore;
using IW4MAdmin.Plugins.Stats.Cheat;
using IW4MAdmin.Plugins.Stats.Cheat;
using IW4MAdmin.Plugins.Stats.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Services;
namespace IW4MAdmin.Plugins.Stats.Helpers
{

View File

@ -118,6 +118,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ServerId = serverId,
Skill = 0.0,
SPM = 0.0,
EloRating = 200.0,
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
@ -145,6 +146,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
//await statsSvc.ClientStatSvc.SaveChangesAsync();
}
if (clientStats.EloRating == 0.0)
{
clientStats.EloRating = clientStats.Skill;
}
// set these on connecting
clientStats.LastActive = DateTime.UtcNow;
clientStats.LastStatCalculation = DateTime.UtcNow;
@ -479,6 +485,30 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// process the attacker's stats after the kills
attackerStats = UpdateStats(attackerStats);
// calulate elo
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1)
{
double attackerLobbyRating = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Average(cs => cs.Value.EloRating);
double victimLobbyRating = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != victimStats.ClientId)
.Average(cs => cs.Value.EloRating);
double attackerEloDifference = Math.Log(attackerLobbyRating) - Math.Log(attackerStats.EloRating);
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / 3.0));
double victimEloDifference = Math.Log(victimLobbyRating) - Math.Log(victimStats.EloRating);
double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference / 3.0));
attackerStats.EloRating += 24.0 * (1 - winPercentage);
victimStats.EloRating -= 24.0 * winPercentage;
attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2));
victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2));
}
// update after calculation
attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds;
victimStats.TimePlayed += (int)(DateTime.UtcNow - victimStats.LastActive).TotalSeconds;

View File

@ -22,9 +22,8 @@ namespace IW4MAdmin.Plugins.Stats.Models
public int Kills { get; set; }
[Required]
public int Deaths { get; set; }
public double EloRating { get; set; }
public virtual ICollection<EFHitLocationCount> HitLocations { get; set; }
[NotMapped]
public double KDR
{

View File

@ -114,7 +114,9 @@ namespace IW4MAdmin.Plugins.Stats
int kills = clientStats.Sum(c => c.Kills);
int deaths = clientStats.Sum(c => c.Deaths);
double kdr = Math.Round(kills / (double)deaths, 2);
double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Where(c => c.Skill > 0).Count(), 2);
var validSkillValues = clientStats.Where(c => c.Skill > 0);
int skillPlayTime = validSkillValues.Sum(s => s.TimePlayed);
double skill = Math.Round(validSkillValues.Sum(c => c.Skill * c.TimePlayed / skillPlayTime), 2);
double spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Where(c => c.SPM > 0).Count(), 1);
return new List<ProfileMeta>()
@ -241,7 +243,7 @@ namespace IW4MAdmin.Plugins.Stats
}).ToList();
messageMeta.Add(new ProfileMeta()
{
Key = "Messages",
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = messages.Count
});

View File

@ -0,0 +1,436 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using SharedLibraryCore.Database;
using SharedLibraryCore.Objects;
using System;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20180516023249_AddEloField")]
partial class AddEloField
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
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<int>("HitLoc");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<int>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
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.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>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
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.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("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.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.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
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<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.ToTable("Vector3");
});
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.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.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.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);
});
#pragma warning restore 612, 618
}
}
}

View File

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

View File

@ -102,6 +102,8 @@ namespace SharedLibraryCore.Migrations
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");

View File

@ -250,7 +250,7 @@ namespace SharedLibraryCore.RCon
if (FailedReceives >= 4)
{
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} {socketConnection.RemoteEndPoint}");
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} {socketConnection.RemoteEndPoint.ToString()}");
}
}
@ -258,7 +258,7 @@ namespace SharedLibraryCore.RCon
{
if (FailedReceives >= 4)
{
Log.WriteVerbose($"Resumed receive RCon connection from {socketConnection.RemoteEndPoint}");
Log.WriteVerbose($"Resumed receive RCon connection from {socketConnection.RemoteEndPoint.ToString()}");
FailedReceives = 0;
}
}

View File

@ -80,7 +80,10 @@
</div>
</div>
<div id="profile_meta" class="text-center text-sm-right pt-2 mt-md-4 pt-md-3 mr-4 pr-4 mr-md-0 ml-4 pl-4 ml-md-0 pr-md-0 pl-md-0">
<div class="profile-meta-entry">
<span class="profile-meta-value text-primary">@Model.ConnectionCount</span>
<span class="profile-meta-value text-muted"> @loc["WEBFRONT_CLIENT_META_CONNECTIONS"]</span>
</div>
</div>
</div>

View File

@ -8,19 +8,24 @@
@{
for (int i = 0; i < Model.ChatHistory.Count; i++)
{
string message = @Model.ChatHistory[i].Message;
if (Model.ChatHistory[i] == null ||
Model.ChatHistory[i].Message == null ||
Model.ChatHistory[i].Name == null)
{
continue;
}
if (Model.ChatHistory[i].Message == "CONNECTED")
{
<span class="text-light"><span class="oi oi-account-login mr-2 text-success"> </span>@Model.ChatHistory[i].Name</span><br />
}
if (Model.ChatHistory[i].Message == "DISCONNECTED")
{
<span class="text-light"><span class="oi oi-account-logout mr-2 text-danger"> </span>@Model.ChatHistory[i].Name</span><br />
}
if (Model.ChatHistory[i].Message != "CONNECTED" && Model.ChatHistory[i].Message != "DISCONNECTED")
{
<span class="text-light">@Model.ChatHistory[i].Name</span><span> &mdash; @message.Substring(0, Math.Min(65, message.Length)) </span><br />
<span class="text-light">@Model.ChatHistory[i].Name</span><span> &mdash; @Model.ChatHistory[i].Message.Substring(0, Math.Min(65, Model.ChatHistory[i].Message.Length)) </span><br />
}
}
}
@ -55,7 +60,13 @@
@{
for (int i = 0; i < Model.ChatHistory.Count; i++)
{
string message = @Model.ChatHistory[i].Message;
if (Model.ChatHistory[i] == null ||
Model.ChatHistory[i].Message == null ||
Model.ChatHistory[i].Name == null)
{
continue;
}
if (Model.ChatHistory[i].Message == "CONNECTED")
{
<span class="text-light"><span class="oi oi-account-login mr-2 text-success"> </span>@Model.ChatHistory[i].Name</span><br />
@ -67,7 +78,7 @@
}
if (Model.ChatHistory[i].Message != "CONNECTED" && Model.ChatHistory[i].Message != "DISCONNECTED")
{
<span class="text-light">@Model.ChatHistory[i].Name</span><span> &mdash; @message.Substring(0, Math.Min(65, message.Length)) </span><br />
<span class="text-light">@Model.ChatHistory[i].Name</span><span> &mdash; @Model.ChatHistory[i].Message.Substring(0, Math.Min(65, Model.ChatHistory[i].Message.Length)) </span><br />
}
}
}