diff --git a/project/assets/configs/hideout.json b/project/assets/configs/hideout.json index 50106891..98d1a658 100644 --- a/project/assets/configs/hideout.json +++ b/project/assets/configs/hideout.json @@ -29,23 +29,23 @@ ], "craftTimeOverride": 40, "directRewards": { - "66572c82ad599021091c6118": ["5c0e874186f7745dc7616606"], - "66572be36a723f7f005a066e": ["5b3b713c5acfc4330140bd8d"], - "655c669103999d3c810c025b": ["635267ab3c89e2112001f826"], - "66572cbdad599021091c611a": ["60a7ad2a2198820d95707a2e"], - "5c0530ee86f774697952d952": ["6389c8c5dbfd5e4b95197e6b"], - "66572b8d80b1cd4b6a67847f": ["5bc9b9ecd4351e3bac122519", "62a09dd4621468534a797ac7"], - "5c093ca986f7740a1867ab12": ["5732ee6a24597719ae0c0281"], - "665ee77ccf2d642e98220bca": ["5857a8bc2459772bad15db29"], - "59faff1d86f7746c51718c9c": [ + "66572c82ad599021091c6118": {"rewardTpls": ["5c0e874186f7745dc7616606"], "craftTimeSeconds": 100}, + "66572be36a723f7f005a066e": {"rewardTpls": ["5b3b713c5acfc4330140bd8d"], "craftTimeSeconds": 100}, + "655c669103999d3c810c025b": {"rewardTpls": ["635267ab3c89e2112001f826"], "craftTimeSeconds": 100}, + "66572cbdad599021091c611a": {"rewardTpls": ["60a7ad2a2198820d95707a2e"], "craftTimeSeconds": 100}, + "5c0530ee86f774697952d952": {"rewardTpls": ["6389c8c5dbfd5e4b95197e6b"], "craftTimeSeconds": 100}, + "66572b8d80b1cd4b6a67847f": {"rewardTpls": ["5bc9b9ecd4351e3bac122519", "62a09dd4621468534a797ac7"], "craftTimeSeconds": 100}, + "5c093ca986f7740a1867ab12": {"rewardTpls": ["5732ee6a24597719ae0c0281"], "craftTimeSeconds": 100}, + "665ee77ccf2d642e98220bca": {"rewardTpls": ["5857a8bc2459772bad15db29"], "craftTimeSeconds": 100}, + "59faff1d86f7746c51718c9c": {"rewardTpls": [ "5c12620d86f7743f8b198b72", "5c12620d86f7743f8b198b72", "5e2aedd986f7746d404f3aa4", "5e2aedd986f7746d404f3aa4" - ] + ], "craftTimeSeconds": 100} }, "directRewardStackSize": { - "exampleTpl": {"min": 1000, "max": 50000} + "exampleParentId": {"min": 1000, "max": 50000} }, "rewardItemBlacklist": [], "additionalRewardItemPool": [], diff --git a/project/src/models/spt/config/IHideoutConfig.ts b/project/src/models/spt/config/IHideoutConfig.ts index 915e1fe1..877595a5 100644 --- a/project/src/models/spt/config/IHideoutConfig.ts +++ b/project/src/models/spt/config/IHideoutConfig.ts @@ -24,7 +24,7 @@ export interface ICultistCircleSettings { /** -1 means no override */ craftTimeOverride: number; /** Specific reward pool when player sacrificed one specific item */ - directRewards: Record; + directRewards: Record; directRewardStackSize: Record; /** Item tpls to exclude from the reward pool */ rewardItemBlacklist: string[]; @@ -34,5 +34,10 @@ export interface ICultistCircleSettings { } export interface CraftTimeThreshhold extends MinMax { - timeSeconds: number; + craftTimeSeconds: number; +} + +export interface DirectRewardSettings { + rewardTpls: string[]; + craftTimeSeconds: number; } diff --git a/project/src/services/CircleOfCultistService.ts b/project/src/services/CircleOfCultistService.ts index 3878a6d0..c0923f08 100644 --- a/project/src/services/CircleOfCultistService.ts +++ b/project/src/services/CircleOfCultistService.ts @@ -20,7 +20,7 @@ import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { HideoutAreas } from "@spt/models/enums/HideoutAreas"; import { ItemTpl } from "@spt/models/enums/ItemTpl"; import { SkillTypes } from "@spt/models/enums/SkillTypes"; -import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig"; +import { DirectRewardSettings, IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt/routers/EventOutputHolder"; import { ConfigServer } from "@spt/servers/ConfigServer"; @@ -97,6 +97,10 @@ export class CircleOfCultistService { // Get the rouble amount we generate rewards with from cost of sacrified items * above multipler const rewardAmountRoubles = sacrificedItemCostRoubles * rewardAmountMultiplier; + // Has player sacrified a single item in directReward dict + const directRewardSettings = this.hideoutConfig.cultistCircle.directRewards[sacrificedItems[0]._tpl]; + const hasSacrificedSingleItemFlaggedInConfig = sacrificedItems.length === 1 && !!directRewardSettings; + // Create production in pmc profile this.registerCircleOfCultistProduction( sessionId, @@ -104,6 +108,7 @@ export class CircleOfCultistService { cultistCraftData._id, sacrificedItems, rewardAmountRoubles, + directRewardSettings, ); const output = this.eventOutputHolder.getOutput(sessionId); @@ -115,13 +120,9 @@ export class CircleOfCultistService { } } - // Player has sacrified a single item that's in directReward dict, return specific reward pool let rewards: Item[][]; - if (sacrificedItems.length === 1 && this.hideoutConfig.cultistCircle.directRewards[sacrificedItems[0]._tpl]) { - rewards = this.getExplicitRewards( - this.hideoutConfig.cultistCircle.directRewards[sacrificedItems[0]._tpl], - cultistCircleStashId, - ); + if (hasSacrificedSingleItemFlaggedInConfig) { + rewards = this.getExplicitRewards(directRewardSettings, cultistCircleStashId); } else { const rewardItemPool = this.getCultistCircleRewardPool(sessionId, pmcData); rewards = this.getRewardsWithinBudget(rewardItemPool, rewardAmountRoubles, cultistCircleStashId); @@ -159,36 +160,59 @@ export class CircleOfCultistService { return output; } + /** + * Register production inside player profile + * @param sessionId Session id + * @param pmcData Player profile + * @param recipeId Recipe id + * @param sacrificedItems Items player sacrificed + * @param rewardAmountRoubles Rouble amount to reward player in items with + * @param directRewardSettings OPTIONAL: If craft is giving direct rewards + */ protected registerCircleOfCultistProduction( sessionId: string, pmcData: IPmcData, recipeId: string, sacrificedItems: Item[], rewardAmountRoubles: number, + directRewardSettings?: DirectRewardSettings, ): void { + // Create circle production/craft object to add to player profile const cultistProduction = this.hideoutHelper.initProduction( recipeId, - this.getCircleCraftTime(rewardAmountRoubles), + this.getCircleCraftTimeSeconds(rewardAmountRoubles, directRewardSettings), false, true, ); + + // Add items player sacrificed cultistProduction.GivenItemsInStart = sacrificedItems; - // Add circle production to profile + // Add circle production to profile keyed to recipe id pmcData.Hideout.Production[recipeId] = cultistProduction; } /** * Get the circle craft time as seconds, value is based on reward item value + * OR rewards are direct, then use custom craft time defined in oarameter object * @param rewardAmountRoubles Value of rewards in roubles + * @param directRewardSettings OPTIONAL: If craft is giving direct rewards * @returns craft time seconds */ - protected getCircleCraftTime(rewardAmountRoubles: number): number { + protected getCircleCraftTimeSeconds( + rewardAmountRoubles: number, + directRewardSettings?: DirectRewardSettings, + ): number { // Edge case, check if override exists if (this.hideoutConfig.cultistCircle.craftTimeOverride !== -1) { return this.hideoutConfig.cultistCircle.craftTimeOverride; } + // Craft is rewarding items directly, use custom craft time + if (directRewardSettings) { + return directRewardSettings.craftTimeSeconds; + } + const thresholds = this.hideoutConfig.cultistCircle.craftTimeThreshholds; const matchingThreshold = thresholds.find( (craftThreshold) => craftThreshold.min <= rewardAmountRoubles && craftThreshold.max >= rewardAmountRoubles, @@ -198,7 +222,7 @@ export class CircleOfCultistService { return this.timeUtil.getHoursAsSeconds(12); } - return matchingThreshold.timeSeconds; + return matchingThreshold.craftTimeSeconds; } /** @@ -319,10 +343,10 @@ export class CircleOfCultistService { * @param cultistCircleStashId Id of stash item * @returns Array of item arrays */ - protected getExplicitRewards(rewardTpls: string[], cultistCircleStashId: string): Item[][] { + protected getExplicitRewards(explicitRewardSettings: DirectRewardSettings, cultistCircleStashId: string): Item[][] { // Prep rewards array (reward can be item with children, hence array of arrays) const rewards: Item[][] = []; - for (const rewardTpl of rewardTpls) { + for (const rewardTpl of explicitRewardSettings.rewardTpls) { if ( this.itemHelper.armorItemHasRemovableOrSoftInsertSlots(rewardTpl) || this.itemHelper.isOfBaseclass(rewardTpl, BaseClasses.WEAPON) @@ -345,7 +369,7 @@ export class CircleOfCultistService { } // Some items can have variable stack size, e.g. ammo - const stackSize = this.getExplicitRewardStackSize(rewardTpl); + const stackSize = this.getExplicitRewardBaseTypeStackSize(rewardTpl); // Not a weapon/armor, standard single item const rewardItem: Item = { @@ -365,8 +389,21 @@ export class CircleOfCultistService { return rewards; } - protected getExplicitRewardStackSize(rewardTpl: string) { - const settings = this.hideoutConfig.cultistCircle.directRewardStackSize[rewardTpl]; + /** + * Explicit rewards have thier own stack sizes as they dont use a reward rouble pool + * @param rewardTpl Item being rewarded to get stack size of + * @returns stack size of item + */ + protected getExplicitRewardBaseTypeStackSize(rewardTpl: string) { + const itemDetails = this.itemHelper.getItem(rewardTpl); + if (!itemDetails[0]) { + this.logger.warning(`${rewardTpl} is not an item, setting stack size to 1`); + + return 1; + } + + // Look for parent in dict + const settings = this.hideoutConfig.cultistCircle.directRewardStackSize[itemDetails[1]._parent]; if (!settings) { return 1; } @@ -512,6 +549,12 @@ export class CircleOfCultistService { }); } + /** + * Get all recipes the player has access to, includes base + unlocked recipes + * @param unlockedRecipes Recipes player has flagged as unlocked + * @param allRecipes All recipes + * @returns Array of recipes + */ protected getPlayerAccessibleRecipes( unlockedRecipes: string[], allRecipes: IHideoutProductionData,