Fix daily reward budget not using preset prices for some items (!256)

- Switch item cost calculation to use a new helper that checks for a preset first, to more accurately keep track of the budget
- Subtract weapon price from reward budget
- Decrement reward counter for skipped ammo
- Add a new helper for filtering the reward pool by budget, so it's consistent in the two places it's done
- Add a metric boatload of debug output for daily generation

Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/256
Co-authored-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
Co-committed-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
This commit is contained in:
DrakiaXYZ 2024-03-11 00:03:41 +00:00 committed by chomp
parent 561c5a7ffc
commit 15257ea263
2 changed files with 59 additions and 24 deletions

View File

@ -116,7 +116,10 @@ export class RepeatableQuestRewardGenerator
// Possible improvement -> draw trader-specific items e.g. with this.itemHelper.isOfBaseclass(val._id, ItemHelper.BASECLASS.FoodDrink) // Possible improvement -> draw trader-specific items e.g. with this.itemHelper.isOfBaseclass(val._id, ItemHelper.BASECLASS.FoodDrink)
let roublesBudget = rewardRoubles; let roublesBudget = rewardRoubles;
let rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId); const rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId);
this.logger.debug(
`Generating daily quest for ${traderId} with budget ${roublesBudget} for ${rewardNumItems} items`,
);
const rewards: IQuestRewards = { Started: [], Success: [], Fail: [] }; const rewards: IQuestRewards = { Started: [], Success: [], Fail: [] };
@ -152,6 +155,8 @@ export class RepeatableQuestRewardGenerator
const presetPrice = this.itemHelper.getItemAndChildrenPrice(tpls); const presetPrice = this.itemHelper.getItemAndChildrenPrice(tpls);
if (presetPrice <= roublesBudget) if (presetPrice <= roublesBudget)
{ {
this.logger.debug(` Added weapon ${tpls[0]} with price ${presetPrice}`);
roublesBudget -= presetPrice;
chosenPreset = this.jsonUtil.clone(randomPreset); chosenPreset = this.jsonUtil.clone(randomPreset);
break; break;
} }
@ -179,6 +184,7 @@ export class RepeatableQuestRewardGenerator
// Don't reward ammo that stacks to less than what's defined in config // Don't reward ammo that stacks to less than what's defined in config
if (itemSelected._props.StackMaxSize < repeatableConfig.rewardAmmoStackMinSize) if (itemSelected._props.StackMaxSize < repeatableConfig.rewardAmmoStackMinSize)
{ {
i--;
continue; continue;
} }
@ -199,18 +205,17 @@ export class RepeatableQuestRewardGenerator
rewards.Success.push(this.generateRewardItem(itemSelected._id, rewardItemStackCount, rewardIndex)); rewards.Success.push(this.generateRewardItem(itemSelected._id, rewardItemStackCount, rewardIndex));
rewardIndex++; rewardIndex++;
const itemCost = this.itemHelper.getStaticItemPrice(itemSelected._id); const itemCost = this.presetHelper.getDefaultPresetOrItemPrice(itemSelected._id);
roublesBudget -= rewardItemStackCount * itemCost; roublesBudget -= rewardItemStackCount * itemCost;
this.logger.debug(` Added item ${itemSelected._id} with price ${rewardItemStackCount * itemCost}`);
// If we still have budget narrow down possible items // If we still have budget narrow down possible items
if (roublesBudget > 0) if (roublesBudget > 0)
{ {
// Filter possible reward items to only items with a price below the remaining budget // Filter possible reward items to only items with a price below the remaining budget
rewardItemPool = rewardItemPool.filter((x) => if (!this.filterRewardPoolWithinBudget(rewardItemPool, roublesBudget, 0))
this.itemHelper.getStaticItemPrice(x._id) < roublesBudget
);
if (rewardItemPool.length === 0)
{ {
this.logger.debug(` Reward pool empty with ${roublesBudget} remaining`);
break; // No reward items left, exit break; // No reward items left, exit
} }
} }
@ -232,23 +237,49 @@ export class RepeatableQuestRewardGenerator
}; };
rewards.Success.push(reward); rewards.Success.push(reward);
rewardIndex++; rewardIndex++;
this.logger.debug(` Adding ${rewardReputation} trader reputation reward`);
} }
// Chance of adding skill reward // Chance of adding skill reward
if (this.randomUtil.getChance100(skillRewardChance * 100)) if (this.randomUtil.getChance100(skillRewardChance * 100))
{ {
const targetSkill = this.randomUtil.getArrayValue(questConfig.possibleSkillRewards);
const reward: IQuestReward = { const reward: IQuestReward = {
target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards), target: targetSkill,
value: skillPointReward, value: skillPointReward,
type: QuestRewardType.SKILL, type: QuestRewardType.SKILL,
index: rewardIndex, index: rewardIndex,
}; };
rewards.Success.push(reward); rewards.Success.push(reward);
this.logger.debug(` Adding ${skillPointReward} skill points to ${targetSkill}`);
} }
return rewards; return rewards;
} }
/**
* @param rewardItems List of reward items to filter
* @param roublesBudget The budget remaining for rewards
* @param minPrice The minimum priced item to include
* @returns True if any items remain in `rewardItems`, false otherwise
*/
protected filterRewardPoolWithinBudget(
rewardItems: ITemplateItem[],
roublesBudget: number,
minPrice: number,
): boolean
{
rewardItems.filter((item) =>
{
const itemPrice = this.presetHelper.getDefaultPresetOrItemPrice(item._id);
return itemPrice < roublesBudget && itemPrice > minPrice;
});
return (rewardItems.length > 0);
}
/** /**
* Get a randomised number a reward items stack size should be based on its handbook price * Get a randomised number a reward items stack size should be based on its handbook price
* @param item Reward item to get stack size for * @param item Reward item to get stack size for
@ -256,7 +287,7 @@ export class RepeatableQuestRewardGenerator
*/ */
protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number
{ {
const rewardItemPrice = this.itemHelper.getStaticItemPrice(item._id); const rewardItemPrice = this.presetHelper.getDefaultPresetOrItemPrice(item._id);
if (rewardItemPrice < 3000) if (rewardItemPrice < 3000)
{ {
return this.randomUtil.getArrayValue([2, 3, 4]); return this.randomUtil.getArrayValue([2, 3, 4]);
@ -278,7 +309,7 @@ export class RepeatableQuestRewardGenerator
*/ */
protected canIncreaseRewardItemStackSize(item: ITemplateItem, maxRoublePriceToStack: number): boolean protected canIncreaseRewardItemStackSize(item: ITemplateItem, maxRoublePriceToStack: number): boolean
{ {
return this.itemHelper.getStaticItemPrice(item._id) < maxRoublePriceToStack return this.presetHelper.getDefaultPresetOrItemPrice(item._id) < maxRoublePriceToStack
&& !this.itemHelper.isOfBaseclasses(item._id, [ && !this.itemHelper.isOfBaseclasses(item._id, [
BaseClasses.WEAPON, BaseClasses.WEAPON,
BaseClasses.ARMORED_EQUIPMENT, BaseClasses.ARMORED_EQUIPMENT,
@ -324,21 +355,8 @@ export class RepeatableQuestRewardGenerator
const rewardableItemPool = this.getRewardableItems(repeatableConfig, traderId); const rewardableItemPool = this.getRewardableItems(repeatableConfig, traderId);
const minPrice = Math.min(25000, 0.5 * roublesBudget); const minPrice = Math.min(25000, 0.5 * roublesBudget);
let rewardableItemPoolWithinBudget = rewardableItemPool.filter((item) => let rewardableItemPoolWithinBudget = rewardableItemPool.map((x) => x[1]);
{ if (!this.filterRewardPoolWithinBudget(rewardableItemPoolWithinBudget, roublesBudget, minPrice))
// Get default preset if it exists
const defaultPreset = this.presetHelper.getDefaultPreset(item[0]);
// Bundle up tpls we want price for
const tpls = defaultPreset ? defaultPreset._items.map((item) => item._tpl) : [item[0]];
// Get price of tpls
const itemPrice = this.itemHelper.getItemAndChildrenPrice(tpls);
return itemPrice < roublesBudget && itemPrice > minPrice;
}).map((x) => x[1]);
if (rewardableItemPoolWithinBudget.length === 0)
{ {
this.logger.warning( this.logger.warning(
this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", { this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", {

View File

@ -160,4 +160,21 @@ export class PresetHelper
return ""; return "";
} }
/**
* Return the price of the preset for the given item tpl, or for the tpl itself if no preset exists
* @param tpl The item template to get the price of
* @returns The price of the given item preset, or base item if no preset exists
*/
public getDefaultPresetOrItemPrice(tpl: string): number
{
// Get default preset if it exists
const defaultPreset = this.getDefaultPreset(tpl);
// Bundle up tpls we want price for
const tpls = defaultPreset ? defaultPreset._items.map((item) => item._tpl) : [tpl];
// Get price of tpls
return this.itemHelper.getItemAndChildrenPrice(tpls);
}
} }