added top player stats

fix for some commands returning multiple matches found when target not required
This commit is contained in:
RaidMax 2018-05-28 20:30:31 -05:00
parent 897ec0d0c1
commit 2204686b08
137 changed files with 426 additions and 41 deletions

4
.gitignore vendored
View File

@ -223,4 +223,6 @@ global.min.js
bootstrap-custom.css
bootstrap-custom.min.css
**/Master/static
**/Master/dev_env
**/Master/dev_env
/WebfrontCore/Views/Plugins/Stats
/WebfrontCore/wwwroot/images/icons

View File

@ -20,6 +20,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using IW4MAdmin.Application.API.Master;
using System.Reflection;
namespace IW4MAdmin.Application
{
@ -510,5 +511,7 @@ namespace IW4MAdmin.Application
{
OnEvent.Set();
}
public IList<Assembly> GetPluginAssemblies() => SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
}
}

View File

@ -308,7 +308,7 @@ namespace IW4MAdmin
List<Player> matchingPlayers;
if (E.Target == null) // Find active player including quotes (multiple words)
if (E.Target == null && C.RequiresTarget) // Find active player including quotes (multiple words)
{
matchingPlayers = GetClientByName(E.Data.Trim());
if (matchingPlayers.Count > 1)
@ -333,7 +333,7 @@ namespace IW4MAdmin
}
}
if (E.Target == null) // Find active player as single word
if (E.Target == null && C.RequiresTarget) // Find active player as single word
{
matchingPlayers = GetClientByName(Args[0]);
if (matchingPlayers.Count > 1)

View File

@ -1,5 +1,8 @@
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IW4ScriptCommands.Commands
@ -12,6 +15,48 @@ namespace IW4ScriptCommands.Commands
public override async Task ExecuteAsync(GameEvent E)
{
List<string> teamAssignments = new List<string>();
var clients = E.Owner.GetPlayersAsList().Select(c => new
{
Num = c.ClientNumber,
Elo = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).EloRating,
CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team
})
.OrderByDescending(c => c.Elo)
.ToList();
int team = 0;
for (int i = 0; i < clients.Count(); i++)
{
if (i == 0)
{
team = 1;
continue;
}
if (i == 1)
{
team = 2;
continue;
}
if (i == 2)
{
team = 2;
continue;
}
if (i % 2 == 0)
{
if (team == 1)
team = 2;
else
team = 1;
}
teamAssignments.Add($"{clients[i].Num},{team}");
}
string args = string.Join(",", teamAssignments);
await E.Owner.SetDvarAsync("sv_iw4madmin_commandargs", args);
await E.Owner.ExecuteCommandAsync("sv_iw4madmin_command balance");
await E.Origin.Tell("Balance command sent");
}

View File

@ -13,6 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
<ProjectReference Include="..\Stats\Stats.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -27,7 +27,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2;
public const double MaxStrainBan = 0.4;
public const double MaxStrainBan = 1.12;
public const double MaxOffset = 1.2;
public const double MaxStrainFlag = 0.36;

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace IW4MAdmin.Plugins.Stats.Config
{
class StatsConfiguration : IBaseConfiguration
public class StatsConfiguration : IBaseConfiguration
{
public bool EnableAntiCheat { get; set; }
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }

View File

@ -11,6 +11,9 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.Commands;
using IW4MAdmin.Plugins.Stats.Models;
using System.Text.RegularExpressions;
using IW4MAdmin.Plugins.Stats.Web.Dtos;
using SharedLibraryCore.Database;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Plugins.Stats.Helpers
{
@ -36,6 +39,90 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Servers = null;
}
public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId];
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count)
{
using (var context = new DatabaseContext())
{
context.ChangeTracker.AutoDetectChangesEnabled = false;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
var iqClientIds = (from stat in context.Set<EFClientStatistics>()
join client in context.Clients
on stat.ClientId equals client.ClientId
#if !DEBUG
where stat.TimePlayed >= 3600
where client.Level != Player.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
where stat.Performance > 60
#endif
group stat by stat.ClientId into s
orderby s.Average(cs => cs.Performance) descending
select s.First().ClientId)
.Skip(start)
.Take(count);
var clientIds = await iqClientIds.ToListAsync();
var iqStats = (from stat in context.Set<EFClientStatistics>()
join client in context.Clients
on stat.ClientId equals client.ClientId
where clientIds.Contains(client.ClientId)
select new
{
client.CurrentAlias.Name,
client.ClientId,
stat.Kills,
stat.Deaths,
stat.EloRating,
stat.Skill,
stat.TimePlayed,
client.LastConnection,
client.TotalConnectionTime
});
var stats = await iqStats.ToListAsync();
var groupedSelection = stats.GroupBy(s => s.ClientId).Select(s =>
new TopStatsInfo()
{
Name = s.Select(c => c.Name).FirstOrDefault(),
// weighted based on time played
Performance = Math.Round
(s
.Where(c => (c.Skill + c.EloRating) / 2.0 > 0)
.Sum(c => (c.Skill + c.EloRating) / 2.0 * c.TimePlayed) /
s.Where(c => (c.Skill + c.EloRating) / 2.0 > 0)
.Sum(c => c.TimePlayed), 2),
// ditto
KDR = Math.Round(s
.Where(c => c.Deaths > 0)
.Sum(c => ((c.Kills / (double)c.Deaths) * c.TimePlayed) /
s.Where(d => d.Deaths > 0)
.Sum(d => d.TimePlayed)), 2),
ClientId = s.Select(c => c.ClientId).FirstOrDefault(),
Deaths = s.Sum(cs => cs.Deaths),
Kills = s.Sum(cs => cs.Kills),
LastSeen = Utilities.GetTimePassed(s.First().LastConnection, false),
TimePlayed = Math.Round(s.First().TotalConnectionTime / 3600.0, 1).ToString("#,##0"),
});
var statList = groupedSelection.OrderByDescending(s => s.Performance).ToList();
// set the ranking numerically
int i = start + 1;
foreach (var stat in statList)
{
stat.Ranking = i;
i++;
}
return statList;
}
}
/// <summary>
/// Add a server to the StatManager server pool
/// </summary>
@ -495,35 +582,35 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calulate elo
if (Servers[attackerStats.ServerId].PlayerStats.Count > 1)
{
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs =>
Servers[attackerStats.ServerId].IsTeamBased ?
cs.Value.Team != attackerStats.Team :
cs.Value.Team != IW4Info.Team.Spectator)
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ?
validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) :
attackerStats.EloRating;
var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != victimStats.ClientId)
.Where(cs =>
Servers[attackerStats.ServerId].IsTeamBased ?
cs.Value.Team != victimStats.Team :
cs.Value.Team != IW4Info.Team.Spectator)
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs =>
Servers[attackerStats.ServerId].IsTeamBased ?
cs.Value.Team != attackerStats.Team :
cs.Value.Team != IW4Info.Team.Spectator)
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/
double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ?
validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) :
attackerStats.EloRating;
var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != victimStats.ClientId)
.Where(cs =>
Servers[attackerStats.ServerId].IsTeamBased ?
cs.Value.Team != victimStats.Team :
cs.Value.Team != IW4Info.Team.Spectator)
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/
double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - Math.Log(Math.Max(1, attackerStats.EloRating));
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
// double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating));
// double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E));
// double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating));
// double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E));
attackerStats.EloRating += 6.0 * (1 - winPercentage);
victimStats.EloRating -= 6.0 * (1 - winPercentage);

View File

@ -16,7 +16,7 @@ using IW4MAdmin.Plugins.Stats.Models;
namespace IW4MAdmin.Plugins.Stats
{
class Plugin : IPlugin
public class Plugin : IPlugin
{
public string Name => "Simple Stats";

View File

@ -14,12 +14,9 @@
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup>
<ItemGroup>
<None Remove="Cheat\Strain.cs~RF16f7b3.TMP" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
<ProjectReference Include="..\..\WebfrontCore\WebfrontCore.csproj" />
</ItemGroup>
<ItemGroup>
@ -30,4 +27,8 @@
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;" />
</Target>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="xcopy /E /K /Y /C /I &quot;$(ProjectDir)Web\Views&quot; &quot;$(SolutionDir)WebfrontCore\Views\Plugins&quot;&#xD;&#xA;xcopy /E /K /Y /C /I &quot;$(ProjectDir)Web\wwwroot\images&quot; &quot;$(SolutionDir)WebfrontCore\wwwroot\images&quot;" />
</Target>
</Project>

View File

@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using WebfrontCore.Controllers;
namespace IW4MAdmin.Plugins.Stats.Web.Controllers
{
public class StatsController : BaseController
{
[HttpGet]
public async Task<IActionResult> TopPlayersAsync()
{
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_DESC"];
return View("Index", await Plugin.Manager.GetTopStats(0, 15));
}
[HttpGet]
public async Task<IActionResult> GetTopPlayersAsync(int count, int offset)
{
return View("_List", await Plugin.Manager.GetTopStats(offset, count));
}
}
}

View File

@ -0,0 +1,20 @@
using SharedLibraryCore.Dtos;
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Plugins.Stats.Web.Dtos
{
public class TopStatsInfo : SharedInfo
{
public int Ranking { get; set; }
public string Name { get; set; }
public int ClientId { get; set; }
public double KDR { get; set; }
public double Performance { get; set; }
public string TimePlayed { get; set; }
public string LastSeen { get; set; }
public int Kills { get; set; }
public int Deaths { get; set; }
}
}

View File

@ -0,0 +1,13 @@
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
<h4 class="pb-2 text-center ">@ViewBag.Title</h4>
<div id="stats_top_players" class="row border-top border-bottom">
@await Html.PartialAsync("_List", Model)
</div>
@section scripts {
<environment include="Development">
<script type="text/javascript" src="~/js/loader.js"></script>
</environment>
<script>initLoader('/Stats/GetTopPlayersAsync', '#stats_top_players');</script>
}

View File

@ -0,0 +1,50 @@
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
@{
Layout = null;
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set;
double getDeviation(double deviations) => Math.Pow(Math.E, 5.0813 + (deviations * 0.8694));
string rankIcon(double elo)
{
if (elo >= getDeviation(-1) && elo < getDeviation(-0.25))
return "0_no-place/menu_div_no_place.png";
if (elo >= getDeviation(-0.25) && elo < getDeviation(0.25))
return "1_iron/menu_div_iron_sub03.png";
if (elo >= getDeviation(0.25) && elo < getDeviation(0.6875))
return "2_bronze/menu_div_bronze_sub03.png";
if (elo >= getDeviation(0.6875) && elo < getDeviation(1))
return "3_silver/menu_div_silver_sub03.png";
if (elo >= getDeviation(1) && elo < getDeviation(1.25))
return "4_gold/menu_div_gold_sub03.png";
if (elo >= getDeviation(1.25) && elo < getDeviation(1.5))
return "5_platinum/menu_div_platinum_sub03.png";
if (elo >= getDeviation(1.5) && elo < getDeviation(1.75))
return "6_semipro/menu_div_semipro_sub03.png";
if (elo >= getDeviation(1.75))
return "7_pro/menu_div_pro_sub03.png";
return "0_no-place/menu_div_no_place.png";
}
}
<table class="table table-striped mb-0" style="background-color:rgba(0, 0, 0, 0.1)">
@foreach (var stat in Model)
{
<tr>
<td style="vertical-align: middle">
<div class="">
<h2 class="text-muted">#@stat.Ranking &mdash; @Html.ActionLink(stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })</h2>
<span class="text-primary">@stat.Performance</span><span class="text-muted"> @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]</span><br />
<span class="text-primary">@stat.KDR</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KDR"]</span>
<span class="text-primary">@stat.Kills</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KILLS"]</span>
<span class="text-primary">@stat.Deaths</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_DEATHS"]</span><br />
<span class="text-muted">@loc["WEBFRONT_PROFILE_PLAYER"]</span> <span class="text-primary"> @stat.TimePlayed </span><span class="text-muted">@loc["GLOBAL_HOURS"]</span><br />
<span class="text-muted">@loc["WEBFRONT_PROFILE_LSEEN"]</span><span class="text-primary"> @stat.LastSeen </span><span class="text-muted">@loc["WEBFRONT_PENALTY_TEMPLATE_AGO"]</span>
</div>
</td>
<td class="text-right ml-0 pl-0" style="vertical-align: middle">
<div>
<img src="/images/icons/@rankIcon(stat.Performance)" />
</div>
</td>
</tr>
}
</table>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

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