Compare commits
9 Commits
release/pr
...
feature/zo
Author | SHA1 | Date | |
---|---|---|---|
|
53320c5076 | ||
|
e9c1d37cb5 | ||
|
108aac83a5 | ||
|
3c9a1f62a3 | ||
|
02eca5637f | ||
|
7d67a3dfc9 | ||
|
0d6aaa1d9d | ||
|
5ffe293455 | ||
|
f8f98578ea |
@ -9,6 +9,7 @@ using Data.Models.Client.Stats;
|
|||||||
using Data.Models.Client.Stats.Reference;
|
using Data.Models.Client.Stats.Reference;
|
||||||
using Data.Models.Misc;
|
using Data.Models.Misc;
|
||||||
using Data.Models.Server;
|
using Data.Models.Server;
|
||||||
|
using Data.Models.Zombie;
|
||||||
|
|
||||||
namespace Data.Context
|
namespace Data.Context
|
||||||
{
|
{
|
||||||
@ -47,6 +48,16 @@ namespace Data.Context
|
|||||||
public DbSet<EFServerSnapshot> ServerSnapshots { get;set; }
|
public DbSet<EFServerSnapshot> ServerSnapshots { get;set; }
|
||||||
public DbSet<EFClientConnectionHistory> ConnectionHistory { get; set; }
|
public DbSet<EFClientConnectionHistory> ConnectionHistory { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Zombie
|
||||||
|
|
||||||
|
public DbSet<ZombieMatch> ZombieMatches { get; set; }
|
||||||
|
public DbSet<ZombieMatchClientStat> ZombieMatchClientStats { get; set; }
|
||||||
|
public DbSet<ZombieRoundClientStat> ZombieRoundClientStats { get; set; }
|
||||||
|
public DbSet<ZombieAggregateClientStat> ZombieClientStatAggregates { get; set; }
|
||||||
|
public DbSet<ZombieClientStatRecord> ZombieClientStatRecords { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void SetAuditColumns()
|
private void SetAuditColumns()
|
||||||
@ -62,10 +73,6 @@ namespace Data.Context
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DatabaseContext(DbContextOptions options) : base(options)
|
protected DatabaseContext(DbContextOptions options) : base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -161,6 +168,13 @@ namespace Data.Context
|
|||||||
modelBuilder.Entity<EFPenaltyIdentifier>().ToTable("EFPenaltyIdentifiers");
|
modelBuilder.Entity<EFPenaltyIdentifier>().ToTable("EFPenaltyIdentifiers");
|
||||||
modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot));
|
modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot));
|
||||||
modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory));
|
modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory));
|
||||||
|
|
||||||
|
modelBuilder.Entity(typeof(ZombieMatch)).ToTable($"EF{nameof(ZombieMatch)}");
|
||||||
|
modelBuilder.Entity(typeof(ZombieMatchClientStat)).ToTable($"EF{nameof(ZombieMatchClientStat)}");
|
||||||
|
modelBuilder.Entity(typeof(ZombieRoundClientStat)).ToTable($"EF{nameof(ZombieRoundClientStat)}");
|
||||||
|
modelBuilder.Entity(typeof(ZombieAggregateClientStat)).ToTable($"EF{nameof(ZombieAggregateClientStat)}");
|
||||||
|
modelBuilder.Entity(typeof(ZombieClientStat)).ToTable($"EF{nameof(ZombieClientStat)}");
|
||||||
|
modelBuilder.Entity(typeof(ZombieClientStatRecord)).ToTable($"EF{nameof(ZombieClientStatRecord)}");
|
||||||
|
|
||||||
Models.Configuration.StatsModelConfiguration.Configure(modelBuilder);
|
Models.Configuration.StatsModelConfiguration.Configure(modelBuilder);
|
||||||
|
|
||||||
|
1984
Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.Designer.cs
generated
Normal file
1984
Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
297
Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.cs
Normal file
297
Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.cs
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
public partial class AddZombieStatsInitial : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "PerformanceBucket",
|
||||||
|
table: "EFServers",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "PerformanceBucket",
|
||||||
|
table: "EFClientRankingHistory",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EFZombieMatch",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ZombieMatchId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
MapId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
ServerId = table.Column<long>(type: "INTEGER", nullable: true),
|
||||||
|
ClientsCompleted = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
MatchStartDate = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
MatchEndDate = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
EFClientClientId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
CreatedDateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
UpdatedDateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EFZombieMatch", x => x.ZombieMatchId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieMatch_EFClients_EFClientClientId",
|
||||||
|
column: x => x.EFClientClientId,
|
||||||
|
principalTable: "EFClients",
|
||||||
|
principalColumn: "ClientId");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieMatch_EFMaps_MapId",
|
||||||
|
column: x => x.MapId,
|
||||||
|
principalTable: "EFMaps",
|
||||||
|
principalColumn: "MapId");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieMatch_EFServers_ServerId",
|
||||||
|
column: x => x.ServerId,
|
||||||
|
principalTable: "EFServers",
|
||||||
|
principalColumn: "ServerId");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EFZombieClientStat",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ZombieClientStatId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
MatchId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Kills = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Deaths = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
DamageDealt = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
DamageReceived = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Headshots = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Melees = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Downs = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Revives = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
PointsEarned = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
|
PointsSpent = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
|
PerksConsumed = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
PowerupsGrabbed = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
CreatedDateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
UpdatedDateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EFZombieClientStat", x => x.ZombieClientStatId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieClientStat_EFClients_ClientId",
|
||||||
|
column: x => x.ClientId,
|
||||||
|
principalTable: "EFClients",
|
||||||
|
principalColumn: "ClientId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieClientStat_EFZombieMatch_MatchId",
|
||||||
|
column: x => x.MatchId,
|
||||||
|
principalTable: "EFZombieMatch",
|
||||||
|
principalColumn: "ZombieMatchId");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EFZombieAggregateClientStat",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ZombieClientStatId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
AverageKillsPerDown = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
AverageDowns = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
AverageRevives = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
HeadshotPercentage = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
AlivePercentage = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
AverageMelees = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
AverageRoundReached = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
AveragePoints = table.Column<double>(type: "REAL", nullable: false),
|
||||||
|
HighestRound = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
TotalRoundsPlayed = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
TotalMatchesPlayed = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
TotalMatchesCompleted = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
EFClientClientId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EFZombieAggregateClientStat", x => x.ZombieClientStatId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieAggregateClientStat_EFClients_EFClientClientId",
|
||||||
|
column: x => x.EFClientClientId,
|
||||||
|
principalTable: "EFClients",
|
||||||
|
principalColumn: "ClientId");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieAggregateClientStat_EFZombieClientStat_ZombieClientStatId",
|
||||||
|
column: x => x.ZombieClientStatId,
|
||||||
|
principalTable: "EFZombieClientStat",
|
||||||
|
principalColumn: "ZombieClientStatId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EFZombieMatchClientStat",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ZombieClientStatId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
EFClientClientId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EFZombieMatchClientStat", x => x.ZombieClientStatId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieMatchClientStat_EFClients_EFClientClientId",
|
||||||
|
column: x => x.EFClientClientId,
|
||||||
|
principalTable: "EFClients",
|
||||||
|
principalColumn: "ClientId");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieMatchClientStat_EFZombieClientStat_ZombieClientStatId",
|
||||||
|
column: x => x.ZombieClientStatId,
|
||||||
|
principalTable: "EFZombieClientStat",
|
||||||
|
principalColumn: "ZombieClientStatId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EFZombieRoundClientStat",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ZombieClientStatId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
StartTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
EndTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
Duration = table.Column<TimeSpan>(type: "TEXT", nullable: true),
|
||||||
|
TimeAlive = table.Column<TimeSpan>(type: "TEXT", nullable: true),
|
||||||
|
RoundNumber = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Points = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
EFClientClientId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EFZombieRoundClientStat", x => x.ZombieClientStatId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieRoundClientStat_EFClients_EFClientClientId",
|
||||||
|
column: x => x.EFClientClientId,
|
||||||
|
principalTable: "EFClients",
|
||||||
|
principalColumn: "ClientId");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieRoundClientStat_EFZombieClientStat_ZombieClientStatId",
|
||||||
|
column: x => x.ZombieClientStatId,
|
||||||
|
principalTable: "EFZombieClientStat",
|
||||||
|
principalColumn: "ZombieClientStatId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EFZombieClientStatRecord",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ZombieClientStatRecordId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Type = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Value = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ClientId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
RoundId = table.Column<long>(type: "INTEGER", nullable: true),
|
||||||
|
CreatedDateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
UpdatedDateTime = table.Column<DateTimeOffset>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EFZombieClientStatRecord", x => x.ZombieClientStatRecordId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieClientStatRecord_EFClients_ClientId",
|
||||||
|
column: x => x.ClientId,
|
||||||
|
principalTable: "EFClients",
|
||||||
|
principalColumn: "ClientId");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EFZombieClientStatRecord_EFZombieRoundClientStat_RoundId",
|
||||||
|
column: x => x.RoundId,
|
||||||
|
principalTable: "EFZombieRoundClientStat",
|
||||||
|
principalColumn: "ZombieClientStatId");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieAggregateClientStat_EFClientClientId",
|
||||||
|
table: "EFZombieAggregateClientStat",
|
||||||
|
column: "EFClientClientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieClientStat_ClientId",
|
||||||
|
table: "EFZombieClientStat",
|
||||||
|
column: "ClientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieClientStat_MatchId",
|
||||||
|
table: "EFZombieClientStat",
|
||||||
|
column: "MatchId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieClientStatRecord_ClientId",
|
||||||
|
table: "EFZombieClientStatRecord",
|
||||||
|
column: "ClientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieClientStatRecord_RoundId",
|
||||||
|
table: "EFZombieClientStatRecord",
|
||||||
|
column: "RoundId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieMatch_EFClientClientId",
|
||||||
|
table: "EFZombieMatch",
|
||||||
|
column: "EFClientClientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieMatch_MapId",
|
||||||
|
table: "EFZombieMatch",
|
||||||
|
column: "MapId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieMatch_ServerId",
|
||||||
|
table: "EFZombieMatch",
|
||||||
|
column: "ServerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieMatchClientStat_EFClientClientId",
|
||||||
|
table: "EFZombieMatchClientStat",
|
||||||
|
column: "EFClientClientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EFZombieRoundClientStat_EFClientClientId",
|
||||||
|
table: "EFZombieRoundClientStat",
|
||||||
|
column: "EFClientClientId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EFZombieAggregateClientStat");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EFZombieClientStatRecord");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EFZombieMatchClientStat");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EFZombieRoundClientStat");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EFZombieClientStat");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EFZombieMatch");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PerformanceBucket",
|
||||||
|
table: "EFServers");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PerformanceBucket",
|
||||||
|
table: "EFClientRankingHistory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -438,6 +438,9 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.Property<bool>("Newest")
|
b.Property<bool>("Newest")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PerformanceBucket")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<double?>("PerformanceMetric")
|
b.Property<double?>("PerformanceMetric")
|
||||||
.HasColumnType("REAL");
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
@ -812,6 +815,7 @@ namespace Data.Migrations.Sqlite
|
|||||||
|
|
||||||
b.Property<string>("SearchableIPAddress")
|
b.Property<string>("SearchableIPAddress")
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasMaxLength(255)
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
||||||
|
|
||||||
@ -1071,6 +1075,9 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.Property<bool>("IsPasswordProtected")
|
b.Property<bool>("IsPasswordProtected")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("PerformanceBucket")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("Port")
|
b.Property<int>("Port")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -1160,6 +1167,239 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.ToTable("Vector3", (string)null);
|
b.ToTable("Vector3", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieClientStat", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("ZombieClientStatId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("DamageDealt")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DamageReceived")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Deaths")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Downs")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Headshots")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kills")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MatchId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Melees")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PerksConsumed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("PointsEarned")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("PointsSpent")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PowerupsGrabbed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Revives")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UpdatedDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("ZombieClientStatId");
|
||||||
|
|
||||||
|
b.HasIndex("ClientId");
|
||||||
|
|
||||||
|
b.HasIndex("MatchId");
|
||||||
|
|
||||||
|
b.ToTable("EFZombieClientStat", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieClientStatRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ZombieClientStatRecordId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("ClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long?>("RoundId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UpdatedDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("ZombieClientStatRecordId");
|
||||||
|
|
||||||
|
b.HasIndex("ClientId");
|
||||||
|
|
||||||
|
b.HasIndex("RoundId");
|
||||||
|
|
||||||
|
b.ToTable("EFZombieClientStatRecord", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ZombieMatchId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ClientsCompleted")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("EFClientClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MapId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("MatchEndDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("MatchStartDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long?>("ServerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UpdatedDateTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("ZombieMatchId");
|
||||||
|
|
||||||
|
b.HasIndex("EFClientClientId");
|
||||||
|
|
||||||
|
b.HasIndex("MapId");
|
||||||
|
|
||||||
|
b.HasIndex("ServerId");
|
||||||
|
|
||||||
|
b.ToTable("EFZombieMatch", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieAggregateClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("Data.Models.Zombie.ZombieClientStat");
|
||||||
|
|
||||||
|
b.Property<double>("AlivePercentage")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AverageDowns")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AverageKillsPerDown")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AverageMelees")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AveragePoints")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AverageRevives")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("AverageRoundReached")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int?>("EFClientClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("HeadshotPercentage")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int>("HighestRound")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TotalMatchesCompleted")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TotalMatchesPlayed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TotalRoundsPlayed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasIndex("EFClientClientId");
|
||||||
|
|
||||||
|
b.ToTable("EFZombieAggregateClientStat", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("Data.Models.Zombie.ZombieClientStat");
|
||||||
|
|
||||||
|
b.Property<int?>("EFClientClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasIndex("EFClientClientId");
|
||||||
|
|
||||||
|
b.ToTable("EFZombieMatchClientStat", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieRoundClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("Data.Models.Zombie.ZombieClientStat");
|
||||||
|
|
||||||
|
b.Property<TimeSpan?>("Duration")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("EFClientClientId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("EndTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Points")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("RoundNumber")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("StartTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<TimeSpan?>("TimeAlive")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasIndex("EFClientClientId");
|
||||||
|
|
||||||
|
b.ToTable("EFZombieRoundClientStat", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
|
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot")
|
b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot")
|
||||||
@ -1601,6 +1841,96 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.Navigation("Server");
|
b.Navigation("Server");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.Models.Client.EFClient", "Client")
|
||||||
|
.WithMany("ZombieClientStats")
|
||||||
|
.HasForeignKey("ClientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Zombie.ZombieMatch", "Match")
|
||||||
|
.WithMany("ClientStats")
|
||||||
|
.HasForeignKey("MatchId");
|
||||||
|
|
||||||
|
b.Navigation("Client");
|
||||||
|
|
||||||
|
b.Navigation("Match");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieClientStatRecord", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.Models.Client.EFClient", "Client")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ClientId");
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Zombie.ZombieRoundClientStat", "Round")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoundId");
|
||||||
|
|
||||||
|
b.Navigation("Client");
|
||||||
|
|
||||||
|
b.Navigation("Round");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.Models.Client.EFClient", null)
|
||||||
|
.WithMany("ZombieMatches")
|
||||||
|
.HasForeignKey("EFClientClientId");
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MapId");
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ServerId");
|
||||||
|
|
||||||
|
b.Navigation("Map");
|
||||||
|
|
||||||
|
b.Navigation("Server");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieAggregateClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.Models.Client.EFClient", null)
|
||||||
|
.WithMany("ZombieAggregateClientStats")
|
||||||
|
.HasForeignKey("EFClientClientId");
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Zombie.ZombieClientStat", null)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.Models.Client.EFClient", null)
|
||||||
|
.WithMany("ZombieMatchClientStats")
|
||||||
|
.HasForeignKey("EFClientClientId");
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Zombie.ZombieClientStat", null)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("Data.Models.Zombie.ZombieMatchClientStat", "ZombieClientStatId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieRoundClientStat", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Data.Models.Client.EFClient", null)
|
||||||
|
.WithMany("ZombieRoundClientStats")
|
||||||
|
.HasForeignKey("EFClientClientId");
|
||||||
|
|
||||||
|
b.HasOne("Data.Models.Zombie.ZombieClientStat", null)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("Data.Models.Zombie.ZombieRoundClientStat", "ZombieClientStatId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Data.Models.Client.EFClient", b =>
|
modelBuilder.Entity("Data.Models.Client.EFClient", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AdministeredPenalties");
|
b.Navigation("AdministeredPenalties");
|
||||||
@ -1608,6 +1938,16 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.Navigation("Meta");
|
b.Navigation("Meta");
|
||||||
|
|
||||||
b.Navigation("ReceivedPenalties");
|
b.Navigation("ReceivedPenalties");
|
||||||
|
|
||||||
|
b.Navigation("ZombieAggregateClientStats");
|
||||||
|
|
||||||
|
b.Navigation("ZombieClientStats");
|
||||||
|
|
||||||
|
b.Navigation("ZombieMatchClientStats");
|
||||||
|
|
||||||
|
b.Navigation("ZombieMatches");
|
||||||
|
|
||||||
|
b.Navigation("ZombieRoundClientStats");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
|
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
|
||||||
@ -1631,6 +1971,11 @@ namespace Data.Migrations.Sqlite
|
|||||||
|
|
||||||
b.Navigation("ReceivedPenalties");
|
b.Navigation("ReceivedPenalties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("ClientStats");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Data.Models.Zombie;
|
||||||
|
|
||||||
namespace Data.Models.Client
|
namespace Data.Models.Client
|
||||||
{
|
{
|
||||||
@ -83,5 +84,10 @@ namespace Data.Models.Client
|
|||||||
public virtual ICollection<EFMeta> Meta { get; set; }
|
public virtual ICollection<EFMeta> Meta { get; set; }
|
||||||
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
|
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
|
||||||
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
|
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
|
||||||
|
public virtual ICollection<ZombieAggregateClientStat> ZombieAggregateClientStats { get; set; }
|
||||||
|
public virtual ICollection<ZombieClientStat> ZombieClientStats { get; set; }
|
||||||
|
public virtual ICollection<ZombieMatch> ZombieMatches { get; set; }
|
||||||
|
public virtual ICollection<ZombieMatchClientStat> ZombieMatchClientStats { get; set; }
|
||||||
|
public virtual ICollection<ZombieRoundClientStat> ZombieRoundClientStats { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,5 +25,6 @@ namespace Data.Models.Client.Stats
|
|||||||
public int? Ranking { get; set; }
|
public int? Ranking { get; set; }
|
||||||
public double? ZScore { get; set; }
|
public double? ZScore { get; set; }
|
||||||
public double? PerformanceMetric { get; set; }
|
public double? PerformanceMetric { get; set; }
|
||||||
|
public string PerformanceBucket { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
Data/Models/DatedRecord.cs
Normal file
9
Data/Models/DatedRecord.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Data.Models;
|
||||||
|
|
||||||
|
public class DatedRecord
|
||||||
|
{
|
||||||
|
public DateTimeOffset CreatedDateTime { get; set; } = DateTimeOffset.UtcNow;
|
||||||
|
public DateTimeOffset? UpdatedDateTime { get; set; }
|
||||||
|
}
|
@ -15,6 +15,7 @@ namespace Data.Models.Server
|
|||||||
public Reference.Game? GameName { get; set; }
|
public Reference.Game? GameName { get; set; }
|
||||||
public string HostName { get; set; }
|
public string HostName { get; set; }
|
||||||
public bool IsPasswordProtected { get; set; }
|
public bool IsPasswordProtected { get; set; }
|
||||||
|
public string PerformanceBucket { get; set; }
|
||||||
public long Id => ServerId;
|
public long Id => ServerId;
|
||||||
public string Value => EndPoint;
|
public string Value => EndPoint;
|
||||||
}
|
}
|
||||||
|
44
Data/Models/Zombie/ZombieAggregateClientStat.cs
Normal file
44
Data/Models/Zombie/ZombieAggregateClientStat.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Data.Models.Zombie;
|
||||||
|
|
||||||
|
public class ZombieAggregateClientStat : ZombieClientStat
|
||||||
|
{
|
||||||
|
#region Average
|
||||||
|
|
||||||
|
public double AverageKillsPerDown { get; set; }
|
||||||
|
public double AverageDowns { get; set; }
|
||||||
|
public double AverageRevives { get; set; }
|
||||||
|
public double HeadshotPercentage { get; set; }
|
||||||
|
public double AlivePercentage { get; set; }
|
||||||
|
public double AverageMelees { get; set; }
|
||||||
|
public double AverageRoundReached { get; set; }
|
||||||
|
public double AveragePoints { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Totals
|
||||||
|
|
||||||
|
public int HighestRound { get; set; }
|
||||||
|
public int TotalRoundsPlayed { get; set; }
|
||||||
|
public int TotalMatchesPlayed { get; set; }
|
||||||
|
public int TotalMatchesCompleted { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public static readonly string[] RecordsKeys =
|
||||||
|
{
|
||||||
|
nameof(AverageKillsPerDown),
|
||||||
|
nameof(AverageDowns),
|
||||||
|
nameof(AverageRevives),
|
||||||
|
nameof(HeadshotPercentage),
|
||||||
|
nameof(AlivePercentage),
|
||||||
|
nameof(AverageMelees),
|
||||||
|
nameof(AverageRoundReached),
|
||||||
|
nameof(AveragePoints),
|
||||||
|
nameof(HighestRound),
|
||||||
|
nameof(TotalRoundsPlayed),
|
||||||
|
nameof(TotalMatchesPlayed)
|
||||||
|
};
|
||||||
|
}
|
34
Data/Models/Zombie/ZombieClientStat.cs
Normal file
34
Data/Models/Zombie/ZombieClientStat.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Data.Models.Client;
|
||||||
|
|
||||||
|
namespace Data.Models.Zombie;
|
||||||
|
|
||||||
|
public abstract class ZombieClientStat : DatedRecord
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long ZombieClientStatId { get; set; }
|
||||||
|
|
||||||
|
public int? MatchId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(MatchId))]
|
||||||
|
public virtual ZombieMatch? Match { get; set; }
|
||||||
|
|
||||||
|
public int ClientId { get; set; }
|
||||||
|
[ForeignKey(nameof(ClientId))]
|
||||||
|
public virtual EFClient? Client { get; set; }
|
||||||
|
|
||||||
|
public int Kills { get; set; }
|
||||||
|
public int Deaths { get; set; }
|
||||||
|
public int DamageDealt { get; set; }
|
||||||
|
public int DamageReceived { get; set; }
|
||||||
|
public int Headshots { get; set; }
|
||||||
|
public int Melees { get; set; }
|
||||||
|
public int Downs { get; set; }
|
||||||
|
public int Revives { get; set; }
|
||||||
|
public long PointsEarned { get; set; }
|
||||||
|
public long PointsSpent { get; set; }
|
||||||
|
public int PerksConsumed { get; set; }
|
||||||
|
public int PowerupsGrabbed { get; set; }
|
||||||
|
}
|
29
Data/Models/Zombie/ZombieClientStatRecord.cs
Normal file
29
Data/Models/Zombie/ZombieClientStatRecord.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Data.Models.Client;
|
||||||
|
|
||||||
|
namespace Data.Models.Zombie;
|
||||||
|
|
||||||
|
public enum RecordType
|
||||||
|
{
|
||||||
|
Maximum,
|
||||||
|
Minimum
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ZombieClientStatRecord : DatedRecord
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int ZombieClientStatRecordId { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
public string Value { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int? ClientId { get; set; }
|
||||||
|
[ForeignKey(nameof(ClientId))]
|
||||||
|
public virtual EFClient? Client { get; set; }
|
||||||
|
|
||||||
|
public long? RoundId { get; set; }
|
||||||
|
[ForeignKey(nameof(RoundId))]
|
||||||
|
public virtual ZombieRoundClientStat? Round { get; set; }
|
||||||
|
}
|
30
Data/Models/Zombie/ZombieMatch.cs
Normal file
30
Data/Models/Zombie/ZombieMatch.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Data.Models.Client.Stats.Reference;
|
||||||
|
using Data.Models.Server;
|
||||||
|
|
||||||
|
namespace Data.Models.Zombie;
|
||||||
|
|
||||||
|
public class ZombieMatch : DatedRecord
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int ZombieMatchId { get; set; }
|
||||||
|
|
||||||
|
public int? MapId { get; set; }
|
||||||
|
[ForeignKey(nameof(MapId))]
|
||||||
|
public virtual EFMap? Map { get; set; }
|
||||||
|
|
||||||
|
public long? ServerId { get; set; }
|
||||||
|
[ForeignKey(nameof(ServerId))]
|
||||||
|
public virtual EFServer? Server { get; set; }
|
||||||
|
|
||||||
|
public int ClientsCompleted { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<ZombieClientStat>? ClientStats { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset MatchStartDate { get; set; } = DateTimeOffset.UtcNow;
|
||||||
|
public DateTimeOffset? MatchEndDate { get; set; }
|
||||||
|
}
|
6
Data/Models/Zombie/ZombieMatchClientStat.cs
Normal file
6
Data/Models/Zombie/ZombieMatchClientStat.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Data.Models.Zombie;
|
||||||
|
|
||||||
|
public class ZombieMatchClientStat : ZombieClientStat
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
13
Data/Models/Zombie/ZombieRoundClientStat.cs
Normal file
13
Data/Models/Zombie/ZombieRoundClientStat.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Data.Models.Zombie;
|
||||||
|
|
||||||
|
public class ZombieRoundClientStat : ZombieClientStat
|
||||||
|
{
|
||||||
|
public DateTimeOffset StartTime { get; set; } = DateTimeOffset.UtcNow;
|
||||||
|
public DateTimeOffset? EndTime { get; set; }
|
||||||
|
public TimeSpan? Duration { get; set; }
|
||||||
|
public TimeSpan? TimeAlive { get; set; }
|
||||||
|
public int RoundNumber { get; set; }
|
||||||
|
public int Points { get; set; }
|
||||||
|
}
|
@ -52,6 +52,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ParserH1MOD.js = Plugins\ScriptPlugins\ParserH1MOD.js
|
Plugins\ScriptPlugins\ParserH1MOD.js = Plugins\ScriptPlugins\ParserH1MOD.js
|
||||||
Plugins\ScriptPlugins\ParserPlutoniumT5.js = Plugins\ScriptPlugins\ParserPlutoniumT5.js
|
Plugins\ScriptPlugins\ParserPlutoniumT5.js = Plugins\ScriptPlugins\ParserPlutoniumT5.js
|
||||||
Plugins\ScriptPlugins\ServerBanner.js = Plugins\ScriptPlugins\ServerBanner.js
|
Plugins\ScriptPlugins\ServerBanner.js = Plugins\ScriptPlugins\ServerBanner.js
|
||||||
|
Plugins\ScriptPlugins\ParserBOIII.js = Plugins\ScriptPlugins\ParserBOIII.js
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
||||||
@ -101,6 +102,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pluto IW5", "Pluto IW5", "{
|
|||||||
GameFiles\AntiCheat\IW5\README.MD = GameFiles\AntiCheat\IW5\README.MD
|
GameFiles\AntiCheat\IW5\README.MD = GameFiles\AntiCheat\IW5\README.MD
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZombieStats", "Plugins\ZombieStats\ZombieStats.csproj", "{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -429,6 +432,30 @@ Global
|
|||||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|x86.Build.0 = Release|Any CPU
|
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -450,6 +477,7 @@ Global
|
|||||||
{3EA564BD-3AC6-479B-96B6-CB059DCD0C77} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
{3EA564BD-3AC6-479B-96B6-CB059DCD0C77} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||||
{866F453D-BC89-457F-8B55-485494759B31} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
{866F453D-BC89-457F-8B55-485494759B31} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||||
{603725A4-BC0B-423B-955B-762C89E1C4C2} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
{603725A4-BC0B-423B-955B-762C89E1C4C2} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||||
|
{3D92B950-6C9D-4B6D-B01B-F7AEC91787BD} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||||
|
@ -43,7 +43,7 @@ var plugin = {
|
|||||||
eventParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef';
|
eventParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef';
|
||||||
eventParser.GameName = 8; // BO3
|
eventParser.GameName = 8; // BO3
|
||||||
eventParser.Configuration.GameDirectory = 'usermaps';
|
eventParser.Configuration.GameDirectory = 'usermaps';
|
||||||
eventParser.Configuration.Say.Pattern = '^(chat|chatteam);(?:[0-9]+);([0-9]+);([0-9]+);(.+);(.*)$';
|
eventParser.Configuration.Say.Pattern = '^(chat|chatteam);(?:[0-9]+);([a-f0-9]+);([0-9]+);(.+);(.*)$';
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function() {},
|
onUnloadAsync: function() {},
|
||||||
|
@ -5,7 +5,7 @@ namespace Stats.Client.Abstractions
|
|||||||
public interface IServerDistributionCalculator
|
public interface IServerDistributionCalculator
|
||||||
{
|
{
|
||||||
Task Initialize();
|
Task Initialize();
|
||||||
Task<double> GetZScoreForServer(long serverId, double value);
|
Task<double> GetZScoreForServerOrBucket(double value, long? serverId = null, string performanceBucket = null);
|
||||||
Task<double?> GetRatingForZScore(double? value);
|
Task<double?> GetRatingForZScore(double? value, string performanceBucket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ public class HitCalculator : IClientStatisticCalculator
|
|||||||
|
|
||||||
foreach (var hitInfo in new[] {attackerHitInfo, victimHitInfo})
|
foreach (var hitInfo in new[] {attackerHitInfo, victimHitInfo})
|
||||||
{
|
{
|
||||||
if (hitInfo.MeansOfDeath == null || hitInfo.Location == null || hitInfo.Weapon == null)
|
if (hitInfo.MeansOfDeath == null || hitInfo.Location == null || hitInfo.Weapon == null || hitInfo.EntityId == 0)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Skipping hit because it does not contain the required data");
|
_logger.LogDebug("Skipping hit because it does not contain the required data");
|
||||||
continue;
|
continue;
|
||||||
|
@ -7,10 +7,9 @@ using Data.Abstractions;
|
|||||||
using Data.Models.Client;
|
using Data.Models.Client;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using IW4MAdmin.Plugins.Stats;
|
using IW4MAdmin.Plugins.Stats;
|
||||||
using IW4MAdmin.Plugins.Stats.Config;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Configuration;
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using Stats.Helpers;
|
using Stats.Helpers;
|
||||||
@ -21,76 +20,119 @@ namespace Stats.Client
|
|||||||
{
|
{
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
private readonly IDataValueCache<EFClientStatistics, Dictionary<long, Extensions.LogParams>>
|
private readonly IDataValueCache<EFClientStatistics, Dictionary<string, Extensions.LogParams>>
|
||||||
_distributionCache;
|
_distributionCache;
|
||||||
|
|
||||||
private readonly IDataValueCache<EFClientStatistics, double>
|
private readonly IDataValueCache<EFClientStatistics, double> _maxZScoreCache;
|
||||||
_maxZScoreCache;
|
|
||||||
|
|
||||||
private readonly IConfigurationHandler<StatsConfiguration> _configurationHandler;
|
private readonly StatsConfiguration _configuration;
|
||||||
private readonly List<long> _serverIds = new List<long>();
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
private readonly List<long> _serverIds = new();
|
||||||
|
|
||||||
private const string DistributionCacheKey = nameof(DistributionCacheKey);
|
private const string DistributionCacheKey = nameof(DistributionCacheKey);
|
||||||
private const string MaxZScoreCacheKey = nameof(MaxZScoreCacheKey);
|
private const string MaxZScoreCacheKey = nameof(MaxZScoreCacheKey);
|
||||||
|
|
||||||
public ServerDistributionCalculator(IDatabaseContextFactory contextFactory,
|
public ServerDistributionCalculator(IDatabaseContextFactory contextFactory,
|
||||||
IDataValueCache<EFClientStatistics, Dictionary<long, Extensions.LogParams>> distributionCache,
|
IDataValueCache<EFClientStatistics, Dictionary<string, Extensions.LogParams>> distributionCache,
|
||||||
IDataValueCache<EFClientStatistics, double> maxZScoreCache,
|
IDataValueCache<EFClientStatistics, double> maxZScoreCache,
|
||||||
IConfigurationHandlerFactory configFactory)
|
StatsConfiguration config, ApplicationConfiguration appConfig)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_distributionCache = distributionCache;
|
_distributionCache = distributionCache;
|
||||||
_maxZScoreCache = maxZScoreCache;
|
_maxZScoreCache = maxZScoreCache;
|
||||||
_configurationHandler = configFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
_configuration = config;
|
||||||
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
await LoadServers();
|
await LoadServers();
|
||||||
_distributionCache.SetCacheItem((async (set, token) =>
|
|
||||||
|
_distributionCache.SetCacheItem(async (set, token) =>
|
||||||
{
|
{
|
||||||
await _configurationHandler.BuildAsync();
|
var validPlayTime = _configuration.TopPlayersMinPlayTime;
|
||||||
var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3;
|
var distributions = new Dictionary<string, Extensions.LogParams>();
|
||||||
|
|
||||||
var distributions = new Dictionary<long, Extensions.LogParams>();
|
|
||||||
|
|
||||||
await LoadServers();
|
await LoadServers();
|
||||||
|
|
||||||
|
var iqPerformances = set
|
||||||
|
.Where(s => s.Skill > 0)
|
||||||
|
.Where(s => s.EloRating > 0)
|
||||||
|
.Where(s => s.Client.Level != EFClient.Permission.Banned);
|
||||||
|
|
||||||
foreach (var serverId in _serverIds)
|
foreach (var serverId in _serverIds)
|
||||||
{
|
{
|
||||||
var performance = await set
|
var performances = await iqPerformances.Where(s => s.ServerId == serverId)
|
||||||
.Where(s => s.ServerId == serverId)
|
|
||||||
.Where(s => s.Skill > 0)
|
|
||||||
.Where(s => s.EloRating > 0)
|
|
||||||
.Where(s => s.Client.Level != EFClient.Permission.Banned)
|
|
||||||
.Where(s => s.TimePlayed >= validPlayTime)
|
.Where(s => s.TimePlayed >= validPlayTime)
|
||||||
.Where(s => s.UpdatedAt >= Extensions.FifteenDaysAgo())
|
.Where(s => s.UpdatedAt >= Extensions.FifteenDaysAgo())
|
||||||
.Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0).ToListAsync();
|
.Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0)
|
||||||
var distributionParams = performance.GenerateDistributionParameters();
|
.ToListAsync(token);
|
||||||
distributions.Add(serverId, distributionParams);
|
var distributionParams = performances.GenerateDistributionParameters();
|
||||||
|
distributions.Add(serverId.ToString(), distributionParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var server in _appConfig.Servers)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(server.PerformanceBucket))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bucketConfig =
|
||||||
|
_configuration.PerformanceBuckets.FirstOrDefault(bucket =>
|
||||||
|
bucket.Name == server.PerformanceBucket) ?? new PerformanceBucketConfiguration();
|
||||||
|
|
||||||
|
var oldestPerf = DateTimeOffset.UtcNow - bucketConfig.RankingExpiration;
|
||||||
|
var performances = await iqPerformances
|
||||||
|
.Where(perf => perf.Server.PerformanceBucket == server.PerformanceBucket)
|
||||||
|
.Where(perf => perf.TimePlayed >= bucketConfig.ClientMinPlayTime.TotalSeconds)
|
||||||
|
.Where(perf => perf.UpdatedAt >= oldestPerf)
|
||||||
|
.Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0)
|
||||||
|
.ToListAsync(token);
|
||||||
|
var distributionParams = performances.GenerateDistributionParameters();
|
||||||
|
distributions.Add(server.PerformanceBucket, distributionParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return distributions;
|
return distributions;
|
||||||
}), DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromHours(1));
|
}, DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromHours(1));
|
||||||
|
|
||||||
_maxZScoreCache.SetCacheItem(async (set, token) =>
|
foreach (var server in _appConfig.Servers)
|
||||||
{
|
{
|
||||||
await _configurationHandler.BuildAsync();
|
_maxZScoreCache.SetCacheItem(async (set, ids, token) =>
|
||||||
var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3;
|
{
|
||||||
|
var validPlayTime = _configuration.TopPlayersMinPlayTime;
|
||||||
|
var oldestStat = TimeSpan.FromSeconds(_configuration.TopPlayersMinPlayTime);
|
||||||
|
var performanceBucket = (string)ids.FirstOrDefault();
|
||||||
|
|
||||||
var zScore = await set
|
if (!string.IsNullOrEmpty(performanceBucket))
|
||||||
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime))
|
{
|
||||||
.Where(s => s.Skill > 0)
|
var bucketConfig =
|
||||||
.Where(s => s.EloRating > 0)
|
_configuration.PerformanceBuckets.FirstOrDefault(cfg =>
|
||||||
.GroupBy(stat => stat.ClientId)
|
cfg.Name == performanceBucket) ?? new PerformanceBucketConfiguration();
|
||||||
.Select(group =>
|
|
||||||
group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed))
|
validPlayTime = (int)bucketConfig.ClientMinPlayTime.TotalSeconds;
|
||||||
.MaxAsync(avgZScore => (double?) avgZScore, token);
|
oldestStat = bucketConfig.RankingExpiration;
|
||||||
return zScore ?? 0;
|
}
|
||||||
}, MaxZScoreCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30));
|
|
||||||
|
var zScore = await set
|
||||||
|
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime, oldestStat))
|
||||||
|
.Where(s => s.Skill > 0)
|
||||||
|
.Where(s => s.EloRating > 0)
|
||||||
|
.Where(stat =>
|
||||||
|
performanceBucket == null || performanceBucket == stat.Server.PerformanceBucket)
|
||||||
|
.GroupBy(stat => stat.ClientId)
|
||||||
|
.Select(group =>
|
||||||
|
group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed))
|
||||||
|
.MaxAsync(avgZScore => (double?)avgZScore, token);
|
||||||
|
|
||||||
|
return zScore ?? 0;
|
||||||
|
}, MaxZScoreCacheKey, new[] { server.PerformanceBucket },
|
||||||
|
Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30));
|
||||||
|
|
||||||
|
await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new[] { server.PerformanceBucket });
|
||||||
|
}
|
||||||
|
|
||||||
await _distributionCache.GetCacheItem(DistributionCacheKey, new CancellationToken());
|
await _distributionCache.GetCacheItem(DistributionCacheKey, new CancellationToken());
|
||||||
await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new CancellationToken());
|
|
||||||
|
|
||||||
/*foreach (var serverId in _serverIds)
|
/*foreach (var serverId in _serverIds)
|
||||||
{
|
{
|
||||||
@ -131,16 +173,28 @@ namespace Stats.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<double> GetZScoreForServer(long serverId, double value)
|
public async Task<double> GetZScoreForServerOrBucket(double value, long? serverId = null,
|
||||||
|
string performanceBucket = null)
|
||||||
{
|
{
|
||||||
var serverParams = await _distributionCache.GetCacheItem(DistributionCacheKey, new CancellationToken());
|
if (serverId is null && performanceBucket is null)
|
||||||
if (!serverParams.ContainsKey(serverId))
|
|
||||||
{
|
{
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sdParams = serverParams[serverId];
|
var serverParams = await _distributionCache.GetCacheItem(DistributionCacheKey, new CancellationToken());
|
||||||
if (sdParams.Sigma == 0)
|
Extensions.LogParams sdParams = null;
|
||||||
|
|
||||||
|
if (serverId is not null && serverParams.TryGetValue(serverId.ToString(), out var sdParams1))
|
||||||
|
{
|
||||||
|
sdParams = sdParams1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (performanceBucket is not null && serverParams.TryGetValue(performanceBucket, out var sdParams2))
|
||||||
|
{
|
||||||
|
sdParams = sdParams2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sdParams is null || sdParams.Sigma == 0)
|
||||||
{
|
{
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
@ -149,9 +203,9 @@ namespace Stats.Client
|
|||||||
return zScore;
|
return zScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<double?> GetRatingForZScore(double? value)
|
public async Task<double?> GetRatingForZScore(double? value, string performanceBucket)
|
||||||
{
|
{
|
||||||
var maxZScore = await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new CancellationToken());
|
var maxZScore = await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new [] { performanceBucket });
|
||||||
return maxZScore == 0 ? null : value.GetRatingForZScore(maxZScore);
|
return maxZScore == 0 ? null : value.GetRatingForZScore(maxZScore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
Plugins/Stats/Config/PerformanceBucketConfiguration.cs
Normal file
10
Plugins/Stats/Config/PerformanceBucketConfiguration.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Stats.Config;
|
||||||
|
|
||||||
|
public class PerformanceBucketConfiguration
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public TimeSpan ClientMinPlayTime { get; set; } = TimeSpan.FromHours(3);
|
||||||
|
public TimeSpan RankingExpiration { get; set; } = TimeSpan.FromDays(15);
|
||||||
|
}
|
@ -19,6 +19,8 @@ namespace Stats.Config
|
|||||||
public int MostKillsClientLimit { get; set; } = 5;
|
public int MostKillsClientLimit { get; set; } = 5;
|
||||||
public bool EnableAdvancedMetrics { get; set; } = true;
|
public bool EnableAdvancedMetrics { get; set; } = true;
|
||||||
|
|
||||||
|
public List<PerformanceBucketConfiguration> PerformanceBuckets { get; set; } = new();
|
||||||
|
|
||||||
public WeaponNameParserConfiguration[] WeaponNameParserConfigurations { get; set; } = {
|
public WeaponNameParserConfiguration[] WeaponNameParserConfigurations { get; set; } = {
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
|
@ -145,11 +145,12 @@ namespace Stats.Helpers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Expression<Func<EFClientStatistics, bool>> GetRankingFunc(int minPlayTime, double? zScore = null,
|
public static Expression<Func<EFClientStatistics, bool>> GetRankingFunc(int minPlayTime, TimeSpan expiration, double? zScore = null,
|
||||||
long? serverId = null)
|
long? serverId = null)
|
||||||
{
|
{
|
||||||
return (stats) => (serverId == null || stats.ServerId == serverId) &&
|
var oldestStat = DateTime.UtcNow.Subtract(expiration);
|
||||||
stats.UpdatedAt >= Extensions.FifteenDaysAgo() &&
|
return stats => (serverId == null || stats.ServerId == serverId) &&
|
||||||
|
stats.UpdatedAt >= oldestStat &&
|
||||||
stats.Client.Level != EFClient.Permission.Banned &&
|
stats.Client.Level != EFClient.Permission.Banned &&
|
||||||
stats.TimePlayed >= minPlayTime
|
stats.TimePlayed >= minPlayTime
|
||||||
&& (zScore == null || stats.ZScore > zScore);
|
&& (zScore == null || stats.ZScore > zScore);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using IW4MAdmin.Plugins.Stats.Cheat;
|
using IW4MAdmin.Plugins.Stats.Cheat;
|
||||||
using IW4MAdmin.Plugins.Stats.Config;
|
|
||||||
using IW4MAdmin.Plugins.Stats.Web.Dtos;
|
using IW4MAdmin.Plugins.Stats.Web.Dtos;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
@ -1185,93 +1184,123 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
{
|
{
|
||||||
await using var context = _contextFactory.CreateContext();
|
await using var context = _contextFactory.CreateContext();
|
||||||
var minPlayTime = _config.TopPlayersMinPlayTime;
|
var minPlayTime = _config.TopPlayersMinPlayTime;
|
||||||
|
var oldestStat = DateTimeOffset.UtcNow - Extensions.FifteenDaysAgo();
|
||||||
|
|
||||||
|
var performanceBucket =
|
||||||
|
(await _serverCache.FirstAsync(server => server.Id == serverId)).PerformanceBucket;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(performanceBucket))
|
||||||
|
{
|
||||||
|
var bucketConfig = _config.PerformanceBuckets.FirstOrDefault(cfg => cfg.Name == performanceBucket) ??
|
||||||
|
new PerformanceBucketConfiguration();
|
||||||
|
|
||||||
|
minPlayTime = (int)bucketConfig.ClientMinPlayTime.TotalSeconds;
|
||||||
|
oldestStat = bucketConfig.RankingExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldestStateDate = DateTime.UtcNow - oldestStat;
|
||||||
var performances = await context.Set<EFClientStatistics>()
|
var performances = await context.Set<EFClientStatistics>()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(stat => stat.ClientId == clientId)
|
.Where(stat => stat.ClientId == clientId)
|
||||||
.Where(stat => stat.ServerId != serverId) // ignore the one we're currently tracking
|
.Where(stat => stat.ServerId != serverId) // ignore the one we're currently tracking
|
||||||
.Where(stats => stats.UpdatedAt >= Extensions.FifteenDaysAgo())
|
.Where(stats => stats.UpdatedAt >= oldestStateDate)
|
||||||
.Where(stats => stats.TimePlayed >= minPlayTime)
|
.Where(stats => stats.TimePlayed >= minPlayTime)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (clientStats.TimePlayed >= minPlayTime)
|
if (clientStats.TimePlayed >= minPlayTime)
|
||||||
{
|
{
|
||||||
clientStats.ZScore = await _serverDistributionCalculator.GetZScoreForServer(serverId,
|
await UpdateForServer(clientId, clientStats, context, minPlayTime, oldestStat, serverId);
|
||||||
clientStats.Performance);
|
|
||||||
|
|
||||||
var serverRanking = await context.Set<EFClientStatistics>()
|
|
||||||
.Where(stats => stats.ClientId != clientStats.ClientId)
|
|
||||||
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(
|
|
||||||
_config.TopPlayersMinPlayTime, clientStats.ZScore, serverId))
|
|
||||||
.CountAsync();
|
|
||||||
|
|
||||||
var serverRankingSnapshot = new EFClientRankingHistory
|
|
||||||
{
|
|
||||||
ClientId = clientId,
|
|
||||||
ServerId = serverId,
|
|
||||||
ZScore = clientStats.ZScore,
|
|
||||||
Ranking = serverRanking,
|
|
||||||
PerformanceMetric = clientStats.Performance,
|
|
||||||
Newest = true
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Add(serverRankingSnapshot);
|
|
||||||
await PruneOldRankings(context, clientId, serverId);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
performances.Add(clientStats);
|
performances.Add(clientStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (performances.Any(performance => performance.TimePlayed >= minPlayTime))
|
if (performances.Any(performance => performance.TimePlayed >= minPlayTime))
|
||||||
{
|
{
|
||||||
var aggregateZScore =
|
await UpdateAggregateForServerOrBucket(clientId, clientStats, context, performances, minPlayTime,
|
||||||
performances.WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), minPlayTime);
|
oldestStat, performanceBucket);
|
||||||
|
|
||||||
int? aggregateRanking = await context.Set<EFClientStatistics>()
|
|
||||||
.Where(stat => stat.ClientId != clientId)
|
|
||||||
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime))
|
|
||||||
.GroupBy(stat => stat.ClientId)
|
|
||||||
.Where(group =>
|
|
||||||
group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed) >
|
|
||||||
aggregateZScore)
|
|
||||||
.Select(c => c.Key)
|
|
||||||
.CountAsync();
|
|
||||||
|
|
||||||
var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore);
|
|
||||||
|
|
||||||
if (newPerformanceMetric == null)
|
|
||||||
{
|
|
||||||
_log.LogWarning("Could not determine performance metric for {Client} {AggregateZScore}",
|
|
||||||
clientStats.Client?.ToString(), aggregateZScore);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var aggregateRankingSnapshot = new EFClientRankingHistory
|
|
||||||
{
|
|
||||||
ClientId = clientId,
|
|
||||||
ZScore = aggregateZScore,
|
|
||||||
Ranking = aggregateRanking,
|
|
||||||
PerformanceMetric = newPerformanceMetric,
|
|
||||||
Newest = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Add(aggregateRankingSnapshot);
|
|
||||||
|
|
||||||
await PruneOldRankings(context, clientId);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PruneOldRankings(DatabaseContext context, int clientId, long? serverId = null)
|
private async Task UpdateAggregateForServerOrBucket(int clientId, EFClientStatistics clientStats, DatabaseContext context, List<EFClientStatistics> performances,
|
||||||
|
int minPlayTime, TimeSpan oldestStat, string performanceBucket)
|
||||||
|
{
|
||||||
|
var aggregateZScore =
|
||||||
|
performances.Where(performance => performance.Server.PerformanceBucket == performanceBucket)
|
||||||
|
.WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), minPlayTime);
|
||||||
|
|
||||||
|
int? aggregateRanking = await context.Set<EFClientStatistics>()
|
||||||
|
.Where(stat => stat.ClientId != clientId)
|
||||||
|
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime, oldestStat))
|
||||||
|
.GroupBy(stat => stat.ClientId)
|
||||||
|
.Where(group =>
|
||||||
|
group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed) >
|
||||||
|
aggregateZScore)
|
||||||
|
.Select(c => c.Key)
|
||||||
|
.CountAsync();
|
||||||
|
|
||||||
|
var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, performanceBucket);
|
||||||
|
|
||||||
|
if (newPerformanceMetric == null)
|
||||||
|
{
|
||||||
|
_log.LogWarning("Could not determine performance metric for {Client} {AggregateZScore}",
|
||||||
|
clientStats.Client?.ToString(), aggregateZScore);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aggregateRankingSnapshot = new EFClientRankingHistory
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
ZScore = aggregateZScore,
|
||||||
|
Ranking = aggregateRanking,
|
||||||
|
PerformanceMetric = newPerformanceMetric,
|
||||||
|
PerformanceBucket = performanceBucket,
|
||||||
|
Newest = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Add(aggregateRankingSnapshot);
|
||||||
|
|
||||||
|
await PruneOldRankings(context, clientId);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateForServer(int clientId, EFClientStatistics clientStats, DatabaseContext context,
|
||||||
|
int minPlayTime, TimeSpan oldestStat, long? serverId = null)
|
||||||
|
{
|
||||||
|
clientStats.ZScore =
|
||||||
|
await _serverDistributionCalculator.GetZScoreForServerOrBucket(clientStats.Performance, serverId);
|
||||||
|
|
||||||
|
var serverRanking = await context.Set<EFClientStatistics>()
|
||||||
|
.Where(stats => stats.ClientId != clientStats.ClientId)
|
||||||
|
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime, oldestStat,
|
||||||
|
clientStats.ZScore, serverId))
|
||||||
|
.CountAsync();
|
||||||
|
|
||||||
|
var serverRankingSnapshot = new EFClientRankingHistory
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
ServerId = serverId,
|
||||||
|
ZScore = clientStats.ZScore,
|
||||||
|
Ranking = serverRanking,
|
||||||
|
PerformanceMetric = clientStats.Performance,
|
||||||
|
Newest = true
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Add(serverRankingSnapshot);
|
||||||
|
await PruneOldRankings(context, clientId, serverId);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PruneOldRankings(DatabaseContext context, int clientId, long? serverId = null, string performanceBucket = null)
|
||||||
{
|
{
|
||||||
var totalRankingEntries = await context.Set<EFClientRankingHistory>()
|
var totalRankingEntries = await context.Set<EFClientRankingHistory>()
|
||||||
.Where(r => r.ClientId == clientId)
|
.Where(r => r.ClientId == clientId)
|
||||||
.Where(r => r.ServerId == serverId)
|
.Where(r => r.ServerId == serverId)
|
||||||
|
.Where(r => r.PerformanceBucket == performanceBucket)
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
|
|
||||||
var mostRecent = await context.Set<EFClientRankingHistory>()
|
var mostRecent = await context.Set<EFClientRankingHistory>()
|
||||||
.Where(r => r.ClientId == clientId)
|
.Where(r => r.ClientId == clientId)
|
||||||
.Where(r => r.ServerId == serverId)
|
.Where(r => r.ServerId == serverId)
|
||||||
|
.Where(r => r.PerformanceBucket == performanceBucket)
|
||||||
.FirstOrDefaultAsync(r => r.Newest);
|
.FirstOrDefaultAsync(r => r.Newest);
|
||||||
|
|
||||||
if (mostRecent != null)
|
if (mostRecent != null)
|
||||||
@ -1287,6 +1316,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
var lastRating = await context.Set<EFClientRankingHistory>()
|
var lastRating = await context.Set<EFClientRankingHistory>()
|
||||||
.Where(r => r.ClientId == clientId)
|
.Where(r => r.ClientId == clientId)
|
||||||
.Where(r => r.ServerId == serverId)
|
.Where(r => r.ServerId == serverId)
|
||||||
|
.Where(r => r.PerformanceBucket == performanceBucket)
|
||||||
.OrderBy(r => r.CreatedDateTime)
|
.OrderBy(r => r.CreatedDateTime)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
@ -1413,7 +1443,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
clientStats.SPM = Math.Round(clientStats.SPM, 3);
|
clientStats.SPM = Math.Round(clientStats.SPM, 3);
|
||||||
clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3);
|
var skillFunction = client.GetAdditionalProperty<Func<EFClient, EFClientStatistics, double>>("SkillFunction");
|
||||||
|
|
||||||
|
clientStats.Skill =
|
||||||
|
skillFunction?.Invoke(client, clientStats) ?? Math.Round(clientStats.SPM * KDRWeight, 3);
|
||||||
|
|
||||||
// fixme: how does this happen?
|
// fixme: how does this happen?
|
||||||
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
|
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
|
||||||
@ -1424,7 +1457,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
killSPM = killSpm, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference
|
killSPM = killSpm, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference
|
||||||
});
|
});
|
||||||
clientStats.SPM = 0;
|
clientStats.SPM = 0;
|
||||||
clientStats.Skill = 0;
|
if (skillFunction is null)
|
||||||
|
{
|
||||||
|
clientStats.Skill = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientStats.LastStatCalculation = DateTime.UtcNow;
|
clientStats.LastStatCalculation = DateTime.UtcNow;
|
||||||
|
1
Plugins/ZombieStats/Notice.cs
Normal file
1
Plugins/ZombieStats/Notice.cs
Normal file
@ -0,0 +1 @@
|
|||||||
|
// this component is closed source
|
26
Plugins/ZombieStats/ZombieStats.csproj
Normal file
26
Plugins/ZombieStats/ZombieStats.csproj
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>IW4MAdmin.Plugins.ZombieStats</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.16">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<!--<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2023.4.15.3" />-->
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
@ -54,6 +54,7 @@ namespace SharedLibraryCore.Configuration
|
|||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_CUSTOM_HOSTNAME")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_CUSTOM_HOSTNAME")]
|
||||||
[ConfigurationOptional]
|
[ConfigurationOptional]
|
||||||
public string CustomHostname { get; set; }
|
public string CustomHostname { get; set; }
|
||||||
|
public string PerformanceBucket { get; set; }
|
||||||
|
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
using Data.Models.Client;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerConsumedPerkGameEvent : ClientGameEvent
|
||||||
|
{
|
||||||
|
public EFClient Consumer => Origin;
|
||||||
|
public string PerkName { get; init; }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerDamageGameEvent : ClientDamageEvent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerDownedGameEvent : ClientGameEvent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using Data.Models.Client;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerGrabbedPowerupGameEvent : ClientGameEvent
|
||||||
|
{
|
||||||
|
public EFClient Grabber => Origin;
|
||||||
|
public string PowerupName { get; init; }
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerKilledGameEvent : PlayerDamageGameEvent
|
||||||
|
{
|
||||||
|
public PlayerKilledGameEvent()
|
||||||
|
{
|
||||||
|
RequiredEntity = EventRequiredEntity.Target;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using Data.Models.Client;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerRevivedGameEvent : ClientGameEvent
|
||||||
|
{
|
||||||
|
public EFClient Reviver => Origin;
|
||||||
|
public EFClient Revived => Target;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class PlayerRoundDataGameEvent : ClientGameEvent
|
||||||
|
{
|
||||||
|
public int TotalScore { get; init; }
|
||||||
|
public int CurrentScore { get; init; }
|
||||||
|
public bool IsGameOver { get; init; }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class RoundCompleteGameEvent : GameEventV2
|
||||||
|
{
|
||||||
|
public int RoundNumber { get; init; }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class ZombieDamageGameEvent : ClientDamageEvent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace SharedLibraryCore.Events.Game.GameScript.Zombie;
|
||||||
|
|
||||||
|
public class ZombieKilledGameEvent : ZombieDamageGameEvent
|
||||||
|
{
|
||||||
|
public ZombieKilledGameEvent()
|
||||||
|
{
|
||||||
|
RequiredEntity = EventRequiredEntity.Origin;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
@ -18,13 +19,17 @@ namespace WebfrontCore.Controllers
|
|||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerDataViewer _serverDataViewer;
|
private readonly IServerDataViewer _serverDataViewer;
|
||||||
|
private readonly ILookup<Type, string> _pluginTypeNames;
|
||||||
|
|
||||||
public HomeController(ILogger<HomeController> logger, IManager manager, ITranslationLookup translationLookup,
|
public HomeController(ILogger<HomeController> logger, IManager manager, ITranslationLookup translationLookup,
|
||||||
IServerDataViewer serverDataViewer) : base(manager)
|
IServerDataViewer serverDataViewer, IEnumerable<IPlugin> v1Plugins, IEnumerable<IPluginV2> v2Plugins) : base(manager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_serverDataViewer = serverDataViewer;
|
_serverDataViewer = serverDataViewer;
|
||||||
|
_pluginTypeNames = v1Plugins.Select(plugin => (plugin.GetType(), plugin.Name))
|
||||||
|
.Concat(v2Plugins.Select(plugin => (plugin.GetType(), plugin.Name)))
|
||||||
|
.ToLookup(selector => selector.Item1, selector => selector.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index(Reference.Game? game = null,
|
public async Task<IActionResult> Index(Reference.Game? game = null,
|
||||||
@ -96,9 +101,9 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pluginType = command.GetType().Assembly.GetTypes()
|
var pluginType = command.GetType().Assembly.GetTypes()
|
||||||
.FirstOrDefault(type => typeof(IPlugin).IsAssignableFrom(type));
|
.FirstOrDefault(type => typeof(IPlugin).IsAssignableFrom(type) || typeof(IPluginV2).IsAssignableFrom(type));
|
||||||
return Manager.Plugins.FirstOrDefault(plugin => plugin.GetType() == pluginType)?.Name ??
|
|
||||||
_translationLookup["WEBFRONT_HELP_COMMAND_NATIVE"];
|
return _pluginTypeNames[pluginType].FirstOrDefault() ?? _translationLookup["WEBFRONT_HELP_COMMAND_NATIVE"];
|
||||||
})
|
})
|
||||||
.Select(group => (group.Key, group.AsEnumerable()));
|
.Select(group => (group.Key, group.AsEnumerable()));
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
ViewBag.Description = Model.ClientName.StripColors();
|
ViewBag.Description = Model.ClientName.StripColors();
|
||||||
|
|
||||||
const string headshotKey = "MOD_HEAD_SHOT";
|
const string headshotKey = "MOD_HEAD_SHOT";
|
||||||
const string headshotKey2 = "headshot";
|
const string headshotKey2 = "head";
|
||||||
const string meleeKey = "MOD_MELEE";
|
const string meleeKey = "MOD_MELEE";
|
||||||
|
|
||||||
var suicideKeys = new[] { "MOD_SUICIDE", "MOD_FALLING" };
|
var suicideKeys = new[] { "MOD_SUICIDE", "MOD_FALLING" };
|
||||||
@ -137,7 +137,7 @@
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
var headShots = allPerServer.Any()
|
var headShots = allPerServer.Any()
|
||||||
? allPerServer.Where(hit => hit.MeansOfDeath?.Name == headshotKey || hit.HitLocation?.Name == headshotKey2).Sum(hit => hit.HitCount)
|
? allPerServer.Where(hit => hit.MeansOfDeath?.Name == headshotKey || (hit.HitLocation?.Name?.StartsWith(headshotKey2) ?? false)).Sum(hit => hit.HitCount)
|
||||||
: (int?)null; // want to default to -- in ui instead of 0
|
: (int?)null; // want to default to -- in ui instead of 0
|
||||||
|
|
||||||
var meleeKills = allPerServer.Any()
|
var meleeKills = allPerServer.Any()
|
||||||
|
Loading…
Reference in New Issue
Block a user