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 721a44f09..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 /// @@ -177,9 +198,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++) @@ -190,6 +211,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 /// diff --git a/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor b/Libraries/SPTarkov.Server.Web/Components/Profiles/ProfileRecordDetails.razor index 47a25650f..2f8617cf7 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))" /> @@ -401,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 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) @@ -712,9 +722,9 @@ private static string GetQuestPendingLabel(ProfileQuestEditModel quest) { - if (quest.PendingAction == ProfileQuestEditAction.Complete) + if (quest.PendingAction == ProfileQuestEditAction.AvailableForFinish) { - return quest.IsNew ? "Add and complete" : "Complete"; + return quest.IsNew ? "Add and mark ready to finish" : "Available For Finish"; } if (quest.PendingAction == ProfileQuestEditAction.Restart) 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/Models/Profiles/ProfileQuestEditModel.cs b/Libraries/SPTarkov.Server.Web/Models/Profiles/ProfileQuestEditModel.cs index e24f06f9a..0438e6bbc 100644 --- a/Libraries/SPTarkov.Server.Web/Models/Profiles/ProfileQuestEditModel.cs +++ b/Libraries/SPTarkov.Server.Web/Models/Profiles/ProfileQuestEditModel.cs @@ -30,6 +30,6 @@ public ProfileQuestEditModel Clone() public enum ProfileQuestEditAction { None, - Complete, + AvailableForFinish, Restart, } diff --git a/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor b/Libraries/SPTarkov.Server.Web/Pages/ProfileControlPage.razor index 8f94f44eb..a95164141 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 () => @@ -446,14 +463,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; @@ -617,8 +635,8 @@ switch (questEdit.PendingAction) { - case ProfileQuestEditAction.Complete: - CompleteQuestStatus(quest, now); + case ProfileQuestEditAction.AvailableForFinish: + AvailableForFinishQuestStatus(quest, now); break; case ProfileQuestEditAction.Restart: RestartQuestStatus(quest, now); @@ -646,12 +664,12 @@ }; } - private static void CompleteQuestStatus(ProfileQuestStatus quest, long timestamp) + private static void AvailableForFinishQuestStatus(ProfileQuestStatus quest, long timestamp) { - quest.Status = QuestStatusEnum.Success; + quest.Status = QuestStatusEnum.AvailableForFinish; quest.AvailableAfter = null; quest.StatusTimers ??= []; - quest.StatusTimers[QuestStatusEnum.Success] = timestamp; + quest.StatusTimers[QuestStatusEnum.AvailableForFinish] = timestamp; } private static void RestartQuestStatus(ProfileQuestStatus quest, long timestamp)