Warning fixes and ProfileValidatorHelper cleanup (#551)
* Fix warnings in ProfileHelper.cs * Refactor ProfileValidatorHelper.cs * reduce nesting
This commit is contained in:
@@ -12,7 +12,6 @@ using SPTarkov.Server.Core.Servers;
|
||||
using SPTarkov.Server.Core.Services;
|
||||
using SPTarkov.Server.Core.Utils;
|
||||
using SPTarkov.Server.Core.Utils.Cloners;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace SPTarkov.Server.Core.Helpers;
|
||||
|
||||
@@ -28,8 +27,8 @@ public class ProfileHelper(
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
protected static readonly FrozenSet<string> _gameEditionsWithFreeRefresh = ["edge_of_darkness", "unheard_edition"];
|
||||
protected readonly InventoryConfig _inventoryConfig = configServer.GetConfig<InventoryConfig>();
|
||||
protected static readonly FrozenSet<string> GameEditionsWithFreeRefresh = ["edge_of_darkness", "unheard_edition"];
|
||||
protected readonly InventoryConfig InventoryConfig = configServer.GetConfig<InventoryConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Remove/reset a completed quest condition from players profile quest data
|
||||
@@ -41,12 +40,10 @@ public class ProfileHelper(
|
||||
foreach (var questId in questConditionId)
|
||||
{
|
||||
var conditionId = questId.Value;
|
||||
var profileQuest = pmcData.Quests.FirstOrDefault(q => q.QId == conditionId);
|
||||
var profileQuest = pmcData.Quests?.FirstOrDefault(q => q.QId == conditionId);
|
||||
|
||||
if (profileQuest != null) // Remove condition
|
||||
{
|
||||
profileQuest.CompletedConditions.Remove(conditionId);
|
||||
}
|
||||
// Remove condition
|
||||
profileQuest?.CompletedConditions?.Remove(conditionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,14 +70,14 @@ public class ProfileHelper(
|
||||
return output;
|
||||
}
|
||||
|
||||
var FullProfileClone = cloner.Clone(GetFullProfile(sessionId));
|
||||
var fullProfileClone = cloner.Clone(GetFullProfile(sessionId))!;
|
||||
|
||||
// Sanitize any data the client can not receive
|
||||
SanitizeProfileForClient(FullProfileClone);
|
||||
SanitizeProfileForClient(fullProfileClone);
|
||||
|
||||
// PMC must be at array index 0, scav at 1
|
||||
output.Add(FullProfileClone.CharacterData.PmcData);
|
||||
output.Add(FullProfileClone.CharacterData.ScavData);
|
||||
output.Add(fullProfileClone.CharacterData!.PmcData!);
|
||||
output.Add(fullProfileClone.CharacterData!.ScavData!);
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -93,7 +90,7 @@ public class ProfileHelper(
|
||||
{
|
||||
// Remove `loyaltyLevel` from `TradersInfo`, as otherwise it causes the client to not
|
||||
// properly calculate the player's `loyaltyLevel`
|
||||
foreach (var trader in clonedProfile.CharacterData.PmcData.TradersInfo.Values)
|
||||
foreach (var trader in clonedProfile.CharacterData?.PmcData?.TradersInfo.Values!)
|
||||
{
|
||||
trader.LoyaltyLevel = null;
|
||||
}
|
||||
@@ -103,24 +100,28 @@ public class ProfileHelper(
|
||||
/// Check if a nickname is used by another profile loaded by the server
|
||||
/// </summary>
|
||||
/// <param name="nicknameRequest">nickname request object</param>
|
||||
/// <param name="sessionID">Session id</param>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <returns>True if already in use</returns>
|
||||
public bool IsNicknameTaken(ValidateNicknameRequestData nicknameRequest, MongoId sessionID)
|
||||
public bool IsNicknameTaken(ValidateNicknameRequestData nicknameRequest, MongoId sessionId)
|
||||
{
|
||||
var allProfiles = saveServer.GetProfiles().Values;
|
||||
|
||||
// Find a profile that doesn't have same session id but has same name
|
||||
return allProfiles.Any(p =>
|
||||
ProfileHasInfoProperty(p)
|
||||
&& !StringsMatch(p.ProfileInfo.ProfileId, sessionID)
|
||||
&& // SessionIds dont match
|
||||
StringsMatch(p.CharacterData.PmcData.Info.LowerNickname.ToLowerInvariant(), nicknameRequest.Nickname.ToLowerInvariant())
|
||||
return allProfiles.Any(profile =>
|
||||
// Valid profile
|
||||
ProfileHasInfoProperty(profile)
|
||||
&& profile.ProfileInfo?.ProfileId != sessionId
|
||||
// SessionIds dont match
|
||||
&& StringsMatch(
|
||||
profile.CharacterData?.PmcData?.Info?.LowerNickname?.ToLowerInvariant()!,
|
||||
nicknameRequest.Nickname?.ToLowerInvariant()!
|
||||
)
|
||||
); // Nicknames do
|
||||
}
|
||||
|
||||
protected bool ProfileHasInfoProperty(SptProfile profile)
|
||||
{
|
||||
return profile?.CharacterData?.PmcData?.Info != null;
|
||||
return profile.CharacterData?.PmcData?.Info != null;
|
||||
}
|
||||
|
||||
protected bool StringsMatch(string stringA, string stringB)
|
||||
@@ -131,18 +132,18 @@ public class ProfileHelper(
|
||||
/// <summary>
|
||||
/// Add experience to a PMC inside the players profile
|
||||
/// </summary>
|
||||
/// <param name="sessionID">Session id</param>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="experienceToAdd">Experience to add to PMC character</param>
|
||||
public void AddExperienceToPmc(MongoId sessionID, int experienceToAdd)
|
||||
public void AddExperienceToPmc(MongoId sessionId, int experienceToAdd)
|
||||
{
|
||||
var pmcData = GetPmcProfile(sessionID);
|
||||
if (pmcData != null)
|
||||
var pmcData = GetPmcProfile(sessionId);
|
||||
if (pmcData?.Info != null)
|
||||
{
|
||||
pmcData.Info.Experience += experienceToAdd;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Profile {sessionID} does not exist");
|
||||
logger.Error($"Profile {sessionId} does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,22 +202,22 @@ public class ProfileHelper(
|
||||
Mods = [],
|
||||
ReceivedGifts = [],
|
||||
BlacklistedItemTemplates = [],
|
||||
FreeRepeatableRefreshUsedCount = new(),
|
||||
Migrations = new(),
|
||||
CultistRewards = new(),
|
||||
FreeRepeatableRefreshUsedCount = [],
|
||||
Migrations = [],
|
||||
CultistRewards = [],
|
||||
PendingPrestige = null,
|
||||
ExtraRepeatableQuests = new(),
|
||||
ExtraRepeatableQuests = [],
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get full representation of a players profile json
|
||||
/// </summary>
|
||||
/// <param name="sessionID">Profile id to get</param>
|
||||
/// <param name="sessionId">Profile id to get</param>
|
||||
/// <returns>SptProfile object</returns>
|
||||
public SptProfile GetFullProfile(MongoId sessionID)
|
||||
public SptProfile GetFullProfile(MongoId sessionId)
|
||||
{
|
||||
return saveServer.GetProfile(sessionID);
|
||||
return saveServer.GetProfile(sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -232,23 +233,18 @@ public class ProfileHelper(
|
||||
logger.Error($"Account {accountId} does not exist");
|
||||
}
|
||||
|
||||
return saveServer.GetProfiles().FirstOrDefault(p => p.Value?.ProfileInfo?.Aid == aid).Value;
|
||||
return saveServer.GetProfiles().FirstOrDefault(p => p.Value.ProfileInfo?.Aid == aid).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a ChatRoomMember formatted profile for the given session ID
|
||||
/// </summary>
|
||||
/// <param name="sessionID">The session ID to return the profile for</param>
|
||||
/// <param name="sessionId">The session ID to return the profile for</param>
|
||||
/// <returns></returns>
|
||||
public SearchFriendResponse? GetChatRoomMemberFromSessionId(MongoId sessionID)
|
||||
public SearchFriendResponse? GetChatRoomMemberFromSessionId(MongoId sessionId)
|
||||
{
|
||||
var pmcProfile = GetFullProfile(sessionID)?.CharacterData?.PmcData;
|
||||
if (pmcProfile == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetChatRoomMemberFromPmcProfile(pmcProfile);
|
||||
var pmcProfile = GetFullProfile(sessionId).CharacterData?.PmcData;
|
||||
return pmcProfile is null ? null : GetChatRoomMemberFromPmcProfile(pmcProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -256,15 +252,15 @@ public class ProfileHelper(
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">The PMC profile data to format into a ChatRoomMember structure</param>
|
||||
/// <returns></returns>
|
||||
public SearchFriendResponse? GetChatRoomMemberFromPmcProfile(PmcData pmcProfile)
|
||||
public SearchFriendResponse GetChatRoomMemberFromPmcProfile(PmcData pmcProfile)
|
||||
{
|
||||
return new SearchFriendResponse
|
||||
{
|
||||
Id = pmcProfile.Id.Value,
|
||||
Id = pmcProfile.Id!.Value,
|
||||
Aid = pmcProfile.Aid,
|
||||
Info = new UserDialogDetails
|
||||
{
|
||||
Nickname = pmcProfile.Info.Nickname,
|
||||
Nickname = pmcProfile.Info!.Nickname,
|
||||
Side = pmcProfile.Info.Side,
|
||||
Level = pmcProfile.Info.Level,
|
||||
MemberCategory = pmcProfile.Info.MemberCategory,
|
||||
@@ -276,11 +272,11 @@ public class ProfileHelper(
|
||||
/// <summary>
|
||||
/// Get a PMC profile by its session id
|
||||
/// </summary>
|
||||
/// <param name="sessionID">Profile id to return</param>
|
||||
/// <param name="sessionId">Profile id to return</param>
|
||||
/// <returns>PmcData object</returns>
|
||||
public PmcData? GetPmcProfile(MongoId sessionID)
|
||||
public PmcData? GetPmcProfile(MongoId sessionId)
|
||||
{
|
||||
return GetFullProfile(sessionID)?.CharacterData?.PmcData;
|
||||
return GetFullProfile(sessionId).CharacterData?.PmcData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -297,11 +293,11 @@ public class ProfileHelper(
|
||||
/// <summary>
|
||||
/// Get a full profiles scav-specific sub-profile
|
||||
/// </summary>
|
||||
/// <param name="sessionID">Profiles id</param>
|
||||
/// <param name="sessionId">Profiles id</param>
|
||||
/// <returns>IPmcData object</returns>
|
||||
public PmcData? GetScavProfile(MongoId sessionID)
|
||||
public PmcData? GetScavProfile(MongoId sessionId)
|
||||
{
|
||||
return saveServer.GetProfile(sessionID).CharacterData?.ScavData;
|
||||
return saveServer.GetProfile(sessionId).CharacterData?.ScavData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -340,12 +336,12 @@ public class ProfileHelper(
|
||||
/// <summary>
|
||||
/// is this profile flagged for data removal
|
||||
/// </summary>
|
||||
/// <param name="sessionID">Profile id</param>
|
||||
/// <param name="sessionId">Profile id</param>
|
||||
/// <returns>True if profile is to be wiped of data/progress</returns>
|
||||
/// TODO: logic doesn't feel right to have IsWiped being nullable
|
||||
protected bool IsWiped(MongoId sessionID)
|
||||
protected bool IsWiped(MongoId sessionId)
|
||||
{
|
||||
return saveServer.GetProfile(sessionID)?.ProfileInfo?.IsWiped ?? false;
|
||||
return saveServer.GetProfile(sessionId).ProfileInfo?.IsWiped ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -355,15 +351,15 @@ public class ProfileHelper(
|
||||
/// <returns>profile without secure container</returns>
|
||||
public PmcData RemoveSecureContainer(PmcData profile)
|
||||
{
|
||||
var items = profile.Inventory.Items;
|
||||
var secureContainer = items.FirstOrDefault(i => i.SlotId == "SecuredContainer");
|
||||
var items = profile.Inventory?.Items;
|
||||
var secureContainer = items?.FirstOrDefault(i => i.SlotId == "SecuredContainer");
|
||||
if (secureContainer is not null)
|
||||
{
|
||||
// Find secure container + children
|
||||
var secureContainerAndChildrenIds = items.GetItemWithChildrenTpls(secureContainer.Id).ToHashSet();
|
||||
var secureContainerAndChildrenIds = items?.GetItemWithChildrenTpls(secureContainer.Id).ToHashSet();
|
||||
|
||||
// Remove secure container + its children
|
||||
items.RemoveAll(x => secureContainerAndChildrenIds.Contains(x.Id));
|
||||
items?.RemoveAll(x => (secureContainerAndChildrenIds?.Contains(x.Id) ?? false));
|
||||
}
|
||||
|
||||
return profile;
|
||||
@@ -379,7 +375,7 @@ public class ProfileHelper(
|
||||
public void FlagGiftReceivedInProfile(MongoId playerId, string giftId, int maxCount)
|
||||
{
|
||||
var profileToUpdate = GetFullProfile(playerId);
|
||||
profileToUpdate.SptData.ReceivedGifts ??= [];
|
||||
profileToUpdate.SptData!.ReceivedGifts ??= [];
|
||||
|
||||
var giftData = profileToUpdate.SptData.ReceivedGifts.FirstOrDefault(g => g.GiftId == giftId);
|
||||
if (giftData != null)
|
||||
@@ -410,17 +406,7 @@ public class ProfileHelper(
|
||||
public bool PlayerHasReceivedMaxNumberOfGift(MongoId playerId, string giftId, int maxGiftCount)
|
||||
{
|
||||
var profile = GetFullProfile(playerId);
|
||||
if (profile == null)
|
||||
{
|
||||
if (logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.Debug($"Unable to gift {giftId}, Profile: {playerId} does not exist");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var giftDataFromProfile = profile.SptData.ReceivedGifts?.FirstOrDefault(g => g.GiftId == giftId);
|
||||
var giftDataFromProfile = profile.SptData?.ReceivedGifts?.FirstOrDefault(g => g.GiftId == giftId);
|
||||
if (giftDataFromProfile == null)
|
||||
{
|
||||
return false;
|
||||
@@ -437,7 +423,7 @@ public class ProfileHelper(
|
||||
/// Was Includes in Node so might not be exact?
|
||||
public void IncrementStatCounter(CounterKeyValue[] counters, string keyToIncrement)
|
||||
{
|
||||
var stat = counters.FirstOrDefault(c => c.Key.Contains(keyToIncrement));
|
||||
var stat = counters.FirstOrDefault(c => c.Key != null && c.Key.Contains(keyToIncrement));
|
||||
if (stat != null)
|
||||
{
|
||||
stat.Value++;
|
||||
@@ -452,7 +438,7 @@ public class ProfileHelper(
|
||||
/// <returns>True if player has skill at elite level</returns>
|
||||
public bool HasEliteSkillLevel(SkillTypes skill, PmcData pmcProfile)
|
||||
{
|
||||
var profileSkills = pmcProfile.Skills.Common;
|
||||
var profileSkills = pmcProfile.Skills?.Common;
|
||||
if (profileSkills == null)
|
||||
{
|
||||
return false;
|
||||
@@ -488,7 +474,7 @@ public class ProfileHelper(
|
||||
return;
|
||||
}
|
||||
|
||||
var profileSkills = pmcProfile?.Skills?.Common;
|
||||
var profileSkills = pmcProfile.Skills?.Common;
|
||||
if (profileSkills == null)
|
||||
{
|
||||
logger.Warning($"Unable to add: {pointsToAddToSkill} points to {skill}, Profile has no skills");
|
||||
@@ -508,13 +494,13 @@ public class ProfileHelper(
|
||||
pointsToAddToSkill *= skillProgressRate;
|
||||
}
|
||||
|
||||
if (_inventoryConfig.SkillGainMultipliers.TryGetValue(skill.ToString(), out _))
|
||||
if (InventoryConfig.SkillGainMultipliers.TryGetValue(skill.ToString(), out _))
|
||||
{
|
||||
pointsToAddToSkill *= _inventoryConfig.SkillGainMultipliers[skill.ToString()];
|
||||
pointsToAddToSkill *= InventoryConfig.SkillGainMultipliers[skill.ToString()];
|
||||
}
|
||||
|
||||
profileSkill.Progress += pointsToAddToSkill;
|
||||
profileSkill.Progress = Math.Min(profileSkill?.Progress ?? 0D, 5100); // Prevent skill from ever going above level 51 (5100)
|
||||
profileSkill.Progress = Math.Min(profileSkill.Progress, 5100); // Prevent skill from ever going above level 51 (5100)
|
||||
|
||||
profileSkill.PointsEarnedDuringSession += pointsToAddToSkill;
|
||||
|
||||
@@ -524,11 +510,11 @@ public class ProfileHelper(
|
||||
/// <summary>
|
||||
/// Is the provided session id for a developer account
|
||||
/// </summary>
|
||||
/// <param name="sessionID">Profile id to check</param>
|
||||
/// <param name="sessionId">Profile id to check</param>
|
||||
/// <returns>True if account is developer</returns>
|
||||
public bool IsDeveloperAccount(MongoId sessionID)
|
||||
public bool IsDeveloperAccount(MongoId sessionId)
|
||||
{
|
||||
return GetFullProfile(sessionID)?.ProfileInfo?.Edition?.ToLowerInvariant().StartsWith("spt developer") ?? false;
|
||||
return GetFullProfile(sessionId).ProfileInfo?.Edition?.ToLowerInvariant().StartsWith("spt developer") ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -574,7 +560,7 @@ public class ProfileHelper(
|
||||
|
||||
public bool HasAccessToRepeatableFreeRefreshSystem(PmcData pmcProfile)
|
||||
{
|
||||
return _gameEditionsWithFreeRefresh.Contains(pmcProfile.Info.GameVersion);
|
||||
return GameEditionsWithFreeRefresh.Contains(pmcProfile.Info?.GameVersion ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -586,8 +572,8 @@ public class ProfileHelper(
|
||||
{
|
||||
// Find all pockets in profile, may be multiple as they could have equipment stand
|
||||
// (1 pocket for each upgrade level of equipment stand)
|
||||
var pockets = pmcProfile.Inventory.Items.Where(i => i.SlotId == "Pockets");
|
||||
if (!pockets.Any())
|
||||
var pockets = pmcProfile.Inventory?.Items?.Where(i => i.SlotId == "Pockets");
|
||||
if (pockets is null || !pockets.Any())
|
||||
{
|
||||
logger.Error($"Unable to replace profile: {pmcProfile.Id} pocket tpl with: {newPocketTpl} as Pocket item could not be found.");
|
||||
return;
|
||||
@@ -611,11 +597,11 @@ public class ProfileHelper(
|
||||
foreach (var itemId in profile.Inventory?.FavoriteItems ?? [])
|
||||
{
|
||||
// When viewing another users profile, the client expects a full item with children, so get that
|
||||
var itemAndChildren = profile.Inventory.Items.GetItemWithChildren(itemId);
|
||||
var itemAndChildren = profile.Inventory?.Items?.GetItemWithChildren(itemId);
|
||||
if (itemAndChildren?.Count > 0)
|
||||
{
|
||||
// To get the client to actually see the items, we set the main item's parent to null, so it's treated as a root item
|
||||
var clonedItems = cloner.Clone(itemAndChildren);
|
||||
var clonedItems = cloner.Clone(itemAndChildren)!;
|
||||
clonedItems.First().ParentId = null;
|
||||
|
||||
fullFavorites.AddRange(clonedItems);
|
||||
@@ -627,73 +613,78 @@ public class ProfileHelper(
|
||||
|
||||
public void AddHideoutCustomisationUnlock(SptProfile fullProfile, Reward reward, string source)
|
||||
{
|
||||
if (fullProfile?.CustomisationUnlocks == null)
|
||||
if (reward.Target is null)
|
||||
{
|
||||
fullProfile.CustomisationUnlocks = [];
|
||||
logger.Error("Unable to add hideout customisation unlock, reward.Target is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullProfile?.CustomisationUnlocks?.Any(u => u.Id == reward.Target) ?? false)
|
||||
fullProfile.CustomisationUnlocks ??= [];
|
||||
|
||||
if (fullProfile.CustomisationUnlocks?.Any(u => u.Id == reward.Target) ?? false)
|
||||
{
|
||||
logger.Warning(
|
||||
$"Profile: {fullProfile.ProfileInfo.ProfileId} already has hideout customisation reward: {reward.Target}, skipping"
|
||||
$"Profile: {fullProfile.ProfileInfo?.ProfileId ?? "`ProfileId is null`"} already has hideout customisation reward: {reward.Target}, skipping"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var customisationTemplateDb = databaseService.GetTemplates().Customization;
|
||||
var matchingCustomisation = customisationTemplateDb.GetValueOrDefault(reward.Target, null);
|
||||
|
||||
if (matchingCustomisation is not null)
|
||||
if (!customisationTemplateDb.TryGetValue(reward.Target, out var template))
|
||||
{
|
||||
var rewardToStore = new CustomisationStorage
|
||||
{
|
||||
Id = new MongoId(reward.Target),
|
||||
Source = source,
|
||||
Type = null,
|
||||
};
|
||||
|
||||
switch (matchingCustomisation.Parent)
|
||||
{
|
||||
case CustomisationTypeId.MANNEQUIN_POSE:
|
||||
rewardToStore.Type = CustomisationType.MANNEQUIN_POSE;
|
||||
break;
|
||||
case CustomisationTypeId.GESTURES:
|
||||
rewardToStore.Type = CustomisationType.GESTURE;
|
||||
break;
|
||||
case CustomisationTypeId.FLOOR:
|
||||
rewardToStore.Type = CustomisationType.FLOOR;
|
||||
break;
|
||||
case CustomisationTypeId.DOG_TAGS:
|
||||
rewardToStore.Type = CustomisationType.DOG_TAG;
|
||||
break;
|
||||
case CustomisationTypeId.CEILING:
|
||||
rewardToStore.Type = CustomisationType.CEILING;
|
||||
break;
|
||||
case CustomisationTypeId.WALL:
|
||||
rewardToStore.Type = CustomisationType.WALL;
|
||||
break;
|
||||
case CustomisationTypeId.ENVIRONMENT_UI:
|
||||
rewardToStore.Type = CustomisationType.ENVIRONMENT;
|
||||
break;
|
||||
case CustomisationTypeId.SHOOTING_RANGE_MARK:
|
||||
rewardToStore.Type = CustomisationType.SHOOTING_RANGE_MARK;
|
||||
break;
|
||||
case CustomisationTypeId.VOICE:
|
||||
rewardToStore.Type = CustomisationType.VOICE;
|
||||
break;
|
||||
case CustomisationTypeId.LIGHT:
|
||||
rewardToStore.Type = CustomisationType.LIGHT;
|
||||
break;
|
||||
case CustomisationTypeId.UPPER:
|
||||
rewardToStore.Type = CustomisationType.UPPER;
|
||||
break;
|
||||
default:
|
||||
logger.Error($"Unhandled customisation unlock type: {matchingCustomisation.Parent} not added to profile");
|
||||
return;
|
||||
}
|
||||
|
||||
fullProfile.CustomisationUnlocks.Add(rewardToStore);
|
||||
logger.Error("Unable to find customisation reward template");
|
||||
return;
|
||||
}
|
||||
|
||||
var rewardToStore = new CustomisationStorage
|
||||
{
|
||||
Id = new MongoId(reward.Target),
|
||||
Source = source,
|
||||
Type = null,
|
||||
};
|
||||
|
||||
switch (template.Parent)
|
||||
{
|
||||
case CustomisationTypeId.MANNEQUIN_POSE:
|
||||
rewardToStore.Type = CustomisationType.MANNEQUIN_POSE;
|
||||
break;
|
||||
case CustomisationTypeId.GESTURES:
|
||||
rewardToStore.Type = CustomisationType.GESTURE;
|
||||
break;
|
||||
case CustomisationTypeId.FLOOR:
|
||||
rewardToStore.Type = CustomisationType.FLOOR;
|
||||
break;
|
||||
case CustomisationTypeId.DOG_TAGS:
|
||||
rewardToStore.Type = CustomisationType.DOG_TAG;
|
||||
break;
|
||||
case CustomisationTypeId.CEILING:
|
||||
rewardToStore.Type = CustomisationType.CEILING;
|
||||
break;
|
||||
case CustomisationTypeId.WALL:
|
||||
rewardToStore.Type = CustomisationType.WALL;
|
||||
break;
|
||||
case CustomisationTypeId.ENVIRONMENT_UI:
|
||||
rewardToStore.Type = CustomisationType.ENVIRONMENT;
|
||||
break;
|
||||
case CustomisationTypeId.SHOOTING_RANGE_MARK:
|
||||
rewardToStore.Type = CustomisationType.SHOOTING_RANGE_MARK;
|
||||
break;
|
||||
case CustomisationTypeId.VOICE:
|
||||
rewardToStore.Type = CustomisationType.VOICE;
|
||||
break;
|
||||
case CustomisationTypeId.LIGHT:
|
||||
rewardToStore.Type = CustomisationType.LIGHT;
|
||||
break;
|
||||
case CustomisationTypeId.UPPER:
|
||||
rewardToStore.Type = CustomisationType.UPPER;
|
||||
break;
|
||||
default:
|
||||
logger.Error($"Unhandled customisation unlock type: {template.Parent} not added to profile");
|
||||
return;
|
||||
}
|
||||
|
||||
fullProfile.CustomisationUnlocks?.Add(rewardToStore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -702,12 +693,16 @@ public class ProfileHelper(
|
||||
/// <param name="accountEdition">Edition of profile desired, e.g. "Standard"</param>
|
||||
/// <param name="side">Side of profile desired, e.g. "Bear"</param>
|
||||
/// <returns></returns>
|
||||
public TemplateSide GetProfileTemplateForSide(string accountEdition, string side)
|
||||
public TemplateSide? GetProfileTemplateForSide(string accountEdition, string side)
|
||||
{
|
||||
var profileTemplates = databaseService.GetProfileTemplates();
|
||||
|
||||
// Get matching profile 'type' e.g. 'standard'
|
||||
profileTemplates.TryGetValue(accountEdition, out var matchingProfileTemplate);
|
||||
if (!profileTemplates.TryGetValue(accountEdition, out var matchingProfileTemplate))
|
||||
{
|
||||
logger.Error($"Unable to find profile template for account edition: {accountEdition} and side: {side}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get matching profile by 'side' e.g. USEC
|
||||
return string.Equals(side, "bear", StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using SPTarkov.Server.Core.Exceptions.Items;
|
||||
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.Profile;
|
||||
using SPTarkov.Server.Core.Models.Enums;
|
||||
@@ -21,7 +22,7 @@ public class ProfileValidatorHelper(
|
||||
TraderStore traderStore
|
||||
)
|
||||
{
|
||||
protected readonly CoreConfig _coreConfig = configServer.GetConfig<CoreConfig>();
|
||||
protected readonly CoreConfig CoreConfig = configServer.GetConfig<CoreConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Checks profile inventory for items that do not exist inside the items DB
|
||||
@@ -31,44 +32,98 @@ public class ProfileValidatorHelper(
|
||||
/// <exception cref="InvalidModdedItemException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
/// <exception cref="InvalidModdedClothingException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
/// <exception cref="InvalidModdedTraderException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
public void CheckForOrphanedModdedItems(MongoId sessionId, SptProfile fullProfile)
|
||||
/// <remarks>Exceptions thrown are from called methods, this method does not throw exceptions directly, but they are possible.</remarks>
|
||||
public void CheckForOrphanedModdedData(MongoId sessionId, SptProfile fullProfile)
|
||||
{
|
||||
RemoveInvalidItems(sessionId, fullProfile);
|
||||
RemoveInvalidUserBuilds(fullProfile);
|
||||
RemoveInvalidDialogRecords(fullProfile);
|
||||
RemoveInvalidClothing(fullProfile);
|
||||
RemoveInvalidRepeatableQuests(fullProfile);
|
||||
RemoveInvalidTraderPurchases(fullProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all invalid item ids from the provided profile
|
||||
/// </summary>
|
||||
/// <param name="sessionId">SessionId to check</param>
|
||||
/// <param name="fullProfile">Full profile to check</param>
|
||||
/// <exception cref="InvalidModdedItemException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
protected void RemoveInvalidItems(MongoId sessionId, SptProfile fullProfile)
|
||||
{
|
||||
var itemsDb = databaseService.GetItems();
|
||||
var pmcProfile = fullProfile.CharacterData.PmcData;
|
||||
var pmcProfile = fullProfile.CharacterData?.PmcData;
|
||||
|
||||
var invalidItemIds = pmcProfile
|
||||
?.Inventory?.Items?.Where(item => !itemsDb.ContainsKey(item.Template))
|
||||
.Select(item => item.Id)
|
||||
.ToList();
|
||||
|
||||
// No invalid items
|
||||
if (invalidItemIds is null || invalidItemIds.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var invalidItemIds = pmcProfile.Inventory.Items.Where(item => !itemsDb.ContainsKey(item.Template)).Select(item => item.Id).ToList();
|
||||
foreach (var invalidItemId in invalidItemIds)
|
||||
{
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
if (CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
logger.Warning($"Deleting item id: {invalidItemId} from inventory and insurance");
|
||||
|
||||
// Add here so we can remove below
|
||||
pmcProfile.RemoveItem(invalidItemId, sessionId);
|
||||
pmcProfile?.RemoveItem(invalidItemId, sessionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidModdedItemException(serverLocalisationService.GetText("fixer-mod_item_found", invalidItemId.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fullProfile.UserBuildData is not null)
|
||||
/// <summary>
|
||||
/// Checks for and removes invalid user builds containing items that no longer exist
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Full profile to check</param>
|
||||
protected void RemoveInvalidUserBuilds(SptProfile fullProfile)
|
||||
{
|
||||
// No user build data to remove
|
||||
if (fullProfile.UserBuildData is null)
|
||||
{
|
||||
// Remove invalid builds from weapon, equipment and magazine build lists
|
||||
var weaponBuilds = fullProfile.UserBuildData?.WeaponBuilds ?? [];
|
||||
fullProfile.UserBuildData.WeaponBuilds = weaponBuilds
|
||||
.Where(build => !ShouldRemoveWeaponEquipmentBuild("weapon", build, itemsDb))
|
||||
.ToList();
|
||||
|
||||
var equipmentBuilds = fullProfile.UserBuildData.EquipmentBuilds ?? [];
|
||||
fullProfile.UserBuildData.EquipmentBuilds = equipmentBuilds
|
||||
.Where(build => !ShouldRemoveWeaponEquipmentBuild("equipment", build, itemsDb))
|
||||
.ToList();
|
||||
|
||||
var magazineBuild = fullProfile.UserBuildData.MagazineBuilds ?? [];
|
||||
fullProfile.UserBuildData.MagazineBuilds = magazineBuild.Where(build => !ShouldRemoveMagazineBuild(build, itemsDb)).ToList();
|
||||
return;
|
||||
}
|
||||
|
||||
var itemsDb = databaseService.GetItems();
|
||||
|
||||
// Remove invalid builds from weapon, equipment and magazine build lists
|
||||
var weaponBuilds = fullProfile.UserBuildData?.WeaponBuilds ?? [];
|
||||
fullProfile.UserBuildData!.WeaponBuilds = weaponBuilds
|
||||
.Where(build => !ShouldRemoveWeaponEquipmentBuild("weapon", build, itemsDb))
|
||||
.ToList();
|
||||
|
||||
var equipmentBuilds = fullProfile.UserBuildData.EquipmentBuilds ?? [];
|
||||
fullProfile.UserBuildData.EquipmentBuilds = equipmentBuilds
|
||||
.Where(build => !ShouldRemoveWeaponEquipmentBuild("equipment", build, itemsDb))
|
||||
.ToList();
|
||||
|
||||
var magazineBuild = fullProfile.UserBuildData.MagazineBuilds ?? [];
|
||||
fullProfile.UserBuildData.MagazineBuilds = magazineBuild.Where(build => !ShouldRemoveMagazineBuild(build, itemsDb)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for and remove invalid user dialogs
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Full profile to check</param>
|
||||
/// <exception cref="InvalidModdedTraderException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
protected void RemoveInvalidDialogRecords(SptProfile fullProfile)
|
||||
{
|
||||
if (fullProfile.DialogueRecords is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var itemsDb = databaseService.GetItems();
|
||||
|
||||
// Iterate over dialogs, looking for messages with items not found in item db, remove message if item found
|
||||
foreach (var dialog in fullProfile.DialogueRecords)
|
||||
{
|
||||
@@ -96,47 +151,79 @@ public class ProfileValidatorHelper(
|
||||
var itemsToRemove = message.Items.Data.Where(item => !itemsDb.ContainsKey(item.Template)).ToList();
|
||||
foreach (var itemToRemove in itemsToRemove)
|
||||
{
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
message.Items.Data.Remove(itemToRemove);
|
||||
logger.Warning(
|
||||
$"Item: {itemToRemove.Template} has resulted in the deletion of message: {message.Id} from dialog: {dialog.Key}"
|
||||
);
|
||||
}
|
||||
else
|
||||
// We've found an item to remove, but the remove config isn't enabled, throw an exception
|
||||
if (!CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
throw new InvalidModdedItemException(
|
||||
serverLocalisationService.GetText("fixer-mod_item_found", itemToRemove.Template.ToString())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var clothingDb = databaseService.GetTemplates().Customization;
|
||||
foreach (
|
||||
var clothingItem in fullProfile
|
||||
.CustomisationUnlocks.Where(customisation => customisation.Type == CustomisationType.SUITE)
|
||||
.ToList() // We're removing element, ToList to allow that to occur
|
||||
)
|
||||
{
|
||||
if (!clothingDb.ContainsKey(clothingItem.Id))
|
||||
{
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
fullProfile.CustomisationUnlocks.Remove(clothingItem);
|
||||
logger.Warning($"Non-default clothing purchase: {clothingItem} removed from profile");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidModdedClothingException(
|
||||
serverLocalisationService.GetText("fixer-clothing_item_found", clothingItem.ToString())
|
||||
message.Items.Data.Remove(itemToRemove);
|
||||
logger.Warning(
|
||||
$"Item: {itemToRemove.Template} has resulted in the deletion of message: {message.Id} from dialog: {dialog.Key}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var repeatable in fullProfile.CharacterData.PmcData.RepeatableQuests ?? [])
|
||||
/// <summary>
|
||||
/// Check for and remove invalid clothing items
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Full profile to check</param>
|
||||
/// <exception cref="InvalidModdedClothingException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
protected void RemoveInvalidClothing(SptProfile fullProfile)
|
||||
{
|
||||
var clothingDb = databaseService.GetTemplates().Customization;
|
||||
|
||||
// We're removing element, ToList to allow that to occur
|
||||
var clothingItems = fullProfile
|
||||
.CustomisationUnlocks?.Where(customisation => customisation.Type == CustomisationType.SUITE)
|
||||
.ToList();
|
||||
|
||||
// Nothing to remove
|
||||
if (clothingItems is null || clothingItems.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var clothingItem in clothingItems)
|
||||
{
|
||||
// Valid item, skip
|
||||
if (clothingDb.ContainsKey(clothingItem.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found a clothing item to remove but the fixer isn't enabled, throw an exception
|
||||
if (!CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
throw new InvalidModdedClothingException(
|
||||
serverLocalisationService.GetText("fixer-clothing_item_found", clothingItem.ToString())
|
||||
);
|
||||
}
|
||||
|
||||
fullProfile.CustomisationUnlocks?.Remove(clothingItem);
|
||||
logger.Warning($"Non-default clothing purchase: {clothingItem} removed from profile");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for and remove invalid repeatable quests
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Full profile to check</param>
|
||||
/// <exception cref="InvalidModdedTraderException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
protected void RemoveInvalidRepeatableQuests(SptProfile fullProfile)
|
||||
{
|
||||
// Nothing to remove
|
||||
if (fullProfile.CharacterData?.PmcData?.RepeatableQuests is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var itemsDb = databaseService.GetItems();
|
||||
foreach (var repeatable in fullProfile.CharacterData.PmcData.RepeatableQuests)
|
||||
{
|
||||
if (repeatable.ActiveQuests is null)
|
||||
{
|
||||
@@ -148,20 +235,19 @@ public class ProfileValidatorHelper(
|
||||
{
|
||||
if (!DoesTraderExist(activeQuest.TraderId))
|
||||
{
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
logger.Warning(
|
||||
$"Non-default quest: {activeQuest.Id} from trader: {activeQuest.TraderId} removed from RepeatableQuests list in profile"
|
||||
);
|
||||
repeatable.ActiveQuests.Remove(activeQuest);
|
||||
}
|
||||
else
|
||||
// We found a trader that doesn't exist, but the fixer isnt enabled, throw an exception
|
||||
if (!CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
throw new InvalidModdedTraderException(
|
||||
serverLocalisationService.GetText("fixer-trader_found", activeQuest.TraderId.ToString())
|
||||
);
|
||||
}
|
||||
|
||||
repeatable.ActiveQuests.Remove(activeQuest);
|
||||
logger.Warning(
|
||||
$"Non-default quest: {activeQuest.Id} from trader: {activeQuest.TraderId} removed from RepeatableQuests list in profile"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -173,7 +259,7 @@ public class ProfileValidatorHelper(
|
||||
// Get Item rewards only
|
||||
foreach (var successReward in activeQuest.Rewards["Success"].Where(reward => reward.Type == RewardType.Item))
|
||||
{
|
||||
if (successReward.Items.Any(item => !itemsDb.ContainsKey(item.Template)))
|
||||
if (successReward.Items?.Any(item => !itemsDb.ContainsKey(item.Template)) ?? false)
|
||||
{
|
||||
logger.Warning(
|
||||
$"Non-default repeatable quest: {activeQuest.Id} from trader: {activeQuest.TraderId} removed from RepeatableQuests list in profile"
|
||||
@@ -183,18 +269,33 @@ public class ProfileValidatorHelper(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (traderId, _) in fullProfile.TraderPurchases.Where(traderPurchase => !DoesTraderExist(traderPurchase.Key)))
|
||||
/// <summary>
|
||||
/// Check for and remove invalid trader purchases from traders that no longer exist
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Full profile to check</param>
|
||||
/// <exception cref="InvalidModdedTraderException">Thrown if <see cref="GameFixes.RemoveModItemsFromProfile">RemoveModItemsFromProfile</see> is false.</exception>
|
||||
protected void RemoveInvalidTraderPurchases(SptProfile fullProfile)
|
||||
{
|
||||
var purchases = fullProfile.TraderPurchases?.Where(traderPurchase => !DoesTraderExist(traderPurchase.Key));
|
||||
|
||||
// Nothing to remove
|
||||
if (purchases is null || !purchases.Any())
|
||||
{
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
logger.Warning($"Non-default trader: {traderId} purchase removed from traderPurchases list in profile");
|
||||
fullProfile.TraderPurchases.Remove(traderId);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (traderId, _) in purchases)
|
||||
{
|
||||
// We have purchases to remove and the fixer isn't enabled, throw an exception
|
||||
if (!CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
throw new InvalidModdedTraderException(serverLocalisationService.GetText("fixer-trader_found", traderId.ToString()));
|
||||
}
|
||||
|
||||
logger.Warning($"Non-default trader: {traderId} purchase removed from traderPurchases list in profile");
|
||||
fullProfile.TraderPurchases?.Remove(traderId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +315,7 @@ public class ProfileValidatorHelper(
|
||||
{
|
||||
logger.Error(serverLocalisationService.GetText("fixer-mod_item_found", item.Template.ToString()));
|
||||
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
if (CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
logger.Warning($"Item: {item.Template} has resulted in the deletion of {buildType} build: {build.Name}");
|
||||
|
||||
@@ -234,7 +335,7 @@ public class ProfileValidatorHelper(
|
||||
{
|
||||
logger.Error(serverLocalisationService.GetText("fixer-mod_item_found", item.Template.ToString()));
|
||||
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
if (CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
logger.Warning($"Item: {item.Template} has resulted in the deletion of {buildType} build: {build.Name}");
|
||||
|
||||
@@ -270,7 +371,7 @@ public class ProfileValidatorHelper(
|
||||
{
|
||||
logger.Error(serverLocalisationService.GetText("fixer-mod_item_found", item.TemplateId.ToString()));
|
||||
|
||||
if (_coreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
if (CoreConfig.Fixes.RemoveModItemsFromProfile)
|
||||
{
|
||||
logger.Warning($"Item: {item.TemplateId} has resulted in the deletion of magazine build: {magazineBuild.Name}");
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class ProfileValidatorService(
|
||||
profile.Deserialize<SptProfile>(JsonUtil.JsonSerializerOptionsNoIndent)
|
||||
?? throw new InvalidOperationException($"Could not deserialize the profile.");
|
||||
|
||||
profileValidatorHelper.CheckForOrphanedModdedItems(new Models.Common.MongoId(profileId), sptReadyProfile);
|
||||
profileValidatorHelper.CheckForOrphanedModdedData(new Models.Common.MongoId(profileId), sptReadyProfile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user