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.Enums; namespace SPTarkov.Server.Core.Extensions; public static class ProfileExtensions { /// /// Return all quest items current in the supplied profile /// /// Profile to get quest items from /// List of item objects public static IEnumerable GetQuestItemsInProfile(this PmcData profile) { return profile?.Inventory?.Items.Where(i => i.ParentId == profile.Inventory.QuestRaidItems).ToList(); } /// /// Upgrade hideout wall from starting level to interactable level if necessary stations have been upgraded /// /// Profile to upgrade wall in public static void UnlockHideoutWallInProfile(this PmcData profile) { var profileHideoutAreas = profile.Hideout.Areas; var waterCollector = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.WaterCollector); var medStation = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.MedStation); var wall = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.EmergencyWall); // No collector or med station, skip if (waterCollector is null && medStation is null) { return; } // If med-station > level 1 AND water collector > level 1 AND wall is level 0 if (waterCollector?.Level >= 1 && medStation?.Level >= 1 && wall?.Level <= 0) { wall.Level = 3; } } /// /// Does the provided profile contain any condition counters /// /// Profile to check for condition counters /// Profile has condition counters public static bool ProfileHasConditionCounters(this PmcData profile) { if (profile.TaskConditionCounters is null) { return false; } return profile.TaskConditionCounters.Count > 0; } /// /// Get a specific common skill from supplied profile /// /// Player profile /// Skill to look up and return value from /// Common skill object from desired profile public static CommonSkill? GetSkillFromProfile(this PmcData profile, SkillTypes skill) { return profile?.Skills?.Common?.FirstOrDefault(s => s.Id == skill); } /// /// Get the scav karma level for a profile /// Is also the fence trader rep level /// /// pmc profile /// karma level public static double GetScavKarmaLevel(this PmcData pmcData) { // can be empty during profile creation if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo)) { return 0; } if (fenceInfo.Standing > 6) { return 6; } return Math.Floor(fenceInfo.Standing ?? 0); } public static Skills GetSkillsOrDefault(this PmcData profile) { return profile?.Skills ?? GetDefaultSkills(); } private static Skills GetDefaultSkills() { return new Skills { Common = [], Mastering = [], Points = 0, }; } /// /// Recursively checks if the given item is /// inside the stash, that is it has the stash as /// ancestor with slotId=hideout /// /// Player profile /// Item to look for /// True if item exists inside stash public static bool IsItemInStash(this PmcData pmcData, Item itemToCheck) { // Start recursive check return pmcData.IsParentInStash(itemToCheck.Id); } public static bool IsParentInStash(this PmcData pmcData, MongoId itemId) { // Item not found / has no parent var item = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemId); if (item?.ParentId is null) { return false; } // Root level. Items parent is the stash with slotId "hideout" if (item.ParentId == pmcData.Inventory.Stash && item.SlotId == "hideout") { return true; } // Recursive case: Check the items parent return IsParentInStash(pmcData, item.ParentId); } /// /// Iterate over all bonuses and sum up all bonuses of desired type in provided profile /// /// Player profile /// Bonus to sum up /// Summed bonus value or 0 if no bonus found public static double GetBonusValueFromProfile(this PmcData pmcProfile, BonusType desiredBonus) { var bonuses = pmcProfile?.Bonuses?.Where(b => b.Type == desiredBonus); if (!bonuses.Any()) { return 0; } // Sum all bonuses found above return bonuses?.Sum(bonus => bonus?.Value ?? 0) ?? 0; } public static bool PlayerIsFleaBanned(this PmcData pmcProfile, long currentTimestamp) { return pmcProfile?.Info?.Bans?.Any(b => b.BanType == BanType.RagFair && currentTimestamp < b.DateTime) ?? false; } /// /// Calculates the current level of a player based on their accumulated experience points. /// This method iterates through an experience table to determine the highest level achieved /// by comparing the player's experience against cumulative thresholds. /// /// Player profile /// Experience table from globals.json /// /// The calculated level of the player as an integer, or null if the level cannot be determined. /// This value is also assigned to within the provided profile. /// public static int? CalculateLevel(this PmcData pmcData, ExpTable[] expTable) { var accExp = 0; for (var i = 0; i < expTable.Length; i++) { accExp += expTable[i].Experience; if (pmcData.Info.Experience < accExp) { break; } pmcData.Info.Level = i + 1; } return pmcData.Info.Level; } /// /// Does the provided item have a root item with the provided id /// /// Profile with items /// Item to check /// Root item id to check for /// True when item has rootId, false when not public static bool DoesItemHaveRootId(this PmcData pmcData, Item item, MongoId rootId) { var currentItem = item; while (currentItem is not null) { // If we've found the equipment root ID, return true if (currentItem.Id == rootId) { return true; } // Otherwise get the parent item currentItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == currentItem.ParentId); } return false; } /// /// Get status of a quest in player profile by its id /// /// Profile to search /// Quest id to look up /// QuestStatus enum public static QuestStatusEnum GetQuestStatus(this PmcData pmcData, MongoId questId) { var quest = pmcData.Quests?.FirstOrDefault(q => q.QId == questId); return quest?.Status ?? QuestStatusEnum.Locked; } /// /// Use values from the profiles template to reset all body part max values /// /// Profile to update /// Template used to create profile public static void ResetMaxLimbHp(this PmcData profile, TemplateSide profileTemplate) { foreach (var (partKey, bodyPart) in profile.Health.BodyParts) { bodyPart.Health.Maximum = profileTemplate.Character.Health.BodyParts[partKey].Health.Maximum; } } }