From 8507ae0a9a31956a9ade837ef41a8a33243e5426 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Fri, 24 Jul 2026 23:16:14 -0500 Subject: [PATCH 1/5] add helper for getting level from experience value --- .../Helpers/Profile/ProfileHelper.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs index 721a44f09..3e45ab238 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs @@ -190,6 +190,33 @@ public void AddExperienceToPmc(MongoId sessionId, int experienceToAdd) return exp; } + /// + /// Get level from an expected experience value + /// + /// Experience number to get level for + /// Level expected + public int GetLevelFromExperience(int experience) + { + var expTable = globalTable.Configuration.Exp.Level.ExperienceTable; + + var cumulative = 0; + + for (var level = 1; level <= expTable.Length; level++) + { + if (level > 1) + { + cumulative += expTable[level - 1].Experience; + } + + if (experience < cumulative) + { + return level - 1; + } + } + + return expTable.Length; + } + /// /// Get the max level a player can be /// From a0691ad6f93471f5043b2a4025e67ee2babc2a44 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Fri, 24 Jul 2026 23:17:02 -0500 Subject: [PATCH 2/5] fix profile validation for level and exp --- .../Profiles/ProfileRecordDetails.razor | 38 ++++++++++++------- .../Pages/ProfileControlPage.razor | 5 ++- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor index 47a25650f..97c6fe6f6 100644 --- a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor +++ b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor @@ -1,9 +1,10 @@ @using System.Globalization +@using SPTarkov.Server.Core.Helpers.Profile @using SPTarkov.Server.Core.Models.Spt.Tables @using SPTarkov.Server.Web.Models.Database @using SPTarkov.Server.Web.Models.Profiles -@using SPTarkov.Server.Web.Services @inject GlobalTable GlobalTable +@inject ProfileHelper ProfileHelper @inject WebLocalizationService WebLocalizationService @@ -198,7 +199,8 @@ Step="1000" Immediate="true" Disabled="@(!_editModel.HasCharacter)" - @bind-Value="_editModel.PmcExperience" /> + Value="_editModel.PmcExperience" + ValueChanged="@(v => SetCharacterExperience(CharacterTarget.Pmc, v))" /> + Value="_editModel.ScavExperience" + ValueChanged="@(v => SetCharacterExperience(CharacterTarget.Scav, v))" /> @@ -531,7 +534,7 @@ public EventCallback OnClose { get; set; } private const int MaxSkillLevel = 51; - private int MaxCharacterLevel => GlobalTable.Configuration.Exp.Level.ExperienceTable.Length - 1; + private int MaxCharacterLevel => GlobalTable.Configuration.Exp.Level.ExperienceTable.Length; private ProfileDetails? _detailsReference; private ProfileEditModel? _editModel; @@ -597,26 +600,33 @@ if (target == CharacterTarget.Pmc) { _editModel.PmcLevel = clamped; - _editModel.PmcExperience = GetLevelStartXp(clamped); + _editModel.PmcExperience = ProfileHelper.GetExperience(clamped); } else { _editModel.ScavLevel = clamped; - _editModel.ScavExperience = GetLevelStartXp(clamped); + _editModel.ScavExperience = ProfileHelper.GetExperience(clamped); } } - private int GetLevelStartXp(int level) + private void SetCharacterExperience(CharacterTarget target, int? experience) { - var table = GlobalTable.Configuration.Exp.Level.ExperienceTable; - - level = Math.Clamp(level, 1, table.Length - 1); + if (_editModel is null || !experience.HasValue) + return; - var exp = 0; - for (var i = 0; i < level; i++) - exp += table[i].Experience; + var exp = Math.Max(0, experience.Value); + var level = ProfileHelper.GetLevelFromExperience(exp); - return exp; + if (target == CharacterTarget.Pmc) + { + _editModel.PmcExperience = exp; + _editModel.PmcLevel = level; + } + else + { + _editModel.ScavExperience = exp; + _editModel.ScavLevel = level; + } } private static void SetSkillLevel(ProfileSkillEditModel skill, int value) diff --git a/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor b/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor index 8f94f44eb..dfb9fde6f 100644 --- a/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor +++ b/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor @@ -446,14 +446,15 @@ return false; } - var maxLevel = GlobalTable.Configuration.Exp.Level.ExperienceTable.Length - 1; + var maxLevel = GlobalTable.Configuration.Exp.Level.ExperienceTable.Length; if (editModel.PmcLevel < 1 || editModel.PmcLevel > maxLevel || editModel.ScavLevel < 1 || editModel.ScavLevel > maxLevel) { Snackbar.Add(string.Format(L("profiles-character-level-range"), maxLevel), Severity.Warning); return false; } - if (editModel.PmcExperience != ProfileHelper.GetExperience(editModel.PmcLevel.Value) || editModel.ScavExperience != ProfileHelper.GetExperience(editModel.ScavLevel.Value)) + if (ProfileHelper.GetLevelFromExperience(editModel.PmcExperience.Value) != editModel.PmcLevel.Value || + ProfileHelper.GetLevelFromExperience(editModel.ScavExperience.Value) != editModel.ScavLevel.Value) { Snackbar.Add(L("profiles-experience-mismatch-level"), Severity.Warning); return false; From b6fd709640bd8821b3be9e53af63edfe4e034263 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Fri, 24 Jul 2026 23:19:22 -0500 Subject: [PATCH 3/5] let profiles reach level 79 to match live --- .../SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs index 3e45ab238..ee357589e 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs @@ -177,9 +177,9 @@ public void AddExperienceToPmc(MongoId sessionId, int experienceToAdd) var expTable = globalTable.Configuration.Exp.Level.ExperienceTable; int? exp = 0; - if (playerLevel >= expTable.Length) // make sure to not go out of bounds + if (playerLevel > expTable.Length) { - playerLevel = expTable.Length - 1; + playerLevel = expTable.Length; } for (var i = 0; i < playerLevel; i++) From a720abf1b04589a74b8bfb0095d1ca754782a826 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Fri, 24 Jul 2026 23:47:44 -0500 Subject: [PATCH 4/5] add clear mail dialogues button for a profile --- .../SPT_Data/database/locales/web/en.json | 2 ++ .../Helpers/Profile/ProfileHelper.cs | 21 +++++++++++++++++++ .../Components/Profiles/ProfileTable.razor | 13 ++++++++++++ .../Pages/ProfileControlPage.razor | 17 +++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/web/en.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/web/en.json index cb7296a63..efd5a372b 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/web/en.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/web/en.json @@ -175,6 +175,8 @@ "profiles-prestige-no-data": "No prestige data is available.", "profiles-quest-queued-once": "A quest can only be queued once.", "profiles-reloaded": "Reloaded {0}.", + "profiles-clear-messages": "Cleared {1} dialogues for {0}.", + "profiles-no-messages": "No dialogues for {0}.", "profiles-saved": "Saved {0}.", "profiles-selected-profile-missing": "Could not identify the selected profile.", "profiles-skill-range": "Skill levels must be 0-51 and progress must be 0-99.99.", diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs index ee357589e..983b4a584 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Profile/ProfileHelper.cs @@ -51,6 +51,27 @@ public void RemoveQuestConditionFromProfile(PmcData pmcData, Dictionary + /// Removes all trader/pmc/etc dialogues from a profile. + /// Useful in scenarios where the profile has too many dialogues to properly handle saving/loading due to size + /// + /// Player profile + /// Whether anything was cleared and the number of dialogues removed + public (bool Cleared, int Count) RemoveAllDialoguesFromProfile(MongoId sessionId) + { + var profile = GetFullProfile(sessionId); + var dialogues = profile.DialogueRecords ?? (profile.DialogueRecords = new Dictionary()); + + var count = dialogues.Count; + if (count == 0) + { + return (false, 0); + } + + dialogues.Clear(); + return (true, count); + } + /// /// Get all profiles from server /// diff --git a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileTable.razor b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileTable.razor index 373007440..7bc297dcd 100644 --- a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileTable.razor +++ b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileTable.razor @@ -66,6 +66,16 @@ + + + + + OnClearMessages { get; set; } + [Parameter] public EventCallback OnSaveProfile { get; set; } diff --git a/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor b/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor index dfb9fde6f..69fa4aeb9 100644 --- a/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor +++ b/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor @@ -79,6 +79,7 @@ IsBusy="@_isBusy" OnRefresh="@(() => RefreshProfiles())" OnSaveAll="SaveAllProfiles" + OnClearMessages="ClearMessages" OnSaveProfile="SaveProfile" OnReloadProfile="ReloadProfile" SelectedProfileChanged="SelectProfile" /> @@ -201,6 +202,22 @@ }); } + private async Task ClearMessages(ProfileSummary profile) + { + await RunProfileAction(async () => + { + var result = ProfileHelper.RemoveAllDialoguesFromProfile(profile.Id); + if (result.Cleared) + { + await SaveServer.SaveProfileAsync(new MongoId(profile.Id)); + Snackbar.Add(string.Format(L("profiles-clear-messages"), profile.Username, result.Count), Severity.Success); + return; + } + + Snackbar.Add(string.Format(L("profiles-no-messages"), profile.Username), Severity.Warning); + }); + } + private async Task SaveProfile(ProfileSummary profile) { await RunProfileAction(async () => From 5314514a4a9c5acb56340b08553fa186ed330324 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Sat, 25 Jul 2026 00:00:25 -0500 Subject: [PATCH 5/5] Mark quests Available for Finish instead of Success --- .../Components/Profiles/ProfileRecordDetails.razor | 8 ++++---- .../Models/Profiles/ProfileQuestEditModel.cs | 2 +- .../SPTarkov.Server.Web/Pages/ProfileControlPage.razor | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor index 97c6fe6f6..2f8617cf7 100644 --- a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor +++ b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor @@ -404,8 +404,8 @@ Color="Color.Secondary" Size="Size.Small" StartIcon="@Icons.Material.Filled.Done" - OnClick="@(() => SetQuestAction(context, ProfileQuestEditAction.Complete))"> - Complete + OnClick="@(() => SetQuestAction(context, ProfileQuestEditAction.AvailableForFinish))"> + Ready To Finish