partially implemented ProfileFixerService
This commit is contained in:
@@ -1,22 +1,60 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Utils;
|
||||
using System.Text.RegularExpressions;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class ProfileFixerService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly JsonUtil _jsonUtil;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly DatabaseService _databaseService;
|
||||
|
||||
public ProfileFixerService(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
JsonUtil jsonUtil,
|
||||
ItemHelper itemHelper,
|
||||
DatabaseService databaseService)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_jsonUtil = jsonUtil;
|
||||
_itemHelper = itemHelper;
|
||||
_databaseService = databaseService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find issues in the pmc profile data that may cause issues and fix them
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">profile to check and fix</param>
|
||||
public void CheckForAndFixPmcProfileIssues(PmcData pmcProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
RemoveDanglingConditionCounters(pmcProfile);
|
||||
RemoveDanglingTaskConditionCounters(pmcProfile);
|
||||
RemoveOrphanedQuests(pmcProfile);
|
||||
VerifyQuestProductionUnlocks(pmcProfile);
|
||||
FixFavorites(pmcProfile);
|
||||
|
||||
if (pmcProfile.Hideout is not null)
|
||||
{
|
||||
AddHideoutEliteSlots(pmcProfile);
|
||||
}
|
||||
|
||||
if (pmcProfile.Skills is not null)
|
||||
{
|
||||
CheckForSkillsOverMaxLevel(pmcProfile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +64,38 @@ public class ProfileFixerService
|
||||
/// <param name="fullProfile"></param>
|
||||
public void CheckForAndFixDialogueAttachments(SptProfile fullProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
foreach (var traderDialoguesKvP in fullProfile.DialogueRecords)
|
||||
{
|
||||
if (traderDialoguesKvP.Value.Messages is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var traderDialogues = traderDialoguesKvP.Value;
|
||||
foreach (var message in traderDialogues.Messages) {
|
||||
// Skip any messages without attached items
|
||||
if (message.Items?.Data is null || message.Items?.Stash is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip any messages that don't have a stashId collision with the player's equipment ID
|
||||
if (message.Items?.Stash != fullProfile.CharacterData?.PmcData?.Inventory?.Equipment)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise we need to generate a new unique stash ID for this message's attachments
|
||||
message.Items.Stash = _hashUtil.Generate();
|
||||
message.Items.Data = _itemHelper.AdoptOrphanedItems(message.Items.Stash, message.Items.Data);
|
||||
|
||||
// Because `adoptOrphanedItems` sets the slotId to `hideout`, we need to re-set it to `main` to work with mail
|
||||
foreach (var item in message.Items.Data.Where(item => item.SlotId == "hideout"))
|
||||
{
|
||||
item.SlotId = "main";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,7 +104,7 @@ public class ProfileFixerService
|
||||
/// <param name="scavProfile">profile to check and fix</param>
|
||||
public void CheckForAndFixScavProfileIssues(PmcData scavProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +113,106 @@ public class ProfileFixerService
|
||||
/// <param name="pmcProfile">Profile to check items of</param>
|
||||
public void FixProfileBreakingInventoryItemIssues(PmcData pmcProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Create a mapping of all inventory items, keyed by _id value
|
||||
var itemMapping = pmcProfile.Inventory.Items
|
||||
.GroupBy(item => item.Id)
|
||||
.ToDictionary(x => x.Key, x => x.ToList());
|
||||
|
||||
foreach (var mappingKvP in itemMapping) {
|
||||
// Only one item for this id, not a dupe
|
||||
if (mappingKvP.Value.Count == 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.Warning($"{ mappingKvP.Value.Count - 1} duplicate(s) found for item: {mappingKvP.Key}");
|
||||
var itemAJson = _jsonUtil.Serialize(mappingKvP.Value[0]);
|
||||
var itemBJson = _jsonUtil.Serialize(mappingKvP.Value[1]);
|
||||
if (itemAJson == itemBJson)
|
||||
{
|
||||
// Both items match, we can safely delete one (A)
|
||||
var indexOfItemToRemove = pmcProfile.Inventory.Items.IndexOf(mappingKvP.Value[0]);
|
||||
pmcProfile.Inventory.Items.RemoveAt(indexOfItemToRemove);
|
||||
_logger.Warning($"Deleted duplicate item: {mappingKvP.Key}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Items are different, replace ID with unique value
|
||||
// Only replace ID if items have no children, we dont want orphaned children
|
||||
var itemsHaveChildren = pmcProfile.Inventory.Items.Any((x) => x.ParentId == mappingKvP.Key);
|
||||
if (!itemsHaveChildren)
|
||||
{
|
||||
var itemToAdjust = pmcProfile.Inventory.Items.FirstOrDefault((x) => x.Id == mappingKvP.Key);
|
||||
itemToAdjust.Id = _hashUtil.Generate();
|
||||
_logger.Warning($"Replace duplicate item Id: {mappingKvP.Key} with {itemToAdjust.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over all inventory items
|
||||
foreach (var item in pmcProfile.Inventory.Items.Where((x) => x.SlotId is not null)) {
|
||||
if (item.Upd is null)
|
||||
{
|
||||
// Ignore items without a upd object
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check items with a tags for non-alphanumeric characters and remove
|
||||
Regex regxp = new Regex("[^a-zA-Z0-9 -]");
|
||||
if (item.Upd.Tag?.Name is not null && !regxp.IsMatch(item.Upd.Tag.Name))
|
||||
{
|
||||
_logger.Warning($"Fixed item: { item.Id}s Tag value, removed invalid characters");
|
||||
item.Upd.Tag.Name = regxp.Replace(item.Upd.Tag.Name, "");
|
||||
}
|
||||
|
||||
// Check items with StackObjectsCount (undefined)
|
||||
if (item.Upd.StackObjectsCount is null)
|
||||
{
|
||||
_logger.Warning($"Fixed item: {item.Id}s undefined StackObjectsCount value, now set to 1");
|
||||
item.Upd.StackObjectsCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over clothing
|
||||
var customizationDb = _databaseService.GetTemplates().Customization;
|
||||
var customizationDbArray = customizationDb.Values;
|
||||
var playerIsUsec = pmcProfile.Info.Side.ToLower() == "usec";
|
||||
|
||||
// Check Head
|
||||
if (customizationDb[pmcProfile.Customization.Head] is null)
|
||||
{
|
||||
var defaultHead = playerIsUsec
|
||||
? customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultUsecHead")
|
||||
: customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultBearHead");
|
||||
pmcProfile.Customization.Head = defaultHead.Id;
|
||||
}
|
||||
|
||||
// check Body
|
||||
if (customizationDb[pmcProfile.Customization.Body] is null)
|
||||
{
|
||||
var defaultBody = playerIsUsec
|
||||
? customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultUsecBody")
|
||||
: customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultBearBody");
|
||||
pmcProfile.Customization.Body = defaultBody.Id;
|
||||
}
|
||||
|
||||
// check Hands
|
||||
if (customizationDb[pmcProfile.Customization.Hands] is null)
|
||||
{
|
||||
var defaultHands = playerIsUsec
|
||||
? customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultUsecHands")
|
||||
: customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultBearHands");
|
||||
pmcProfile.Customization.Hands = defaultHands.Id;
|
||||
}
|
||||
|
||||
// check Hands
|
||||
if (customizationDb[pmcProfile.Customization.Feet] is null)
|
||||
{
|
||||
var defaultFeet = playerIsUsec
|
||||
? customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultUsecFeet")
|
||||
: customizationDbArray.FirstOrDefault((x) => x.Name == "DefaultBearFeet");
|
||||
pmcProfile.Customization.Feet = defaultFeet.Id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +222,16 @@ public class ProfileFixerService
|
||||
/// <param name="pmcProfile">profile to remove old counters from</param>
|
||||
public void RemoveDanglingConditionCounters(PmcData pmcProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (pmcProfile.TaskConditionCounters is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var counterKvP in pmcProfile.TaskConditionCounters
|
||||
.Where(counterKvP => counterKvP.Value.SourceId is null))
|
||||
{
|
||||
pmcProfile.TaskConditionCounters.Remove(counterKvP.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,12 +240,50 @@ public class ProfileFixerService
|
||||
/// <param name="pmcProfile">Player profile to check</param>
|
||||
protected void RemoveDanglingTaskConditionCounters(PmcData pmcProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (pmcProfile.TaskConditionCounters is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var taskConditionKeysToRemove = new List<string>();
|
||||
var activeRepeatableQuests = GetActiveRepeatableQuests(pmcProfile.RepeatableQuests);
|
||||
var achievements = _databaseService.GetAchievements();
|
||||
|
||||
// Loop over TaskConditionCounters objects and add once we want to remove to counterKeysToRemove
|
||||
foreach (var TaskConditionCounterKvP in pmcProfile.TaskConditionCounters) {
|
||||
// Only check if profile has repeatable quests
|
||||
if (pmcProfile.RepeatableQuests is not null && activeRepeatableQuests.Count > 0)
|
||||
{
|
||||
var existsInActiveRepeatableQuests = activeRepeatableQuests.Any(
|
||||
(quest) => quest.Id == TaskConditionCounterKvP.Value.SourceId);
|
||||
var existsInQuests = pmcProfile.Quests.Any(
|
||||
(quest) => quest.QId == TaskConditionCounterKvP.Value.SourceId);
|
||||
var isAchievementTracker = achievements.Any(
|
||||
(quest) => quest.Id == TaskConditionCounterKvP.Value.SourceId);
|
||||
|
||||
// If task conditions id is neither in activeQuests, quests or achievements - it's stale and should be cleaned up
|
||||
if (!(existsInActiveRepeatableQuests || existsInQuests || isAchievementTracker))
|
||||
{
|
||||
taskConditionKeysToRemove.Add(TaskConditionCounterKvP.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var counterKeyToRemove in taskConditionKeysToRemove) {
|
||||
_logger.Debug($"Removed: {counterKeyToRemove} TaskConditionCounter object");
|
||||
pmcProfile.TaskConditionCounters.Remove(counterKeyToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<RepeatableQuest> GetActiveRepeatableQuests(List<PmcDataRepeatableQuest> repeatableQuests)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var activeQuests = new List<RepeatableQuest>();
|
||||
foreach (var repeatableQuest in repeatableQuests.Where(questType => questType.ActiveQuests?.Count > 0)) {
|
||||
// daily/weekly collection has active quests in them, add to array and return
|
||||
activeQuests.AddRange(repeatableQuest.ActiveQuests);
|
||||
}
|
||||
|
||||
return activeQuests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +292,20 @@ public class ProfileFixerService
|
||||
/// <param name="pmcProfile">Profile to remove dead quests from</param>
|
||||
protected void RemoveOrphanedQuests(PmcData pmcProfile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var quests = _databaseService.GetQuests();
|
||||
var profileQuests = pmcProfile.Quests;
|
||||
|
||||
var activeRepeatableQuests = GetActiveRepeatableQuests(pmcProfile.RepeatableQuests);
|
||||
|
||||
for (var i = profileQuests.Count - 1; i >= 0; i--)
|
||||
{
|
||||
|
||||
if (!quests.ContainsKey(profileQuests[i].QId) || activeRepeatableQuests.Any((x) => x.Id == profileQuests[i].QId))
|
||||
{
|
||||
profileQuests.RemoveAt(i);
|
||||
_logger.Success("Successfully removed orphaned quest that doesn't exist in quest data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user