diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 718b8db9..cf9db97d 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -136,6 +136,9 @@ namespace IW4MAdmin.Application public IEnumerable Plugins { get; } public IInteractionRegistration InteractionRegistration { get; } + public IList>, long?, string, bool, Task>> CustomStatsMetrics { get; } = + new List>, long?, string, bool, Task>>(); + public async Task ExecuteEvent(GameEvent newEvent) { ProcessingEvents.TryAdd(newEvent.IncrementalId, newEvent); @@ -249,7 +252,13 @@ namespace IW4MAdmin.Application { var thisIndex = index; Interlocked.Increment(ref index); - return ProcessUpdateHandler(server, thisIndex); + return ProcessUpdateHandler(server, thisIndex).ContinueWith(result => + { + if (result.IsFaulted) + { + _logger.LogError(result.Exception, "Encountered unexpected error processing updates"); + } + }, CancellationToken); })); } @@ -598,19 +607,22 @@ namespace IW4MAdmin.Application public async Task Start() { _eventHandlerTokenSource = new CancellationTokenSource(); - - var eventHandlerThread = new Thread(() => + + var eventHandlerTask = Task.Run(() => { _coreEventHandler.StartProcessing(_eventHandlerTokenSource.Token); - }) - { - Name = nameof(CoreEventHandler) - }; + }, _eventHandlerTokenSource.Token); - eventHandlerThread.Start(); await UpdateServerStates(); _eventHandlerTokenSource.Cancel(); - eventHandlerThread.Join(); + + try + { + await eventHandlerTask; + } + catch (OperationCanceledException) + { + } } public async Task Stop() diff --git a/Application/CoreEventHandler.cs b/Application/CoreEventHandler.cs index 37f135f8..8a0fd24e 100644 --- a/Application/CoreEventHandler.cs +++ b/Application/CoreEventHandler.cs @@ -45,15 +45,13 @@ namespace IW4MAdmin.Application public void StartProcessing(CancellationToken token) { - _cancellationToken = token; - - while (!_cancellationToken.IsCancellationRequested) + while (!token.IsCancellationRequested) { _onEventReady.Reset(); try { - _onProcessingEvents.Wait(_cancellationToken); + _onProcessingEvents.Wait(token); if (!_runningEventTasks.TryDequeue(out var coreEvent)) { @@ -62,7 +60,7 @@ namespace IW4MAdmin.Application _onProcessingEvents.Release(1); } - _onEventReady.Wait(_cancellationToken); + _onEventReady.Wait(token); continue; } diff --git a/Application/DefaultSettings.json b/Application/DefaultSettings.json index 7851bed7..fb9deeb0 100644 --- a/Application/DefaultSettings.json +++ b/Application/DefaultSettings.json @@ -2439,6 +2439,7 @@ "left_foot": "Left Foot", "left_arm_upper": "Upper Left Arm", "left_arm_lower": "Lower Left Arm", + "head": "Head", "gl": "Rifle Grenade", "bigammo": "Round Drum", "scoped": "Sniper Scope", @@ -2446,34 +2447,238 @@ "aperture": "Aperture Sight", "flash": "Flash Hider", "silenced": "Silencer", - "molotov": "Molotov Cocktail", "sticky": "N° 74 ST", "m2": "M2 Flamethrower", "artillery": "Artillery Strike", "dog": "Attack Dogs", "colt": "Colt M1911", - "357magnum": ".357 Magnum", + "sw_357": ".357 Magnum", "walther": "Walther P38", - "tokarev": "Tokarev TT-33", - "shotgun": "M1897 Trench Gun", "doublebarreledshotgun": "Double-Barreled Shotgun", - "mp40": "MP40", - "type100smg": "Type 100", - "ppsh": "PPSh-41", - "svt40": "SVT-40", - "gewehr43": "Gewehr 43", + "doublebarrel": "Double-Barreled Shotgun", + "30cal": "Browning M1919", + "bar": "BAR", + "fg42": "FG42", "m1garand": "M1 Garand", + "mp40": "MP40", + "ppsh": "PPSh-41", + "gewehr43": "Gewehr 43", + "svt40": "SVT-40", + "nambu": "Nambu", + "m1garand_bayonet": "M1 Garand Bayonet", + "m1a1carbine_bayonet": "M1A1 Carbine Bayonet", + "kar98k_bayonet": "Kar98k Bayonet", + "mosin_rifle_bayonet": "Mosin-Nagant Bayonet", + "mg42": "MG42", + "colt45": "Colt M1911", + "sten_silenced": "Silenced Sten", + "type99_lmg": "Type 99", + "dp28": "DP-28", + "mine_shoebox": "PMD-6", + "mine_bouncing_betty": "Bouncing Betty", + "357magnum": ".357 Magnum", + "ptrs41": "PTRS-41", + "remingtonmodel11": "Remington Model 11", + "tabun_grenade": "Tabun Gas", + "signal_flare": "Signal Flare", + "shotgun_double_barreled": "Double-Barreled Shotgun", + "m7_launcher": "M7 Grenade Launcher", + "fg42_telescopic": "Telescopic Sight", + "springfield_scoped": "Scoped Springfield", + "m1garand_gl": "M1 Garand w/ Launcher", + "sticky_grenade": "N\u00ba 74 ST", + "tokarev_tt30": "Tokarev TT-33", + "mg42_bipod": "Deployable MG42", + "dp28_bipod": "Deployable DP-28", + "fg42_bipod": "Deployable FG42", + "bar_bipod": "Deployable BAR", + "30cal_bipod": "Deployable Browning M1919", + "type99_lmg_bipod": "Deployable Type 99", + "type100smg": "Type 100", "stg44": "STG-44", "m1carbine": "M1A1 Carbine", "type99lmg": "Type 99", - "bar": "BAR", - "dp28": "DP-28", - "mg42": "MG42", - "fg42": "FG42", - "30cal": "Browning M1919", - "type99rifle": "Arisaka", - "mosinrifle": "Mosin-Nagant", - "ptrs41": "PTRS-41" + "syrette": "Syrette", + "supportgunner": "Support Gunner", + "bren": "Bren LMG", + "rifleman": "Rifleman", + "lee_enfield": "Lee-Enfield", + "kar98k": "Kar98k", + "luger": "Luger", + "m1a1carbine": "M1A1 Carbine", + "mosin_rifle": "Mosin-Nagant", + "mosin_rifle_scoped": "Scoped Mosin-Nagant", + "sniper": "Sniper", + "submachinegunner": "Submachine Gunner", + "mp44": "MP44", + "springfield": "Springfield", + "mosinnagantammo": "Mosin-Nagant Ammo", + "sten": "Sten", + "armyengineer": "Army Engineer", + "thompson": "Thompson", + "fastauto": "Fast-Auto", + "slowauto": "Slow-Auto", + "fullauto": "Full-Auto", + "semiauto": "Semi-Auto", + "m2fraggrenade": "M2 Frag Grenade", + "mk1_frag_grenade": "MK1 Frag Grenade", + "russiangrenade": "RGD-33 Stick Grenade", + "germangrenade": "Stielhandgranate", + "panzerschrek": "Panzerschrek", + "panzerfaust": "Panzerfaust 60", + "scopedkar98k": "Scoped Kar98k", + "holdpin": "Hold-Pin", + "cookoff": "Cook-Off", + "medicplaceholder": "Medic", + "fraggrenade": "Frag", + "m8_white_smoke": "Smoke", + "shotgun": "M1897 Trench Gun", + "greasegun": "Grease Gun", + "pps42": "PPS42", + "webley": "Webley", + "scopedg43": "Scoped Gewehr 43", + "defaultweapon": "Default Weapon", + "satchel": "Satchel Charge", + "anm8_smoke_grenade": "AN-M8 Smoke Grenade", + "no77_wp_smoke_grenade": "No.77 WP Smoke Grenade", + "nebelhandgranate": "Nebelhandgranate", + "rgd1_smoke_grenade": "RGD-1 Smoke Grenade", + "potato": "Potato", + "no_ammo": "No Ammo", + "no_frag_grenade": "No Primary Grenades Remaining", + "no_smoke_grenade": "No Smoke Grenades Remaining", + "no_flash_grenade": "No Flashbang Grenades Remaining", + "noecial_grenade": "No Special Grenades Remaining", + "location_selector": "Select a location", + "smoke_grenade": "Smoke Grenade", + "flash_grenade": "Flash Grenade", + "concussion_grenade": "Stun Grenade", + "smgs": "Submachine Guns", + "assaultrifles": "Assault Rifles", + "shotguns": "Shotguns", + "sniperrifles": "Sniper Rifles", + "target_too_close": "Too Close to Target", + "lockon_required": "Lock-On Required", + "target_not_enough_clearance": "Not Enough Room To Fire", + "no_attachment": "No Attachment", + "silencer": "Suppressor", + "grenade_launcher": "Grenade Launcher", + "no_camo": "No Camo", + "golden_camo": "Golden", + "prestige_camo": "Prestige", + "binoculars": "Binoculars", + "grip": "Grip", + "m16a4_grenadier": "M16A4 Grenadier", + "panzershrek": "Panzershrek", + "bazooka": "M9A1 Bazooka", + "bazooka_man": "Bazooka", + "tokarev": "Tokarev TT-33", + "russian_flag": "Red Army Banner", + "stg-44": "STG-44", + "mortar_round": "Mortar Round", + "molotov": "Molotov Cocktail", + "fireblob": "Napalm Blob (Fire on Ground)", + "m2_flamethrower": "M2 Flamethrower", + "flamethrower_gunner": "Flamethrower", + "kar98k_scoped": "Scoped Kar98k", + "lee_enfield_scoped": "Scoped Lee-Enfield", + "type100_smg": "Type 100", + "type99_rifle": "Arisaka", + "type99_rifle_bayonet": "Arisaka Bayonet", + "type99_rifle_scoped": "Scoped Arisaka", + "walther_p38": "Walther P38", + "shotgunner": "Shotgunner", + "doublebarrel_sawed_grip": "Sawed-Off Double-Barreled Shotgun w/ Grip", + "antitank_gunner": "Anti-Tank Gunner", + "springfield_no_attachment": "No Attachment", + "springfield_bayonet": "Springfield Bayonet", + "springfield_rifle_grenade": "Rifle Grenade", + "type99_rifle_rifle_grenade": "Rifle Grenade", + "type99_rifle_no_attachment": "No Attachment", + "kar98k_no_attachment": "No Attachment", + "kar98k_rifle_grenade": "Rifle Grenade", + "mosin_rifle_no_attachment": "No Attachment", + "mosin_rifle_rifle_grenade": "Rifle Grenade", + "svt40_no_attachment": "No Attachment", + "svt40_flash": "Flash Hider", + "svt40_aperture": "Aperture Sight", + "svt40_telescopic": "Telescopic Sight", + "svt40_select_fire": "Select Fire", + "gewehr43_no_attachment": "No Attachment", + "gewehr43_silenced": "Suppressor", + "gewehr43_aperture": "Aperture Sight", + "gewehr43_telescopic": "Telescopic Sight", + "gewehr43_rifle_grenade": "Rifle Grenade", + "m1garand_no_attachment": "No Attachment", + "m1garand_rifle_grenade": "Rifle Grenade", + "m1garand_scoped": "Sniper Scope", + "m1garand_flash": "Flash Hider", + "m1a1carbine_no_attachment": "No Attachment", + "m1a1carbine_flash": "Flash Hider", + "m1a1carbine_bigammo": "Box Magazine", + "m1a1carbine_aperture": "Aperture Sight", + "stg-44_no_attachment": "No Attachment", + "stg-44_flash": "Flash Hider", + "stg-44_aperture": "Aperture Sight", + "stg-44_telescopic": "Telescopic Sight", + "stg-44_select_fire": "Select Fire", + "thompson_no_attachment": "No Attachment", + "thompson_silenced": "Suppressor", + "thompson_aperture": "Aperture Sight", + "thompson_bigammo": "Round Drum", + "type100_smg_no_attachment": "No Attachment", + "type100_smg_silenced": "Suppressor", + "type100_smg_bigammo": "Box Magazine", + "type100_smg_aperture": "Aperture Sight", + "mp40_no_attachment": "No Attachment", + "mp40_silenced": "Suppressor", + "mp40_aperture": "Aperture Sight", + "mp40_bigammo": "Dual Magazines", + "ppsh_no_attachment": "No Attachment", + "ppsh_aperture": "Aperture Sight", + "ppsh_bigammo": "Round Drum", + "shotgun_no_attachment": "No Attachment", + "shotgun_grip": "Grip", + "shotgun_bayonet": "Bayonet", + "shotgun_double_barreled_no_attachment": "No Attachment", + "shotgun_double_barreled_grip": "Grip", + "doublebarrel_sawed": "Sawed-Off Shotgun", + "sailor": "Crewman", + "fg42_scoped": "Scoped FG42", + "mosin_launcher": "Mosin-Nagant w/ Launcher", + "zombie_melee": "BRAAAINS...", + "ray_gun": "Ray Gun", + "nomad": "Nomad", + "tesla_gun": "Wunderwaffe DG-2", + "30cal_upgraded": "B115 accelerator", + "bar_upgraded": "The Widow Maker", + "colt_upgraded": "C-3000 b1at-ch35", + "shotgun_double_barreled_sawed_grip_upgraded": "The Snuff Box", + "shotgun_double_barreled_upgraded": "24 Bore long range", + "fg42_upgraded": "420 Impeller", + "gewehr43_upgraded": "G115 Compressor", + "m1a1carbine_upgraded": "Widdershins RC-1", + "m1garand_upgraded": "M1000", + "mg42_upgraded": "Barracuda FU-A11", + "mp40_upgraded": "The Afterburner", + "ppsh_upgraded": "The Reaper", + "shotgun_upgraded": "Gut Shot", + "stg-44_upgraded": "Spatz-447 +", + "sw_357_upgraded": ".357 Plus 1 K1L-u", + "thompson_upgraded": "Gibs-o-matic", + "type100_smg_upgraded": "1001 Samurais", + "type99_rifle_upgraded": "The Eviscerator", + "panzerschrek_upgraded": "Longinus", + "ray_gun_upgraded": "Porter's X2 Ray Gun", + "tesla_gun_upgraded": "Wunderwaffe DG-3 JZ", + "m2_flamethrower_upgraded": "FIW Nitrogen cooled", + "ptrs41_upgraded": "The Penetrator", + "m7_launcher_upgraded": "The Imploder", + "nazi_zombies_cap": "NAZI ZOMBIES", + "kar98k_upgraded": "Armageddon", + "m7_launcher_upgraded_nonade": "The Imploder", + "zombie_knuckle_crack": "Pack A Punch Knuckle Crack", + "cymbal_monkey": "Cymbal Monkey" }, "T6" : { diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 129f239c..810b5579 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -98,10 +98,12 @@ namespace IW4MAdmin ServerLogger.LogDebug("Client slot #{clientNumber} now reserved", clientFromLog.ClientNumber); var client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId, GameName); + var foundClient = true; // first time client is connecting to server if (client == null) { + foundClient = false; ServerLogger.LogDebug("Client {client} first time connecting", clientFromLog.ToString()); clientFromLog.CurrentServer = this; client = await Manager.GetClientService().Create(clientFromLog); @@ -109,12 +111,16 @@ namespace IW4MAdmin client.CopyAdditionalProperties(clientFromLog); - // this is only a temporary version until the IPAddress is transmitted - client.CurrentAlias = new EFAlias() + if (foundClient) { - Name = clientFromLog.Name, - IPAddress = clientFromLog.IPAddress - }; + client.CurrentAlias = new EFAlias + { + AliasId = client.CurrentAliasId, + LinkId = client.AliasLinkId, + Name = clientFromLog.Name, + IPAddress = clientFromLog.IPAddress + }; + } // Do the player specific stuff client.ClientNumber = clientFromLog.ClientNumber; @@ -413,10 +419,7 @@ namespace IW4MAdmin { if (E.Origin.State != ClientState.Connected) { - E.Origin.State = ClientState.Connected; - E.Origin.Connections += 1; - - ChatHistory.Add(new ChatInfo() + ChatHistory.Add(new ChatInfo { Name = E.Origin.Name, Message = "CONNECTED", @@ -430,6 +433,10 @@ namespace IW4MAdmin { E.Origin.Tag = clientTag.Value; } + + await E.Origin.OnJoin(E.Origin.IPAddress, Manager.GetApplicationSettings().Configuration().EnableImplicitAccountLinking); + E.Origin.State = ClientState.Connected; + E.Origin.Connections += 1; try { @@ -449,8 +456,6 @@ namespace IW4MAdmin ServerLogger.LogError(ex, "Could not get offline message count for {Client}", E.Origin.ToString()); throw; } - - await E.Origin.OnJoin(E.Origin.IPAddress, Manager.GetApplicationSettings().Configuration().EnableImplicitAccountLinking); } } @@ -907,6 +912,12 @@ namespace IW4MAdmin context.Entry(gameServer).Property(property => property.HostName).IsModified = true; } + if (gameServer.PerformanceBucket != PerformanceBucket) + { + gameServer.PerformanceBucket = PerformanceBucket; + context.Entry(gameServer).Property(property => property.PerformanceBucket).IsModified = true; + } + if (gameServer.IsPasswordProtected != !string.IsNullOrEmpty(GamePassword)) { gameServer.IsPasswordProtected = !string.IsNullOrEmpty(GamePassword); diff --git a/Data/Context/DatabaseContext.cs b/Data/Context/DatabaseContext.cs index 60311d74..81f18afd 100644 --- a/Data/Context/DatabaseContext.cs +++ b/Data/Context/DatabaseContext.cs @@ -57,6 +57,7 @@ namespace Data.Context public DbSet ZombieRoundClientStats { get; set; } public DbSet ZombieClientStatAggregates { get; set; } public DbSet ZombieClientStatRecords { get; set; } + public DbSet ZombieEvents { get; set; } #endregion @@ -102,6 +103,19 @@ namespace Data.Context client.NetworkId, client.GameName }); + + + /* entity.HasMany(prop => prop.ZombieMatchClientStats) + .WithOne(prop => prop.Client) + .HasForeignKey(prop => prop.ClientId); + + entity.HasMany(prop => prop.ZombieRoundClientStats) + .WithOne(prop => prop.Client) + .HasForeignKey(prop => prop.ClientId); + + entity.HasMany(prop => prop.ZombieAggregateClientStats) + .WithOne(prop => prop.Client) + .HasForeignKey(prop => prop.ClientId);*/ }); modelBuilder.Entity(entity => @@ -171,12 +185,34 @@ namespace Data.Context modelBuilder.Entity().ToTable(nameof(EFServerSnapshot)); modelBuilder.Entity().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)}"); + modelBuilder.Entity().ToTable($"EF{nameof(ZombieMatch)}"); + + modelBuilder.Entity(ent => + { + ent.ToTable($"EF{nameof(ZombieClientStat)}"); + ent.HasOne(prop => prop.Client) + .WithMany(prop => prop.ZombieClientStats) + .HasForeignKey(prop => prop.ClientId); + }); + + modelBuilder.Entity(ent => + { + ent.ToTable($"EF{nameof(ZombieMatchClientStat)}"); + }); + + modelBuilder.Entity(ent => + { + ent.ToTable($"EF{nameof(ZombieRoundClientStat)}"); + }); + + modelBuilder.Entity(ent => + { + ent.ToTable($"EF{nameof(ZombieAggregateClientStat)}"); + }); + + modelBuilder.Entity().ToTable($"EF{nameof(ZombieEvents)}"); + + modelBuilder.Entity().ToTable($"EF{nameof(ZombieClientStatRecord)}"); Models.Configuration.StatsModelConfiguration.Configure(modelBuilder); diff --git a/Data/Migrations/Postgresql/20240212035747_InitialZombieStats.Designer.cs b/Data/Migrations/Postgresql/20240212035747_InitialZombieStats.Designer.cs new file mode 100644 index 00000000..5c1affc5 --- /dev/null +++ b/Data/Migrations/Postgresql/20240212035747_InitialZombieStats.Designer.cs @@ -0,0 +1,2083 @@ +// +using System; +using Data.MigrationContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Data.Migrations.Postgresql +{ + [DbContext(typeof(PostgresqlDatabaseContext))] + [Migration("20240212035747_InitialZombieStats")] + partial class InitialZombieStats + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ACSnapshotVector3Id")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("SnapshotId") + .HasColumnType("integer"); + + b.Property("Vector3Id") + .HasColumnType("integer"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AliasLinkId") + .HasColumnType("integer"); + + b.Property("Connections") + .HasColumnType("integer"); + + b.Property("CurrentAliasId") + .HasColumnType("integer"); + + b.Property("FirstConnection") + .HasColumnType("timestamp without time zone"); + + b.Property("GameName") + .HasColumnType("integer"); + + b.Property("LastConnection") + .HasColumnType("timestamp without time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Masked") + .HasColumnType("boolean"); + + b.Property("NetworkId") + .HasColumnType("bigint"); + + b.Property("Password") + .HasColumnType("text"); + + b.Property("PasswordSalt") + .HasColumnType("text"); + + b.Property("TotalConnectionTime") + .HasColumnType("integer"); + + b.HasKey("ClientId"); + + b.HasAlternateKey("NetworkId", "GameName"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("LastConnection"); + + b.HasIndex("NetworkId"); + + b.ToTable("EFClients", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.Property("ClientConnectionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientConnectionId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("ConnectionType") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("ClientConnectionId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientConnectionHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("KillId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AttackerId") + .HasColumnType("integer"); + + b.Property("Damage") + .HasColumnType("integer"); + + b.Property("DeathOriginVector3Id") + .HasColumnType("integer"); + + b.Property("DeathType") + .HasColumnType("integer"); + + b.Property("Fraction") + .HasColumnType("double precision"); + + b.Property("HitLoc") + .HasColumnType("integer"); + + b.Property("IsKill") + .HasColumnType("boolean"); + + b.Property("KillOriginVector3Id") + .HasColumnType("integer"); + + b.Property("Map") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("VictimId") + .HasColumnType("integer"); + + b.Property("ViewAnglesVector3Id") + .HasColumnType("integer"); + + b.Property("VisibilityPercentage") + .HasColumnType("double precision"); + + b.Property("Weapon") + .HasColumnType("integer"); + + b.Property("WeaponReference") + .HasColumnType("text"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MessageId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("SentIngame") + .HasColumnType("boolean"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TimeSent") + .HasColumnType("timestamp without time zone"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SnapshotId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CurrentSessionLength") + .HasColumnType("integer"); + + b.Property("CurrentStrain") + .HasColumnType("double precision"); + + b.Property("CurrentViewAngleId") + .HasColumnType("integer"); + + b.Property("Deaths") + .HasColumnType("integer"); + + b.Property("Distance") + .HasColumnType("double precision"); + + b.Property("EloRating") + .HasColumnType("double precision"); + + b.Property("HitDestinationId") + .HasColumnType("integer"); + + b.Property("HitLocation") + .HasColumnType("integer"); + + b.Property("HitLocationReference") + .HasColumnType("text"); + + b.Property("HitOriginId") + .HasColumnType("integer"); + + b.Property("HitType") + .HasColumnType("integer"); + + b.Property("Hits") + .HasColumnType("integer"); + + b.Property("Kills") + .HasColumnType("integer"); + + b.Property("LastStrainAngleId") + .HasColumnType("integer"); + + b.Property("RecoilOffset") + .HasColumnType("double precision"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SessionAngleOffset") + .HasColumnType("double precision"); + + b.Property("SessionAverageSnapValue") + .HasColumnType("double precision"); + + b.Property("SessionSPM") + .HasColumnType("double precision"); + + b.Property("SessionScore") + .HasColumnType("integer"); + + b.Property("SessionSnapHits") + .HasColumnType("integer"); + + b.Property("StrainAngleBetween") + .HasColumnType("double precision"); + + b.Property("TimeSinceLastEvent") + .HasColumnType("integer"); + + b.Property("WeaponId") + .HasColumnType("integer"); + + b.Property("WeaponReference") + .HasColumnType("text"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFACSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.Property("ClientHitStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientHitStatisticId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("DamageInflicted") + .HasColumnType("integer"); + + b.Property("DamageReceived") + .HasColumnType("integer"); + + b.Property("DeathCount") + .HasColumnType("integer"); + + b.Property("HitCount") + .HasColumnType("integer"); + + b.Property("HitLocationId") + .HasColumnType("integer"); + + b.Property("KillCount") + .HasColumnType("integer"); + + b.Property("MeansOfDeathId") + .HasColumnType("integer"); + + b.Property("ReceivedHitCount") + .HasColumnType("integer"); + + b.Property("Score") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SuicideCount") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("UsageSeconds") + .HasColumnType("integer"); + + b.Property("WeaponAttachmentComboId") + .HasColumnType("integer"); + + b.Property("WeaponId") + .HasColumnType("integer"); + + b.HasKey("ClientHitStatisticId"); + + b.HasIndex("ClientId"); + + b.HasIndex("HitLocationId"); + + b.HasIndex("MeansOfDeathId"); + + b.HasIndex("ServerId"); + + b.HasIndex("WeaponAttachmentComboId"); + + b.HasIndex("WeaponId"); + + b.ToTable("EFClientHitStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.Property("ClientRankingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientRankingHistoryId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Newest") + .HasColumnType("boolean"); + + b.Property("PerformanceBucket") + .HasColumnType("text"); + + b.Property("PerformanceMetric") + .HasColumnType("double precision"); + + b.Property("Ranking") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ZScore") + .HasColumnType("double precision"); + + b.HasKey("ClientRankingHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("Ranking"); + + b.HasIndex("ServerId"); + + b.HasIndex("UpdatedDateTime"); + + b.HasIndex("ZScore"); + + b.ToTable("EFClientRankingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RatingHistoryId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AverageSnapValue") + .HasColumnType("double precision"); + + b.Property("Deaths") + .HasColumnType("integer"); + + b.Property("EloRating") + .HasColumnType("double precision"); + + b.Property("Kills") + .HasColumnType("integer"); + + b.Property("MaxStrain") + .HasColumnType("double precision"); + + b.Property("RollingWeightedKDR") + .HasColumnType("double precision"); + + b.Property("SPM") + .HasColumnType("double precision"); + + b.Property("Skill") + .HasColumnType("double precision"); + + b.Property("SnapHitCount") + .HasColumnType("integer"); + + b.Property("TimePlayed") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ZScore") + .HasColumnType("double precision"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ZScore"); + + b.HasIndex("ClientId", "TimePlayed", "ZScore"); + + b.ToTable("EFClientStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HitLocationCountId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("EFClientStatisticsClientId") + .HasColumnType("integer") + .HasColumnName("EFClientStatisticsClientId"); + + b.Property("EFClientStatisticsServerId") + .HasColumnType("bigint") + .HasColumnName("EFClientStatisticsServerId"); + + b.Property("HitCount") + .HasColumnType("integer"); + + b.Property("HitOffsetAverage") + .HasColumnType("real"); + + b.Property("Location") + .HasColumnType("integer"); + + b.Property("MaxAngleDistance") + .HasColumnType("real"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("EFClientStatisticsServerId"); + + b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); + + b.ToTable("EFHitLocationCounts", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RatingId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ActivityAmount") + .HasColumnType("integer"); + + b.Property("Newest") + .HasColumnType("boolean"); + + b.Property("Performance") + .HasColumnType("double precision"); + + b.Property("Ranking") + .HasColumnType("integer"); + + b.Property("RatingHistoryId") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); + + b.ToTable("EFRating", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => + { + b.Property("HitLocationId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HitLocationId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("HitLocationId"); + + b.HasIndex("Name"); + + b.ToTable("EFHitLocations", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => + { + b.Property("MapId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MapId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("MapId"); + + b.ToTable("EFMaps", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => + { + b.Property("MeansOfDeathId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeansOfDeathId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("MeansOfDeathId"); + + b.ToTable("EFMeansOfDeath", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => + { + b.Property("WeaponId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WeaponId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("WeaponId"); + + b.HasIndex("Name"); + + b.ToTable("EFWeapons", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => + { + b.Property("WeaponAttachmentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WeaponAttachmentId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("WeaponAttachmentId"); + + b.ToTable("EFWeaponAttachments", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.Property("WeaponAttachmentComboId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WeaponAttachmentComboId")); + + b.Property("Attachment1Id") + .HasColumnType("integer"); + + b.Property("Attachment2Id") + .HasColumnType("integer"); + + b.Property("Attachment3Id") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("WeaponAttachmentComboId"); + + b.HasIndex("Attachment1Id"); + + b.HasIndex("Attachment2Id"); + + b.HasIndex("Attachment3Id"); + + b.ToTable("EFWeaponAttachmentCombos", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AliasId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("DateAdded") + .HasColumnType("timestamp without time zone"); + + b.Property("IPAddress") + .HasColumnType("integer"); + + b.Property("LinkId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("character varying(24)"); + + b.Property("SearchableIPAddress") + .ValueGeneratedOnAddOrUpdate() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true); + + b.Property("SearchableName") + .HasMaxLength(24) + .HasColumnType("character varying(24)"); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableIPAddress"); + + b.HasIndex("SearchableName"); + + b.HasIndex("Name", "IPAddress"); + + b.ToTable("EFAlias", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AliasLinkId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ChangeHistoryId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Comment") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CurrentValue") + .HasColumnType("text"); + + b.Property("ImpersonationEntityId") + .HasColumnType("integer"); + + b.Property("OriginEntityId") + .HasColumnType("integer"); + + b.Property("PreviousValue") + .HasColumnType("text"); + + b.Property("TargetEntityId") + .HasColumnType("integer"); + + b.Property("TimeChanged") + .HasColumnType("timestamp without time zone"); + + b.Property("TypeOfChange") + .HasColumnType("integer"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MetaId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Extra") + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("LinkedMetaId") + .HasColumnType("integer"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.HasIndex("LinkedMetaId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PenaltyId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AutomatedOffense") + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp without time zone"); + + b.Property("IsEvadedOffense") + .HasColumnType("boolean"); + + b.Property("LinkId") + .HasColumnType("integer"); + + b.Property("OffenderId") + .HasColumnType("integer"); + + b.Property("Offense") + .IsRequired() + .HasColumnType("text"); + + b.Property("PunisherId") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.Property("PenaltyIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PenaltyIdentifierId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IPv4Address") + .HasColumnType("integer"); + + b.Property("NetworkId") + .HasColumnType("bigint"); + + b.Property("PenaltyId") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("PenaltyIdentifierId"); + + b.HasIndex("IPv4Address"); + + b.HasIndex("NetworkId"); + + b.HasIndex("PenaltyId"); + + b.ToTable("EFPenaltyIdentifiers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.Property("InboxMessageId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("InboxMessageId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("DestinationClientId") + .HasColumnType("integer"); + + b.Property("IsDelivered") + .HasColumnType("boolean"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SourceClientId") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("InboxMessageId"); + + b.HasIndex("DestinationClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServer", b => + { + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("EndPoint") + .HasColumnType("text"); + + b.Property("GameName") + .HasColumnType("integer"); + + b.Property("HostName") + .HasColumnType("text"); + + b.Property("IsPasswordProtected") + .HasColumnType("boolean"); + + b.Property("PerformanceBucket") + .HasColumnType("text"); + + b.Property("Port") + .HasColumnType("integer"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.Property("ServerSnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ServerSnapshotId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("CapturedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ClientCount") + .HasColumnType("integer"); + + b.Property("ConnectionInterrupted") + .HasColumnType("boolean"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("PeriodBlock") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.HasKey("ServerSnapshotId"); + + b.HasIndex("CapturedAt"); + + b.HasIndex("MapId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("StatisticId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TotalKills") + .HasColumnType("bigint"); + + b.Property("TotalPlayTime") + .HasColumnType("bigint"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Vector3Id")); + + b.Property("X") + .HasColumnType("real"); + + b.Property("Y") + .HasColumnType("real"); + + b.Property("Z") + .HasColumnType("real"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStat", b => + { + b.Property("ZombieClientStatId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieClientStatId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DamageDealt") + .HasColumnType("bigint"); + + b.Property("DamageReceived") + .HasColumnType("integer"); + + b.Property("Deaths") + .HasColumnType("integer"); + + b.Property("Downs") + .HasColumnType("integer"); + + b.Property("HeadshotKills") + .HasColumnType("integer"); + + b.Property("Headshots") + .HasColumnType("integer"); + + b.Property("Kills") + .HasColumnType("integer"); + + b.Property("MatchId") + .HasColumnType("integer"); + + b.Property("Melees") + .HasColumnType("integer"); + + b.Property("PerksConsumed") + .HasColumnType("integer"); + + b.Property("PointsEarned") + .HasColumnType("bigint"); + + b.Property("PointsSpent") + .HasColumnType("bigint"); + + b.Property("PowerupsGrabbed") + .HasColumnType("integer"); + + b.Property("Revives") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ZombieClientStatId"); + + b.HasIndex("ClientId"); + + b.HasIndex("MatchId"); + + b.ToTable("EFZombieClientStat", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStatRecord", b => + { + b.Property("ZombieClientStatRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieClientStatRecordId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoundId") + .HasColumnType("bigint"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ZombieClientStatRecordId"); + + b.HasIndex("ClientId"); + + b.HasIndex("RoundId"); + + b.ToTable("EFZombieClientStatRecord", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieEventLog", b => + { + b.Property("ZombieEventLogId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieEventLogId")); + + b.Property("AssociatedClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("MatchId") + .HasColumnType("integer"); + + b.Property("NumericalValue") + .HasColumnType("double precision"); + + b.Property("SourceClientId") + .HasColumnType("integer"); + + b.Property("TextualValue") + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ZombieEventLogId"); + + b.HasIndex("AssociatedClientId"); + + b.HasIndex("MatchId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("EFZombieEvents", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + b.Property("ZombieMatchId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieMatchId")); + + b.Property("ClientsCompleted") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("MatchEndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("MatchStartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ZombieMatchId"); + + 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("AlivePercentage") + .HasColumnType("double precision"); + + b.Property("AverageDowns") + .HasColumnType("double precision"); + + b.Property("AverageKillsPerDown") + .HasColumnType("double precision"); + + b.Property("AverageMelees") + .HasColumnType("double precision"); + + b.Property("AveragePoints") + .HasColumnType("double precision"); + + b.Property("AverageRevives") + .HasColumnType("double precision"); + + b.Property("AverageRoundReached") + .HasColumnType("double precision"); + + b.Property("HeadshotPercentage") + .HasColumnType("double precision"); + + b.Property("HighestRound") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TotalMatchesCompleted") + .HasColumnType("integer"); + + b.Property("TotalMatchesPlayed") + .HasColumnType("integer"); + + b.Property("TotalRoundsPlayed") + .HasColumnType("integer"); + + b.HasIndex("ServerId"); + + b.ToTable("EFZombieAggregateClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.ToTable("EFZombieMatchClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieRoundClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.Property("Duration") + .HasColumnType("interval"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Points") + .HasColumnType("integer"); + + b.Property("RoundNumber") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeAlive") + .HasColumnType("interval"); + + b.ToTable("EFZombieRoundClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Snapshot"); + + b.Navigation("Vector"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.HasOne("Data.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AliasLink"); + + b.Navigation("CurrentAlias"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.HasOne("Data.Models.Client.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("Data.Models.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + + b.Navigation("Attacker"); + + b.Navigation("DeathOrigin"); + + b.Navigation("KillOrigin"); + + b.Navigation("Server"); + + b.Navigation("Victim"); + + b.Navigation("ViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("CurrentViewAngle"); + + b.Navigation("HitDestination"); + + b.Navigation("HitOrigin"); + + b.Navigation("LastStrainAngle"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFHitLocation", "HitLocation") + .WithMany() + .HasForeignKey("HitLocationId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFMeansOfDeath", "MeansOfDeath") + .WithMany() + .HasForeignKey("MeansOfDeathId"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", "WeaponAttachmentCombo") + .WithMany() + .HasForeignKey("WeaponAttachmentComboId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") + .WithMany() + .HasForeignKey("WeaponId"); + + b.Navigation("Client"); + + b.Navigation("HitLocation"); + + b.Navigation("MeansOfDeath"); + + b.Navigation("Server"); + + b.Navigation("Weapon"); + + b.Navigation("WeaponAttachmentCombo"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("EFClientStatisticsClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.EFClientStatistics", null) + .WithMany("HitLocations") + .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.HasOne("Data.Models.Client.Stats.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("RatingHistory"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment1") + .WithMany() + .HasForeignKey("Attachment1Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment2") + .WithMany() + .HasForeignKey("Attachment2Id"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") + .WithMany() + .HasForeignKey("Attachment3Id"); + + b.Navigation("Attachment1"); + + b.Navigation("Attachment2"); + + b.Navigation("Attachment3"); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId"); + + b.HasOne("Data.Models.EFMeta", "LinkedMeta") + .WithMany() + .HasForeignKey("LinkedMetaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Client"); + + b.Navigation("LinkedMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId"); + + b.HasOne("Data.Models.Client.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("Offender"); + + b.Navigation("Punisher"); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.HasOne("Data.Models.EFPenalty", "Penalty") + .WithMany() + .HasForeignKey("PenaltyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Penalty"); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "DestinationClient") + .WithMany() + .HasForeignKey("DestinationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationClient"); + + b.Navigation("Server"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map") + .WithMany() + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Map"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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() + .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.ZombieEventLog", b => + { + b.HasOne("Data.Models.Client.EFClient", "AssociatedClient") + .WithMany() + .HasForeignKey("AssociatedClientId"); + + b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") + .WithMany() + .HasForeignKey("MatchId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId"); + + b.Navigation("AssociatedClient"); + + b.Navigation("Match"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + 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.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + 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.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieRoundClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Navigation("AdministeredPenalties"); + + b.Navigation("Meta"); + + b.Navigation("ReceivedPenalties"); + + b.Navigation("ZombieClientStats"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Navigation("PredictedViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Navigation("HitLocations"); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Navigation("Children"); + + b.Navigation("ReceivedPenalties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/Postgresql/20240212035747_InitialZombieStats.cs b/Data/Migrations/Postgresql/20240212035747_InitialZombieStats.cs new file mode 100644 index 00000000..17f6c3bf --- /dev/null +++ b/Data/Migrations/Postgresql/20240212035747_InitialZombieStats.cs @@ -0,0 +1,319 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Data.Migrations.Postgresql +{ + /// + public partial class InitialZombieStats : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PerformanceBucket", + table: "EFServers", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "PerformanceBucket", + table: "EFClientRankingHistory", + type: "text", + nullable: true); + + migrationBuilder.CreateTable( + name: "EFZombieMatch", + columns: table => new + { + ZombieMatchId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + MapId = table.Column(type: "integer", nullable: true), + ServerId = table.Column(type: "bigint", nullable: true), + ClientsCompleted = table.Column(type: "integer", nullable: false), + MatchStartDate = table.Column(type: "timestamp with time zone", nullable: false), + MatchEndDate = table.Column(type: "timestamp with time zone", nullable: true), + CreatedDateTime = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedDateTime = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EFZombieMatch", x => x.ZombieMatchId); + 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(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + MatchId = table.Column(type: "integer", nullable: true), + ClientId = table.Column(type: "integer", nullable: false), + Kills = table.Column(type: "integer", nullable: false), + Deaths = table.Column(type: "integer", nullable: false), + DamageDealt = table.Column(type: "bigint", nullable: false), + DamageReceived = table.Column(type: "integer", nullable: false), + Headshots = table.Column(type: "integer", nullable: false), + HeadshotKills = table.Column(type: "integer", nullable: false), + Melees = table.Column(type: "integer", nullable: false), + Downs = table.Column(type: "integer", nullable: false), + Revives = table.Column(type: "integer", nullable: false), + PointsEarned = table.Column(type: "bigint", nullable: false), + PointsSpent = table.Column(type: "bigint", nullable: false), + PerksConsumed = table.Column(type: "integer", nullable: false), + PowerupsGrabbed = table.Column(type: "integer", nullable: false), + CreatedDateTime = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedDateTime = table.Column(type: "timestamp with time zone", 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: "EFZombieEvents", + columns: table => new + { + ZombieEventLogId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EventType = table.Column(type: "integer", nullable: false), + SourceClientId = table.Column(type: "integer", nullable: true), + AssociatedClientId = table.Column(type: "integer", nullable: true), + NumericalValue = table.Column(type: "double precision", nullable: true), + TextualValue = table.Column(type: "text", nullable: true), + MatchId = table.Column(type: "integer", nullable: true), + CreatedDateTime = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedDateTime = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EFZombieEvents", x => x.ZombieEventLogId); + table.ForeignKey( + name: "FK_EFZombieEvents_EFClients_AssociatedClientId", + column: x => x.AssociatedClientId, + principalTable: "EFClients", + principalColumn: "ClientId"); + table.ForeignKey( + name: "FK_EFZombieEvents_EFClients_SourceClientId", + column: x => x.SourceClientId, + principalTable: "EFClients", + principalColumn: "ClientId"); + table.ForeignKey( + name: "FK_EFZombieEvents_EFZombieMatch_MatchId", + column: x => x.MatchId, + principalTable: "EFZombieMatch", + principalColumn: "ZombieMatchId"); + }); + + migrationBuilder.CreateTable( + name: "EFZombieAggregateClientStat", + columns: table => new + { + ZombieClientStatId = table.Column(type: "bigint", nullable: false), + ServerId = table.Column(type: "bigint", nullable: true), + AverageKillsPerDown = table.Column(type: "double precision", nullable: false), + AverageDowns = table.Column(type: "double precision", nullable: false), + AverageRevives = table.Column(type: "double precision", nullable: false), + HeadshotPercentage = table.Column(type: "double precision", nullable: false), + AlivePercentage = table.Column(type: "double precision", nullable: false), + AverageMelees = table.Column(type: "double precision", nullable: false), + AverageRoundReached = table.Column(type: "double precision", nullable: false), + AveragePoints = table.Column(type: "double precision", nullable: false), + HighestRound = table.Column(type: "integer", nullable: false), + TotalRoundsPlayed = table.Column(type: "integer", nullable: false), + TotalMatchesPlayed = table.Column(type: "integer", nullable: false), + TotalMatchesCompleted = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFZombieAggregateClientStat", x => x.ZombieClientStatId); + table.ForeignKey( + name: "FK_EFZombieAggregateClientStat_EFServers_ServerId", + column: x => x.ServerId, + principalTable: "EFServers", + principalColumn: "ServerId"); + table.ForeignKey( + name: "FK_EFZombieAggregateClientStat_EFZombieClientStat_ZombieClient~", + column: x => x.ZombieClientStatId, + principalTable: "EFZombieClientStat", + principalColumn: "ZombieClientStatId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EFZombieMatchClientStat", + columns: table => new + { + ZombieClientStatId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFZombieMatchClientStat", x => x.ZombieClientStatId); + table.ForeignKey( + name: "FK_EFZombieMatchClientStat_EFZombieClientStat_ZombieClientStat~", + column: x => x.ZombieClientStatId, + principalTable: "EFZombieClientStat", + principalColumn: "ZombieClientStatId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EFZombieRoundClientStat", + columns: table => new + { + ZombieClientStatId = table.Column(type: "bigint", nullable: false), + StartTime = table.Column(type: "timestamp with time zone", nullable: false), + EndTime = table.Column(type: "timestamp with time zone", nullable: true), + Duration = table.Column(type: "interval", nullable: true), + TimeAlive = table.Column(type: "interval", nullable: true), + RoundNumber = table.Column(type: "integer", nullable: false), + Points = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFZombieRoundClientStat", x => x.ZombieClientStatId); + table.ForeignKey( + name: "FK_EFZombieRoundClientStat_EFZombieClientStat_ZombieClientStat~", + column: x => x.ZombieClientStatId, + principalTable: "EFZombieClientStat", + principalColumn: "ZombieClientStatId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EFZombieClientStatRecord", + columns: table => new + { + ZombieClientStatRecordId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Type = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: false), + ClientId = table.Column(type: "integer", nullable: true), + RoundId = table.Column(type: "bigint", nullable: true), + CreatedDateTime = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedDateTime = table.Column(type: "timestamp with time zone", 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_ServerId", + table: "EFZombieAggregateClientStat", + column: "ServerId"); + + 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_EFZombieEvents_AssociatedClientId", + table: "EFZombieEvents", + column: "AssociatedClientId"); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieEvents_MatchId", + table: "EFZombieEvents", + column: "MatchId"); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieEvents_SourceClientId", + table: "EFZombieEvents", + column: "SourceClientId"); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieMatch_MapId", + table: "EFZombieMatch", + column: "MapId"); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieMatch_ServerId", + table: "EFZombieMatch", + column: "ServerId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EFZombieAggregateClientStat"); + + migrationBuilder.DropTable( + name: "EFZombieClientStatRecord"); + + migrationBuilder.DropTable( + name: "EFZombieEvents"); + + 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"); + } + } +} diff --git a/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs b/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs index a4ba4b40..6434c6d2 100644 --- a/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Data.Migrations.Postgresql { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("ProductVersion", "8.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -459,6 +459,9 @@ namespace Data.Migrations.Postgresql b.Property("Newest") .HasColumnType("boolean"); + b.Property("PerformanceBucket") + .HasColumnType("text"); + b.Property("PerformanceMetric") .HasColumnType("double precision"); @@ -1125,6 +1128,9 @@ namespace Data.Migrations.Postgresql b.Property("IsPasswordProtected") .HasColumnType("boolean"); + b.Property("PerformanceBucket") + .HasColumnType("text"); + b.Property("Port") .HasColumnType("integer"); @@ -1222,6 +1228,278 @@ namespace Data.Migrations.Postgresql b.ToTable("Vector3", (string)null); }); + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStat", b => + { + b.Property("ZombieClientStatId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieClientStatId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DamageDealt") + .HasColumnType("bigint"); + + b.Property("DamageReceived") + .HasColumnType("integer"); + + b.Property("Deaths") + .HasColumnType("integer"); + + b.Property("Downs") + .HasColumnType("integer"); + + b.Property("HeadshotKills") + .HasColumnType("integer"); + + b.Property("Headshots") + .HasColumnType("integer"); + + b.Property("Kills") + .HasColumnType("integer"); + + b.Property("MatchId") + .HasColumnType("integer"); + + b.Property("Melees") + .HasColumnType("integer"); + + b.Property("PerksConsumed") + .HasColumnType("integer"); + + b.Property("PointsEarned") + .HasColumnType("bigint"); + + b.Property("PointsSpent") + .HasColumnType("bigint"); + + b.Property("PowerupsGrabbed") + .HasColumnType("integer"); + + b.Property("Revives") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ZombieClientStatId"); + + b.HasIndex("ClientId"); + + b.HasIndex("MatchId"); + + b.ToTable("EFZombieClientStat", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStatRecord", b => + { + b.Property("ZombieClientStatRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieClientStatRecordId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoundId") + .HasColumnType("bigint"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ZombieClientStatRecordId"); + + b.HasIndex("ClientId"); + + b.HasIndex("RoundId"); + + b.ToTable("EFZombieClientStatRecord", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieEventLog", b => + { + b.Property("ZombieEventLogId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieEventLogId")); + + b.Property("AssociatedClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("MatchId") + .HasColumnType("integer"); + + b.Property("NumericalValue") + .HasColumnType("double precision"); + + b.Property("SourceClientId") + .HasColumnType("integer"); + + b.Property("TextualValue") + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ZombieEventLogId"); + + b.HasIndex("AssociatedClientId"); + + b.HasIndex("MatchId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("EFZombieEvents", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + b.Property("ZombieMatchId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ZombieMatchId")); + + b.Property("ClientsCompleted") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("MatchEndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("MatchStartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ZombieMatchId"); + + 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("AlivePercentage") + .HasColumnType("double precision"); + + b.Property("AverageDowns") + .HasColumnType("double precision"); + + b.Property("AverageKillsPerDown") + .HasColumnType("double precision"); + + b.Property("AverageMelees") + .HasColumnType("double precision"); + + b.Property("AveragePoints") + .HasColumnType("double precision"); + + b.Property("AverageRevives") + .HasColumnType("double precision"); + + b.Property("AverageRoundReached") + .HasColumnType("double precision"); + + b.Property("HeadshotPercentage") + .HasColumnType("double precision"); + + b.Property("HighestRound") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TotalMatchesCompleted") + .HasColumnType("integer"); + + b.Property("TotalMatchesPlayed") + .HasColumnType("integer"); + + b.Property("TotalRoundsPlayed") + .HasColumnType("integer"); + + b.HasIndex("ServerId"); + + b.ToTable("EFZombieAggregateClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.ToTable("EFZombieMatchClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieRoundClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.Property("Duration") + .HasColumnType("interval"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Points") + .HasColumnType("integer"); + + b.Property("RoundNumber") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeAlive") + .HasColumnType("interval"); + + b.ToTable("EFZombieRoundClientStat", (string)null); + }); + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => { b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") @@ -1663,6 +1941,107 @@ namespace Data.Migrations.Postgresql 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() + .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.ZombieEventLog", b => + { + b.HasOne("Data.Models.Client.EFClient", "AssociatedClient") + .WithMany() + .HasForeignKey("AssociatedClientId"); + + b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") + .WithMany() + .HasForeignKey("MatchId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId"); + + b.Navigation("AssociatedClient"); + + b.Navigation("Match"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + 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.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + 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.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieRoundClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Data.Models.Client.EFClient", b => { b.Navigation("AdministeredPenalties"); @@ -1670,6 +2049,8 @@ namespace Data.Migrations.Postgresql b.Navigation("Meta"); b.Navigation("ReceivedPenalties"); + + b.Navigation("ZombieClientStats"); }); modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => diff --git a/Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.Designer.cs b/Data/Migrations/Sqlite/20230905194120_IntitialZombieStats.Designer.cs similarity index 97% rename from Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.Designer.cs rename to Data/Migrations/Sqlite/20230905194120_IntitialZombieStats.Designer.cs index 41ee9104..8bb4b764 100644 --- a/Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.Designer.cs +++ b/Data/Migrations/Sqlite/20230905194120_IntitialZombieStats.Designer.cs @@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Data.Migrations.Sqlite { [DbContext(typeof(SqliteDatabaseContext))] - [Migration("20230507181011_AddZombieStatsInitial")] - partial class AddZombieStatsInitial + [Migration("20230905194120_IntitialZombieStats")] + partial class IntitialZombieStats { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -1117,6 +1117,8 @@ namespace Data.Migrations.Sqlite b.HasKey("ServerSnapshotId"); + b.HasIndex("CapturedAt"); + b.HasIndex("MapId"); b.HasIndex("ServerId"); @@ -1181,7 +1183,7 @@ namespace Data.Migrations.Sqlite b.Property("CreatedDateTime") .HasColumnType("TEXT"); - b.Property("DamageDealt") + b.Property("DamageDealt") .HasColumnType("INTEGER"); b.Property("DamageReceived") @@ -1337,15 +1339,15 @@ namespace Data.Migrations.Sqlite b.Property("AverageRoundReached") .HasColumnType("REAL"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - b.Property("HeadshotPercentage") .HasColumnType("REAL"); b.Property("HighestRound") .HasColumnType("INTEGER"); + b.Property("ServerId") + .HasColumnType("INTEGER"); + b.Property("TotalMatchesCompleted") .HasColumnType("INTEGER"); @@ -1355,7 +1357,7 @@ namespace Data.Migrations.Sqlite b.Property("TotalRoundsPlayed") .HasColumnType("INTEGER"); - b.HasIndex("EFClientClientId"); + b.HasIndex("ServerId"); b.ToTable("EFZombieAggregateClientStat", (string)null); }); @@ -1364,11 +1366,6 @@ namespace Data.Migrations.Sqlite { b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - - b.HasIndex("EFClientClientId"); - b.ToTable("EFZombieMatchClientStat", (string)null); }); @@ -1379,9 +1376,6 @@ namespace Data.Migrations.Sqlite b.Property("Duration") .HasColumnType("TEXT"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - b.Property("EndTime") .HasColumnType("TEXT"); @@ -1397,8 +1391,6 @@ namespace Data.Migrations.Sqlite b.Property("TimeAlive") .HasColumnType("TEXT"); - b.HasIndex("EFClientClientId"); - b.ToTable("EFZombieRoundClientStat", (string)null); }); @@ -1852,7 +1844,7 @@ namespace Data.Migrations.Sqlite .IsRequired(); b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") - .WithMany("ClientStats") + .WithMany() .HasForeignKey("MatchId"); b.Navigation("Client"); @@ -1896,23 +1888,21 @@ namespace Data.Migrations.Sqlite modelBuilder.Entity("Data.Models.Zombie.ZombieAggregateClientStat", b => { - b.HasOne("Data.Models.Client.EFClient", null) - .WithMany("ZombieAggregateClientStats") - .HasForeignKey("EFClientClientId"); + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); b.HasOne("Data.Models.Zombie.ZombieClientStat", null) .WithOne() .HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Server"); }); 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") @@ -1922,10 +1912,6 @@ namespace Data.Migrations.Sqlite 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") @@ -1941,15 +1927,9 @@ namespace Data.Migrations.Sqlite 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 => @@ -1973,11 +1953,6 @@ namespace Data.Migrations.Sqlite b.Navigation("ReceivedPenalties"); }); - - modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => - { - b.Navigation("ClientStats"); - }); #pragma warning restore 612, 618 } } diff --git a/Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.cs b/Data/Migrations/Sqlite/20230905194120_IntitialZombieStats.cs similarity index 88% rename from Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.cs rename to Data/Migrations/Sqlite/20230905194120_IntitialZombieStats.cs index 8d213b59..01c32838 100644 --- a/Data/Migrations/Sqlite/20230507181011_AddZombieStatsInitial.cs +++ b/Data/Migrations/Sqlite/20230905194120_IntitialZombieStats.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Data.Migrations.Sqlite { - public partial class AddZombieStatsInitial : Migration + public partial class IntitialZombieStats : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -66,7 +66,7 @@ namespace Data.Migrations.Sqlite ClientId = table.Column(type: "INTEGER", nullable: false), Kills = table.Column(type: "INTEGER", nullable: false), Deaths = table.Column(type: "INTEGER", nullable: false), - DamageDealt = table.Column(type: "INTEGER", nullable: false), + DamageDealt = table.Column(type: "INTEGER", nullable: false), DamageReceived = table.Column(type: "INTEGER", nullable: false), Headshots = table.Column(type: "INTEGER", nullable: false), Melees = table.Column(type: "INTEGER", nullable: false), @@ -101,6 +101,7 @@ namespace Data.Migrations.Sqlite { ZombieClientStatId = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), + ServerId = table.Column(type: "INTEGER", nullable: true), AverageKillsPerDown = table.Column(type: "REAL", nullable: false), AverageDowns = table.Column(type: "REAL", nullable: false), AverageRevives = table.Column(type: "REAL", nullable: false), @@ -112,17 +113,16 @@ namespace Data.Migrations.Sqlite HighestRound = table.Column(type: "INTEGER", nullable: false), TotalRoundsPlayed = table.Column(type: "INTEGER", nullable: false), TotalMatchesPlayed = table.Column(type: "INTEGER", nullable: false), - TotalMatchesCompleted = table.Column(type: "INTEGER", nullable: false), - EFClientClientId = table.Column(type: "INTEGER", nullable: true) + TotalMatchesCompleted = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { table.PrimaryKey("PK_EFZombieAggregateClientStat", x => x.ZombieClientStatId); table.ForeignKey( - name: "FK_EFZombieAggregateClientStat_EFClients_EFClientClientId", - column: x => x.EFClientClientId, - principalTable: "EFClients", - principalColumn: "ClientId"); + name: "FK_EFZombieAggregateClientStat_EFServers_ServerId", + column: x => x.ServerId, + principalTable: "EFServers", + principalColumn: "ServerId"); table.ForeignKey( name: "FK_EFZombieAggregateClientStat_EFZombieClientStat_ZombieClientStatId", column: x => x.ZombieClientStatId, @@ -136,17 +136,11 @@ namespace Data.Migrations.Sqlite columns: table => new { ZombieClientStatId = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - EFClientClientId = table.Column(type: "INTEGER", nullable: true) + .Annotation("Sqlite:Autoincrement", 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, @@ -166,17 +160,11 @@ namespace Data.Migrations.Sqlite Duration = table.Column(type: "TEXT", nullable: true), TimeAlive = table.Column(type: "TEXT", nullable: true), RoundNumber = table.Column(type: "INTEGER", nullable: false), - Points = table.Column(type: "INTEGER", nullable: false), - EFClientClientId = table.Column(type: "INTEGER", nullable: true) + Points = table.Column(type: "INTEGER", nullable: false) }, 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, @@ -215,9 +203,9 @@ namespace Data.Migrations.Sqlite }); migrationBuilder.CreateIndex( - name: "IX_EFZombieAggregateClientStat_EFClientClientId", + name: "IX_EFZombieAggregateClientStat_ServerId", table: "EFZombieAggregateClientStat", - column: "EFClientClientId"); + column: "ServerId"); migrationBuilder.CreateIndex( name: "IX_EFZombieClientStat_ClientId", @@ -253,16 +241,6 @@ namespace Data.Migrations.Sqlite 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) diff --git a/Data/Migrations/Sqlite/20230906230124_AddZombieStatsEventLog.Designer.cs b/Data/Migrations/Sqlite/20230906230124_AddZombieStatsEventLog.Designer.cs new file mode 100644 index 00000000..ff2da29b --- /dev/null +++ b/Data/Migrations/Sqlite/20230906230124_AddZombieStatsEventLog.Designer.cs @@ -0,0 +1,2010 @@ +// +using System; +using Data.MigrationContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Data.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20230906230124_AddZombieStatsEventLog")] + partial class AddZombieStatsEventLog + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("SnapshotId") + .HasColumnType("INTEGER"); + + b.Property("Vector3Id") + .HasColumnType("INTEGER"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AliasLinkId") + .HasColumnType("INTEGER"); + + b.Property("Connections") + .HasColumnType("INTEGER"); + + b.Property("CurrentAliasId") + .HasColumnType("INTEGER"); + + b.Property("FirstConnection") + .HasColumnType("TEXT"); + + b.Property("GameName") + .HasColumnType("INTEGER"); + + b.Property("LastConnection") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Masked") + .HasColumnType("INTEGER"); + + b.Property("NetworkId") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("PasswordSalt") + .HasColumnType("TEXT"); + + b.Property("TotalConnectionTime") + .HasColumnType("INTEGER"); + + b.HasKey("ClientId"); + + b.HasAlternateKey("NetworkId", "GameName"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("LastConnection"); + + b.HasIndex("NetworkId"); + + b.ToTable("EFClients", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.Property("ClientConnectionId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("ConnectionType") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ClientConnectionId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientConnectionHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AttackerId") + .HasColumnType("INTEGER"); + + b.Property("Damage") + .HasColumnType("INTEGER"); + + b.Property("DeathOriginVector3Id") + .HasColumnType("INTEGER"); + + b.Property("DeathType") + .HasColumnType("INTEGER"); + + b.Property("Fraction") + .HasColumnType("REAL"); + + b.Property("HitLoc") + .HasColumnType("INTEGER"); + + b.Property("IsKill") + .HasColumnType("INTEGER"); + + b.Property("KillOriginVector3Id") + .HasColumnType("INTEGER"); + + b.Property("Map") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("VictimId") + .HasColumnType("INTEGER"); + + b.Property("ViewAnglesVector3Id") + .HasColumnType("INTEGER"); + + b.Property("VisibilityPercentage") + .HasColumnType("REAL"); + + b.Property("Weapon") + .HasColumnType("INTEGER"); + + b.Property("WeaponReference") + .HasColumnType("TEXT"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("SentIngame") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TimeSent") + .HasColumnType("TEXT"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CurrentSessionLength") + .HasColumnType("INTEGER"); + + b.Property("CurrentStrain") + .HasColumnType("REAL"); + + b.Property("CurrentViewAngleId") + .HasColumnType("INTEGER"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("Distance") + .HasColumnType("REAL"); + + b.Property("EloRating") + .HasColumnType("REAL"); + + b.Property("HitDestinationId") + .HasColumnType("INTEGER"); + + b.Property("HitLocation") + .HasColumnType("INTEGER"); + + b.Property("HitLocationReference") + .HasColumnType("TEXT"); + + b.Property("HitOriginId") + .HasColumnType("INTEGER"); + + b.Property("HitType") + .HasColumnType("INTEGER"); + + b.Property("Hits") + .HasColumnType("INTEGER"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("LastStrainAngleId") + .HasColumnType("INTEGER"); + + b.Property("RecoilOffset") + .HasColumnType("REAL"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SessionAngleOffset") + .HasColumnType("REAL"); + + b.Property("SessionAverageSnapValue") + .HasColumnType("REAL"); + + b.Property("SessionSPM") + .HasColumnType("REAL"); + + b.Property("SessionScore") + .HasColumnType("INTEGER"); + + b.Property("SessionSnapHits") + .HasColumnType("INTEGER"); + + b.Property("StrainAngleBetween") + .HasColumnType("REAL"); + + b.Property("TimeSinceLastEvent") + .HasColumnType("INTEGER"); + + b.Property("WeaponId") + .HasColumnType("INTEGER"); + + b.Property("WeaponReference") + .HasColumnType("TEXT"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFACSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.Property("ClientHitStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DamageInflicted") + .HasColumnType("INTEGER"); + + b.Property("DamageReceived") + .HasColumnType("INTEGER"); + + b.Property("DeathCount") + .HasColumnType("INTEGER"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("HitLocationId") + .HasColumnType("INTEGER"); + + b.Property("KillCount") + .HasColumnType("INTEGER"); + + b.Property("MeansOfDeathId") + .HasColumnType("INTEGER"); + + b.Property("ReceivedHitCount") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SuicideCount") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("UsageSeconds") + .HasColumnType("INTEGER"); + + b.Property("WeaponAttachmentComboId") + .HasColumnType("INTEGER"); + + b.Property("WeaponId") + .HasColumnType("INTEGER"); + + b.HasKey("ClientHitStatisticId"); + + b.HasIndex("ClientId"); + + b.HasIndex("HitLocationId"); + + b.HasIndex("MeansOfDeathId"); + + b.HasIndex("ServerId"); + + b.HasIndex("WeaponAttachmentComboId"); + + b.HasIndex("WeaponId"); + + b.ToTable("EFClientHitStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.Property("ClientRankingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Newest") + .HasColumnType("INTEGER"); + + b.Property("PerformanceBucket") + .HasColumnType("TEXT"); + + b.Property("PerformanceMetric") + .HasColumnType("REAL"); + + b.Property("Ranking") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("ZScore") + .HasColumnType("REAL"); + + b.HasKey("ClientRankingHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("Ranking"); + + b.HasIndex("ServerId"); + + b.HasIndex("UpdatedDateTime"); + + b.HasIndex("ZScore"); + + b.ToTable("EFClientRankingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AverageSnapValue") + .HasColumnType("REAL"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("EloRating") + .HasColumnType("REAL"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("MaxStrain") + .HasColumnType("REAL"); + + b.Property("RollingWeightedKDR") + .HasColumnType("REAL"); + + b.Property("SPM") + .HasColumnType("REAL"); + + b.Property("Skill") + .HasColumnType("REAL"); + + b.Property("SnapHitCount") + .HasColumnType("INTEGER"); + + b.Property("TimePlayed") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("ZScore") + .HasColumnType("REAL"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ZScore"); + + b.HasIndex("ClientId", "TimePlayed", "ZScore"); + + b.ToTable("EFClientStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("EFClientStatisticsClientId") + .HasColumnType("INTEGER") + .HasColumnName("EFClientStatisticsClientId"); + + b.Property("EFClientStatisticsServerId") + .HasColumnType("INTEGER") + .HasColumnName("EFClientStatisticsServerId"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("HitOffsetAverage") + .HasColumnType("REAL"); + + b.Property("Location") + .HasColumnType("INTEGER"); + + b.Property("MaxAngleDistance") + .HasColumnType("REAL"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("EFClientStatisticsServerId"); + + b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); + + b.ToTable("EFHitLocationCounts", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ActivityAmount") + .HasColumnType("INTEGER"); + + b.Property("Newest") + .HasColumnType("INTEGER"); + + b.Property("Performance") + .HasColumnType("REAL"); + + b.Property("Ranking") + .HasColumnType("INTEGER"); + + b.Property("RatingHistoryId") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); + + b.ToTable("EFRating", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => + { + b.Property("HitLocationId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("HitLocationId"); + + b.HasIndex("Name"); + + b.ToTable("EFHitLocations", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => + { + b.Property("MapId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("MapId"); + + b.ToTable("EFMaps", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => + { + b.Property("MeansOfDeathId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("MeansOfDeathId"); + + b.ToTable("EFMeansOfDeath", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => + { + b.Property("WeaponId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponId"); + + b.HasIndex("Name"); + + b.ToTable("EFWeapons", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => + { + b.Property("WeaponAttachmentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponAttachmentId"); + + b.ToTable("EFWeaponAttachments", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.Property("WeaponAttachmentComboId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attachment1Id") + .HasColumnType("INTEGER"); + + b.Property("Attachment2Id") + .HasColumnType("INTEGER"); + + b.Property("Attachment3Id") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponAttachmentComboId"); + + b.HasIndex("Attachment1Id"); + + b.HasIndex("Attachment2Id"); + + b.HasIndex("Attachment3Id"); + + b.ToTable("EFWeaponAttachmentCombos", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("IPAddress") + .HasColumnType("INTEGER"); + + b.Property("LinkId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("SearchableIPAddress") + .ValueGeneratedOnAddOrUpdate() + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true); + + b.Property("SearchableName") + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableIPAddress"); + + b.HasIndex("SearchableName"); + + b.HasIndex("Name", "IPAddress"); + + b.ToTable("EFAlias", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CurrentValue") + .HasColumnType("TEXT"); + + b.Property("ImpersonationEntityId") + .HasColumnType("INTEGER"); + + b.Property("OriginEntityId") + .HasColumnType("INTEGER"); + + b.Property("PreviousValue") + .HasColumnType("TEXT"); + + b.Property("TargetEntityId") + .HasColumnType("INTEGER"); + + b.Property("TimeChanged") + .HasColumnType("TEXT"); + + b.Property("TypeOfChange") + .HasColumnType("INTEGER"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Extra") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("LinkedMetaId") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.HasIndex("LinkedMetaId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AutomatedOffense") + .HasColumnType("TEXT"); + + b.Property("Expires") + .HasColumnType("TEXT"); + + b.Property("IsEvadedOffense") + .HasColumnType("INTEGER"); + + b.Property("LinkId") + .HasColumnType("INTEGER"); + + b.Property("OffenderId") + .HasColumnType("INTEGER"); + + b.Property("Offense") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PunisherId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.Property("PenaltyIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("IPv4Address") + .HasColumnType("INTEGER"); + + b.Property("NetworkId") + .HasColumnType("INTEGER"); + + b.Property("PenaltyId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("PenaltyIdentifierId"); + + b.HasIndex("IPv4Address"); + + b.HasIndex("NetworkId"); + + b.HasIndex("PenaltyId"); + + b.ToTable("EFPenaltyIdentifiers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.Property("InboxMessageId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DestinationClientId") + .HasColumnType("INTEGER"); + + b.Property("IsDelivered") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SourceClientId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("InboxMessageId"); + + b.HasIndex("DestinationClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServer", b => + { + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("EndPoint") + .HasColumnType("TEXT"); + + b.Property("GameName") + .HasColumnType("INTEGER"); + + b.Property("HostName") + .HasColumnType("TEXT"); + + b.Property("IsPasswordProtected") + .HasColumnType("INTEGER"); + + b.Property("PerformanceBucket") + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.Property("ServerSnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("CapturedAt") + .HasColumnType("TEXT"); + + b.Property("ClientCount") + .HasColumnType("INTEGER"); + + b.Property("ConnectionInterrupted") + .HasColumnType("INTEGER"); + + b.Property("MapId") + .HasColumnType("INTEGER"); + + b.Property("PeriodBlock") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.HasKey("ServerSnapshotId"); + + b.HasIndex("CapturedAt"); + + b.HasIndex("MapId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TotalKills") + .HasColumnType("INTEGER"); + + b.Property("TotalPlayTime") + .HasColumnType("INTEGER"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("Z") + .HasColumnType("REAL"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStat", b => + { + b.Property("ZombieClientStatId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DamageDealt") + .HasColumnType("INTEGER"); + + b.Property("DamageReceived") + .HasColumnType("INTEGER"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("Downs") + .HasColumnType("INTEGER"); + + b.Property("Headshots") + .HasColumnType("INTEGER"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("Melees") + .HasColumnType("INTEGER"); + + b.Property("PerksConsumed") + .HasColumnType("INTEGER"); + + b.Property("PointsEarned") + .HasColumnType("INTEGER"); + + b.Property("PointsSpent") + .HasColumnType("INTEGER"); + + b.Property("PowerupsGrabbed") + .HasColumnType("INTEGER"); + + b.Property("Revives") + .HasColumnType("INTEGER"); + + b.Property("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("ZombieClientStatRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ZombieClientStatRecordId"); + + b.HasIndex("ClientId"); + + b.HasIndex("RoundId"); + + b.ToTable("EFZombieClientStatRecord", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieEventLog", b => + { + b.Property("ZombieEventLogId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssociatedClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("NumericalValue") + .HasColumnType("REAL"); + + b.Property("SourceClientId") + .HasColumnType("INTEGER"); + + b.Property("TextualValue") + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ZombieEventLogId"); + + b.HasIndex("AssociatedClientId"); + + b.HasIndex("MatchId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("EFZombieEvents", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + b.Property("ZombieMatchId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientsCompleted") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("MapId") + .HasColumnType("INTEGER"); + + b.Property("MatchEndDate") + .HasColumnType("TEXT"); + + b.Property("MatchStartDate") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ZombieMatchId"); + + 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("AlivePercentage") + .HasColumnType("REAL"); + + b.Property("AverageDowns") + .HasColumnType("REAL"); + + b.Property("AverageKillsPerDown") + .HasColumnType("REAL"); + + b.Property("AverageMelees") + .HasColumnType("REAL"); + + b.Property("AveragePoints") + .HasColumnType("REAL"); + + b.Property("AverageRevives") + .HasColumnType("REAL"); + + b.Property("AverageRoundReached") + .HasColumnType("REAL"); + + b.Property("HeadshotPercentage") + .HasColumnType("REAL"); + + b.Property("HighestRound") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TotalMatchesCompleted") + .HasColumnType("INTEGER"); + + b.Property("TotalMatchesPlayed") + .HasColumnType("INTEGER"); + + b.Property("TotalRoundsPlayed") + .HasColumnType("INTEGER"); + + b.HasIndex("ServerId"); + + b.ToTable("EFZombieAggregateClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.ToTable("EFZombieMatchClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieRoundClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.Property("Duration") + .HasColumnType("TEXT"); + + b.Property("EndTime") + .HasColumnType("TEXT"); + + b.Property("Points") + .HasColumnType("INTEGER"); + + b.Property("RoundNumber") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TimeAlive") + .HasColumnType("TEXT"); + + b.ToTable("EFZombieRoundClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Snapshot"); + + b.Navigation("Vector"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.HasOne("Data.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AliasLink"); + + b.Navigation("CurrentAlias"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.HasOne("Data.Models.Client.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("Data.Models.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + + b.Navigation("Attacker"); + + b.Navigation("DeathOrigin"); + + b.Navigation("KillOrigin"); + + b.Navigation("Server"); + + b.Navigation("Victim"); + + b.Navigation("ViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("CurrentViewAngle"); + + b.Navigation("HitDestination"); + + b.Navigation("HitOrigin"); + + b.Navigation("LastStrainAngle"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFHitLocation", "HitLocation") + .WithMany() + .HasForeignKey("HitLocationId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFMeansOfDeath", "MeansOfDeath") + .WithMany() + .HasForeignKey("MeansOfDeathId"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", "WeaponAttachmentCombo") + .WithMany() + .HasForeignKey("WeaponAttachmentComboId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") + .WithMany() + .HasForeignKey("WeaponId"); + + b.Navigation("Client"); + + b.Navigation("HitLocation"); + + b.Navigation("MeansOfDeath"); + + b.Navigation("Server"); + + b.Navigation("Weapon"); + + b.Navigation("WeaponAttachmentCombo"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("EFClientStatisticsClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.EFClientStatistics", null) + .WithMany("HitLocations") + .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.HasOne("Data.Models.Client.Stats.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("RatingHistory"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment1") + .WithMany() + .HasForeignKey("Attachment1Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment2") + .WithMany() + .HasForeignKey("Attachment2Id"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") + .WithMany() + .HasForeignKey("Attachment3Id"); + + b.Navigation("Attachment1"); + + b.Navigation("Attachment2"); + + b.Navigation("Attachment3"); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId"); + + b.HasOne("Data.Models.EFMeta", "LinkedMeta") + .WithMany() + .HasForeignKey("LinkedMetaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Client"); + + b.Navigation("LinkedMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId"); + + b.HasOne("Data.Models.Client.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("Offender"); + + b.Navigation("Punisher"); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.HasOne("Data.Models.EFPenalty", "Penalty") + .WithMany() + .HasForeignKey("PenaltyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Penalty"); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "DestinationClient") + .WithMany() + .HasForeignKey("DestinationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationClient"); + + b.Navigation("Server"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map") + .WithMany() + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Map"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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() + .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.ZombieEventLog", b => + { + b.HasOne("Data.Models.Client.EFClient", "AssociatedClient") + .WithMany() + .HasForeignKey("AssociatedClientId"); + + b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") + .WithMany() + .HasForeignKey("MatchId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId"); + + b.Navigation("AssociatedClient"); + + b.Navigation("Match"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + 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.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + 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.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieRoundClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Navigation("AdministeredPenalties"); + + b.Navigation("Meta"); + + b.Navigation("ReceivedPenalties"); + + b.Navigation("ZombieClientStats"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Navigation("PredictedViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Navigation("HitLocations"); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Navigation("Children"); + + b.Navigation("ReceivedPenalties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/Sqlite/20230906230124_AddZombieStatsEventLog.cs b/Data/Migrations/Sqlite/20230906230124_AddZombieStatsEventLog.cs new file mode 100644 index 00000000..78e3f08e --- /dev/null +++ b/Data/Migrations/Sqlite/20230906230124_AddZombieStatsEventLog.cs @@ -0,0 +1,99 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Data.Migrations.Sqlite +{ + public partial class AddZombieStatsEventLog : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_EFZombieMatch_EFClients_EFClientClientId", + table: "EFZombieMatch"); + + migrationBuilder.DropIndex( + name: "IX_EFZombieMatch_EFClientClientId", + table: "EFZombieMatch"); + + migrationBuilder.DropColumn( + name: "EFClientClientId", + table: "EFZombieMatch"); + + migrationBuilder.CreateTable( + name: "EFZombieEvents", + columns: table => new + { + ZombieEventLogId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EventType = table.Column(type: "INTEGER", nullable: false), + SourceClientId = table.Column(type: "INTEGER", nullable: true), + AssociatedClientId = table.Column(type: "INTEGER", nullable: true), + NumericalValue = table.Column(type: "REAL", nullable: true), + TextualValue = table.Column(type: "TEXT", nullable: true), + MatchId = table.Column(type: "INTEGER", nullable: true), + CreatedDateTime = table.Column(type: "TEXT", nullable: false), + UpdatedDateTime = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EFZombieEvents", x => x.ZombieEventLogId); + table.ForeignKey( + name: "FK_EFZombieEvents_EFClients_AssociatedClientId", + column: x => x.AssociatedClientId, + principalTable: "EFClients", + principalColumn: "ClientId"); + table.ForeignKey( + name: "FK_EFZombieEvents_EFClients_SourceClientId", + column: x => x.SourceClientId, + principalTable: "EFClients", + principalColumn: "ClientId"); + table.ForeignKey( + name: "FK_EFZombieEvents_EFZombieMatch_MatchId", + column: x => x.MatchId, + principalTable: "EFZombieMatch", + principalColumn: "ZombieMatchId"); + }); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieEvents_AssociatedClientId", + table: "EFZombieEvents", + column: "AssociatedClientId"); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieEvents_MatchId", + table: "EFZombieEvents", + column: "MatchId"); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieEvents_SourceClientId", + table: "EFZombieEvents", + column: "SourceClientId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EFZombieEvents"); + + migrationBuilder.AddColumn( + name: "EFClientClientId", + table: "EFZombieMatch", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_EFZombieMatch_EFClientClientId", + table: "EFZombieMatch", + column: "EFClientClientId"); + + migrationBuilder.AddForeignKey( + name: "FK_EFZombieMatch_EFClients_EFClientClientId", + table: "EFZombieMatch", + column: "EFClientClientId", + principalTable: "EFClients", + principalColumn: "ClientId"); + } + } +} diff --git a/Data/Migrations/Sqlite/20240212024708_AddHeadshotKillsToZombieClientState.Designer.cs b/Data/Migrations/Sqlite/20240212024708_AddHeadshotKillsToZombieClientState.Designer.cs new file mode 100644 index 00000000..78c0df24 --- /dev/null +++ b/Data/Migrations/Sqlite/20240212024708_AddHeadshotKillsToZombieClientState.Designer.cs @@ -0,0 +1,2016 @@ +// +using System; +using Data.MigrationContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Data.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20240212024708_AddHeadshotKillsToZombieClientState")] + partial class AddHeadshotKillsToZombieClientState + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.1"); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("SnapshotId") + .HasColumnType("INTEGER"); + + b.Property("Vector3Id") + .HasColumnType("INTEGER"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AliasLinkId") + .HasColumnType("INTEGER"); + + b.Property("Connections") + .HasColumnType("INTEGER"); + + b.Property("CurrentAliasId") + .HasColumnType("INTEGER"); + + b.Property("FirstConnection") + .HasColumnType("TEXT"); + + b.Property("GameName") + .HasColumnType("INTEGER"); + + b.Property("LastConnection") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Masked") + .HasColumnType("INTEGER"); + + b.Property("NetworkId") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("PasswordSalt") + .HasColumnType("TEXT"); + + b.Property("TotalConnectionTime") + .HasColumnType("INTEGER"); + + b.HasKey("ClientId"); + + b.HasAlternateKey("NetworkId", "GameName"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("LastConnection"); + + b.HasIndex("NetworkId"); + + b.ToTable("EFClients", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.Property("ClientConnectionId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("ConnectionType") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ClientConnectionId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientConnectionHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AttackerId") + .HasColumnType("INTEGER"); + + b.Property("Damage") + .HasColumnType("INTEGER"); + + b.Property("DeathOriginVector3Id") + .HasColumnType("INTEGER"); + + b.Property("DeathType") + .HasColumnType("INTEGER"); + + b.Property("Fraction") + .HasColumnType("REAL"); + + b.Property("HitLoc") + .HasColumnType("INTEGER"); + + b.Property("IsKill") + .HasColumnType("INTEGER"); + + b.Property("KillOriginVector3Id") + .HasColumnType("INTEGER"); + + b.Property("Map") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("VictimId") + .HasColumnType("INTEGER"); + + b.Property("ViewAnglesVector3Id") + .HasColumnType("INTEGER"); + + b.Property("VisibilityPercentage") + .HasColumnType("REAL"); + + b.Property("Weapon") + .HasColumnType("INTEGER"); + + b.Property("WeaponReference") + .HasColumnType("TEXT"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("SentIngame") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TimeSent") + .HasColumnType("TEXT"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CurrentSessionLength") + .HasColumnType("INTEGER"); + + b.Property("CurrentStrain") + .HasColumnType("REAL"); + + b.Property("CurrentViewAngleId") + .HasColumnType("INTEGER"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("Distance") + .HasColumnType("REAL"); + + b.Property("EloRating") + .HasColumnType("REAL"); + + b.Property("HitDestinationId") + .HasColumnType("INTEGER"); + + b.Property("HitLocation") + .HasColumnType("INTEGER"); + + b.Property("HitLocationReference") + .HasColumnType("TEXT"); + + b.Property("HitOriginId") + .HasColumnType("INTEGER"); + + b.Property("HitType") + .HasColumnType("INTEGER"); + + b.Property("Hits") + .HasColumnType("INTEGER"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("LastStrainAngleId") + .HasColumnType("INTEGER"); + + b.Property("RecoilOffset") + .HasColumnType("REAL"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SessionAngleOffset") + .HasColumnType("REAL"); + + b.Property("SessionAverageSnapValue") + .HasColumnType("REAL"); + + b.Property("SessionSPM") + .HasColumnType("REAL"); + + b.Property("SessionScore") + .HasColumnType("INTEGER"); + + b.Property("SessionSnapHits") + .HasColumnType("INTEGER"); + + b.Property("StrainAngleBetween") + .HasColumnType("REAL"); + + b.Property("TimeSinceLastEvent") + .HasColumnType("INTEGER"); + + b.Property("WeaponId") + .HasColumnType("INTEGER"); + + b.Property("WeaponReference") + .HasColumnType("TEXT"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFACSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.Property("ClientHitStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DamageInflicted") + .HasColumnType("INTEGER"); + + b.Property("DamageReceived") + .HasColumnType("INTEGER"); + + b.Property("DeathCount") + .HasColumnType("INTEGER"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("HitLocationId") + .HasColumnType("INTEGER"); + + b.Property("KillCount") + .HasColumnType("INTEGER"); + + b.Property("MeansOfDeathId") + .HasColumnType("INTEGER"); + + b.Property("ReceivedHitCount") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SuicideCount") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("UsageSeconds") + .HasColumnType("INTEGER"); + + b.Property("WeaponAttachmentComboId") + .HasColumnType("INTEGER"); + + b.Property("WeaponId") + .HasColumnType("INTEGER"); + + b.HasKey("ClientHitStatisticId"); + + b.HasIndex("ClientId"); + + b.HasIndex("HitLocationId"); + + b.HasIndex("MeansOfDeathId"); + + b.HasIndex("ServerId"); + + b.HasIndex("WeaponAttachmentComboId"); + + b.HasIndex("WeaponId"); + + b.ToTable("EFClientHitStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.Property("ClientRankingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Newest") + .HasColumnType("INTEGER"); + + b.Property("PerformanceBucket") + .HasColumnType("TEXT"); + + b.Property("PerformanceMetric") + .HasColumnType("REAL"); + + b.Property("Ranking") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("ZScore") + .HasColumnType("REAL"); + + b.HasKey("ClientRankingHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("Ranking"); + + b.HasIndex("ServerId"); + + b.HasIndex("UpdatedDateTime"); + + b.HasIndex("ZScore"); + + b.ToTable("EFClientRankingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AverageSnapValue") + .HasColumnType("REAL"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("EloRating") + .HasColumnType("REAL"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("MaxStrain") + .HasColumnType("REAL"); + + b.Property("RollingWeightedKDR") + .HasColumnType("REAL"); + + b.Property("SPM") + .HasColumnType("REAL"); + + b.Property("Skill") + .HasColumnType("REAL"); + + b.Property("SnapHitCount") + .HasColumnType("INTEGER"); + + b.Property("TimePlayed") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("ZScore") + .HasColumnType("REAL"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ZScore"); + + b.HasIndex("ClientId", "TimePlayed", "ZScore"); + + b.ToTable("EFClientStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("EFClientStatisticsClientId") + .HasColumnType("INTEGER") + .HasColumnName("EFClientStatisticsClientId"); + + b.Property("EFClientStatisticsServerId") + .HasColumnType("INTEGER") + .HasColumnName("EFClientStatisticsServerId"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("HitOffsetAverage") + .HasColumnType("REAL"); + + b.Property("Location") + .HasColumnType("INTEGER"); + + b.Property("MaxAngleDistance") + .HasColumnType("REAL"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("EFClientStatisticsServerId"); + + b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); + + b.ToTable("EFHitLocationCounts", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ActivityAmount") + .HasColumnType("INTEGER"); + + b.Property("Newest") + .HasColumnType("INTEGER"); + + b.Property("Performance") + .HasColumnType("REAL"); + + b.Property("Ranking") + .HasColumnType("INTEGER"); + + b.Property("RatingHistoryId") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); + + b.ToTable("EFRating", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => + { + b.Property("HitLocationId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("HitLocationId"); + + b.HasIndex("Name"); + + b.ToTable("EFHitLocations", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => + { + b.Property("MapId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("MapId"); + + b.ToTable("EFMaps", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => + { + b.Property("MeansOfDeathId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("MeansOfDeathId"); + + b.ToTable("EFMeansOfDeath", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => + { + b.Property("WeaponId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponId"); + + b.HasIndex("Name"); + + b.ToTable("EFWeapons", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => + { + b.Property("WeaponAttachmentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponAttachmentId"); + + b.ToTable("EFWeaponAttachments", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.Property("WeaponAttachmentComboId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attachment1Id") + .HasColumnType("INTEGER"); + + b.Property("Attachment2Id") + .HasColumnType("INTEGER"); + + b.Property("Attachment3Id") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponAttachmentComboId"); + + b.HasIndex("Attachment1Id"); + + b.HasIndex("Attachment2Id"); + + b.HasIndex("Attachment3Id"); + + b.ToTable("EFWeaponAttachmentCombos", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("IPAddress") + .HasColumnType("INTEGER"); + + b.Property("LinkId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("SearchableIPAddress") + .ValueGeneratedOnAddOrUpdate() + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true); + + b.Property("SearchableName") + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableIPAddress"); + + b.HasIndex("SearchableName"); + + b.HasIndex("Name", "IPAddress"); + + b.ToTable("EFAlias", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CurrentValue") + .HasColumnType("TEXT"); + + b.Property("ImpersonationEntityId") + .HasColumnType("INTEGER"); + + b.Property("OriginEntityId") + .HasColumnType("INTEGER"); + + b.Property("PreviousValue") + .HasColumnType("TEXT"); + + b.Property("TargetEntityId") + .HasColumnType("INTEGER"); + + b.Property("TimeChanged") + .HasColumnType("TEXT"); + + b.Property("TypeOfChange") + .HasColumnType("INTEGER"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Extra") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("LinkedMetaId") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.HasIndex("LinkedMetaId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AutomatedOffense") + .HasColumnType("TEXT"); + + b.Property("Expires") + .HasColumnType("TEXT"); + + b.Property("IsEvadedOffense") + .HasColumnType("INTEGER"); + + b.Property("LinkId") + .HasColumnType("INTEGER"); + + b.Property("OffenderId") + .HasColumnType("INTEGER"); + + b.Property("Offense") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PunisherId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.Property("PenaltyIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("IPv4Address") + .HasColumnType("INTEGER"); + + b.Property("NetworkId") + .HasColumnType("INTEGER"); + + b.Property("PenaltyId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("PenaltyIdentifierId"); + + b.HasIndex("IPv4Address"); + + b.HasIndex("NetworkId"); + + b.HasIndex("PenaltyId"); + + b.ToTable("EFPenaltyIdentifiers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.Property("InboxMessageId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DestinationClientId") + .HasColumnType("INTEGER"); + + b.Property("IsDelivered") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SourceClientId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("InboxMessageId"); + + b.HasIndex("DestinationClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServer", b => + { + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("EndPoint") + .HasColumnType("TEXT"); + + b.Property("GameName") + .HasColumnType("INTEGER"); + + b.Property("HostName") + .HasColumnType("TEXT"); + + b.Property("IsPasswordProtected") + .HasColumnType("INTEGER"); + + b.Property("PerformanceBucket") + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.Property("ServerSnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("CapturedAt") + .HasColumnType("TEXT"); + + b.Property("ClientCount") + .HasColumnType("INTEGER"); + + b.Property("ConnectionInterrupted") + .HasColumnType("INTEGER"); + + b.Property("MapId") + .HasColumnType("INTEGER"); + + b.Property("PeriodBlock") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.HasKey("ServerSnapshotId"); + + b.HasIndex("CapturedAt"); + + b.HasIndex("MapId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TotalKills") + .HasColumnType("INTEGER"); + + b.Property("TotalPlayTime") + .HasColumnType("INTEGER"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("Z") + .HasColumnType("REAL"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStat", b => + { + b.Property("ZombieClientStatId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DamageDealt") + .HasColumnType("INTEGER"); + + b.Property("DamageReceived") + .HasColumnType("INTEGER"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("Downs") + .HasColumnType("INTEGER"); + + b.Property("HeadshotKills") + .HasColumnType("INTEGER"); + + b.Property("Headshots") + .HasColumnType("INTEGER"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("Melees") + .HasColumnType("INTEGER"); + + b.Property("PerksConsumed") + .HasColumnType("INTEGER"); + + b.Property("PointsEarned") + .HasColumnType("INTEGER"); + + b.Property("PointsSpent") + .HasColumnType("INTEGER"); + + b.Property("PowerupsGrabbed") + .HasColumnType("INTEGER"); + + b.Property("Revives") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ZombieClientStatId"); + + b.HasIndex("ClientId"); + + b.HasIndex("MatchId"); + + b.ToTable("EFZombieClientStat", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieClientStatRecord", b => + { + b.Property("ZombieClientStatRecordId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RoundId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ZombieClientStatRecordId"); + + b.HasIndex("ClientId"); + + b.HasIndex("RoundId"); + + b.ToTable("EFZombieClientStatRecord", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieEventLog", b => + { + b.Property("ZombieEventLogId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssociatedClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("NumericalValue") + .HasColumnType("REAL"); + + b.Property("SourceClientId") + .HasColumnType("INTEGER"); + + b.Property("TextualValue") + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ZombieEventLogId"); + + b.HasIndex("AssociatedClientId"); + + b.HasIndex("MatchId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("EFZombieEvents", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + b.Property("ZombieMatchId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientsCompleted") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("MapId") + .HasColumnType("INTEGER"); + + b.Property("MatchEndDate") + .HasColumnType("TEXT"); + + b.Property("MatchStartDate") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ZombieMatchId"); + + 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("AlivePercentage") + .HasColumnType("REAL"); + + b.Property("AverageDowns") + .HasColumnType("REAL"); + + b.Property("AverageKillsPerDown") + .HasColumnType("REAL"); + + b.Property("AverageMelees") + .HasColumnType("REAL"); + + b.Property("AveragePoints") + .HasColumnType("REAL"); + + b.Property("AverageRevives") + .HasColumnType("REAL"); + + b.Property("AverageRoundReached") + .HasColumnType("REAL"); + + b.Property("HeadshotPercentage") + .HasColumnType("REAL"); + + b.Property("HighestRound") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TotalMatchesCompleted") + .HasColumnType("INTEGER"); + + b.Property("TotalMatchesPlayed") + .HasColumnType("INTEGER"); + + b.Property("TotalRoundsPlayed") + .HasColumnType("INTEGER"); + + b.HasIndex("ServerId"); + + b.ToTable("EFZombieAggregateClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.ToTable("EFZombieMatchClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieRoundClientStat", b => + { + b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); + + b.Property("Duration") + .HasColumnType("TEXT"); + + b.Property("EndTime") + .HasColumnType("TEXT"); + + b.Property("Points") + .HasColumnType("INTEGER"); + + b.Property("RoundNumber") + .HasColumnType("INTEGER"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("TimeAlive") + .HasColumnType("TEXT"); + + b.ToTable("EFZombieRoundClientStat", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Snapshot"); + + b.Navigation("Vector"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.HasOne("Data.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AliasLink"); + + b.Navigation("CurrentAlias"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.HasOne("Data.Models.Client.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("Data.Models.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + + b.Navigation("Attacker"); + + b.Navigation("DeathOrigin"); + + b.Navigation("KillOrigin"); + + b.Navigation("Server"); + + b.Navigation("Victim"); + + b.Navigation("ViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("CurrentViewAngle"); + + b.Navigation("HitDestination"); + + b.Navigation("HitOrigin"); + + b.Navigation("LastStrainAngle"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFHitLocation", "HitLocation") + .WithMany() + .HasForeignKey("HitLocationId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFMeansOfDeath", "MeansOfDeath") + .WithMany() + .HasForeignKey("MeansOfDeathId"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", "WeaponAttachmentCombo") + .WithMany() + .HasForeignKey("WeaponAttachmentComboId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") + .WithMany() + .HasForeignKey("WeaponId"); + + b.Navigation("Client"); + + b.Navigation("HitLocation"); + + b.Navigation("MeansOfDeath"); + + b.Navigation("Server"); + + b.Navigation("Weapon"); + + b.Navigation("WeaponAttachmentCombo"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("EFClientStatisticsClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.EFClientStatistics", null) + .WithMany("HitLocations") + .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.HasOne("Data.Models.Client.Stats.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("RatingHistory"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment1") + .WithMany() + .HasForeignKey("Attachment1Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment2") + .WithMany() + .HasForeignKey("Attachment2Id"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") + .WithMany() + .HasForeignKey("Attachment3Id"); + + b.Navigation("Attachment1"); + + b.Navigation("Attachment2"); + + b.Navigation("Attachment3"); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId"); + + b.HasOne("Data.Models.EFMeta", "LinkedMeta") + .WithMany() + .HasForeignKey("LinkedMetaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Client"); + + b.Navigation("LinkedMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId"); + + b.HasOne("Data.Models.Client.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("Offender"); + + b.Navigation("Punisher"); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.HasOne("Data.Models.EFPenalty", "Penalty") + .WithMany() + .HasForeignKey("PenaltyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Penalty"); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "DestinationClient") + .WithMany() + .HasForeignKey("DestinationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationClient"); + + b.Navigation("Server"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map") + .WithMany() + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Map"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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() + .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.ZombieEventLog", b => + { + b.HasOne("Data.Models.Client.EFClient", "AssociatedClient") + .WithMany() + .HasForeignKey("AssociatedClientId"); + + b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") + .WithMany() + .HasForeignKey("MatchId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId"); + + b.Navigation("AssociatedClient"); + + b.Navigation("Match"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => + { + 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.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Zombie.ZombieMatchClientStat", b => + { + 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.Zombie.ZombieClientStat", null) + .WithOne() + .HasForeignKey("Data.Models.Zombie.ZombieRoundClientStat", "ZombieClientStatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Navigation("AdministeredPenalties"); + + b.Navigation("Meta"); + + b.Navigation("ReceivedPenalties"); + + b.Navigation("ZombieClientStats"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Navigation("PredictedViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Navigation("HitLocations"); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Navigation("Children"); + + b.Navigation("ReceivedPenalties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/Sqlite/20240212024708_AddHeadshotKillsToZombieClientState.cs b/Data/Migrations/Sqlite/20240212024708_AddHeadshotKillsToZombieClientState.cs new file mode 100644 index 00000000..28c4ce61 --- /dev/null +++ b/Data/Migrations/Sqlite/20240212024708_AddHeadshotKillsToZombieClientState.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Data.Migrations.Sqlite +{ + /// + public partial class AddHeadshotKillsToZombieClientState : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "HeadshotKills", + table: "EFZombieClientStat", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HeadshotKills", + table: "EFZombieClientStat"); + } + } +} diff --git a/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs b/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs index 5869c6e8..578b7a19 100644 --- a/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Data.Migrations.Sqlite protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.1"); modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => { @@ -1181,7 +1181,7 @@ namespace Data.Migrations.Sqlite b.Property("CreatedDateTime") .HasColumnType("TEXT"); - b.Property("DamageDealt") + b.Property("DamageDealt") .HasColumnType("INTEGER"); b.Property("DamageReceived") @@ -1193,6 +1193,9 @@ namespace Data.Migrations.Sqlite b.Property("Downs") .HasColumnType("INTEGER"); + b.Property("HeadshotKills") + .HasColumnType("INTEGER"); + b.Property("Headshots") .HasColumnType("INTEGER"); @@ -1230,6 +1233,8 @@ namespace Data.Migrations.Sqlite b.HasIndex("MatchId"); b.ToTable("EFZombieClientStat", (string)null); + + b.UseTptMappingStrategy(); }); modelBuilder.Entity("Data.Models.Zombie.ZombieClientStatRecord", b => @@ -1271,6 +1276,47 @@ namespace Data.Migrations.Sqlite b.ToTable("EFZombieClientStatRecord", (string)null); }); + modelBuilder.Entity("Data.Models.Zombie.ZombieEventLog", b => + { + b.Property("ZombieEventLogId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssociatedClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("NumericalValue") + .HasColumnType("REAL"); + + b.Property("SourceClientId") + .HasColumnType("INTEGER"); + + b.Property("TextualValue") + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ZombieEventLogId"); + + b.HasIndex("AssociatedClientId"); + + b.HasIndex("MatchId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("EFZombieEvents", (string)null); + }); + modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => { b.Property("ZombieMatchId") @@ -1283,9 +1329,6 @@ namespace Data.Migrations.Sqlite b.Property("CreatedDateTime") .HasColumnType("TEXT"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - b.Property("MapId") .HasColumnType("INTEGER"); @@ -1303,8 +1346,6 @@ namespace Data.Migrations.Sqlite b.HasKey("ZombieMatchId"); - b.HasIndex("EFClientClientId"); - b.HasIndex("MapId"); b.HasIndex("ServerId"); @@ -1337,15 +1378,15 @@ namespace Data.Migrations.Sqlite b.Property("AverageRoundReached") .HasColumnType("REAL"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - b.Property("HeadshotPercentage") .HasColumnType("REAL"); b.Property("HighestRound") .HasColumnType("INTEGER"); + b.Property("ServerId") + .HasColumnType("INTEGER"); + b.Property("TotalMatchesCompleted") .HasColumnType("INTEGER"); @@ -1355,7 +1396,7 @@ namespace Data.Migrations.Sqlite b.Property("TotalRoundsPlayed") .HasColumnType("INTEGER"); - b.HasIndex("EFClientClientId"); + b.HasIndex("ServerId"); b.ToTable("EFZombieAggregateClientStat", (string)null); }); @@ -1364,11 +1405,6 @@ namespace Data.Migrations.Sqlite { b.HasBaseType("Data.Models.Zombie.ZombieClientStat"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - - b.HasIndex("EFClientClientId"); - b.ToTable("EFZombieMatchClientStat", (string)null); }); @@ -1379,9 +1415,6 @@ namespace Data.Migrations.Sqlite b.Property("Duration") .HasColumnType("TEXT"); - b.Property("EFClientClientId") - .HasColumnType("INTEGER"); - b.Property("EndTime") .HasColumnType("TEXT"); @@ -1397,8 +1430,6 @@ namespace Data.Migrations.Sqlite b.Property("TimeAlive") .HasColumnType("TEXT"); - b.HasIndex("EFClientClientId"); - b.ToTable("EFZombieRoundClientStat", (string)null); }); @@ -1852,7 +1883,7 @@ namespace Data.Migrations.Sqlite .IsRequired(); b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") - .WithMany("ClientStats") + .WithMany() .HasForeignKey("MatchId"); b.Navigation("Client"); @@ -1875,12 +1906,29 @@ namespace Data.Migrations.Sqlite b.Navigation("Round"); }); + modelBuilder.Entity("Data.Models.Zombie.ZombieEventLog", b => + { + b.HasOne("Data.Models.Client.EFClient", "AssociatedClient") + .WithMany() + .HasForeignKey("AssociatedClientId"); + + b.HasOne("Data.Models.Zombie.ZombieMatch", "Match") + .WithMany() + .HasForeignKey("MatchId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId"); + + b.Navigation("AssociatedClient"); + + b.Navigation("Match"); + + b.Navigation("SourceClient"); + }); + 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"); @@ -1896,23 +1944,21 @@ namespace Data.Migrations.Sqlite modelBuilder.Entity("Data.Models.Zombie.ZombieAggregateClientStat", b => { - b.HasOne("Data.Models.Client.EFClient", null) - .WithMany("ZombieAggregateClientStats") - .HasForeignKey("EFClientClientId"); + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); b.HasOne("Data.Models.Zombie.ZombieClientStat", null) .WithOne() .HasForeignKey("Data.Models.Zombie.ZombieAggregateClientStat", "ZombieClientStatId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Server"); }); 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") @@ -1922,10 +1968,6 @@ namespace Data.Migrations.Sqlite 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") @@ -1941,15 +1983,7 @@ namespace Data.Migrations.Sqlite 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 => @@ -1973,11 +2007,6 @@ namespace Data.Migrations.Sqlite b.Navigation("ReceivedPenalties"); }); - - modelBuilder.Entity("Data.Models.Zombie.ZombieMatch", b => - { - b.Navigation("ClientStats"); - }); #pragma warning restore 612, 618 } } diff --git a/Data/Models/Client/EFClient.cs b/Data/Models/Client/EFClient.cs index 6cb11468..79fedc77 100644 --- a/Data/Models/Client/EFClient.cs +++ b/Data/Models/Client/EFClient.cs @@ -84,10 +84,6 @@ namespace Data.Models.Client public virtual ICollection Meta { get; set; } public virtual ICollection ReceivedPenalties { get; set; } public virtual ICollection AdministeredPenalties { get; set; } - public virtual ICollection ZombieAggregateClientStats { get; set; } public virtual ICollection ZombieClientStats { get; set; } - public virtual ICollection ZombieMatches { get; set; } - public virtual ICollection ZombieMatchClientStats { get; set; } - public virtual ICollection ZombieRoundClientStats { get; set; } } } diff --git a/Data/Models/DatedRecord.cs b/Data/Models/DatedRecord.cs index 559c725b..f7fabce4 100644 --- a/Data/Models/DatedRecord.cs +++ b/Data/Models/DatedRecord.cs @@ -2,8 +2,9 @@ namespace Data.Models; -public class DatedRecord +public class DatedRecord : IdentifierRecord { public DateTimeOffset CreatedDateTime { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset? UpdatedDateTime { get; set; } + public override long Id { get; } } diff --git a/Data/Models/IdentifierRecord.cs b/Data/Models/IdentifierRecord.cs new file mode 100644 index 00000000..99da333f --- /dev/null +++ b/Data/Models/IdentifierRecord.cs @@ -0,0 +1,6 @@ +namespace Data.Models; + +public abstract class IdentifierRecord +{ + public abstract long Id { get; } +} diff --git a/Data/Models/Zombie/ZombieAggregateClientStat.cs b/Data/Models/Zombie/ZombieAggregateClientStat.cs index d77b21e7..14254854 100644 --- a/Data/Models/Zombie/ZombieAggregateClientStat.cs +++ b/Data/Models/Zombie/ZombieAggregateClientStat.cs @@ -1,9 +1,15 @@ using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Data.Models.Server; namespace Data.Models.Zombie; public class ZombieAggregateClientStat : ZombieClientStat { + public long? ServerId { get; set; } + [ForeignKey(nameof(ServerId))] + public EFServer Server { get; set; } + #region Average public double AverageKillsPerDown { get; set; } @@ -41,4 +47,8 @@ public class ZombieAggregateClientStat : ZombieClientStat nameof(TotalRoundsPlayed), nameof(TotalMatchesPlayed) }; + + public static readonly string[] SkillKeys = + RecordsKeys.Except(new[] { nameof(TotalMatchesPlayed), nameof(TotalRoundsPlayed), nameof(AverageDowns) }) + .ToArray(); } diff --git a/Data/Models/Zombie/ZombieClientStat.cs b/Data/Models/Zombie/ZombieClientStat.cs index 97e4c749..c2e0b246 100644 --- a/Data/Models/Zombie/ZombieClientStat.cs +++ b/Data/Models/Zombie/ZombieClientStat.cs @@ -9,7 +9,9 @@ public abstract class ZombieClientStat : DatedRecord { [Key] public long ZombieClientStatId { get; set; } - + + [NotMapped] public override long Id => ZombieClientStatId; + public int? MatchId { get; set; } [ForeignKey(nameof(MatchId))] @@ -17,13 +19,14 @@ public abstract class ZombieClientStat : DatedRecord public int ClientId { get; set; } [ForeignKey(nameof(ClientId))] - public virtual EFClient? Client { get; set; } + public virtual EFClient Client { get; set; } public int Kills { get; set; } public int Deaths { get; set; } - public int DamageDealt { get; set; } + public long DamageDealt { get; set; } public int DamageReceived { get; set; } public int Headshots { get; set; } + public int HeadshotKills { get; set; } public int Melees { get; set; } public int Downs { get; set; } public int Revives { get; set; } diff --git a/Data/Models/Zombie/ZombieClientStatRecord.cs b/Data/Models/Zombie/ZombieClientStatRecord.cs index 356eab89..5003fb7c 100644 --- a/Data/Models/Zombie/ZombieClientStatRecord.cs +++ b/Data/Models/Zombie/ZombieClientStatRecord.cs @@ -15,6 +15,9 @@ public class ZombieClientStatRecord : DatedRecord { [Key] public int ZombieClientStatRecordId { get; set; } + + [NotMapped] public override long Id => ZombieClientStatRecordId; + public string Name { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; diff --git a/Data/Models/Zombie/ZombieEventLog.cs b/Data/Models/Zombie/ZombieEventLog.cs new file mode 100644 index 00000000..deb07ed5 --- /dev/null +++ b/Data/Models/Zombie/ZombieEventLog.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Data.Models.Client; + +namespace Data.Models.Zombie; + +public enum EventLogType +{ + Default = 0, + PerformanceCluster = 1, + DamageTaken = 2, + Downed = 3, + Died = 4, + Revived = 5, + WasRevived = 6, + PerkConsumed = 7, + PowerupGrabbed = 8, + RoundCompleted = 9, + JoinedMatch = 10, + LeftMatch = 11, + MatchStarted = 12, + MatchEnded = 13 +} + +public class ZombieEventLog : DatedRecord +{ + [Key] + public long ZombieEventLogId { get; set; } + + [NotMapped] public override long Id => ZombieEventLogId; + + public EventLogType EventType { get; set; } + + public int? SourceClientId { get; set; } + [ForeignKey(nameof(SourceClientId))] + public EFClient SourceClient { get; set; } + + public int? AssociatedClientId { get; set; } + [ForeignKey(nameof(AssociatedClientId))] + public EFClient AssociatedClient { get; set; } + + public double? NumericalValue { get; set; } + public string TextualValue { get; set; } + + public int? MatchId { get; set; } + [ForeignKey(nameof(MatchId))] + public ZombieMatch Match { get; set; } +} diff --git a/Data/Models/Zombie/ZombieMatch.cs b/Data/Models/Zombie/ZombieMatch.cs index c58135f2..13950386 100644 --- a/Data/Models/Zombie/ZombieMatch.cs +++ b/Data/Models/Zombie/ZombieMatch.cs @@ -1,6 +1,5 @@ #nullable enable using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Data.Models.Client.Stats.Reference; @@ -12,6 +11,8 @@ public class ZombieMatch : DatedRecord { [Key] public int ZombieMatchId { get; set; } + + [NotMapped] public override long Id => ZombieMatchId; public int? MapId { get; set; } [ForeignKey(nameof(MapId))] @@ -22,8 +23,6 @@ public class ZombieMatch : DatedRecord public virtual EFServer? Server { get; set; } public int ClientsCompleted { get; set; } - - public virtual ICollection? ClientStats { get; set; } public DateTimeOffset MatchStartDate { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset? MatchEndDate { get; set; } diff --git a/Data/Models/Zombie/ZombieMatchClientStat.cs b/Data/Models/Zombie/ZombieMatchClientStat.cs index bb24206f..ad83d808 100644 --- a/Data/Models/Zombie/ZombieMatchClientStat.cs +++ b/Data/Models/Zombie/ZombieMatchClientStat.cs @@ -1,6 +1,8 @@ -namespace Data.Models.Zombie; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Data.Models.Zombie; public class ZombieMatchClientStat : ZombieClientStat { - + [NotMapped] public int? JoinedRound { get; set; } } diff --git a/Plugins/Stats/Client/ServerDistributionCalculator.cs b/Plugins/Stats/Client/ServerDistributionCalculator.cs index 6ff30797..a0e4735d 100644 --- a/Plugins/Stats/Client/ServerDistributionCalculator.cs +++ b/Plugins/Stats/Client/ServerDistributionCalculator.cs @@ -57,7 +57,7 @@ namespace Stats.Client var iqPerformances = set .Where(s => s.Skill > 0) - .Where(s => s.EloRating > 0) + .Where(s => s.EloRating >= 0) .Where(s => s.Client.Level != EFClient.Permission.Banned); foreach (var serverId in _serverIds) @@ -71,30 +71,33 @@ namespace Stats.Client distributions.Add(serverId.ToString(), distributionParams); } - foreach (var server in _appConfig.Servers) + foreach (var performanceBucketGroup in _appConfig.Servers.GroupBy(server => server.PerformanceBucket)) { - if (string.IsNullOrWhiteSpace(server.PerformanceBucket)) + if (string.IsNullOrWhiteSpace(performanceBucketGroup.Key)) { continue; } + var performanceBucket = performanceBucketGroup.Key; + var bucketConfig = _configuration.PerformanceBuckets.FirstOrDefault(bucket => - bucket.Name == server.PerformanceBucket) ?? new PerformanceBucketConfiguration(); + bucket.Name == performanceBucket) ?? new PerformanceBucketConfiguration(); - var oldestPerf = DateTimeOffset.UtcNow - bucketConfig.RankingExpiration; + var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration; var performances = await iqPerformances - .Where(perf => perf.Server.PerformanceBucket == server.PerformanceBucket) + .Where(perf => perf.Server.PerformanceBucket == performanceBucket) .Where(perf => perf.TimePlayed >= bucketConfig.ClientMinPlayTime.TotalSeconds) .Where(perf => perf.UpdatedAt >= oldestPerf) + .Where(perf => perf.Skill < 999999) .Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0) .ToListAsync(token); var distributionParams = performances.GenerateDistributionParameters(); - distributions.Add(server.PerformanceBucket, distributionParams); + distributions.Add(performanceBucket, distributionParams); } return distributions; - }, DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromHours(1)); + }, DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(1)); foreach (var server in _appConfig.Servers) { @@ -117,7 +120,7 @@ namespace Stats.Client var zScore = await set .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime, oldestStat)) .Where(s => s.Skill > 0) - .Where(s => s.EloRating > 0) + .Where(s => s.EloRating >= 1) .Where(stat => performanceBucket == null || performanceBucket == stat.Server.PerformanceBucket) .GroupBy(stat => stat.ClientId) @@ -127,7 +130,7 @@ namespace Stats.Client return zScore ?? 0; }, MaxZScoreCacheKey, new[] { server.PerformanceBucket }, - Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30)); + Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromMinutes(30)); await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new[] { server.PerformanceBucket }); } @@ -199,6 +202,8 @@ namespace Stats.Client return 0.0; } + value = Math.Max(1, value); + var zScore = (Math.Log(value) - sdParams.Mean) / sdParams.Sigma; return zScore; } diff --git a/Plugins/Stats/Dtos/AdvancedStatsInfo.cs b/Plugins/Stats/Dtos/AdvancedStatsInfo.cs index bb09093c..1a22ef54 100644 --- a/Plugins/Stats/Dtos/AdvancedStatsInfo.cs +++ b/Plugins/Stats/Dtos/AdvancedStatsInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Data.Models; using Data.Models.Client; using Data.Models.Client.Stats; using SharedLibraryCore.Dtos; @@ -25,5 +26,6 @@ namespace Stats.Dtos public List ByAttachmentCombo { get; set; } public List Ratings { get; set; } public List LegacyStats { get; set; } + public List CustomMetrics { get; set; } = new(); } } diff --git a/Plugins/Stats/Dtos/TopStatsInfo.cs b/Plugins/Stats/Dtos/TopStatsInfo.cs index 56403ed7..70ecbeb2 100644 --- a/Plugins/Stats/Dtos/TopStatsInfo.cs +++ b/Plugins/Stats/Dtos/TopStatsInfo.cs @@ -1,7 +1,7 @@ using SharedLibraryCore.Dtos; using System; using System.Collections.Generic; -using System.Text; +using Data.Models; namespace IW4MAdmin.Plugins.Stats.Web.Dtos { @@ -22,6 +22,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Dtos public List PerformanceHistory { get; set; } public double? ZScore { get; set; } public long? ServerId { get; set; } + public List Metrics { get; } = new(); } public class PerformanceHistory diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 748e1819..7e290055 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -110,25 +110,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return 0; } - public Expression> GetNewRankingFunc(int? clientId = null, - long? serverId = null) + private Expression> GetNewRankingFunc(TimeSpan oldestStat, TimeSpan minPlayTime, long? serverId = null) { - return (ranking) => ranking.ServerId == serverId - && ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned - && ranking.CreatedDateTime >= Extensions.FifteenDaysAgo() - && ranking.ZScore != null - && ranking.PerformanceMetric != null - && ranking.Newest - && ranking.Client.TotalConnectionTime >= - _config.TopPlayersMinPlayTime; + var oldestDate = DateTime.UtcNow - oldestStat; + return ranking => ranking.ServerId == serverId + && ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned + && ranking.CreatedDateTime >= oldestDate + && ranking.ZScore != null + && ranking.PerformanceMetric != null + && ranking.Newest + && ranking.Client.TotalConnectionTime >= (int)minPlayTime.TotalSeconds; } public async Task GetTotalRankedPlayers(long serverId) { + var bucketConfig = await GetBucketConfig(serverId); + await using var context = _contextFactory.CreateContext(enableTracking: false); return await context.Set() - .Where(GetNewRankingFunc(serverId: serverId)) + .Where(GetNewRankingFunc(bucketConfig.RankingExpiration, bucketConfig.ClientMinPlayTime, serverId: serverId)) .CountAsync(); } @@ -143,12 +144,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public DateTime CreatedDateTime { get; set; } } - public async Task> GetNewTopStats(int start, int count, long? serverId = null) + public async Task> GetNewTopStats(int start, int count, long? serverId = null, string performanceBucket = null) { - await using var context = _contextFactory.CreateContext(false); + var bucketConfig = await GetBucketConfig(serverId); + await using var context = _contextFactory.CreateContext(false); var clientIdsList = await context.Set() - .Where(GetNewRankingFunc(serverId: serverId)) + .Where(GetNewRankingFunc(bucketConfig.RankingExpiration, bucketConfig.ClientMinPlayTime, serverId: serverId)) .OrderByDescending(ranking => ranking.PerformanceMetric) .Select(ranking => ranking.ClientId) .Skip(start) @@ -233,9 +235,77 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .OrderBy(r => r.Ranking) .ToList(); + foreach (var topStatsInfo in finished) + { + topStatsInfo.Metrics.AddRange(new EFMeta[] + { + new() + { + Extra = "Kills", + Value = topStatsInfo.Kills.ToNumericalString(), + Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"] + }, + new() + { + Extra = "Deaths", + Value = topStatsInfo.Deaths.ToNumericalString(), + Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"] + }, + new() + { + Extra = "KDR", + Value = topStatsInfo.KDR.ToNumericalString(), + Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"] + }, + new() + { + Extra = "TimePlayed", + Value = topStatsInfo.TimePlayedValue.HumanizeForCurrentCulture(), + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"] + }, + new() + { + Extra = "LastSeen", + Value = topStatsInfo.LastSeenValue.HumanizeForCurrentCulture(), + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_LSEEN"] + } + }); + } + + foreach (var customMetricFunc in Plugin.ServerManager.CustomStatsMetrics) + { + await customMetricFunc(finished.ToDictionary(kvp => kvp.ClientId, kvp => kvp.Metrics), serverId, + performanceBucket, true); + } + return finished; } + private async Task GetBucketConfig(long? serverId) + { + var defaultConfig = new PerformanceBucketConfiguration + { + ClientMinPlayTime = TimeSpan.FromSeconds(_config.TopPlayersMinPlayTime), + RankingExpiration = DateTime.UtcNow - Extensions.FifteenDaysAgo() + }; + + if (serverId is null) + { + return defaultConfig; + } + + var performanceBucket = + (await _serverCache.FirstAsync(server => server.Id == serverId)).PerformanceBucket; + + if (string.IsNullOrEmpty(performanceBucket)) + { + return defaultConfig; + } + + return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Name == performanceBucket) ?? + defaultConfig; + } + public async Task> GetTopStats(int start, int count, long? serverId = null) { if (_config.EnableAdvancedMetrics) @@ -1179,54 +1249,41 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public async Task UpdateHistoricalRanking(int clientId, EFClientStatistics clientStats, long serverId) { - await using var context = _contextFactory.CreateContext(); - var minPlayTime = _config.TopPlayersMinPlayTime; - var oldestStat = DateTimeOffset.UtcNow - Extensions.FifteenDaysAgo(); + var bucketConfig = await GetBucketConfig(serverId); - 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; + await using var context = _contextFactory.CreateContext(); + var oldestStateDate = DateTime.UtcNow - bucketConfig.RankingExpiration; var performances = await context.Set() .AsNoTracking() + .Include(stat => stat.Server) .Where(stat => stat.ClientId == clientId) .Where(stat => stat.ServerId != serverId) // ignore the one we're currently tracking .Where(stats => stats.UpdatedAt >= oldestStateDate) - .Where(stats => stats.TimePlayed >= minPlayTime) + .Where(stats => stats.TimePlayed >= (int)bucketConfig.ClientMinPlayTime.TotalSeconds) .ToListAsync(); - if (clientStats.TimePlayed >= minPlayTime) + if (clientStats.TimePlayed >= bucketConfig.ClientMinPlayTime.TotalSeconds) { - await UpdateForServer(clientId, clientStats, context, minPlayTime, oldestStat, serverId); + await UpdateForServer(clientId, clientStats, context, (int)bucketConfig.ClientMinPlayTime.TotalSeconds, bucketConfig.RankingExpiration, serverId); + clientStats.Server = await _serverCache.FirstAsync(server => server.Id == serverId); performances.Add(clientStats); } - if (performances.Any(performance => performance.TimePlayed >= minPlayTime)) + if (performances.Any(performance => performance.TimePlayed >= (int)bucketConfig.ClientMinPlayTime.TotalSeconds)) { - await UpdateAggregateForServerOrBucket(clientId, clientStats, context, performances, minPlayTime, - oldestStat, performanceBucket); + await UpdateAggregateForServerOrBucket(clientId, clientStats, context, performances, bucketConfig); } } - private async Task UpdateAggregateForServerOrBucket(int clientId, EFClientStatistics clientStats, DatabaseContext context, List performances, - int minPlayTime, TimeSpan oldestStat, string performanceBucket) + private async Task UpdateAggregateForServerOrBucket(int clientId, EFClientStatistics clientStats, DatabaseContext context, List performances, PerformanceBucketConfiguration bucketConfig) { var aggregateZScore = - performances.Where(performance => performance.Server.PerformanceBucket == performanceBucket) - .WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), minPlayTime); + performances.Where(performance => performance.Server.PerformanceBucket == bucketConfig.Name) + .WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), (int)bucketConfig.ClientMinPlayTime.TotalSeconds); int? aggregateRanking = await context.Set() .Where(stat => stat.ClientId != clientId) - .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime, oldestStat)) + .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc((int)bucketConfig.ClientMinPlayTime.TotalSeconds, bucketConfig.RankingExpiration)) .GroupBy(stat => stat.ClientId) .Where(group => group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed) > @@ -1234,7 +1291,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .Select(c => c.Key) .CountAsync(); - var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, performanceBucket); + var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, bucketConfig.Name); if (newPerformanceMetric == null) { @@ -1249,13 +1306,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers ZScore = aggregateZScore, Ranking = aggregateRanking, PerformanceMetric = newPerformanceMetric, - PerformanceBucket = performanceBucket, + PerformanceBucket = bucketConfig.Name, Newest = true, }; context.Add(aggregateRankingSnapshot); - await PruneOldRankings(context, clientId); + await PruneOldRankings(context, clientId, performanceBucket: bucketConfig.Name); await context.SaveChangesAsync(); } @@ -1364,6 +1421,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers attackerStats.EloRating = Math.Max(0, Math.Round(attackerStats.EloRating, 2)); victimStats.EloRating = Math.Max(0, Math.Round(victimStats.EloRating, 2)); + var attackerEloRatingFunc = + attacker.GetAdditionalProperty>("EloRatingFunction"); + + attackerStats.EloRating = + attackerEloRatingFunc?.Invoke(attacker, attackerStats) ?? attackerStats.EloRating; + + var victimEloRatingFunc = + victim.GetAdditionalProperty>("EloRatingFunction"); + + victimStats.EloRating = + attackerEloRatingFunc?.Invoke(victim, victimStats) ?? victimStats.EloRating; + // update after calculation attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds; victimStats.TimePlayed += (int)(DateTime.UtcNow - victimStats.LastActive).TotalSeconds; @@ -1428,7 +1497,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers ? (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds : clientStats.TimePlayed + (int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds; - double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0)); + double SPMAgainstPlayWeight = totalPlayTime == 0 ? killSpm : timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0)); // calculate the new weight against average times the weight against play time clientStats.SPM = (killSpm * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight)); @@ -1446,7 +1515,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers skillFunction?.Invoke(client, clientStats) ?? Math.Round(clientStats.SPM * KDRWeight, 3); // fixme: how does this happen? - if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) + if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill) || double.IsInfinity(clientStats.Skill)) { _log.LogWarning("clientStats SPM/Skill NaN {@killInfo}", new diff --git a/Plugins/Stats/Helpers/WeaponNameExtensions.cs b/Plugins/Stats/Helpers/WeaponNameExtensions.cs index 76da43b0..5d857f76 100644 --- a/Plugins/Stats/Helpers/WeaponNameExtensions.cs +++ b/Plugins/Stats/Helpers/WeaponNameExtensions.cs @@ -1,10 +1,20 @@ -using Data.Models.Client.Stats; +using System.Linq; +using Data.Models.Client.Stats; namespace Stats.Helpers { public static class WeaponNameExtensions { - public static string RebuildWeaponName(this EFClientHitStatistic stat) => - $"{stat.Weapon?.Name}{string.Join("_", stat.WeaponAttachmentCombo?.Attachment1?.Name, stat.WeaponAttachmentCombo?.Attachment2?.Name, stat.WeaponAttachmentCombo?.Attachment3?.Name)}"; + public static string RebuildWeaponName(this EFClientHitStatistic stat) + { + var attachments = + new[] + { + stat.WeaponAttachmentCombo?.Attachment1?.Name, stat.WeaponAttachmentCombo?.Attachment2?.Name, + stat.WeaponAttachmentCombo?.Attachment3?.Name + }.Where(a => !string.IsNullOrEmpty(a)); + + return $"{stat.Weapon?.Name?.Replace("zombie_", "").Replace("_zombie", "")}{string.Join("_", attachments)}"; + } } -} \ No newline at end of file +} diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index fabc6da2..0d114f57 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -117,6 +117,7 @@ public class Plugin : IPluginV2 } }; IGameEventSubscriptions.MatchEnded += OnMatchEvent; + IGameEventSubscriptions.RoundEnded += (roundEndedEvent, token) => _statManager.Sync(roundEndedEvent.Server, token); IGameEventSubscriptions.MatchStarted += OnMatchEvent; IGameEventSubscriptions.ScriptEventTriggered += OnScriptEvent; IGameEventSubscriptions.ClientKilled += OnClientKilled; diff --git a/Plugins/ZombieStats/ZombieStats.csproj b/Plugins/ZombieStats/ZombieStats.csproj index ade84533..301f637e 100644 --- a/Plugins/ZombieStats/ZombieStats.csproj +++ b/Plugins/ZombieStats/ZombieStats.csproj @@ -1,21 +1,14 @@ - net6.0 + net8.0 enable enable IW4MAdmin.Plugins.ZombieStats - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + diff --git a/SharedLibraryCore/Configuration/GameStringConfiguration.cs b/SharedLibraryCore/Configuration/GameStringConfiguration.cs index 4099d817..888484e3 100644 --- a/SharedLibraryCore/Configuration/GameStringConfiguration.cs +++ b/SharedLibraryCore/Configuration/GameStringConfiguration.cs @@ -15,11 +15,11 @@ namespace SharedLibraryCore.Configuration if (!ContainsKey(game.Value)) { - return key.Transform(To.TitleCase); + return key; } var strings = this[game.Value]; - return !strings.ContainsKey(key) ? key.Transform(To.TitleCase) : strings[key]; + return !strings.TryGetValue(key, out var value) ? key : value; } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Events/EventExtensions.cs b/SharedLibraryCore/Events/EventExtensions.cs index 2d7813b2..26e0e844 100644 --- a/SharedLibraryCore/Events/EventExtensions.cs +++ b/SharedLibraryCore/Events/EventExtensions.cs @@ -24,8 +24,16 @@ public static class EventExtensions { if (token == CancellationToken.None) { - // special case to allow tasks like request after delay to run longer - await handler(eventArgType, token); + try + { + // special case to allow tasks like request after delay to run longer + await handler(eventArgType, token); + } + catch (Exception ex) + { + // todo: static logger + Console.WriteLine("InvokeAsync: " + ex); + } } using var timeoutToken = new CancellationTokenSource(Utilities.DefaultCommandTimeout); @@ -36,9 +44,10 @@ public static class EventExtensions { await handler(eventArgType, tokenSource.Token); } - catch (Exception) + catch (Exception ex) { - // ignored + // todo: static logger + Console.WriteLine("InvokeAsync: " + ex); } } } diff --git a/SharedLibraryCore/Events/Game/GameScript/Zombie/PlayerRoundDataGameEvent.cs b/SharedLibraryCore/Events/Game/GameScript/Zombie/PlayerRoundDataGameEvent.cs index 5ccb088e..5b2f56da 100644 --- a/SharedLibraryCore/Events/Game/GameScript/Zombie/PlayerRoundDataGameEvent.cs +++ b/SharedLibraryCore/Events/Game/GameScript/Zombie/PlayerRoundDataGameEvent.cs @@ -4,5 +4,6 @@ public class PlayerRoundDataGameEvent : ClientGameEvent { public int TotalScore { get; init; } public int CurrentScore { get; init; } + public int CurrentRound { get; init; } public bool IsGameOver { get; init; } } diff --git a/SharedLibraryCore/Events/Game/GameScript/Zombie/RoundCompleteGameEvent.cs b/SharedLibraryCore/Events/Game/GameScript/Zombie/RoundEndEvent.cs similarity index 68% rename from SharedLibraryCore/Events/Game/GameScript/Zombie/RoundCompleteGameEvent.cs rename to SharedLibraryCore/Events/Game/GameScript/Zombie/RoundEndEvent.cs index ed6974de..d3520755 100644 --- a/SharedLibraryCore/Events/Game/GameScript/Zombie/RoundCompleteGameEvent.cs +++ b/SharedLibraryCore/Events/Game/GameScript/Zombie/RoundEndEvent.cs @@ -1,6 +1,6 @@ namespace SharedLibraryCore.Events.Game.GameScript.Zombie; -public class RoundCompleteGameEvent : GameEventV2 +public class RoundEndEvent : GameEventV2 { public int RoundNumber { get; init; } } diff --git a/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs b/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs index 986728fb..c2766259 100644 --- a/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs +++ b/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using SharedLibraryCore.Events; using SharedLibraryCore.Events.Game; +using SharedLibraryCore.Events.Game.GameScript.Zombie; namespace SharedLibraryCore.Interfaces.Events; @@ -22,6 +23,12 @@ public interface IGameEventSubscriptions /// static event Func MatchEnded; + /// + /// Raised when game log prints round ended + /// typically only triggered when using a script integration + /// + static event Func RoundEnded; + /// /// Raised when game log printed that client has entered the match /// J;clientNetworkId;clientSlotNumber;clientName @@ -97,6 +104,7 @@ public interface IGameEventSubscriptions { MatchStartEvent matchStartEvent => MatchStarted?.InvokeAsync(matchStartEvent, token) ?? Task.CompletedTask, MatchEndEvent matchEndEvent => MatchEnded?.InvokeAsync(matchEndEvent, token) ?? Task.CompletedTask, + RoundEndEvent roundEndEvent => RoundEnded?.InvokeAsync(roundEndEvent, token) ?? Task.CompletedTask, ClientEnterMatchEvent clientEnterMatchEvent => ClientEnteredMatch?.InvokeAsync(clientEnterMatchEvent, token) ?? Task.CompletedTask, ClientExitMatchEvent clientExitMatchEvent => ClientExitedMatch?.InvokeAsync(clientExitMatchEvent, token) ?? Task.CompletedTask, ClientJoinTeamEvent clientJoinTeamEvent => ClientJoinedTeam?.InvokeAsync(clientJoinTeamEvent, token) ?? Task.CompletedTask, diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index 8805f939..9dd520cd 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Data.Models; using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Events; @@ -113,5 +114,6 @@ namespace SharedLibraryCore.Interfaces IAlertManager AlertManager { get; } IInteractionRegistration InteractionRegistration { get; } + IList>, long?, string, bool, Task>> CustomStatsMetrics { get; } } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 44c7ade5..e6837d1b 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -83,6 +83,7 @@ namespace SharedLibraryCore RConConnectionFactory = rconConnectionFactory; ServerLogger = logger; DefaultSettings = serviceProvider.GetRequiredService(); + PerformanceBucket = ServerConfig.PerformanceBucket; InitializeTokens(); InitializeAutoMessages(); } @@ -163,6 +164,7 @@ namespace SharedLibraryCore public bool IsInitialized { get; set; } public int Port { get; protected set; } public int ListenPort => Port; + public string PerformanceBucket { get; init; } public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty); public abstract Task ExecuteCommandAsync(string command, CancellationToken token = default); public abstract Task SetDvarAsync(string name, object value, CancellationToken token = default); diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index a61fa00d..9e40f21b 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -40,7 +40,8 @@ namespace SharedLibraryCore.Services AliasLink = client.AliasLink, Password = client.Password, PasswordSalt = client.PasswordSalt, - GameName = client.GameName + GameName = client.GameName, + CurrentAliasId = client.CurrentAliasId }) .FirstOrDefault(client => client.NetworkId == networkId && client.GameName == game) ); @@ -595,7 +596,6 @@ namespace SharedLibraryCore.Services entity.CurrentAlias = newAlias; await context.SaveChangesAsync(); - entity.CurrentAliasId = newAlias.AliasId; } /// diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 1ea1736a..d738a021 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -46,7 +46,7 @@ namespace SharedLibraryCore public static Encoding EncodingType; public static Layout CurrentLocalization = new Layout(new Dictionary()); - public static TimeSpan DefaultCommandTimeout { get; set; } = new(0, 0, /*Utilities.IsDevelopment ? 360 : */25); + public static TimeSpan DefaultCommandTimeout { get; set; } = new(0, 0, Utilities.IsDevelopment ? 360 : 25); public static char[] DirectorySeparatorChars = { '\\', '/' }; public static char CommandPrefix { get; set; } = '!'; @@ -1224,6 +1224,16 @@ namespace SharedLibraryCore } public static string ToNumericalString(this int value) + { + return ToNumericalString((long)value); + } + + public static string ToNumericalString(this long? value) + { + return value?.ToNumericalString(); + } + + public static string ToNumericalString(this long value) { return value.ToString("#,##0", CurrentLocalization.Culture); } diff --git a/WebfrontCore/Controllers/Client/ClientStatisticsController.cs b/WebfrontCore/Controllers/Client/ClientStatisticsController.cs index 55de0e4e..f330ede3 100644 --- a/WebfrontCore/Controllers/Client/ClientStatisticsController.cs +++ b/WebfrontCore/Controllers/Client/ClientStatisticsController.cs @@ -1,6 +1,8 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Data.Models; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Configuration; @@ -48,6 +50,11 @@ namespace WebfrontCore.Controllers matchedServerId = server.LegacyDatabaseId; } + foreach (var statMetricFunc in Manager.CustomStatsMetrics) + { + await statMetricFunc(new Dictionary> { { id, hitInfo.CustomMetrics } }, matchedServerId, null, false); + } + hitInfo.TotalRankedClients = await _serverDataViewer.RankedClientsCountAsync(matchedServerId, token); return View("~/Views/Client/Statistics/Advanced.cshtml", hitInfo); diff --git a/WebfrontCore/Views/Client/Message/_Item.cshtml b/WebfrontCore/Views/Client/Message/_Item.cshtml index afad0cf4..e1d933cc 100644 --- a/WebfrontCore/Views/Client/Message/_Item.cshtml +++ b/WebfrontCore/Views/Client/Message/_Item.cshtml @@ -21,7 +21,7 @@ } - + @message.When.ToStandardFormat() @@ -51,7 +51,7 @@ }
- +
@message.When.ToStandardFormat()
diff --git a/WebfrontCore/Views/Client/Statistics/Advanced.cshtml b/WebfrontCore/Views/Client/Statistics/Advanced.cshtml index 999915d0..9c062889 100644 --- a/WebfrontCore/Views/Client/Statistics/Advanced.cshtml +++ b/WebfrontCore/Views/Client/Statistics/Advanced.cshtml @@ -31,9 +31,15 @@ } var rebuiltName = stat.RebuildWeaponName(); var name = config.GetStringForGame(rebuiltName, stat.Weapon?.Game); - return !rebuiltName.Equals(name, StringComparison.InvariantCultureIgnoreCase) - ? name - : config.GetStringForGame(stat.Weapon.Name, stat.Weapon.Game); + + if (!rebuiltName.Equals(name)) + { + return name; + } + + rebuiltName = config.GetStringForGame(stat.Weapon?.Name, stat.Weapon.Game); + + return rebuiltName.Equals(name) ? name.Transform(To.TitleCase) : rebuiltName; } string GetWeaponAttachmentName(EFWeaponAttachmentCombo attachment) @@ -230,7 +236,11 @@ Name = ViewBag.Localization["WEBFRONT_ADV_STATS_TOTAL_ACTIVE_TIME"] as string, Value = activeTime?.HumanizeForCurrentCulture() } - }; + }.Concat(Model.CustomMetrics.Select(metric => new + { + Name = metric.Key, + metric.Value + })); }
@@ -362,7 +372,7 @@ }).WithRows(weapons, weapon => new[] { GetWeaponNameForHit(weapon), - GetWeaponAttachmentName(weapon.WeaponAttachmentCombo) ?? "--", + GetWeaponAttachmentName(weapon.WeaponAttachmentCombo) ?? "-", weapon.KillCount.ToNumericalString(), weapon.HitCount.ToNumericalString(), weapon.DamageInflicted.ToNumericalString(), diff --git a/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml b/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml index f4bbb0ac..2baf8e57 100644 --- a/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml +++ b/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml @@ -67,21 +67,13 @@
-
- @stat.Kills.ToNumericalString() @loc["PLUGINS_STATS_TEXT_KILLS"] -
-
- @stat.Deaths.ToNumericalString() @loc["PLUGINS_STATS_TEXT_DEATHS"]
-
-
- @stat.KDR @loc["PLUGINS_STATS_TEXT_KDR"] -
-
- @stat.TimePlayedValue.HumanizeForCurrentCulture() @loc["WEBFRONT_PROFILE_PLAYER"] -
-
- @stat.LastSeenValue.HumanizeForCurrentCulture() @loc["WEBFRONT_PROFILE_LSEEN"] -
+ @foreach (var meta in stat.Metrics) + { +
+ @meta.Value + @meta.Key +
+ }
diff --git a/WebfrontCore/Views/Server/_Scoreboard.cshtml b/WebfrontCore/Views/Server/_Scoreboard.cshtml index 153310d7..8bb0ef08 100644 --- a/WebfrontCore/Views/Server/_Scoreboard.cshtml +++ b/WebfrontCore/Views/Server/_Scoreboard.cshtml @@ -69,7 +69,7 @@ else @(client.Deaths ?? 0) @Math.Round(client.Kdr ?? 0, 2) @Math.Round(client.ScorePerMinute ?? 0) - @(client.ZScore is null or 0 ? "--" : Math.Round(client.ZScore.Value, 2).ToString(CultureInfo.CurrentCulture)) + @(client.ZScore is null or 0 ? "-" : Math.Round(client.ZScore.Value, 2).ToString(CultureInfo.CurrentCulture)) @client.Ping @@ -94,7 +94,7 @@ else
@(client.Deaths ?? 0)
@Math.Round(client.Kdr ?? 0, 2)
@Math.Round(client.ScorePerMinute ?? 0)
-
@(client.ZScore is null or 0 ? "--" : Math.Round(client.ZScore.Value, 2).ToString(CultureInfo.CurrentCulture))
+
@(client.ZScore is null or 0 ? "-" : Math.Round(client.ZScore.Value, 2).ToString(CultureInfo.CurrentCulture))
@client.Ping