diff --git a/Libraries/SPTarkov.Server.Core/Controllers/LauncherV2Controller.cs b/Libraries/SPTarkov.Server.Core/Controllers/LauncherV2Controller.cs index f5beaa2ba..ccf64a601 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/LauncherV2Controller.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/LauncherV2Controller.cs @@ -1,13 +1,11 @@ using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Launcher; -using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Launcher; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Models.Spt.Tables; using SPTarkov.Server.Core.Servers; -using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Services.Locales; using SPTarkov.Server.Core.Utils; using Info = SPTarkov.Server.Core.Models.Eft.Profile.Info; @@ -174,11 +172,6 @@ protected MongoId GetSessionId(LoginRequestData info) return MongoId.Empty(); } - public SptProfile GetProfile(MongoId sessionId) - { - return saveServer.GetProfile(sessionId); - } - public MiniProfile? GetMiniProfileFromUsername(LoginRequestData info) { return profileController.GetMiniProfile(GetSessionId(info)); diff --git a/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs b/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs index f1f83afa3..4b4a0c236 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs @@ -73,6 +73,8 @@ public MiniProfile GetMiniProfile(MongoId sessionId) InvalidOrUnloadableProfile = profile.ProfileInfo?.InvalidOrUnloadableProfile, SptData = profileHelper.GetDefaultSptDataObject(), Wipe = true, + ProfileCurrency = profileHelper.GetAccountCurrency(profile), + ProfileStats = profileHelper.GetProfileStats(profile), }; } @@ -91,6 +93,8 @@ public MiniProfile GetMiniProfile(MongoId sessionId) InvalidOrUnloadableProfile = profile.ProfileInfo?.InvalidOrUnloadableProfile, SptData = profile.SptData, Wipe = profile.ProfileInfo?.IsWiped ?? false, + ProfileCurrency = profileHelper.GetAccountCurrency(profile), + ProfileStats = profileHelper.GetProfileStats(profile), }; } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs index 974ba73ee..362c5bac3 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.Extensions.Logging; using SPTarkov.Common.Models.Logging; using SPTarkov.DI.Annotations; @@ -5,9 +6,11 @@ using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; +using SPTarkov.Server.Core.Models.Eft.Launcher; using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Models.Spt.Helper; using SPTarkov.Server.Core.Models.Spt.Tables; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services.Locales; @@ -837,4 +840,212 @@ public bool GetProfileTemplateFlagValue(string accountEdition, string flagKey) return matchingProfileTemplate.CustomFlags.GetValueOrDefault(flagKey, false); } + + /// + /// Gets the currencies in the profiles inventory + /// + /// Full profile + /// + public ProfileCurrency GetAccountCurrency(SptProfile profile) + { + var returnObject = new ProfileCurrency + { + Roubles = 0, + Euros = 0, + Dollars = 0, + GP = 0, + }; + + foreach (var item in profile.CharacterData?.PmcData?.Inventory?.Items ?? []) + { + var stackSize = item.GetItemStackSize(); + + if (item.Template == Money.ROUBLES) + { + returnObject.Roubles += stackSize; + } + + if (item.Template == Money.DOLLARS) + { + returnObject.Dollars += stackSize; + } + + if (item.Template == Money.EUROS) + { + returnObject.Euros += stackSize; + } + + if (item.Template == Money.GP) + { + returnObject.GP += stackSize; + } + } + + return returnObject; + } + + /// + /// Currently returns a template of data for the profiles stats + /// + /// Full profile + /// + public ProfileStats GetProfileStats(SptProfile profile) + { + var pmcStats = ReadStats(profile.CharacterData?.PmcData); + var scavStats = ReadStats(profile.CharacterData?.ScavData); + var overallStats = CombineStats(pmcStats, scavStats); + var accountLifetime = FormatOverallLifetime(profile.CharacterData?.PmcData?.Info?.RegistrationDate ?? 0); + + return new ProfileStats + { + Overall = FormatStats(overallStats, accountLifetime), + Pmc = FormatStats(pmcStats, accountLifetime), + Scav = FormatStats(scavStats, accountLifetime), + }; + } + + private static CharacterStats ReadStats(PmcData? character) + { + var eft = character?.Stats?.Eft; + var counters = eft?.OverallCounters?.Items; + + var transitCounter = GetStat(counters, "ExitStatus", "Transit"); + + return new CharacterStats + { + Raids = GetStat(counters, "Sessions"), + Survived = GetStat(counters, "ExitStatus", "Survived") + transitCounter, + Killed = GetStat(counters, "ExitStatus", "Killed"), + Deaths = GetStat(counters, "Deaths"), + Kills = GetStat(counters, "Kills"), + RunThrough = GetStat(counters, "ExitStatus", "Runner"), + Awols = GetStat(counters, "ExitStatus", "Left"), + Mias = GetStat(counters, "ExitStatus", "MissingInAction"), + LongestWinStreak = GetStat(counters, "LongestWinStreak"), + TotalInGameTime = eft?.TotalInGameTime ?? 0, + }; + } + + private static CharacterStats CombineStats(CharacterStats pmcStats, CharacterStats scavStats) + { + return new CharacterStats + { + Raids = pmcStats.Raids + scavStats.Raids, + Survived = pmcStats.Survived + scavStats.Survived, + Killed = pmcStats.Killed + scavStats.Killed, + Deaths = pmcStats.Deaths + scavStats.Deaths, + Kills = pmcStats.Kills + scavStats.Kills, + RunThrough = pmcStats.RunThrough + scavStats.RunThrough, + Awols = pmcStats.Awols + scavStats.Awols, + Mias = pmcStats.Mias + scavStats.Mias, + LongestWinStreak = pmcStats.LongestWinStreak + scavStats.LongestWinStreak, + TotalInGameTime = pmcStats.TotalInGameTime + scavStats.TotalInGameTime, + }; + } + + private static Dictionary FormatStats(CharacterStats stats, string accountLifetime) + { + var killDeathRatio = + stats.Deaths > 0 + ? Math.Round(stats.Kills / (double)stats.Deaths, 2).ToString(CultureInfo.InvariantCulture) + : stats.Kills.ToString(CultureInfo.InvariantCulture); + var averageLifeSpan = stats.Raids > 0 ? FormatMinSeconds(stats.TotalInGameTime / stats.Raids) : "-"; + + return new Dictionary + { + { "Raids", stats.Raids.ToString(CultureInfo.InvariantCulture) }, + { "Survived", stats.Survived.ToString(CultureInfo.InvariantCulture) }, + { "KIA", stats.Killed.ToString(CultureInfo.InvariantCulture) }, + { "Kills", stats.Kills.ToString(CultureInfo.InvariantCulture) }, + { "RunThrough", stats.RunThrough.ToString(CultureInfo.InvariantCulture) }, + { "AWOL", stats.Awols.ToString(CultureInfo.InvariantCulture) }, + { "MIA", stats.Mias.ToString(CultureInfo.InvariantCulture) }, + { "KD", killDeathRatio }, + { "SurvivalRate", FormatPercentage(stats.Survived, stats.Raids) }, + { "AverageLifeSpan", averageLifeSpan }, + { "Online", FormatHoursMins(stats.TotalInGameTime) }, + { "LeaveRate", FormatPercentage(stats.Awols, stats.Raids) }, + { "SurvivalsInARow", stats.LongestWinStreak.ToString(CultureInfo.InvariantCulture) }, + { "AccountLifetime", accountLifetime }, + }; + } + + private static string FormatPercentage(long value, long total) + { + if (total <= 0) + { + return "-"; + } + + var percentage = (long)(value / (double)total * 100); + return $"{percentage.ToString(CultureInfo.InvariantCulture)}%"; + } + + private static long GetStat(IEnumerable? counters, params string[] requiredTags) + { + var value = counters?.FirstOrDefault(counter => counter.Key?.IsSupersetOf(requiredTags) == true)?.Value; + + return value ?? 0; + } + + /// + /// Taken from the client with interpolation instead, returns a format of MM:SS + /// + /// + /// + private static string FormatMinSeconds(long seconds) + { + var timeSpan = TimeSpan.FromSeconds(seconds); + return $"{timeSpan.Minutes:00}:{timeSpan.Seconds:00}"; + } + + /// + /// Taken from the client with interpolation instead, returns a format of HH:MM + /// + /// + /// + private static string FormatHoursMins(long seconds) + { + var timeSpan = TimeSpan.FromSeconds(seconds); + return $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}"; + } + + /// + /// Taken from the client with interpolation instead, returns a format of - or HH:MM or DD or YYDD + /// + /// + /// + private static string FormatOverallLifetime(int regDate) + { + var localDateTimeFromUnixTime = DateTime.UnixEpoch.AddMilliseconds(regDate * 1000.0).ToLocalTime(); + var ts = DateTime.UtcNow - localDateTimeFromUnixTime; + + if (ts <= TimeSpan.FromHours(48)) + { + return $"{ts.TotalHours}h{ts.Minutes:00}m"; + } + + if (ts <= TimeSpan.FromDays(365)) + { + return $"{(int)ts.TotalDays}d"; + } + + var num = (int)(ts.TotalDays / 365); + var num2 = (int)(ts.TotalDays - 365 * num); + return $"{num}y{num2}d"; + } +} + +public record CharacterStats +{ + public long Raids { get; init; } + public long Survived { get; init; } + public long Killed { get; init; } + public long Deaths { get; init; } + public long Kills { get; init; } + public long RunThrough { get; init; } + public long Awols { get; init; } + public long Mias { get; init; } + public long LongestWinStreak { get; init; } + public long TotalInGameTime { get; init; } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/BotBase.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/BotBase.cs index 51f72cde0..bea35f02c 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/BotBase.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/BotBase.cs @@ -568,7 +568,7 @@ public record CounterKeyValue { public HashSet? Key { get; set; } - public double? Value { get; set; } + public long? Value { get; set; } } public record Aggressor diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Launcher/MiniProfile.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Launcher/MiniProfile.cs index 84109a8d1..5f3a87393 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Launcher/MiniProfile.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Launcher/MiniProfile.cs @@ -42,4 +42,37 @@ public record MiniProfile [JsonPropertyName("wipe")] public bool Wipe { get; set; } + + [JsonPropertyName("profileCurrency")] + public ProfileCurrency? ProfileCurrency { get; set; } + + [JsonPropertyName("profileStats")] + public ProfileStats? ProfileStats { get; set; } +} + +public record ProfileStats +{ + [JsonPropertyName("overall")] + public required Dictionary Overall { get; set; } + + [JsonPropertyName("pmc")] + public required Dictionary Pmc { get; set; } + + [JsonPropertyName("scav")] + public required Dictionary Scav { get; set; } +} + +public record ProfileCurrency +{ + [JsonPropertyName("roubles")] + public required int Roubles { get; set; } + + [JsonPropertyName("euros")] + public required int Euros { get; set; } + + [JsonPropertyName("dollars")] + public required int Dollars { get; set; } + + [JsonPropertyName("gp")] + public required int GP { get; set; } }