From d68228b5c998456195b405e0bca42a91c7eb854c Mon Sep 17 00:00:00 2001 From: Cj <161484149+CJ-SPT@users.noreply.github.com> Date: Sun, 22 Jun 2025 04:04:45 -0400 Subject: [PATCH] Quest config nullability and documentation (Part 2) (#415) * Make Interp1 generic * Finish quest controller nullability fixes * More model and nullability improvements and fixes * Rename `specificLocationChance` * rename `bodyPartChance` * finish comments --- .../SPT_Data/configs/quest.json | 35 +- .../Controllers/RepeatableQuestController.cs | 2 +- .../Generators/RepeatableQuestGenerator.cs | 52 ++- .../RepeatableQuestRewardGenerator.cs | 18 +- .../Models/Spt/Config/QuestConfig.cs | 310 +++++++++++++----- .../SPTarkov.Server.Core/Utils/MathUtil.cs | 5 +- 6 files changed, 291 insertions(+), 131 deletions(-) diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json index 9bfe29f1..ecec9e90 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json @@ -440,7 +440,7 @@ } } ], - "bodyPartProb": 0.15, + "bodyPartChance": 15, "bodyParts": [ { "key": "Head", @@ -463,7 +463,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0, + "specificLocationChance": 0, "distLocationBlacklist": ["laboratory", "factory4_day", "factory4_night"], "distProb": 0.15, "maxDist": 75, @@ -666,7 +666,7 @@ } } ], - "bodyPartProb": 0.15, + "bodyPartChance": 15, "bodyParts": [ { "key": "Head", @@ -689,7 +689,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.15, + "specificLocationChance": 15, "distLocationBlacklist": ["laboratory", "factory4_day", "factory4_night"], "distProb": 0.25, "maxDist": 100, @@ -887,7 +887,7 @@ } } ], - "bodyPartProb": 0.15, + "bodyPartChance": 15, "bodyParts": [ { "key": "Head", @@ -910,7 +910,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.15, + "specificLocationChance": 15, "distLocationBlacklist": ["laboratory", "factory4_day", "factory4_night"], "distProb": 0.25, "maxDist": 100, @@ -1214,7 +1214,7 @@ "maxRequestedBulletAmount": 60, "useWhitelist": true, "useBlacklist": false, - "requiredItemsAreFiR": true, + "requiredItemsAreFiR": true, "requiredItemMinDurabilityMinMax": { "min": 60, "max": 80 @@ -1317,7 +1317,7 @@ } } ], - "bodyPartProb": 0.15, + "bodyPartChance": 15, "bodyParts": [ { "key": "Head", @@ -1340,7 +1340,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.15, + "specificLocationChance": 15, "distLocationBlacklist": ["laboratory", "factory4_day", "factory4_night"], "distProb": 0.15, "maxDist": 75, @@ -1538,7 +1538,7 @@ } } ], - "bodyPartProb": 0.15, + "bodyPartChance": 15, "bodyParts": [ { "key": "Head", @@ -1561,7 +1561,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.15, + "specificLocationChance": 15, "distLocationBlacklist": ["laboratory", "factory4_day", "factory4_night"], "distProb": 0.15, "maxDist": 75, @@ -1759,7 +1759,7 @@ } } ], - "bodyPartProb": 0.15, + "bodyPartChance": 15, "bodyParts": [ { "key": "Head", @@ -1782,7 +1782,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.15, + "specificLocationChance": 15, "distLocationBlacklist": ["laboratory", "factory4_day", "factory4_night"], "distProb": 0.15, "maxDist": 100, @@ -1927,6 +1927,7 @@ }, "traderWhitelist": [ { + "name": "fence", "traderId": "579dc571d53a0658a154fbec", "questTypes": ["Completion", "Exploration", "Elimination", "Pickup"], "rewardBaseWhitelist": [ @@ -2046,7 +2047,7 @@ } } ], - "bodyPartProb": 0.2, + "bodyPartChance": 20, "bodyParts": [ { "key": "Head", @@ -2069,7 +2070,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.15, + "specificLocationChance": 15, "distLocationBlacklist": ["laboratory"], "distProb": 0.2, "maxDist": 75, @@ -2183,7 +2184,7 @@ } } ], - "bodyPartProb": 0.4, + "bodyPartChance": 40, "bodyParts": [ { "key": "Head", @@ -2206,7 +2207,7 @@ "data": ["LeftLeg", "RightLeg"] } ], - "specificLocationProb": 0.25, + "specificLocationChance": 25, "distLocationBlacklist": ["laboratory"], "distProb": 0.25, "maxDist": 75, diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RepeatableQuestController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RepeatableQuestController.cs index 7739a6a9..4ad551ae 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/RepeatableQuestController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/RepeatableQuestController.cs @@ -810,7 +810,7 @@ public class RepeatableQuestController( foreach (var target in targetsConfig) { // Target is boss - if (target.Data.IsBoss.GetValueOrDefault(false)) + if (target.Data?.IsBoss ?? false) { questPool.Pool.Elimination.Targets.Add( target.Key, diff --git a/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGenerator.cs index 502e6ad2..aee80cc5 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGenerator.cs @@ -195,7 +195,7 @@ public class RepeatableQuestGenerator( if ( targetsConfig.Count == 0 - || targetsConfig.All(x => x.Data.IsBoss.GetValueOrDefault(false)) + || targetsConfig.All(x => x.Data?.IsBoss ?? false) ) { // There are no more targets left for elimination; delete it as a possible quest type @@ -217,7 +217,7 @@ public class RepeatableQuestGenerator( if ( locations.Contains("any") && ( - eliminationConfig.SpecificLocationProbability < rand.NextDouble() + _randomUtil.GetChance100(eliminationConfig.SpecificLocationChance) || locations.Count <= 1 ) ) @@ -274,7 +274,7 @@ public class RepeatableQuestGenerator( // draw the target body part and calculate the difficulty factor var bodyPartsToClient = new List(); var bodyPartDifficulty = 0d; - if (eliminationConfig.BodyPartProbability > rand.NextDouble()) + if (_randomUtil.GetChance100(eliminationConfig.BodyPartChance)) { // if we add a bodyPart condition, we draw randomly one or two parts // each bodyPart of the BODYPARTS ProbabilityObjectArray includes the string(s) which need to be presented to the client in ProbabilityObjectArray.data @@ -307,7 +307,7 @@ public class RepeatableQuestGenerator( locationKey ); - if (targetsConfig.Data(botTypeToEliminate).IsBoss.GetValueOrDefault(false)) + if (targetsConfig.Data(botTypeToEliminate)?.IsBoss ?? false) { // Get all boss spawn information var bossSpawns = _databaseService @@ -330,11 +330,11 @@ public class RepeatableQuestGenerator( ); // if the boss spawns on nom-blacklisted locations and the current location is allowed we can generate a distance kill requirement isDistanceRequirementAllowed = - isDistanceRequirementAllowed && allowedSpawns.Count() > 0; + isDistanceRequirementAllowed && allowedSpawns.Any(); } if ( - eliminationConfig.DistanceProbability > rand.NextDouble() + _randomUtil.GetChance100(eliminationConfig.DistanceProbability) && isDistanceRequirementAllowed ) { @@ -344,7 +344,6 @@ public class RepeatableQuestGenerator( Math.Abs(rand.NextDouble() - rand.NextDouble()) * (1 + eliminationConfig.MaxDistance - eliminationConfig.MinDistance) + eliminationConfig.MinDistance - ?? 0 ); distance = (int)Math.Ceiling((decimal)(distance / 5)) * 5; @@ -354,7 +353,7 @@ public class RepeatableQuestGenerator( } string? allowedWeaponsCategory = null; - if (eliminationConfig.WeaponCategoryRequirementProbability > rand.NextDouble()) + if (_randomUtil.GetChance100(eliminationConfig.WeaponCategoryRequirementProbability)) { // Filter out close range weapons from far distance requirement if (distance > 50) @@ -411,9 +410,9 @@ public class RepeatableQuestGenerator( var maxDifficulty = DifficultyWeighing(1, 1, 1, 1, 1); var curDifficulty = DifficultyWeighing( targetDifficulty.Value / maxTargetDifficulty, - bodyPartDifficulty / maxBodyPartsDifficulty.Value, + bodyPartDifficulty / maxBodyPartsDifficulty, distanceDifficulty / maxDistDifficulty, - killDifficulty / maxKillDifficulty.Value, + killDifficulty / maxKillDifficulty, allowedWeaponsCategory is not null || allowedWeapon is not null ? 1 : 0 ); @@ -485,24 +484,24 @@ public class RepeatableQuestGenerator( EliminationConfig eliminationConfig ) { - if (targetsConfig.Data(targetKey).IsBoss.GetValueOrDefault(false)) + if (targetsConfig.Data(targetKey)?.IsBoss ?? false) { return _randomUtil.RandInt( - eliminationConfig.MinBossKills.Value, + eliminationConfig.MinBossKills, eliminationConfig.MaxBossKills + 1 ); } - if (targetsConfig.Data(targetKey).IsPmc.GetValueOrDefault(false)) + if (targetsConfig.Data(targetKey)?.IsPmc ?? false) { return _randomUtil.RandInt( - eliminationConfig.MinPmcKills.Value, + eliminationConfig.MinPmcKills, eliminationConfig.MaxPmcKills + 1 ); } return _randomUtil.RandInt( - eliminationConfig.MinKills.Value, + eliminationConfig.MinKills, eliminationConfig.MaxKills + 1 ); } @@ -620,12 +619,7 @@ public class RepeatableQuestGenerator( RepeatableQuestConfig repeatableConfig ) { - var completionConfig = repeatableConfig?.QuestConfig?.Completion; - if (completionConfig is null) - { - _logger.Error("Unable to generate Completion quest, no Completion config found"); - return null; - } + var completionConfig = repeatableConfig.QuestConfig.Completion; var levelsConfig = repeatableConfig.RewardScaling.Levels; var roublesConfig = repeatableConfig.RewardScaling.Roubles; @@ -645,7 +639,7 @@ public class RepeatableQuestGenerator( // Be fair, don't value the items be more expensive than the reward var multiplier = _randomUtil.GetDouble(0.5, 1); var roublesBudget = Math.Floor( - (double)(_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multiplier) + _mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multiplier ); roublesBudget = Math.Max(roublesBudget, 5000d); var itemSelection = itemsToRetrievePool @@ -654,7 +648,7 @@ public class RepeatableQuestGenerator( // We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as // [{"minPlayerLevel": 1, "itemIds": ["id1",...]}, {"minPlayerLevel": 15, "itemIds": ["id3",...]}] - if (repeatableConfig.QuestConfig.Completion.UseWhitelist.GetValueOrDefault(false)) + if (repeatableConfig.QuestConfig.Completion.UseWhitelist) { var itemWhitelist = _databaseService .GetTemplates() @@ -678,7 +672,7 @@ public class RepeatableQuestGenerator( // var missing = itemIdsWhitelisted.filter(l => !flatList.includes(l)); } - if (repeatableConfig.QuestConfig.Completion.UseBlacklist.GetValueOrDefault(false)) + if (repeatableConfig.QuestConfig.Completion.UseBlacklist) { var itemBlacklist = _databaseService .GetTemplates() @@ -714,7 +708,7 @@ public class RepeatableQuestGenerator( // Store the indexes of items we are asking player to supply var distinctItemsToRetrieveCount = _randomUtil.GetInt( 1, - completionConfig.UniqueItemCount.Value + completionConfig.UniqueItemCount ); var chosenRequirementItemsTpls = new List(); var usedItemIndexes = new HashSet(); @@ -753,8 +747,8 @@ public class RepeatableQuestGenerator( var tplChosen = itemSelection[chosenItemIndex]; var itemPrice = _itemHelper.GetItemPrice(tplChosen).Value; - var minValue = completionConfig.MinimumRequestedAmount.Value; - var maxValue = completionConfig.MaximumRequestedAmount.Value; + var minValue = completionConfig.MinimumRequestedAmount; + var maxValue = completionConfig.MaximumRequestedAmount; var value = minValue; @@ -928,7 +922,7 @@ public class RepeatableQuestGenerator( { var explorationConfig = repeatableConfig.QuestConfig.Exploration; var requiresSpecificExtract = - _randomUtil.Random.Next() + _randomUtil.Random.NextDouble() < repeatableConfig.QuestConfig.Exploration.SpecificExits.Probability; if (questTypePool.Pool.Exploration.Locations.Count == 0) @@ -1026,7 +1020,7 @@ public class RepeatableQuestGenerator( var difficulty = _mathUtil.MapToRange( numExtracts, 1, - explorationConfig.MaximumExtracts.Value, + explorationConfig.MaximumExtracts, 0.2, 1 ); diff --git a/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestRewardGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestRewardGenerator.cs index d2758588..e81bf60b 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestRewardGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestRewardGenerator.cs @@ -58,7 +58,7 @@ public class RepeatableQuestRewardGenerator( /// Base Quest config /// Optional: list of tpls to NOT use when picking a reward /// QuestRewards - public QuestRewards GenerateReward( + public QuestRewards? GenerateReward( int pmcLevel, double difficulty, string traderId, @@ -122,13 +122,17 @@ public class RepeatableQuestRewardGenerator( var traderWhitelistDetails = repeatableConfig.TraderWhitelist.FirstOrDefault( traderWhitelist => traderWhitelist.TraderId == traderId ); + + if (traderWhitelistDetails is null) + { + _logger.Error($"Cound not find trader id: {traderId} in whitelist"); + return null; + } + if ( - traderWhitelistDetails?.RewardCanBeWeapon - ?? ( - false - && _randomUtil.GetChance100(traderWhitelistDetails.WeaponRewardChancePercent ?? 0) + traderWhitelistDetails.RewardCanBeWeapon && + _randomUtil.GetChance100(traderWhitelistDetails.WeaponRewardChancePercent) ) - ) { var chosenWeapon = GetRandomWeaponPresetWithinBudget( itemRewardBudget.Value, @@ -365,7 +369,7 @@ public class RepeatableQuestRewardGenerator( { return _randomUtil.RandInt( 1, - (int)Math.Round(_mathUtil.Interp1(pmcLevel, levelsConfig, itemsConfig) ?? 0) + 1 + (int)Math.Round(_mathUtil.Interp1(pmcLevel, levelsConfig, itemsConfig)) + 1 ); } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs index e0ff9dbf..d0509e94 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs @@ -200,7 +200,7 @@ public record RepeatableQuestConfig /// Quest config, holds information on how a task should be generated /// [JsonPropertyName("questConfig")] - public RepeatableQuestTypesConfig QuestConfig { get; set; } + public required RepeatableQuestTypesConfig QuestConfig { get; set; } /// /// Item base types to block when generating rewards @@ -250,32 +250,59 @@ public record RewardScaling [JsonExtensionData] public Dictionary ExtensionData { get; set; } + /// + /// Levels at which to increase to the next level of reward potential + /// [JsonPropertyName("levels")] - public List? Levels { get; set; } + public required List Levels { get; set; } + /// + /// Experience reward tiers + /// [JsonPropertyName("experience")] - public List? Experience { get; set; } + public required List Experience { get; set; } + /// + /// Rouble reward tiers + /// [JsonPropertyName("roubles")] - public List? Roubles { get; set; } + public required List Roubles { get; set; } + /// + /// Gp coin reward tiers + /// [JsonPropertyName("gpCoins")] - public List? GpCoins { get; set; } + public required List GpCoins { get; set; } + /// + /// Item amount reward tiers + /// [JsonPropertyName("items")] - public List? Items { get; set; } + public required List Items { get; set; } + /// + /// reputation amount reward tiers + /// [JsonPropertyName("reputation")] - public List? Reputation { get; set; } + public required List Reputation { get; set; } + /// + /// Reward spread + /// [JsonPropertyName("rewardSpread")] - public double? RewardSpread { get; set; } + public required double RewardSpread { get; set; } + /// + /// Skill reward chance tiers + /// [JsonPropertyName("skillRewardChance")] - public List? SkillRewardChance { get; set; } + public required List SkillRewardChance { get; set; } + /// + /// Skill reward amount tiers + /// [JsonPropertyName("skillPointReward")] - public List? SkillPointReward { get; set; } + public required List SkillPointReward { get; set; } } public record TraderWhitelist @@ -283,23 +310,41 @@ public record TraderWhitelist [JsonExtensionData] public Dictionary ExtensionData { get; set; } - [JsonPropertyName("name")] - public string? Name { get; set; } - + /// + /// Trader Id + /// [JsonPropertyName("traderId")] - public string? TraderId { get; set; } + public required string TraderId { get; set; } + /// + /// Human-readable name + /// + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// + /// Quest types this trader can provide: Completion/Exploration/Elimination. + /// [JsonPropertyName("questTypes")] - public List? QuestTypes { get; set; } + public required List QuestTypes { get; set; } + /// + /// Item categories that the reward can be + /// [JsonPropertyName("rewardBaseWhitelist")] - public List? RewardBaseWhitelist { get; set; } + public required List RewardBaseWhitelist { get; set; } + /// + /// Can this reward be a weapon? + /// [JsonPropertyName("rewardCanBeWeapon")] - public bool? RewardCanBeWeapon { get; set; } + public required bool RewardCanBeWeapon { get; set; } + /// + /// Chance that the reward is a weapon + /// [JsonPropertyName("weaponRewardChancePercent")] - public double? WeaponRewardChancePercent { get; set; } + public required double WeaponRewardChancePercent { get; set; } } public record RepeatableQuestTypesConfig @@ -307,29 +352,50 @@ public record RepeatableQuestTypesConfig [JsonExtensionData] public Dictionary ExtensionData { get; set; } + /// + /// Defines exploration repeatable task generation parameters + /// [JsonPropertyName("Exploration")] - public Exploration? Exploration { get; set; } + public required Exploration Exploration { get; set; } + /// + /// Defines completion repeatable task generation parameters + /// [JsonPropertyName("Completion")] - public Completion? Completion { get; set; } + public required Completion Completion { get; set; } + /// + /// Defines pickup repeatable task generation parameters - TODO: Not implemented/No Data + /// [JsonPropertyName("Pickup")] public Pickup? Pickup { get; set; } + /// + /// Defines elimination repeatable task generation parameters + /// [JsonPropertyName("Elimination")] - public List? Elimination { get; set; } + public required List Elimination { get; set; } } public record Exploration : BaseQuestConfig { + /// + /// Maximum extract count that a per map extract requirement can be generated with + /// [JsonPropertyName("maxExtracts")] - public int? MaximumExtracts { get; set; } + public required int MaximumExtracts { get; set; } + /// + /// Maximum extract count that a specific extract can be generated with + /// [JsonPropertyName("maxExtractsWithSpecificExit")] - public int? MaximumExtractsWithSpecificExit { get; set; } + public required int MaximumExtractsWithSpecificExit { get; set; } + /// + /// Specific extract generation data + /// [JsonPropertyName("specificExits")] - public SpecificExits? SpecificExits { get; set; } + public required SpecificExits SpecificExits { get; set; } } public record SpecificExits @@ -337,44 +403,71 @@ public record SpecificExits [JsonExtensionData] public Dictionary ExtensionData { get; set; } + /// + /// Chance that an operational task is generated with a specific extract + /// [JsonPropertyName("probability")] - public double? Probability { get; set; } + public required double Probability { get; set; } + /// + /// Whitelist of specific extract types + /// [JsonPropertyName("passageRequirementWhitelist")] - public List? PassageRequirementWhitelist { get; set; } + public required List PassageRequirementWhitelist { get; set; } } public record Completion : BaseQuestConfig { + /// + /// Minimum item count that can be requested + /// [JsonPropertyName("minRequestedAmount")] - public int? MinimumRequestedAmount { get; set; } - - [JsonPropertyName("maxRequestedAmount")] - public int? MaximumRequestedAmount { get; set; } - - [JsonPropertyName("uniqueItemCount")] - public int? UniqueItemCount { get; set; } - - [JsonPropertyName("minRequestedBulletAmount")] - public int? MinimumRequestedBulletAmount { get; set; } - - [JsonPropertyName("maxRequestedBulletAmount")] - public int? MaximumRequestedBulletAmount { get; set; } - - [JsonPropertyName("useWhitelist")] - public bool? UseWhitelist { get; set; } - - [JsonPropertyName("useBlacklist")] - public bool? UseBlacklist { get; set; } + public required int MinimumRequestedAmount { get; set; } /// - /// Should supplied items be required FiR + /// Maximum item count that can be requested + /// + [JsonPropertyName("maxRequestedAmount")] + public required int MaximumRequestedAmount { get; set; } + + /// + /// How many unique items should be requested - TODO: This needs to be a range + /// + [JsonPropertyName("uniqueItemCount")] + public required int UniqueItemCount { get; set; } + + /// + /// Minimum bullet count that can be requested - TODO: Not implemented + /// + [JsonPropertyName("minRequestedBulletAmount")] + public required int MinimumRequestedBulletAmount { get; set; } + + /// + /// Maximum bullet count that can be requested - TODO: Not implemented + /// + [JsonPropertyName("maxRequestedBulletAmount")] + public required int MaximumRequestedBulletAmount { get; set; } + + /// + /// Should the item whitelist be used + /// + [JsonPropertyName("useWhitelist")] + public required bool UseWhitelist { get; set; } + + /// + /// Should the item blacklist be used + /// + [JsonPropertyName("useBlacklist")] + public required bool UseBlacklist { get; set; } + + /// + /// Should the supplied items be required FiR /// [JsonPropertyName("requiredItemsAreFiR")] public bool? RequiredItemsAreFiR { get; set; } /// - /// Should supplied items be required FiR + /// Should the supplied items be required FiR /// [JsonPropertyName("requiredItemMinDurabilityMinMax")] public MinMax? RequiredItemMinDurabilityMinMax { get; set; } @@ -414,62 +507,119 @@ public record PickupTypeWithMaxCount public record EliminationConfig : BaseQuestConfig { + /// + /// Level range at which elimination tasks should be generated from this config + /// [JsonPropertyName("levelRange")] - public MinMax? LevelRange { get; set; } + public required MinMax LevelRange { get; set; } + /// + /// Target data probabilities + /// [JsonPropertyName("targets")] - public List>? Targets { get; set; } + public required List> Targets { get; set; } - [JsonPropertyName("bodyPartProb")] - public double? BodyPartProbability { get; set; } + /// + /// Chance that a specific body part is needed as a requirement + /// + [JsonPropertyName("bodyPartChance")] + public required int BodyPartChance { get; set; } + /// + /// If the specific body part requirement is chosen, pick from these body parts + /// [JsonPropertyName("bodyParts")] - public List>>? BodyParts { get; set; } + public required List>> BodyParts { get; set; } - [JsonPropertyName("specificLocationProb")] - public double? SpecificLocationProbability { get; set; } + /// + /// Chance that a specific location modifier is selected + /// + [JsonPropertyName("specificLocationChance")] + public required int SpecificLocationChance { get; set; } + /// + /// Locations that should be blacklisted as a requirement + /// [JsonPropertyName("distLocationBlacklist")] - public List? DistLocationBlacklist { get; set; } + public required List DistLocationBlacklist { get; set; } + /// + /// Probability that a distance requirement is chosen + /// [JsonPropertyName("distProb")] - public double? DistanceProbability { get; set; } + public required double DistanceProbability { get; set; } + /// + /// Maximum distance in meters that can be chosen + /// [JsonPropertyName("maxDist")] - public double? MaxDistance { get; set; } + public required double MaxDistance { get; set; } + /// + /// Minimum distance in meters that can be chosen + /// [JsonPropertyName("minDist")] - public double? MinDistance { get; set; } + public required double MinDistance { get; set; } + /// + /// Maximum amount of kills that can be chosen + /// [JsonPropertyName("maxKills")] - public int? MaxKills { get; set; } + public required int MaxKills { get; set; } + /// + /// Minimum amount of kills that can be chosen + /// [JsonPropertyName("minKills")] - public int? MinKills { get; set; } - - [JsonPropertyName("minBossKills")] - public int? MinBossKills { get; set; } + public required int MinKills { get; set; } + /// + /// Maximum amount of boss kills that can be chosen + /// [JsonPropertyName("maxBossKills")] - public int? MaxBossKills { get; set; } + public required int MaxBossKills { get; set; } - [JsonPropertyName("minPmcKills")] - public int? MinPmcKills { get; set; } + /// + /// Minimum amount of boss kills that can be chosen + /// + [JsonPropertyName("minBossKills")] + public required int MinBossKills { get; set; } + /// + /// Maximum amount of PMC kills that can be chosen + /// [JsonPropertyName("maxPmcKills")] - public int? MaxPmcKills { get; set; } + public required int MaxPmcKills { get; set; } - [JsonPropertyName("weaponCategoryRequirementProb")] - public double? WeaponCategoryRequirementProbability { get; set; } - - [JsonPropertyName("weaponCategoryRequirements")] - public List>>? WeaponCategoryRequirements { get; set; } + /// + /// Minimum amount of PMC kills that can be chosen + /// + [JsonPropertyName("minPmcKills")] + public required int MinPmcKills { get; set; } + /// + /// Probability that a specific weapon requirement is chosen + /// [JsonPropertyName("weaponRequirementProb")] - public double? WeaponRequirementProbability { get; set; } + public required double WeaponRequirementProbability { get; set; } + /// + /// Probability that a weapon category requirement is chosen + /// + [JsonPropertyName("weaponCategoryRequirementProb")] + public required double WeaponCategoryRequirementProbability { get; set; } + + /// + /// If a weapon category requirement is chosen, pick from these categories + /// + [JsonPropertyName("weaponCategoryRequirements")] + public required List>> WeaponCategoryRequirements { get; set; } + + /// + /// If a weapon requirement is chosen, pick from these weapons + /// [JsonPropertyName("weaponRequirements")] - public List>>? WeaponRequirements { get; set; } + public required List>> WeaponRequirements { get; set; } } public record BaseQuestConfig @@ -477,8 +627,11 @@ public record BaseQuestConfig [JsonExtensionData] public Dictionary ExtensionData { get; set; } + /// + /// Possible skills that can be rewarded expirence points + /// [JsonPropertyName("possibleSkillRewards")] - public List? PossibleSkillRewards { get; set; } + public List PossibleSkillRewards { get; set; } } public record BossInfo @@ -486,9 +639,16 @@ public record BossInfo [JsonExtensionData] public Dictionary ExtensionData { get; set; } + + /// + /// Is this target a boss + /// [JsonPropertyName("isBoss")] public bool? IsBoss { get; set; } + /// + /// Is ths target a PMC + /// [JsonPropertyName("isPmc")] public bool? IsPmc { get; set; } } diff --git a/Libraries/SPTarkov.Server.Core/Utils/MathUtil.cs b/Libraries/SPTarkov.Server.Core/Utils/MathUtil.cs index 65e00c64..3a032199 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/MathUtil.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/MathUtil.cs @@ -1,3 +1,4 @@ +using System.Numerics; using SPTarkov.DI.Annotations; namespace SPTarkov.Server.Core.Utils; @@ -98,7 +99,7 @@ public class MathUtil /// Support points in x (of same length as y) /// Support points in y (of same length as x) /// Interpolated value at xp, or null if xp is out of bounds - public double? Interp1(double xp, IReadOnlyList x, IReadOnlyList y) + public T? Interp1(T xp, IReadOnlyList x, IReadOnlyList y) where T : INumber { if (xp > x[^1]) // ^1 is the last index in C# { @@ -120,6 +121,6 @@ public class MathUtil } } - return null; + return default; } }