huge commit for advanced stats feature.

broke data out into its own library.
may be breaking changes with existing plugins
This commit is contained in:
RaidMax
2021-03-22 11:09:25 -05:00
parent db2e1deb2f
commit c5375b661b
505 changed files with 13671 additions and 3271 deletions

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Numerics;
namespace Data.Models.Client.Stats
{
/// <summary>
/// This class houses the information for anticheat snapshots (used for validating a ban)
/// </summary>
public class EFACSnapshot : SharedEntity
{
[Key]
public int SnapshotId { get; set; }
public int ClientId { get; set; }
[ForeignKey("ClientId")]
public EFClient Client { get; set; }
public DateTime When { get; set; }
public int CurrentSessionLength { get; set; }
public int TimeSinceLastEvent { get; set; }
public double EloRating { get; set; }
public int SessionScore { get; set; }
public double SessionSPM { get; set; }
public int Hits { get; set; }
public int Kills { get; set; }
public int Deaths { get; set; }
public double CurrentStrain { get; set; }
public double StrainAngleBetween { get; set; }
public double SessionAngleOffset { get; set; }
public double RecoilOffset { get; set; }
public int LastStrainAngleId { get; set; }
[ForeignKey("LastStrainAngleId")]
public Vector3 LastStrainAngle { get; set; }
public int HitOriginId { get; set; }
[ForeignKey("HitOriginId")]
public Vector3 HitOrigin { get; set; }
public int HitDestinationId { get; set; }
[ForeignKey("HitDestinationId")]
public Vector3 HitDestination { get; set; }
public double Distance { get; set; }
public double SessionAverageSnapValue { get; set; }
public int SessionSnapHits { get; set; }
public int CurrentViewAngleId { get; set; }
[ForeignKey("CurrentViewAngleId")]
public Vector3 CurrentViewAngle { get; set; }
public int WeaponId { get; set; }
public int HitLocation { get; set; }
public int HitType { get; set; }
public virtual ICollection<EFACSnapshotVector3> PredictedViewAngles { get; set; }
[NotMapped]
public string CapturedViewAngles => PredictedViewAngles?.Count > 0 ?
string.Join(", ", PredictedViewAngles.OrderBy(_angle => _angle.ACSnapshotVector3Id).Select(_angle => _angle.Vector.ToString())) :
"";
}
}

View File

@ -0,0 +1,91 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Data.Models.Client.Stats.Reference;
using Data.Models.Server;
using Stats.Models;
namespace Data.Models.Client.Stats
{
public class EFClientHitStatistic : AuditFields
{
[Key]
public int ClientHitStatisticId { get; set; }
[Required]
public int ClientId { get; set; }
[ForeignKey(nameof(ClientId))]
public virtual EFClient Client { get; set; }
public long? ServerId { get; set; }
[ForeignKey(nameof(ServerId))]
public virtual EFServer Server { get; set; }
public int? HitLocationId { get; set; }
[ForeignKey(nameof(HitLocationId))]
public virtual EFHitLocation HitLocation { get; set; }
public int? MeansOfDeathId { get; set; }
[ForeignKey(nameof(MeansOfDeathId))]
public virtual EFMeansOfDeath MeansOfDeath { get; set; }
public int? WeaponId { get; set; }
[ForeignKey(nameof(WeaponId))]
public virtual EFWeapon Weapon { get; set; }
public int? WeaponAttachmentComboId { get; set; }
[ForeignKey(nameof(WeaponAttachmentComboId))]
public virtual EFWeaponAttachmentCombo WeaponAttachmentCombo { get; set; }
/// <summary>
/// how many hits the player got
/// </summary>
public int HitCount { get; set; }
/// <summary>
/// how many kills the player got
/// </summary>
public int KillCount { get; set; }
/// <summary>
/// how much damage the player inflicted
/// </summary>
public int DamageInflicted { get; set; }
/// <summary>
/// how many hits the player received
/// </summary>
public int ReceivedHitCount { get; set; }
/// <summary>
/// how many kills the player received
/// </summary>
public int DeathCount { get; set; }
/// <summary>
/// how much damage the player received
/// </summary>
public int DamageReceived { get; set; }
/// <summary>
/// how many times the player killed themself
/// </summary>
public int SuicideCount { get; set; }
/// <summary>
/// estimation of time spent with the configuration
/// </summary>
public int? UsageSeconds { get; set; }
/// <summary>
/// total in-game score
/// </summary>
public int? Score { get; set; }
}
}

View File

@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Data.Models.Server;
using Stats.Models;
namespace Data.Models.Client.Stats
{
public class EFClientRankingHistory: AuditFields
{
public const int MaxRankingCount = 30;
[Key]
public long ClientRankingHistoryId { get; set; }
[Required]
public int ClientId { get; set; }
[ForeignKey(nameof(ClientId))]
public virtual EFClient Client { get; set; }
public long? ServerId { get; set; }
[ForeignKey(nameof(ServerId))]
public virtual EFServer Server { get; set; }
public bool Newest { get; set; }
public int? Ranking { get; set; }
public double? ZScore { get; set; }
public double? PerformanceMetric { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.Models.Client.Stats
{
public class EFClientRatingHistory : SharedEntity
{
[Key]
public int RatingHistoryId { get; set; }
public int ClientId { get; set; }
[ForeignKey("ClientId")]
public virtual EFClient Client { get; set; }
public virtual ICollection<EFRating> Ratings { get; set; }
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading;
using Data.Models.Server;
namespace Data.Models.Client.Stats
{
public class EFClientStatistics : SharedEntity
{
public EFClientStatistics()
{
ProcessingHit = new SemaphoreSlim(1, 1);
}
~EFClientStatistics()
{
ProcessingHit.Dispose();
}
public int ClientId { get; set; }
[ForeignKey("ClientId")]
public virtual EFClient Client { get; set; }
public long ServerId { get; set; }
[ForeignKey("ServerId")]
public virtual EFServer Server { get; set; }
[Required]
public int Kills { get; set; }
[Required]
public int Deaths { get; set; }
public double EloRating { get; set; }
public double ZScore { get; set; }
public DateTime? UpdatedAt { get; set; }
public virtual ICollection<EFHitLocationCount> HitLocations { get; set; }
public double RollingWeightedKDR { get; set; }
public double AverageSnapValue { get; set; }
public int SnapHitCount { get; set; }
[NotMapped]
public double Performance
{
get => Math.Round(EloRating * 1/3.0 + Skill * 2/3.0, 2);
}
[NotMapped]
public double KDR
{
get => Deaths == 0 ? Kills : Math.Round(Kills / (double)Deaths, 2);
}
[Required]
public double SPM { get; set; }
[Required]
public double Skill { get; set; }
[Required]
public int TimePlayed { get; set; }
[Required]
public double MaxStrain { get; set; }
[NotMapped]
public float AverageHitOffset
{
get => (float)Math.Round(HitLocations.Sum(c => c.HitOffsetAverage) / Math.Max(1, HitLocations.Where(c => c.HitOffsetAverage > 0).Count()), 4);
}
[NotMapped]
public int SessionKills { get; set; }
[NotMapped]
public int SessionDeaths { get; set; }
[NotMapped]
public int KillStreak { get; set; }
[NotMapped]
public int DeathStreak { get; set; }
[NotMapped]
public DateTime LastStatCalculation { get; set; }
[NotMapped]
public int LastScore { get; set; }
[NotMapped]
public DateTime LastActive { get; set; }
[NotMapped]
public double MaxSessionStrain { get; set; }
public void StartNewSession()
{
KillStreak = 0;
DeathStreak = 0;
LastScore = 0;
SessionScores.Add(0);
Team = 0;
}
[NotMapped]
public int SessionScore
{
set => SessionScores[SessionScores.Count - 1] = value;
get
{
lock (SessionScores)
{
return new List<int>(SessionScores).Sum();
}
}
}
[NotMapped]
public int RoundScore => SessionScores[SessionScores.Count - 1];
[NotMapped]
private readonly List<int> SessionScores = new List<int>() { 0 };
[NotMapped]
public int Team { get; set; }
[NotMapped]
public DateTime LastStatHistoryUpdate { get; set; } = DateTime.UtcNow;
[NotMapped]
public double SessionSPM { get; set; }
[NotMapped]
public SemaphoreSlim ProcessingHit { get; private set; }
}
}

View File

@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Data.Models.Server;
namespace Data.Models.Client.Stats
{
public class EFHitLocationCount : SharedEntity
{
[Key]
public int HitLocationCountId { get; set; }
[Required]
public int Location { get; set; }
[Required]
public int HitCount { get; set; }
[Required]
public float HitOffsetAverage { get; set; }
[Required]
public float MaxAngleDistance { get; set; }
[Required]
[Column("EFClientStatisticsClientId")]
public int EFClientStatisticsClientId { get; set; }
[ForeignKey("EFClientStatisticsClientId")]
public EFClient Client { get; set; }
[Column("EFClientStatisticsServerId")]
public long EFClientStatisticsServerId { get; set; }
[ForeignKey("EFClientStatisticsServerId")]
public EFServer Server { get; set; }
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Data.Models.Server;
namespace Data.Models.Client.Stats
{
public class EFRating : SharedEntity
{
[Key]
public int RatingId { get; set; }
public int RatingHistoryId { get; set; }
[ForeignKey("RatingHistoryId")]
public virtual EFClientRatingHistory RatingHistory { get; set; }
// if null, indicates that the rating is an average rating
public long? ServerId { get; set; }
// [ForeignKey("ServerId")] can't make this nullable if this annotation is set
public virtual EFServer Server { get; set; }
[Required]
public double Performance { get; set; }
[Required]
public int Ranking { get; set; }
[Required]
// indicates if the rating is the latest
public bool Newest { get; set; }
[Required]
public int ActivityAmount { get; set; }
[Required]
public DateTime When { get; set; } = DateTime.UtcNow;
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Data.Abstractions;
using Stats.Models;
namespace Data.Models.Client.Stats.Reference
{
public class EFHitLocation : AuditFields, IUniqueId
{
[Key]
public int HitLocationId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Models.Reference.Game Game { get; set; }
public long Id => HitLocationId;
public string Value => Name;
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Data.Abstractions;
using Stats.Models;
namespace Data.Models.Client.Stats.Reference
{
public class EFMap : AuditFields, IUniqueId
{
[Key]
public int MapId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Models.Reference.Game Game { get; set; }
public long Id => MapId;
public string Value => Name;
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Data.Abstractions;
using Stats.Models;
namespace Data.Models.Client.Stats.Reference
{
public class EFMeansOfDeath: AuditFields, IUniqueId
{
[Key]
public int MeansOfDeathId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Models.Reference.Game Game { get; set; }
public long Id => MeansOfDeathId;
public string Value => Name;
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Data.Abstractions;
using Stats.Models;
namespace Data.Models.Client.Stats.Reference
{
public class EFWeapon : AuditFields, IUniqueId
{
[Key]
public int WeaponId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Models.Reference.Game Game { get; set; }
public long Id => WeaponId;
public string Value => Name;
}
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Data.Abstractions;
using Stats.Models;
namespace Data.Models.Client.Stats.Reference
{
public class EFWeaponAttachment : AuditFields, IUniqueId
{
[Key]
public int WeaponAttachmentId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Models.Reference.Game Game { get; set; }
public long Id => WeaponAttachmentId;
public string Value => Name;
}
}

View File

@ -0,0 +1,35 @@
using Data.Abstractions;
using Stats.Models;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.Models.Client.Stats.Reference
{
public class EFWeaponAttachmentCombo : AuditFields, IUniqueId
{
[Key]
public int WeaponAttachmentComboId { get; set; }
[Required]
public Models.Reference.Game Game { get; set; }
[Required]
public int Attachment1Id { get; set; }
[ForeignKey(nameof(Attachment1Id))]
public virtual EFWeaponAttachment Attachment1 { get; set; }
public int? Attachment2Id { get; set; }
[ForeignKey(nameof(Attachment2Id))]
public virtual EFWeaponAttachment Attachment2 { get; set; }
public int? Attachment3Id { get; set; }
[ForeignKey(nameof(Attachment3Id))]
public virtual EFWeaponAttachment Attachment3 { get; set; }
public long Id => WeaponAttachmentComboId;
public string Value => $"{Attachment1Id}{Attachment2Id}{Attachment3Id}";
}
}