diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json
index 8381ad65..41b5a386 100644
--- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json
+++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/quest.json
@@ -597,28 +597,107 @@
}
}
],
- "Completion": {
- "possibleSkillRewards": [
- "Endurance",
- "Strength",
- "Vitality"
- ],
- "minRequestedAmount": 1,
- "maxRequestedAmount": 4,
- "uniqueItemCount": 2,
- "minRequestedBulletAmount": 15,
- "maxRequestedBulletAmount": 40,
- "useWhitelist": true,
- "useBlacklist": false,
- "requiredItemsAreFiR": true,
- "requiredItemMinDurabilityMinMax": {
- "min": 60,
- "max": 80
+ "Completion": [
+ {
+ "levelRange": {
+ "min": 1,
+ "max": 15
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 1,
+ "max": 4
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 1
+ },
+ "requestedBulletCount": {
+ "min": 15,
+ "max": 40
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
},
- "requiredItemTypeBlacklist": [
- "5485a8684bdc2da71d8b4567"
- ]
- },
+ {
+ "levelRange": {
+ "min": 16,
+ "max": 40
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 2,
+ "max": 4
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 2
+ },
+ "requestedBulletCount": {
+ "min": 15,
+ "max": 40
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
+ },
+ {
+ "levelRange": {
+ "min": 41,
+ "max": 100
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 3,
+ "max": 6
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 2
+ },
+ "requestedBulletCount": {
+ "min": 15,
+ "max": 40
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
+ }
+ ],
"Elimination": [
{
"levelRange": {
@@ -1745,28 +1824,107 @@
}
}
],
- "Completion": {
- "possibleSkillRewards": [
- "Endurance",
- "Strength",
- "Vitality"
- ],
- "minRequestedAmount": 4,
- "maxRequestedAmount": 12,
- "uniqueItemCount": 4,
- "minRequestedBulletAmount": 20,
- "maxRequestedBulletAmount": 60,
- "useWhitelist": true,
- "useBlacklist": false,
- "requiredItemsAreFiR": true,
- "requiredItemMinDurabilityMinMax": {
- "min": 60,
- "max": 80
+ "Completion": [
+ {
+ "levelRange": {
+ "min": 1,
+ "max": 15
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 4,
+ "max": 6
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 2
+ },
+ "requestedBulletCount": {
+ "min": 20,
+ "max": 60
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
},
- "requiredItemTypeBlacklist": [
- "5485a8684bdc2da71d8b4567"
- ]
- },
+ {
+ "levelRange": {
+ "min": 16,
+ "max": 40
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 4,
+ "max": 8
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 2
+ },
+ "requestedBulletCount": {
+ "min": 20,
+ "max": 60
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
+ },
+ {
+ "levelRange": {
+ "min": 41,
+ "max": 100
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 6,
+ "max": 12
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 3
+ },
+ "requestedBulletCount": {
+ "min": 20,
+ "max": 60
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
+ }
+ ],
"Elimination": [
{
"levelRange": {
@@ -2887,28 +3045,107 @@
],
"maxItemFetchCount": 3
},
- "Completion": {
- "possibleSkillRewards": [
- "Endurance",
- "Strength",
- "Vitality"
- ],
- "minRequestedAmount": 1,
- "maxRequestedAmount": 3,
- "uniqueItemCount": 1,
- "minRequestedBulletAmount": 15,
- "maxRequestedBulletAmount": 40,
- "useWhitelist": true,
- "useBlacklist": false,
- "requiredItemsAreFiR": true,
- "requiredItemMinDurabilityMinMax": {
- "min": 60,
- "max": 80
+ "Completion": [
+ {
+ "levelRange": {
+ "min": 1,
+ "max": 15
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 1,
+ "max": 3
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 1
+ },
+ "requestedBulletCount": {
+ "min": 15,
+ "max": 40
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
},
- "requiredItemTypeBlacklist": [
- "5485a8684bdc2da71d8b4567"
- ]
- },
+ {
+ "levelRange": {
+ "min": 16,
+ "max": 40
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 2,
+ "max": 5
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 1
+ },
+ "requestedBulletCount": {
+ "min": 15,
+ "max": 40
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
+ },
+ {
+ "levelRange": {
+ "min": 41,
+ "max": 100
+ },
+ "possibleSkillRewards": [
+ "Endurance",
+ "Strength",
+ "Vitality"
+ ],
+ "requestedItemCount": {
+ "min": 4,
+ "max": 6
+ },
+ "uniqueItemCount": {
+ "min": 1,
+ "max": 1
+ },
+ "requestedBulletCount": {
+ "min": 15,
+ "max": 40
+ },
+ "useWhitelist": true,
+ "useBlacklist": false,
+ "requiredItemsAreFiR": true,
+ "requiredItemMinDurabilityMinMax": {
+ "min": 60,
+ "max": 80
+ },
+ "requiredItemTypeBlacklist": [
+ "5485a8684bdc2da71d8b4567"
+ ]
+ }
+ ],
"Elimination": [
{
"levelRange": {
diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/server/en.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/server/en.json
index efca89e5..7b32e000 100644
--- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/server/en.json
+++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locales/server/en.json
@@ -663,6 +663,7 @@
"repair-unable_to_find_item_repair_cost": "Unable to find repair cost for item: %s",
"repair-unable_to_find_trader_details_by_id": "Unable to find trader: %s repair details",
"repeatable-accepted_repeatable_quest_not_found_in_active_quests": "Accepted a repeatable quest: %s which could not be found in the activeQuests array. Please report this bug",
+ "repeatable-completion_config_no_template": "Unable to find completion config for pmc level: {{pmcLevel}}",
"repeatable-completion_quest_whitelist_too_small_or_blacklist_too_restrictive": "Generate Completion Quest: No items remain. Either Whitelist is too small or Blacklist too restrictive",
"repeatable-difficulty_was_nan": "Repeatable Reward Generation: Difficulty was NaN. Setting to 1.",
"repeatable-exploration_config_no_template": "Unable to find exploration config for pmc level: {{pmcLevel}}",
diff --git a/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGeneration/CompletionQuestGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGeneration/CompletionQuestGenerator.cs
index 26eeb644..32d98145 100644
--- a/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGeneration/CompletionQuestGenerator.cs
+++ b/Libraries/SPTarkov.Server.Core/Generators/RepeatableQuestGeneration/CompletionQuestGenerator.cs
@@ -47,7 +47,13 @@ public class CompletionQuestGenerator(
RepeatableQuestConfig repeatableConfig
)
{
- var completionConfig = repeatableConfig.QuestConfig.CompletionConfig;
+ var completionConfig = repeatableQuestHelper.GetCompletionConfigByPmcLevel(pmcLevel, repeatableConfig);
+ if (completionConfig is null)
+ {
+ logger.Warning(localisationService.GetText("repeatable-completion_config_no_template", new { pmcLevel }));
+ return null;
+ }
+
var levelsConfig = repeatableConfig.RewardScaling.Levels;
var roublesConfig = repeatableConfig.RewardScaling.Roubles;
@@ -73,12 +79,12 @@ public class CompletionQuestGenerator(
// 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.CompletionConfig.UseWhitelist)
+ if (completionConfig.UseWhitelist)
{
itemsToRetrievePool = GetWhitelistedItemSelection(itemsToRetrievePool, pmcLevel);
}
- if (repeatableConfig.QuestConfig.CompletionConfig.UseBlacklist)
+ if (completionConfig.UseBlacklist)
{
itemsToRetrievePool = GetBlacklistedItemSelection(itemsToRetrievePool, pmcLevel);
}
@@ -91,7 +97,7 @@ public class CompletionQuestGenerator(
return null;
}
- var selectedItems = GenerateAvailableForFinish(quest, completionConfig, repeatableConfig, itemsToRetrievePool.ToList(), budget);
+ var selectedItems = GenerateAvailableForFinish(quest, completionConfig, itemsToRetrievePool.ToList(), budget);
quest.Rewards = repeatableQuestRewardGenerator.GenerateReward(
pmcLevel,
@@ -240,20 +246,18 @@ public class CompletionQuestGenerator(
///
/// Quest to add the conditions to
/// Completion config
- /// Repeatable config
/// Filtered item selection
/// Budget in roubles
/// Chosen item template Ids
protected List GenerateAvailableForFinish(
RepeatableQuest quest,
CompletionConfig completionConfig,
- RepeatableQuestConfig repeatableConfig,
List itemSelection,
double roublesBudget
)
{
// Store the indexes of items we are asking player to supply
- var distinctItemsToRetrieveCount = randomUtil.GetInt(1, completionConfig.UniqueItemCount);
+ var distinctItemsToRetrieveCount = randomUtil.GetInt(completionConfig.UniqueItemCount.Min, completionConfig.UniqueItemCount.Max);
var chosenRequirementItemsTpls = new List();
var usedItemIndexes = new HashSet();
@@ -289,8 +293,8 @@ public class CompletionQuestGenerator(
var tplChosen = itemSelection[chosenItemIndex];
var itemPrice = itemHelper.GetItemPrice(tplChosen)!.Value;
- var minValue = completionConfig.MinimumRequestedAmount;
- var maxValue = completionConfig.MaximumRequestedAmount;
+ var minValue = completionConfig.RequestedItemCount.Min;
+ var maxValue = completionConfig.RequestedItemCount.Max;
var value = minValue;
@@ -308,7 +312,7 @@ public class CompletionQuestGenerator(
// Push a CompletionCondition with the item and the amount of the item into quest
chosenRequirementItemsTpls.Add(tplChosen);
- quest.Conditions.AvailableForFinish!.Add(GenerateCondition(tplChosen, value, repeatableConfig.QuestConfig.CompletionConfig));
+ quest.Conditions.AvailableForFinish!.Add(GenerateCondition(tplChosen, value, completionConfig));
// Is there budget left for more items
if (roublesBudget > 0)
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/RepeatableQuestHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/RepeatableQuestHelper.cs
index 0904e6a8..ee113a86 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/RepeatableQuestHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/RepeatableQuestHelper.cs
@@ -45,6 +45,19 @@ public class RepeatableQuestHelper(
);
}
+ ///
+ /// Get the relevant completion config based on the current players PMC level
+ ///
+ /// Level of PMC character
+ /// Main repeatable config
+ /// CompletionConfig
+ public CompletionConfig? GetCompletionConfigByPmcLevel(int pmcLevel, RepeatableQuestConfig repeatableConfig)
+ {
+ return repeatableConfig.QuestConfig.CompletionConfig.FirstOrDefault(x =>
+ pmcLevel >= x.LevelRange.Min && pmcLevel <= x.LevelRange.Max
+ );
+ }
+
///
/// Gets a cloned repeatable quest template for the provided type with a unique id
///
diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs
index b8b3d3c4..bf7b1156 100644
--- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs
+++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/QuestConfig.cs
@@ -344,7 +344,7 @@ public record RepeatableQuestTypesConfig
/// Defines completion repeatable task generation parameters
///
[JsonPropertyName("Completion")]
- public required CompletionConfig CompletionConfig { get; set; }
+ public required List CompletionConfig { get; set; }
///
/// Defines pickup repeatable task generation parameters - TODO: Not implemented/No Data - NOTE: Does not work with dynamicLocale
@@ -416,34 +416,28 @@ public record SpecificExits
public record CompletionConfig : BaseQuestConfig
{
///
- /// Minimum item count that can be requested
+ /// Level range at which completion tasks should be generated from this config
///
- [JsonPropertyName("minRequestedAmount")]
- public required int MinimumRequestedAmount { get; set; }
+ [JsonPropertyName("levelRange")]
+ public required MinMax LevelRange { get; set; }
///
- /// Maximum item count that can be requested
+ /// The minimum and maximum amounts that can be requested for an item
///
- [JsonPropertyName("maxRequestedAmount")]
- public required int MaximumRequestedAmount { get; set; }
+ [JsonPropertyName("requestedItemCount")]
+ public required MinMax RequestedItemCount { get; set; }
///
- /// How many unique items should be requested - TODO: This needs to be a range
+ /// How many different unique items should be requested
///
[JsonPropertyName("uniqueItemCount")]
- public required int UniqueItemCount { get; set; }
+ public required MinMax UniqueItemCount { get; set; }
///
- /// Minimum bullet count that can be requested - TODO: Not implemented
+ /// The minimum and maximum amounts that can be requested for bullets - 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; }
+ [JsonPropertyName("requestedBulletCount")]
+ public required MinMax RequestedBulletCount { get; set; }
///
/// Should the item whitelist be used
@@ -467,7 +461,7 @@ public record CompletionConfig : BaseQuestConfig
/// Min/Max durability requirements for the item
///
[JsonPropertyName("requiredItemMinDurabilityMinMax")]
- public required MinMax RequiredItemMinDurabilityMinMax { get; set; }
+ public required MinMax RequiredItemMinDurabilityMinMax { get; set; }
///
/// Blacklisted item types to not collect