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
This commit is contained in:
Cj
2025-06-22 04:04:45 -04:00
committed by GitHub
parent 10880881b4
commit d68228b5c9
6 changed files with 291 additions and 131 deletions
@@ -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,
@@ -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,
@@ -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<string>();
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<string>();
var usedItemIndexes = new HashSet<int>();
@@ -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
);
@@ -58,7 +58,7 @@ public class RepeatableQuestRewardGenerator(
/// <param name="eliminationConfig"> Base Quest config</param>
/// <param name="rewardTplBlacklist"> Optional: list of tpls to NOT use when picking a reward </param>
/// <returns> QuestRewards </returns>
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
);
}
@@ -200,7 +200,7 @@ public record RepeatableQuestConfig
/// Quest config, holds information on how a task should be generated
/// </summary>
[JsonPropertyName("questConfig")]
public RepeatableQuestTypesConfig QuestConfig { get; set; }
public required RepeatableQuestTypesConfig QuestConfig { get; set; }
/// <summary>
/// Item base types to block when generating rewards
@@ -250,32 +250,59 @@ public record RewardScaling
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
/// <summary>
/// Levels at which to increase to the next level of reward potential
/// </summary>
[JsonPropertyName("levels")]
public List<double>? Levels { get; set; }
public required List<double> Levels { get; set; }
/// <summary>
/// Experience reward tiers
/// </summary>
[JsonPropertyName("experience")]
public List<double>? Experience { get; set; }
public required List<double> Experience { get; set; }
/// <summary>
/// Rouble reward tiers
/// </summary>
[JsonPropertyName("roubles")]
public List<double>? Roubles { get; set; }
public required List<double> Roubles { get; set; }
/// <summary>
/// Gp coin reward tiers
/// </summary>
[JsonPropertyName("gpCoins")]
public List<double>? GpCoins { get; set; }
public required List<double> GpCoins { get; set; }
/// <summary>
/// Item amount reward tiers
/// </summary>
[JsonPropertyName("items")]
public List<double>? Items { get; set; }
public required List<double> Items { get; set; }
/// <summary>
/// reputation amount reward tiers
/// </summary>
[JsonPropertyName("reputation")]
public List<double>? Reputation { get; set; }
public required List<double> Reputation { get; set; }
/// <summary>
/// Reward spread
/// </summary>
[JsonPropertyName("rewardSpread")]
public double? RewardSpread { get; set; }
public required double RewardSpread { get; set; }
/// <summary>
/// Skill reward chance tiers
/// </summary>
[JsonPropertyName("skillRewardChance")]
public List<double>? SkillRewardChance { get; set; }
public required List<double> SkillRewardChance { get; set; }
/// <summary>
/// Skill reward amount tiers
/// </summary>
[JsonPropertyName("skillPointReward")]
public List<double>? SkillPointReward { get; set; }
public required List<double> SkillPointReward { get; set; }
}
public record TraderWhitelist
@@ -283,23 +310,41 @@ public record TraderWhitelist
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>
/// Trader Id
/// </summary>
[JsonPropertyName("traderId")]
public string? TraderId { get; set; }
public required string TraderId { get; set; }
/// <summary>
/// Human-readable name
/// </summary>
[JsonPropertyName("name")]
public required string Name { get; set; }
/// <summary>
/// Quest types this trader can provide: Completion/Exploration/Elimination.
/// </summary>
[JsonPropertyName("questTypes")]
public List<string>? QuestTypes { get; set; }
public required List<string> QuestTypes { get; set; }
/// <summary>
/// Item categories that the reward can be
/// </summary>
[JsonPropertyName("rewardBaseWhitelist")]
public List<string>? RewardBaseWhitelist { get; set; }
public required List<string> RewardBaseWhitelist { get; set; }
/// <summary>
/// Can this reward be a weapon?
/// </summary>
[JsonPropertyName("rewardCanBeWeapon")]
public bool? RewardCanBeWeapon { get; set; }
public required bool RewardCanBeWeapon { get; set; }
/// <summary>
/// Chance that the reward is a weapon
/// </summary>
[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<string, object> ExtensionData { get; set; }
/// <summary>
/// Defines exploration repeatable task generation parameters
/// </summary>
[JsonPropertyName("Exploration")]
public Exploration? Exploration { get; set; }
public required Exploration Exploration { get; set; }
/// <summary>
/// Defines completion repeatable task generation parameters
/// </summary>
[JsonPropertyName("Completion")]
public Completion? Completion { get; set; }
public required Completion Completion { get; set; }
/// <summary>
/// Defines pickup repeatable task generation parameters - TODO: Not implemented/No Data
/// </summary>
[JsonPropertyName("Pickup")]
public Pickup? Pickup { get; set; }
/// <summary>
/// Defines elimination repeatable task generation parameters
/// </summary>
[JsonPropertyName("Elimination")]
public List<EliminationConfig>? Elimination { get; set; }
public required List<EliminationConfig> Elimination { get; set; }
}
public record Exploration : BaseQuestConfig
{
/// <summary>
/// Maximum extract count that a per map extract requirement can be generated with
/// </summary>
[JsonPropertyName("maxExtracts")]
public int? MaximumExtracts { get; set; }
public required int MaximumExtracts { get; set; }
/// <summary>
/// Maximum extract count that a specific extract can be generated with
/// </summary>
[JsonPropertyName("maxExtractsWithSpecificExit")]
public int? MaximumExtractsWithSpecificExit { get; set; }
public required int MaximumExtractsWithSpecificExit { get; set; }
/// <summary>
/// Specific extract generation data
/// </summary>
[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<string, object> ExtensionData { get; set; }
/// <summary>
/// Chance that an operational task is generated with a specific extract
/// </summary>
[JsonPropertyName("probability")]
public double? Probability { get; set; }
public required double Probability { get; set; }
/// <summary>
/// Whitelist of specific extract types
/// </summary>
[JsonPropertyName("passageRequirementWhitelist")]
public List<string>? PassageRequirementWhitelist { get; set; }
public required List<string> PassageRequirementWhitelist { get; set; }
}
public record Completion : BaseQuestConfig
{
/// <summary>
/// Minimum item count that can be requested
/// </summary>
[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; }
/// <summary>
/// Should supplied items be required FiR
/// Maximum item count that can be requested
/// </summary>
[JsonPropertyName("maxRequestedAmount")]
public required int MaximumRequestedAmount { get; set; }
/// <summary>
/// How many unique items should be requested - TODO: This needs to be a range
/// </summary>
[JsonPropertyName("uniqueItemCount")]
public required int UniqueItemCount { get; set; }
/// <summary>
/// Minimum bullet count that can be requested - TODO: Not implemented
/// </summary>
[JsonPropertyName("minRequestedBulletAmount")]
public required int MinimumRequestedBulletAmount { get; set; }
/// <summary>
/// Maximum bullet count that can be requested - TODO: Not implemented
/// </summary>
[JsonPropertyName("maxRequestedBulletAmount")]
public required int MaximumRequestedBulletAmount { get; set; }
/// <summary>
/// Should the item whitelist be used
/// </summary>
[JsonPropertyName("useWhitelist")]
public required bool UseWhitelist { get; set; }
/// <summary>
/// Should the item blacklist be used
/// </summary>
[JsonPropertyName("useBlacklist")]
public required bool UseBlacklist { get; set; }
/// <summary>
/// Should the supplied items be required FiR
/// </summary>
[JsonPropertyName("requiredItemsAreFiR")]
public bool? RequiredItemsAreFiR { get; set; }
/// <summary>
/// Should supplied items be required FiR
/// Should the supplied items be required FiR
/// </summary>
[JsonPropertyName("requiredItemMinDurabilityMinMax")]
public MinMax<double>? RequiredItemMinDurabilityMinMax { get; set; }
@@ -414,62 +507,119 @@ public record PickupTypeWithMaxCount
public record EliminationConfig : BaseQuestConfig
{
/// <summary>
/// Level range at which elimination tasks should be generated from this config
/// </summary>
[JsonPropertyName("levelRange")]
public MinMax<int>? LevelRange { get; set; }
public required MinMax<int> LevelRange { get; set; }
/// <summary>
/// Target data probabilities
/// </summary>
[JsonPropertyName("targets")]
public List<ProbabilityObject<string, BossInfo>>? Targets { get; set; }
public required List<ProbabilityObject<string, BossInfo>> Targets { get; set; }
[JsonPropertyName("bodyPartProb")]
public double? BodyPartProbability { get; set; }
/// <summary>
/// Chance that a specific body part is needed as a requirement
/// </summary>
[JsonPropertyName("bodyPartChance")]
public required int BodyPartChance { get; set; }
/// <summary>
/// If the specific body part requirement is chosen, pick from these body parts
/// </summary>
[JsonPropertyName("bodyParts")]
public List<ProbabilityObject<string, List<string>>>? BodyParts { get; set; }
public required List<ProbabilityObject<string, List<string>>> BodyParts { get; set; }
[JsonPropertyName("specificLocationProb")]
public double? SpecificLocationProbability { get; set; }
/// <summary>
/// Chance that a specific location modifier is selected
/// </summary>
[JsonPropertyName("specificLocationChance")]
public required int SpecificLocationChance { get; set; }
/// <summary>
/// Locations that should be blacklisted as a requirement
/// </summary>
[JsonPropertyName("distLocationBlacklist")]
public List<string>? DistLocationBlacklist { get; set; }
public required List<string> DistLocationBlacklist { get; set; }
/// <summary>
/// Probability that a distance requirement is chosen
/// </summary>
[JsonPropertyName("distProb")]
public double? DistanceProbability { get; set; }
public required double DistanceProbability { get; set; }
/// <summary>
/// Maximum distance in meters that can be chosen
/// </summary>
[JsonPropertyName("maxDist")]
public double? MaxDistance { get; set; }
public required double MaxDistance { get; set; }
/// <summary>
/// Minimum distance in meters that can be chosen
/// </summary>
[JsonPropertyName("minDist")]
public double? MinDistance { get; set; }
public required double MinDistance { get; set; }
/// <summary>
/// Maximum amount of kills that can be chosen
/// </summary>
[JsonPropertyName("maxKills")]
public int? MaxKills { get; set; }
public required int MaxKills { get; set; }
/// <summary>
/// Minimum amount of kills that can be chosen
/// </summary>
[JsonPropertyName("minKills")]
public int? MinKills { get; set; }
[JsonPropertyName("minBossKills")]
public int? MinBossKills { get; set; }
public required int MinKills { get; set; }
/// <summary>
/// Maximum amount of boss kills that can be chosen
/// </summary>
[JsonPropertyName("maxBossKills")]
public int? MaxBossKills { get; set; }
public required int MaxBossKills { get; set; }
[JsonPropertyName("minPmcKills")]
public int? MinPmcKills { get; set; }
/// <summary>
/// Minimum amount of boss kills that can be chosen
/// </summary>
[JsonPropertyName("minBossKills")]
public required int MinBossKills { get; set; }
/// <summary>
/// Maximum amount of PMC kills that can be chosen
/// </summary>
[JsonPropertyName("maxPmcKills")]
public int? MaxPmcKills { get; set; }
public required int MaxPmcKills { get; set; }
[JsonPropertyName("weaponCategoryRequirementProb")]
public double? WeaponCategoryRequirementProbability { get; set; }
[JsonPropertyName("weaponCategoryRequirements")]
public List<ProbabilityObject<string, List<string>>>? WeaponCategoryRequirements { get; set; }
/// <summary>
/// Minimum amount of PMC kills that can be chosen
/// </summary>
[JsonPropertyName("minPmcKills")]
public required int MinPmcKills { get; set; }
/// <summary>
/// Probability that a specific weapon requirement is chosen
/// </summary>
[JsonPropertyName("weaponRequirementProb")]
public double? WeaponRequirementProbability { get; set; }
public required double WeaponRequirementProbability { get; set; }
/// <summary>
/// Probability that a weapon category requirement is chosen
/// </summary>
[JsonPropertyName("weaponCategoryRequirementProb")]
public required double WeaponCategoryRequirementProbability { get; set; }
/// <summary>
/// If a weapon category requirement is chosen, pick from these categories
/// </summary>
[JsonPropertyName("weaponCategoryRequirements")]
public required List<ProbabilityObject<string, List<string>>> WeaponCategoryRequirements { get; set; }
/// <summary>
/// If a weapon requirement is chosen, pick from these weapons
/// </summary>
[JsonPropertyName("weaponRequirements")]
public List<ProbabilityObject<string, List<string>>>? WeaponRequirements { get; set; }
public required List<ProbabilityObject<string, List<string>>> WeaponRequirements { get; set; }
}
public record BaseQuestConfig
@@ -477,8 +627,11 @@ public record BaseQuestConfig
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
/// <summary>
/// Possible skills that can be rewarded expirence points
/// </summary>
[JsonPropertyName("possibleSkillRewards")]
public List<string>? PossibleSkillRewards { get; set; }
public List<string> PossibleSkillRewards { get; set; }
}
public record BossInfo
@@ -486,9 +639,16 @@ public record BossInfo
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
/// <summary>
/// Is this target a boss
/// </summary>
[JsonPropertyName("isBoss")]
public bool? IsBoss { get; set; }
/// <summary>
/// Is ths target a PMC
/// </summary>
[JsonPropertyName("isPmc")]
public bool? IsPmc { get; set; }
}
@@ -1,3 +1,4 @@
using System.Numerics;
using SPTarkov.DI.Annotations;
namespace SPTarkov.Server.Core.Utils;
@@ -98,7 +99,7 @@ public class MathUtil
/// <param name="x">Support points in x (of same length as y)</param>
/// <param name="y">Support points in y (of same length as x)</param>
/// <returns>Interpolated value at xp, or null if xp is out of bounds</returns>
public double? Interp1(double xp, IReadOnlyList<double> x, IReadOnlyList<double> y)
public T? Interp1<T>(T xp, IReadOnlyList<T> x, IReadOnlyList<T> y) where T : INumber<T>
{
if (xp > x[^1]) // ^1 is the last index in C#
{
@@ -120,6 +121,6 @@ public class MathUtil
}
}
return null;
return default;
}
}