Skip to content
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
}

Expand All @@ -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),
};
}

Expand Down
211 changes: 211 additions & 0 deletions Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using System.Globalization;
using Microsoft.Extensions.Logging;
using SPTarkov.Common.Models.Logging;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
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;
Expand Down Expand Up @@ -837,4 +840,212 @@ public bool GetProfileTemplateFlagValue(string accountEdition, string flagKey)

return matchingProfileTemplate.CustomFlags.GetValueOrDefault(flagKey, false);
}

/// <summary>
/// Gets the currencies in the profiles inventory
/// </summary>
/// <param name="profile">Full profile</param>
/// <returns></returns>
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;
}

/// <summary>
/// Currently returns a template of data for the profiles stats
/// </summary>
/// <param name="profile">Full profile</param>
/// <returns></returns>
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<string, string> 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<string, string>
{
{ "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<CounterKeyValue>? counters, params string[] requiredTags)
{
var value = counters?.FirstOrDefault(counter => counter.Key?.IsSupersetOf(requiredTags) == true)?.Value;

return value ?? 0;
}

/// <summary>
/// Taken from the client with interpolation instead, returns a format of MM:SS
/// </summary>
/// <param name="seconds"></param>
/// <returns></returns>
private static string FormatMinSeconds(long seconds)
{
var timeSpan = TimeSpan.FromSeconds(seconds);
return $"{timeSpan.Minutes:00}:{timeSpan.Seconds:00}";
}

/// <summary>
/// Taken from the client with interpolation instead, returns a format of HH:MM
/// </summary>
/// <param name="seconds"></param>
/// <returns></returns>
private static string FormatHoursMins(long seconds)
{
var timeSpan = TimeSpan.FromSeconds(seconds);
return $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}";
}

/// <summary>
/// Taken from the client with interpolation instead, returns a format of - or HH:MM or DD or YYDD
/// </summary>
/// <param name="regDate"></param>
/// <returns></returns>
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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ public record CounterKeyValue
{
public HashSet<string>? Key { get; set; }

public double? Value { get; set; }
public long? Value { get; set; }
}

public record Aggressor
Expand Down
33 changes: 33 additions & 0 deletions Libraries/SPTarkov.Server.Core/Models/Eft/Launcher/MiniProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> Overall { get; set; }

[JsonPropertyName("pmc")]
public required Dictionary<string, string> Pmc { get; set; }

[JsonPropertyName("scav")]
public required Dictionary<string, string> 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; }
}
Loading