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)
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: [] };
@ -152,6 +155,8 @@ export class RepeatableQuestRewardGenerator
const presetPrice = this.itemHelper.getItemAndChildrenPrice(tpls);
if (presetPrice <= roublesBudget)
{
this.logger.debug(` Added weapon ${tpls[0]} with price ${presetPrice}`);
roublesBudget -= presetPrice;
chosenPreset = this.jsonUtil.clone(randomPreset);
break;
}
@ -179,6 +184,7 @@ export class RepeatableQuestRewardGenerator
// Don't reward ammo that stacks to less than what's defined in config
if (itemSelected._props.StackMaxSize < repeatableConfig.rewardAmmoStackMinSize)
{
i--;
continue;
}
@ -199,18 +205,17 @@ export class RepeatableQuestRewardGenerator
rewards.Success.push(this.generateRewardItem(itemSelected._id, rewardItemStackCount, rewardIndex));
rewardIndex++;
const itemCost = this.itemHelper.getStaticItemPrice(itemSelected._id);
const itemCost = this.presetHelper.getDefaultPresetOrItemPrice(itemSelected._id);
roublesBudget -= rewardItemStackCount * itemCost;
this.logger.debug(` Added item ${itemSelected._id} with price ${rewardItemStackCount * itemCost}`);
// If we still have budget narrow down possible items
if (roublesBudget > 0)
{
// Filter possible reward items to only items with a price below the remaining budget
rewardItemPool = rewardItemPool.filter((x) =>
this.itemHelper.getStaticItemPrice(x._id) < roublesBudget
);
if (rewardItemPool.length === 0)
if (!this.filterRewardPoolWithinBudget(rewardItemPool, roublesBudget, 0))
{
this.logger.debug(` Reward pool empty with ${roublesBudget} remaining`);
break; // No reward items left, exit
}
}
@ -232,23 +237,49 @@ export class RepeatableQuestRewardGenerator
};
rewards.Success.push(reward);
rewardIndex++;
this.logger.debug(` Adding ${rewardReputation} trader reputation reward`);
}
// Chance of adding skill reward
if (this.randomUtil.getChance100(skillRewardChance * 100))
{
const targetSkill = this.randomUtil.getArrayValue(questConfig.possibleSkillRewards);
const reward: IQuestReward = {
target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards),
target: targetSkill,
value: skillPointReward,
type: QuestRewardType.SKILL,
index: rewardIndex,
};
rewards.Success.push(reward);
this.logger.debug(` Adding ${skillPointReward} skill points to ${targetSkill}`);
}
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
* @param item Reward item to get stack size for
@ -256,7 +287,7 @@ export class RepeatableQuestRewardGenerator
*/
protected getRandomisedRewardItemStackSizeByPrice(item: ITemplateItem): number
{
const rewardItemPrice = this.itemHelper.getStaticItemPrice(item._id);
const rewardItemPrice = this.presetHelper.getDefaultPresetOrItemPrice(item._id);
if (rewardItemPrice < 3000)
{
return this.randomUtil.getArrayValue([2, 3, 4]);
@ -278,7 +309,7 @@ export class RepeatableQuestRewardGenerator
*/
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, [
BaseClasses.WEAPON,
BaseClasses.ARMORED_EQUIPMENT,
@ -324,21 +355,8 @@ export class RepeatableQuestRewardGenerator
const rewardableItemPool = this.getRewardableItems(repeatableConfig, traderId);
const minPrice = Math.min(25000, 0.5 * roublesBudget);
let rewardableItemPoolWithinBudget = rewardableItemPool.filter((item) =>
{
// 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)
let rewardableItemPoolWithinBudget = rewardableItemPool.map((x) => x[1]);
if (!this.filterRewardPoolWithinBudget(rewardableItemPoolWithinBudget, roublesBudget, minPrice))
{
this.logger.warning(
this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", {

View File

@ -160,4 +160,21 @@ export class PresetHelper
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);
}
}