mirror of
https://github.com/RaidMax/IW4M-Admin.git
synced 2025-06-10 15:20:48 -05:00
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:
12
Data/Models/AuditFields.cs
Normal file
12
Data/Models/AuditFields.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Stats.Models
|
||||
{
|
||||
public class AuditFields
|
||||
{
|
||||
[Required]
|
||||
public DateTime CreatedDateTime { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? UpdatedDateTime { get; set; }
|
||||
}
|
||||
}
|
23
Data/Models/Client/EFACSnapshotVector3.cs
Normal file
23
Data/Models/Client/EFACSnapshotVector3.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Numerics;
|
||||
using Data.Models.Client.Stats;
|
||||
|
||||
namespace Data.Models.Client
|
||||
{
|
||||
public class EFACSnapshotVector3 : SharedEntity
|
||||
{
|
||||
[Key]
|
||||
public int ACSnapshotVector3Id { get; set; }
|
||||
|
||||
public int SnapshotId { get; set; }
|
||||
|
||||
[ForeignKey("SnapshotId")]
|
||||
public EFACSnapshot Snapshot { get; set; }
|
||||
|
||||
public int Vector3Id { get; set; }
|
||||
|
||||
[ForeignKey("Vector3Id")]
|
||||
public Vector3 Vector { get; set;}
|
||||
}
|
||||
}
|
86
Data/Models/Client/EFClient.cs
Normal file
86
Data/Models/Client/EFClient.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.Models.Client
|
||||
{
|
||||
public class EFClient : SharedEntity
|
||||
{
|
||||
public enum Permission
|
||||
{
|
||||
/// <summary>
|
||||
/// client has been banned
|
||||
/// </summary>
|
||||
Banned = -1,
|
||||
/// <summary>
|
||||
/// default client state upon first connect
|
||||
/// </summary>
|
||||
User = 0,
|
||||
/// <summary>
|
||||
/// client has been flagged
|
||||
/// </summary>
|
||||
Flagged = 1,
|
||||
/// <summary>
|
||||
/// client is trusted
|
||||
/// </summary>
|
||||
Trusted = 2,
|
||||
/// <summary>
|
||||
/// client is a moderator
|
||||
/// </summary>
|
||||
Moderator = 3,
|
||||
/// <summary>
|
||||
/// client is an administrator
|
||||
/// </summary>
|
||||
Administrator = 4,
|
||||
/// <summary>
|
||||
/// client is a senior administrator
|
||||
/// </summary>
|
||||
SeniorAdmin = 5,
|
||||
/// <summary>
|
||||
/// client is a owner
|
||||
/// </summary>
|
||||
Owner = 6,
|
||||
/// <summary>
|
||||
/// not used
|
||||
/// </summary>
|
||||
Creator = 7,
|
||||
/// <summary>
|
||||
/// reserved for default account
|
||||
/// </summary>
|
||||
Console = 8
|
||||
}
|
||||
|
||||
[Key]
|
||||
public int ClientId { get; set; }
|
||||
public long NetworkId { get; set; }
|
||||
[Required]
|
||||
public int Connections { get; set; }
|
||||
[Required]
|
||||
// in seconds
|
||||
public int TotalConnectionTime { get; set; }
|
||||
[Required]
|
||||
public DateTime FirstConnection { get; set; }
|
||||
[Required]
|
||||
public DateTime LastConnection { get; set; }
|
||||
public bool Masked { get; set; }
|
||||
[Required]
|
||||
public int AliasLinkId { get; set; }
|
||||
[ForeignKey("AliasLinkId")]
|
||||
public virtual EFAliasLink AliasLink { get; set; }
|
||||
[Required]
|
||||
public Permission Level { get; set; }
|
||||
|
||||
[Required]
|
||||
public int CurrentAliasId { get; set; }
|
||||
[ForeignKey("CurrentAliasId")]
|
||||
public virtual EFAlias CurrentAlias { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
public string PasswordSalt { get; set; }
|
||||
// list of meta for the client
|
||||
public virtual ICollection<EFMeta> Meta { get; set; }
|
||||
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
|
||||
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }
|
||||
}
|
||||
}
|
52
Data/Models/Client/EFClientKill.cs
Normal file
52
Data/Models/Client/EFClientKill.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Data.Models.Server;
|
||||
|
||||
namespace Data.Models.Client
|
||||
{
|
||||
public class EFClientKill : SharedEntity
|
||||
{
|
||||
[Key] public long KillId { get; set; }
|
||||
public int VictimId { get; set; }
|
||||
[ForeignKey("VictimId")] public virtual EFClient Victim { get; set; }
|
||||
public int AttackerId { get; set; }
|
||||
[ForeignKey("AttackerId")] public virtual EFClient Attacker { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")] public virtual EFServer Server { get; set; }
|
||||
public int HitLoc { get; set; }
|
||||
public int DeathType { get; set; }
|
||||
public int Damage { get; set; }
|
||||
public int Weapon { get; set; }
|
||||
public Vector3 KillOrigin { get; set; }
|
||||
public Vector3 DeathOrigin { get; set; }
|
||||
public Vector3 ViewAngles { get; set; }
|
||||
public DateTime When { get; set; }
|
||||
public double Fraction { get; set; }
|
||||
public bool IsKill { get; set; }
|
||||
|
||||
public double VisibilityPercentage { get; set; }
|
||||
|
||||
// http://wiki.modsrepository.com/index.php?title=Call_of_Duty_5:_Gameplay_standards for conversion to meters
|
||||
[NotMapped] public double Distance => Vector3.Distance(KillOrigin, DeathOrigin) * 0.0254;
|
||||
public int Map { get; set; }
|
||||
[NotMapped] public long TimeOffset { get; set; }
|
||||
[NotMapped] public bool IsKillstreakKill { get; set; }
|
||||
[NotMapped] public float AdsPercent { get; set; }
|
||||
[NotMapped] public List<Vector3> AnglesList { get; set; }
|
||||
[NotMapped] public int GameName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the attacker was alive after last captured angle
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public bool IsAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the last time the attack button was detected as pressed
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public long TimeSinceLastAttack { get; set; }
|
||||
}
|
||||
}
|
22
Data/Models/Client/EFClientMessage.cs
Normal file
22
Data/Models/Client/EFClientMessage.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Data.Models.Server;
|
||||
|
||||
namespace Data.Models.Client
|
||||
{
|
||||
public class EFClientMessage : SharedEntity
|
||||
{
|
||||
[Key]
|
||||
public long MessageId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public int ClientId { get; set; }
|
||||
[ForeignKey("ClientId")]
|
||||
public virtual EFClient Client { get; set; }
|
||||
public string Message { get; set; }
|
||||
public DateTime TimeSent { get; set; }
|
||||
public bool SentIngame { get; set; }
|
||||
}
|
||||
}
|
59
Data/Models/Client/Stats/EFACSnapshot.cs
Normal file
59
Data/Models/Client/Stats/EFACSnapshot.cs
Normal 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())) :
|
||||
"";
|
||||
}
|
||||
}
|
91
Data/Models/Client/Stats/EFClientHitStatistic.cs
Normal file
91
Data/Models/Client/Stats/EFClientHitStatistic.cs
Normal 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; }
|
||||
}
|
||||
}
|
31
Data/Models/Client/Stats/EFClientRankingHistory.cs
Normal file
31
Data/Models/Client/Stats/EFClientRankingHistory.cs
Normal 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; }
|
||||
}
|
||||
}
|
16
Data/Models/Client/Stats/EFClientRatingHistory.cs
Normal file
16
Data/Models/Client/Stats/EFClientRatingHistory.cs
Normal 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; }
|
||||
}
|
||||
}
|
114
Data/Models/Client/Stats/EFClientStatistics.cs
Normal file
114
Data/Models/Client/Stats/EFClientStatistics.cs
Normal 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; }
|
||||
}
|
||||
}
|
29
Data/Models/Client/Stats/EFHitLocationCount.cs
Normal file
29
Data/Models/Client/Stats/EFHitLocationCount.cs
Normal 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; }
|
||||
}
|
||||
}
|
31
Data/Models/Client/Stats/EFRating.cs
Normal file
31
Data/Models/Client/Stats/EFRating.cs
Normal 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;
|
||||
}
|
||||
}
|
21
Data/Models/Client/Stats/Reference/EFHitLocation.cs
Normal file
21
Data/Models/Client/Stats/Reference/EFHitLocation.cs
Normal 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;
|
||||
}
|
||||
}
|
21
Data/Models/Client/Stats/Reference/EFMap.cs
Normal file
21
Data/Models/Client/Stats/Reference/EFMap.cs
Normal 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;
|
||||
}
|
||||
}
|
21
Data/Models/Client/Stats/Reference/EFMeansOfDeath.cs
Normal file
21
Data/Models/Client/Stats/Reference/EFMeansOfDeath.cs
Normal 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;
|
||||
}
|
||||
}
|
21
Data/Models/Client/Stats/Reference/EFWeapon.cs
Normal file
21
Data/Models/Client/Stats/Reference/EFWeapon.cs
Normal 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;
|
||||
}
|
||||
}
|
21
Data/Models/Client/Stats/Reference/EFWeaponAttachment.cs
Normal file
21
Data/Models/Client/Stats/Reference/EFWeaponAttachment.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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}";
|
||||
}
|
||||
}
|
92
Data/Models/Configuration/StatsModelConfiguration.cs
Normal file
92
Data/Models/Configuration/StatsModelConfiguration.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Client.Stats;
|
||||
using Data.Models.Client.Stats.Reference;
|
||||
using Data.Models.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Data.Models.Configuration
|
||||
{
|
||||
public class StatsModelConfiguration
|
||||
{
|
||||
public static void Configure(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<EFClientStatistics>(entity =>
|
||||
{
|
||||
entity.HasKey(cs => new {cs.ClientId, cs.ServerId});
|
||||
entity.HasIndex(cs => new {cs.ClientId, cs.TimePlayed, PerformancePercentile = cs.ZScore});
|
||||
entity.HasIndex(cs => new {PerformancePercentile = cs.ZScore});
|
||||
entity.ToTable("EFClientStatistics");
|
||||
});
|
||||
|
||||
|
||||
// fix linking from SQLCe
|
||||
builder.Entity<EFHitLocationCount>(entity =>
|
||||
{
|
||||
entity.Property(c => c.EFClientStatisticsClientId)
|
||||
.HasColumnName("EFClientStatisticsClientId");
|
||||
entity.Property(c => c.EFClientStatisticsServerId)
|
||||
.HasColumnName("EFClientStatisticsServerId");
|
||||
|
||||
entity.ToTable("EFHitLocationCounts");
|
||||
});
|
||||
|
||||
|
||||
builder.Entity<EFRating>(entity =>
|
||||
{
|
||||
entity.HasIndex(p => new {p.Performance, p.Ranking, p.When});
|
||||
entity.HasIndex(p => new {p.When, p.ServerId, p.Performance, p.ActivityAmount});
|
||||
entity.ToTable(nameof(EFRating));
|
||||
});
|
||||
|
||||
|
||||
builder.Entity<EFClientMessage>(entity =>
|
||||
{
|
||||
entity.HasIndex(p => p.TimeSent);
|
||||
entity.ToTable("EFClientMessages");
|
||||
});
|
||||
|
||||
builder.Entity<EFClientStatistics>(entity => { entity.ToTable(nameof(EFClientStatistics)); });
|
||||
|
||||
builder.Entity<EFRating>(entity => { entity.ToTable(nameof(EFRating)); });
|
||||
|
||||
builder.Entity<EFClientRatingHistory>(entity => { entity.ToTable(nameof(EFClientRatingHistory)); });
|
||||
|
||||
builder.Entity<EFHitLocationCount>(entity => { entity.ToTable("EFHitLocationCounts"); });
|
||||
|
||||
builder.Entity<EFServerStatistics>(entity => { entity.ToTable("EFServerStatistics"); });
|
||||
|
||||
builder.Entity<EFServer>(entity => { entity.ToTable("EFServers"); });
|
||||
|
||||
builder.Entity<EFClientKill>(entity => { entity.ToTable("EFClientKills"); });
|
||||
|
||||
builder.Entity<Vector3>().ToTable(nameof(Vector3));
|
||||
builder.Entity<EFACSnapshot>().ToTable(nameof(EFACSnapshot));
|
||||
builder.Entity<EFACSnapshotVector3>().ToTable(nameof(EFACSnapshotVector3));
|
||||
|
||||
builder.Entity<EFHitLocation>(entity =>
|
||||
{
|
||||
entity.HasIndex(loc => loc.Name);
|
||||
entity.ToTable("EFHitLocations");
|
||||
});
|
||||
|
||||
builder.Entity<EFWeapon>(entity =>
|
||||
{
|
||||
entity.HasIndex(weapon => weapon.Name);
|
||||
entity.ToTable("EFWeapons");
|
||||
});
|
||||
|
||||
builder.Entity<EFMap>(entity => { entity.ToTable("EFMaps"); });
|
||||
builder.Entity<EFClientHitStatistic>(entity => { entity.ToTable("EFClientHitStatistics"); });
|
||||
builder.Entity<EFWeaponAttachment>(entity => { entity.ToTable("EFWeaponAttachments"); });
|
||||
builder.Entity<EFWeaponAttachmentCombo>(entity => { entity.ToTable("EFWeaponAttachmentCombos"); });
|
||||
builder.Entity<EFMeansOfDeath>(entity => { entity.ToTable("EFMeansOfDeath"); });
|
||||
builder.Entity<EFClientRankingHistory>(entity =>
|
||||
{
|
||||
entity.ToTable(nameof(EFClientRankingHistory));
|
||||
entity.HasIndex(ranking => ranking.Ranking);
|
||||
entity.HasIndex(ranking => ranking.ZScore);
|
||||
entity.HasIndex(ranking => ranking.UpdatedDateTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
31
Data/Models/EFAlias.cs
Normal file
31
Data/Models/EFAlias.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
public partial class EFAlias : SharedEntity
|
||||
{
|
||||
[Key]
|
||||
public int AliasId { get; set; }
|
||||
[Required]
|
||||
public int LinkId { get; set; }
|
||||
[ForeignKey("LinkId")]
|
||||
public virtual EFAliasLink Link { get; set; }
|
||||
[Required]
|
||||
[MaxLength(MAX_NAME_LENGTH)]
|
||||
public string Name { get; set; }
|
||||
[MaxLength(MAX_NAME_LENGTH)]
|
||||
public string SearchableName { get; set; }
|
||||
[Required]
|
||||
public int? IPAddress { get; set; }
|
||||
[Required]
|
||||
public DateTime DateAdded { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public const int MAX_NAME_LENGTH = 24;
|
||||
|
||||
[NotMapped]
|
||||
public const int MIN_NAME_LENGTH = 3;
|
||||
}
|
||||
}
|
19
Data/Models/EFAliasLink.cs
Normal file
19
Data/Models/EFAliasLink.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
public class EFAliasLink : SharedEntity
|
||||
{
|
||||
[Key]
|
||||
public int AliasLinkId { get; set; }
|
||||
public virtual ICollection<EFAlias> Children { get; set; }
|
||||
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
|
||||
|
||||
public EFAliasLink()
|
||||
{
|
||||
Children = new List<EFAlias>();
|
||||
ReceivedPenalties = new List<EFPenalty>();
|
||||
}
|
||||
}
|
||||
}
|
30
Data/Models/EFChangeHistory.cs
Normal file
30
Data/Models/EFChangeHistory.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// This class models the change to different entities
|
||||
/// </summary>
|
||||
public class EFChangeHistory : SharedEntity
|
||||
{
|
||||
public enum ChangeType
|
||||
{
|
||||
Permission,
|
||||
Ban,
|
||||
Command
|
||||
}
|
||||
|
||||
[Key]
|
||||
public int ChangeHistoryId { get; set; }
|
||||
public int OriginEntityId { get; set; }
|
||||
public int TargetEntityId { get; set; }
|
||||
public int? ImpersonationEntityId { get; set; }
|
||||
public ChangeType TypeOfChange { get; set; }
|
||||
public DateTime TimeChanged { get; set; } = DateTime.UtcNow;
|
||||
[MaxLength(128)]
|
||||
public string Comment { get; set; }
|
||||
public string PreviousValue { get; set; }
|
||||
public string CurrentValue { get; set; }
|
||||
}
|
||||
}
|
39
Data/Models/EFMeta.cs
Normal file
39
Data/Models/EFMeta.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Data.Models.Client;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// This class encapsulates any meta fields as a simple string
|
||||
/// </summary>
|
||||
public class EFMeta : SharedEntity
|
||||
{
|
||||
public const string ClientTagName = nameof(ClientTagName);
|
||||
public const string ClientTag = nameof(ClientTag);
|
||||
|
||||
[Key]
|
||||
public int MetaId { get; set; }
|
||||
[Required]
|
||||
public DateTime Created { get; set; } = DateTime.UtcNow;
|
||||
[Required]
|
||||
public DateTime Updated { get; set; } = DateTime.UtcNow;
|
||||
public int? ClientId { get; set; }
|
||||
// this is the client that the meta could belong to
|
||||
[ForeignKey(nameof(ClientId))]
|
||||
public virtual EFClient Client { get; set; }
|
||||
[Required]
|
||||
[MinLength(3)]
|
||||
[StringLength(32)]
|
||||
[MaxLength(32)]
|
||||
public string Key { get; set; }
|
||||
[Required]
|
||||
public string Value { get; set; }
|
||||
public string Extra { get; set; }
|
||||
|
||||
public int? LinkedMetaId { get; set; }
|
||||
[ForeignKey(nameof(LinkedMetaId))]
|
||||
public virtual EFMeta LinkedMeta { get; set; }
|
||||
}
|
||||
}
|
49
Data/Models/EFPenalty.cs
Normal file
49
Data/Models/EFPenalty.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Data.Models.Client;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
public class EFPenalty : SharedEntity
|
||||
{
|
||||
public enum PenaltyType
|
||||
{
|
||||
Report,
|
||||
Warning,
|
||||
Flag,
|
||||
Kick,
|
||||
TempBan,
|
||||
Ban,
|
||||
Unban,
|
||||
Any,
|
||||
Unflag,
|
||||
Other = 100
|
||||
}
|
||||
|
||||
[Key]
|
||||
public int PenaltyId { get; set; }
|
||||
[Required]
|
||||
public int LinkId { get; set; }
|
||||
[ForeignKey("LinkId")]
|
||||
public virtual EFAliasLink Link { get; set; }
|
||||
[Required]
|
||||
public int OffenderId { get; set; }
|
||||
[ForeignKey("OffenderId")]
|
||||
public virtual EFClient Offender { get; set; }
|
||||
[Required]
|
||||
public int PunisherId { get; set; }
|
||||
[ForeignKey("PunisherId")]
|
||||
public virtual EFClient Punisher { get; set; }
|
||||
[Required]
|
||||
public DateTime When { get; set; }
|
||||
[Required]
|
||||
public DateTime? Expires { get; set; }
|
||||
[Required]
|
||||
public string Offense { get; set; }
|
||||
public string AutomatedOffense { get; set; }
|
||||
[Required]
|
||||
public bool IsEvadedOffense { get; set; }
|
||||
public PenaltyType Type { get; set; }
|
||||
}
|
||||
}
|
19
Data/Models/Reference.cs
Normal file
19
Data/Models/Reference.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace Data.Models
|
||||
{
|
||||
public class Reference
|
||||
{
|
||||
public enum Game
|
||||
{
|
||||
COD = -1,
|
||||
UKN = 0,
|
||||
IW3 = 1,
|
||||
IW4 = 2,
|
||||
IW5 = 3,
|
||||
IW6 = 4,
|
||||
T4 = 5,
|
||||
T5 = 6,
|
||||
T6 = 7,
|
||||
T7 = 8
|
||||
}
|
||||
}
|
||||
}
|
21
Data/Models/Server/EFServer.cs
Normal file
21
Data/Models/Server/EFServer.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Data.Abstractions;
|
||||
|
||||
namespace Data.Models.Server
|
||||
{
|
||||
public class EFServer : SharedEntity, IUniqueId
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
||||
public long ServerId { get; set; }
|
||||
[Required]
|
||||
public int Port { get; set; }
|
||||
public string EndPoint { get; set; }
|
||||
public Reference.Game? GameName { get; set; }
|
||||
public string HostName { get; set; }
|
||||
public bool IsPasswordProtected { get; set; }
|
||||
public long Id => ServerId;
|
||||
public string Value => EndPoint;
|
||||
}
|
||||
}
|
16
Data/Models/Server/EFServerStatistics.cs
Normal file
16
Data/Models/Server/EFServerStatistics.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.Models.Server
|
||||
{
|
||||
public class EFServerStatistics : SharedEntity
|
||||
{
|
||||
[Key]
|
||||
public int StatisticId { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
[ForeignKey("ServerId")]
|
||||
public virtual EFServer Server { get; set; }
|
||||
public long TotalKills { get; set; }
|
||||
public long TotalPlayTime { get; set; }
|
||||
}
|
||||
}
|
37
Data/Models/SharedEntity.cs
Normal file
37
Data/Models/SharedEntity.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Data.Abstractions;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
public class SharedEntity : IPropertyExtender
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> _additionalProperties;
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the entity is active
|
||||
/// </summary>
|
||||
public bool Active { get; set; } = true;
|
||||
|
||||
public SharedEntity()
|
||||
{
|
||||
_additionalProperties = new ConcurrentDictionary<string, object>();
|
||||
}
|
||||
|
||||
public T GetAdditionalProperty<T>(string name)
|
||||
{
|
||||
return _additionalProperties.ContainsKey(name) ? (T)_additionalProperties[name] : default;
|
||||
}
|
||||
|
||||
public void SetAdditionalProperty(string name, object value)
|
||||
{
|
||||
if (_additionalProperties.ContainsKey(name))
|
||||
{
|
||||
_additionalProperties[name] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_additionalProperties.TryAdd(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
113
Data/Models/Vector3.cs
Normal file
113
Data/Models/Vector3.cs
Normal file
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Data.Models
|
||||
{
|
||||
public class Vector3
|
||||
{
|
||||
[Key] public int Vector3Id { get; set; }
|
||||
public float X { get; protected set; }
|
||||
public float Y { get; protected set; }
|
||||
public float Z { get; protected set; }
|
||||
|
||||
// this is for EF and really should be somewhere else
|
||||
public Vector3()
|
||||
{
|
||||
}
|
||||
|
||||
public Vector3(float x, float y, float z)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"({X}, {Y}, {Z})";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Vector3 vec)
|
||||
{
|
||||
return vec.X == X && vec.Y == Y && vec.Z == Z;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Vector3 Parse(string s)
|
||||
{
|
||||
bool valid = Regex.Match(s,
|
||||
@"\((-?[0-9]+\.?[0-9]*|-?[0-9]+\.?[0-9]*e-[0-9]+),\ (-?[0-9]+\.?[0-9]*|-?[0-9]+\.?[0-9]*e-[0-9]+),\ (-?[0-9]+\.?[0-9]*|-?[0-9]+\.?[0-9]*e-[0-9]+)\)")
|
||||
.Success;
|
||||
if (!valid)
|
||||
{
|
||||
throw new FormatException("Vector3 is not in correct format");
|
||||
}
|
||||
|
||||
string removeParenthesis = s.Substring(1, s.Length - 2);
|
||||
string[] eachPoint = removeParenthesis.Split(',');
|
||||
|
||||
return new Vector3(
|
||||
float.Parse(eachPoint[0], System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(eachPoint[1], System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture),
|
||||
float.Parse(eachPoint[2], System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public static double Distance(Vector3 a, Vector3 b)
|
||||
{
|
||||
return Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2) + Math.Pow(b.Z - a.Z, 2));
|
||||
}
|
||||
|
||||
public static double AbsoluteDistance(Vector3 a, Vector3 b)
|
||||
{
|
||||
double deltaX = Math.Abs(b.X - a.X);
|
||||
double deltaY = Math.Abs(b.Y - a.Y);
|
||||
|
||||
// this 'fixes' the roll-over angles
|
||||
double dx = deltaX < 360.0 / 2 ? deltaX : 360.0 - deltaX;
|
||||
double dy = deltaY < 360.0 / 2 ? deltaY : 360.0 - deltaY;
|
||||
|
||||
return Math.Sqrt((dx * dx) + (dy * dy));
|
||||
}
|
||||
|
||||
public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c)
|
||||
{
|
||||
double dabX = Math.Abs(a.X - b.X);
|
||||
dabX = dabX < 360.0 / 2 ? dabX : 360.0 - dabX;
|
||||
double dabY = Math.Abs(a.Y - b.Y);
|
||||
dabY = dabY < 360.0 / 2 ? dabY : 360.0 - dabY;
|
||||
|
||||
double dacX = Math.Abs(a.X - c.X);
|
||||
dacX = dacX < 360.0 / 2 ? dacX : 360.0 - dacX;
|
||||
double dacY = Math.Abs(a.Y - c.Y);
|
||||
dacY = dacY < 360.0 / 2 ? dacY : 360.0 - dacY;
|
||||
|
||||
double dbcX = Math.Abs(b.X - c.X);
|
||||
dbcX = dbcX < 360.0 / 2 ? dbcX : 360.0 - dbcX;
|
||||
double dbcY = Math.Abs(b.Y - c.Y);
|
||||
dbcY = dbcY < 360.0 / 2 ? dbcY : 360.0 - dbcY;
|
||||
|
||||
double deltaX = (dabX - dacX - dbcX) / 2.0;
|
||||
deltaX = deltaX < 360.0 / 2 ? deltaX : 360.0 - deltaX;
|
||||
double deltaY = (dabY - dacY - dbcY) / 2.0;
|
||||
deltaY = deltaY < 360.0 / 2 ? deltaY : 360.0 - deltaY;
|
||||
|
||||
return Math.Round(Math.Sqrt((deltaX * deltaX) + (deltaY * deltaY)), 4);
|
||||
}
|
||||
|
||||
public static Vector3 Subtract(Vector3 a, Vector3 b) => new Vector3(b.X - a.X, b.Y - a.Y, b.Z - a.Z);
|
||||
|
||||
public double DotProduct(Vector3 a) => (a.X * this.X) + (a.Y * this.Y) + (a.Z * this.Z);
|
||||
|
||||
public double Magnitude() => Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
|
||||
|
||||
public double AngleBetween(Vector3 a) => Math.Acos(this.DotProduct(a) / (a.Magnitude() * this.Magnitude()));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user