mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-13 05:30:43 -05:00
Refactor quest and achievement reward handling to be more centralized
- Create a new RewardHelper class that contains the majority of shared reward handling - Move a lot of methods from QuestRewardHelper into RewardHelper - Fix a bug in `applyMoneyBoost` that could result in compounding money boost being applied across characters - Route achievement reward handling through RewardHelper
This commit is contained in:
parent
ad782e5906
commit
b541c04bac
@ -594,9 +594,7 @@
|
||||
"quest-handover_wrong_item": "Unable to hand item in for quest: {{questId}}, expected tpl: {{requiredTpl}} but handed in: {{handedInTpl}}",
|
||||
"quest-item_not_found_in_inventory": "changeItemStack() Item with _id: %s not found in inventory",
|
||||
"quest-no_skill_found": "Skill %s not found",
|
||||
"quest-reward_type_not_handled": "Quest reward type: {{rewardType}} not handled for quest: {{questId}} name: {{questName}}",
|
||||
"quest-unable_to_find_compare_condition": "Unrecognised Comparison Method: %s",
|
||||
"quest-unable_to_find_matching_hideout_production": "Unable to find matching hideout craft unlock for quest: {{questName}}, matches found: {{matchCount}}",
|
||||
"quest-unable_to_find_quest_in_db": "Quest id: {{questId}} with type: {{questType}} not found in database",
|
||||
"quest-unable_to_find_quest_in_db_no_quest_rewards": "Unable to find quest: %s in db, unable to give quest rewards to player",
|
||||
"quest-unable_to_find_repeatable_to_replace": "Unable to find repeatable quest in profile to replace, skipping",
|
||||
@ -651,6 +649,8 @@
|
||||
"repeatable-quest_handover_failed_condition_invalid": "Quest handover error: condition not found or incorrect value. qid: {{body.qid}}, condition: {{body.conditionId}}",
|
||||
"repeatable-unable_to_accept_quest_see_log": "Unable to accept quest, see server log for details",
|
||||
"repeatable-unable_to_accept_quest_starting_message_not_found": "Unable to accept quest: {{questId}} cant find quest started message text with id: {{messageId}}",
|
||||
"reward-type_not_handled": "Reward type: {{rewardType}} not handled for quest/achievement: {{questId}}",
|
||||
"reward-unable_to_find_matching_hideout_production": "Unable to find matching hideout craft unlock for quest/achievement: {{questId}}, matches found: {{matchCount}}",
|
||||
"route_onupdate_no_response": "onUpdate: %s route doesn't report success or fail",
|
||||
"scav-missing_karma_level_getting_default": "getScavKarmaLevel() failed, unable to find fence in profile.traderInfo. Defaulting to karma level 0",
|
||||
"scav-missing_karma_settings": "Unable to get karma settings for level %s",
|
||||
|
@ -120,6 +120,7 @@ import { RagfairSellHelper } from "@spt/helpers/RagfairSellHelper";
|
||||
import { RagfairServerHelper } from "@spt/helpers/RagfairServerHelper";
|
||||
import { RagfairSortHelper } from "@spt/helpers/RagfairSortHelper";
|
||||
import { RepairHelper } from "@spt/helpers/RepairHelper";
|
||||
import { RewardHelper } from "@spt/helpers/RewardHelper";
|
||||
import { RepeatableQuestHelper } from "@spt/helpers/RepeatableQuestHelper";
|
||||
import { SecureContainerHelper } from "@spt/helpers/SecureContainerHelper";
|
||||
import { TradeHelper } from "@spt/helpers/TradeHelper";
|
||||
@ -616,6 +617,7 @@ export class Container {
|
||||
depContainer.register<RagfairOfferHelper>("RagfairOfferHelper", { useClass: RagfairOfferHelper });
|
||||
depContainer.register<RagfairServerHelper>("RagfairServerHelper", { useClass: RagfairServerHelper });
|
||||
depContainer.register<RepairHelper>("RepairHelper", { useClass: RepairHelper });
|
||||
depContainer.register<RewardHelper>("RewardHelper", { useClass: RewardHelper });
|
||||
depContainer.register<TraderHelper>("TraderHelper", TraderHelper);
|
||||
depContainer.register<TraderAssortHelper>("TraderAssortHelper", TraderAssortHelper, {
|
||||
lifecycle: Lifecycle.Singleton,
|
||||
|
@ -547,50 +547,6 @@ export class ProfileHelper {
|
||||
return pmcProfile.Info.Bans.some((ban) => ban.banType === BanType.RAGFAIR && currentTimestamp < ban.dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an achievement to player profile + check for and add any hideout customisation unlocks to profile
|
||||
* @param fullProfile Profile to add achievement to
|
||||
* @param achievementId Id of achievement to add
|
||||
*/
|
||||
public addAchievementToProfile(fullProfile: ISptProfile, achievementId: string): void {
|
||||
// Add achievement id to profile with timestamp it was unlocked
|
||||
fullProfile.characters.pmc.Achievements[achievementId] = this.timeUtil.getTimestamp();
|
||||
|
||||
// Check for any customisation unlocks
|
||||
const achievementDataDb = this.databaseService
|
||||
.getTemplates()
|
||||
.achievements.find((achievement) => achievement.id === achievementId);
|
||||
if (!achievementDataDb) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get customisation reward object from achievement db
|
||||
const customizationDirectReward = achievementDataDb.rewards.find(
|
||||
(reward) => reward.type === "CustomizationDirect",
|
||||
);
|
||||
if (!customizationDirectReward) {
|
||||
return;
|
||||
}
|
||||
|
||||
const customisationDataDb = this.databaseService
|
||||
.getHideout()
|
||||
.customisation.globals.find((customisation) => customisation.itemId === customizationDirectReward.target);
|
||||
if (!customisationDataDb) {
|
||||
this.logger.error(
|
||||
`Unable to find customisation data for ${customizationDirectReward.target} in profile ${fullProfile.info.id}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Reward found, add to profile
|
||||
fullProfile.customisationUnlocks.push({
|
||||
id: customizationDirectReward.target,
|
||||
source: CustomisationSource.ACHIEVEMENT,
|
||||
type: customisationDataDb.type as CustomisationType,
|
||||
});
|
||||
}
|
||||
|
||||
public hasAccessToRepeatableFreeRefreshSystem(pmcProfile: IPmcData): boolean {
|
||||
return [GameEditions.EDGE_OF_DARKNESS, GameEditions.UNHEARD].includes(<any>pmcProfile.Info?.GameVersion);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { QuestConditionHelper } from "@spt/helpers/QuestConditionHelper";
|
||||
import { QuestRewardHelper } from "@spt/helpers/QuestRewardHelper";
|
||||
import { RewardHelper } from "@spt/helpers/RewardHelper";
|
||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { Common, IQuestStatus } from "@spt/models/eft/common/tables/IBotBase";
|
||||
@ -46,6 +47,7 @@ export class QuestHelper {
|
||||
@inject("LocaleService") protected localeService: LocaleService,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
@inject("QuestRewardHelper") protected questRewardHelper: QuestRewardHelper,
|
||||
@inject("RewardHelper") protected rewardHelper: RewardHelper,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||
@ -1060,7 +1062,7 @@ export class QuestHelper {
|
||||
// Remove any reward that doesn't pass the game edition check
|
||||
for (const rewardType of Object.keys(quest.rewards)) {
|
||||
quest.rewards[rewardType] = quest.rewards[rewardType].filter((reward: IReward) =>
|
||||
this.questRewardHelper.questRewardIsForGameEdition(reward, gameVersion),
|
||||
this.rewardHelper.rewardIsForGameEdition(reward, gameVersion),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,11 @@ import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage";
|
||||
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
||||
import { IQuest } from "@spt/models/eft/common/tables/IQuest";
|
||||
import { IReward } from "@spt/models/eft/common/tables/IReward";
|
||||
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { QuestStatus } from "@spt/models/enums/QuestStatus";
|
||||
import { RewardType } from "@spt/models/enums/RewardType";
|
||||
import { SkillTypes } from "@spt/models/enums/SkillTypes";
|
||||
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
@ -19,6 +16,8 @@ import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { RewardHelper } from "@spt/helpers/RewardHelper";
|
||||
import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage";
|
||||
|
||||
@injectable()
|
||||
export class QuestRewardHelper {
|
||||
@ -33,6 +32,7 @@ export class QuestRewardHelper {
|
||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
@inject("RewardHelper") protected rewardHelper: RewardHelper,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -78,99 +78,15 @@ export class QuestRewardHelper {
|
||||
|
||||
// e.g. 'Success' or 'AvailableForFinish'
|
||||
const questStateAsString = QuestStatus[state];
|
||||
const gameVersion = pmcProfile.Info.GameVersion;
|
||||
for (const reward of <IReward[]>questDetails.rewards[questStateAsString]) {
|
||||
// Handle quest reward availability for different game versions, notAvailableInGameEditions currently not used
|
||||
if (!this.questRewardIsForGameEdition(reward, gameVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (reward.type) {
|
||||
case RewardType.SKILL:
|
||||
this.profileHelper.addSkillPointsToPlayer(
|
||||
profileData,
|
||||
reward.target as SkillTypes,
|
||||
Number(reward.value),
|
||||
);
|
||||
break;
|
||||
case RewardType.EXPERIENCE:
|
||||
this.profileHelper.addExperienceToPmc(sessionId, Number.parseInt(<string>reward.value)); // this must occur first as the output object needs to take the modified profile exp value
|
||||
break;
|
||||
case RewardType.TRADER_STANDING:
|
||||
this.traderHelper.addStandingToTrader(
|
||||
sessionId,
|
||||
reward.target,
|
||||
Number.parseFloat(<string>reward.value),
|
||||
);
|
||||
break;
|
||||
case RewardType.TRADER_UNLOCK:
|
||||
this.traderHelper.setTraderUnlockedState(reward.target, true, sessionId);
|
||||
break;
|
||||
case RewardType.ITEM:
|
||||
// Handled by getQuestRewardItems() below
|
||||
break;
|
||||
case RewardType.ASSORTMENT_UNLOCK:
|
||||
// Handled by getAssort(), locked assorts are stripped out by `assortHelper.stripLockedLoyaltyAssort()` before being sent to player
|
||||
break;
|
||||
case RewardType.ACHIEVEMENT:
|
||||
this.profileHelper.addAchievementToProfile(fullProfile, reward.target);
|
||||
break;
|
||||
case RewardType.STASH_ROWS:
|
||||
this.profileHelper.addStashRowsBonusToProfile(sessionId, Number.parseInt(<string>reward.value)); // Add specified stash rows from quest reward - requires client restart
|
||||
break;
|
||||
case RewardType.PRODUCTIONS_SCHEME:
|
||||
this.findAndAddHideoutProductionIdToProfile(
|
||||
pmcProfile,
|
||||
reward,
|
||||
questDetails,
|
||||
sessionId,
|
||||
questResponse,
|
||||
);
|
||||
break;
|
||||
case RewardType.POCKETS:
|
||||
this.profileHelper.replaceProfilePocketTpl(pmcProfile, reward.target);
|
||||
break;
|
||||
case RewardType.CUSTOMIZATION_DIRECT:
|
||||
this.profileHelper.addHideoutCustomisationUnlock(
|
||||
fullProfile,
|
||||
reward,
|
||||
CustomisationSource.UNLOCKED_IN_GAME,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
this.logger.error(
|
||||
this.localisationService.getText("quest-reward_type_not_handled", {
|
||||
rewardType: reward.type,
|
||||
questId: questId,
|
||||
questName: questDetails.QuestName,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.getQuestRewardItems(questDetails, state, gameVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the provided quest reward have a game version requirement to be given and does it match
|
||||
* @param reward Reward to check
|
||||
* @param gameVersion Version of game to check reward against
|
||||
* @returns True if it has requirement, false if it doesnt pass check
|
||||
*/
|
||||
public questRewardIsForGameEdition(reward: IReward, gameVersion: string): boolean {
|
||||
if (reward.availableInGameEditions?.length > 0 && !reward.availableInGameEditions?.includes(gameVersion)) {
|
||||
// Reward has edition whitelist and game version isnt in it
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reward.notAvailableInGameEditions?.length > 0 && reward.notAvailableInGameEditions?.includes(gameVersion)) {
|
||||
// Reward has edition blacklist and game version is in it
|
||||
return false;
|
||||
}
|
||||
|
||||
// No whitelist/blacklist or reward isnt blacklisted/whitelisted
|
||||
return true;
|
||||
const rewards = <IReward[]>questDetails.rewards[questStateAsString];
|
||||
return this.rewardHelper.applyRewards(
|
||||
rewards,
|
||||
CustomisationSource.UNLOCKED_IN_GAME,
|
||||
fullProfile,
|
||||
profileData,
|
||||
questId,
|
||||
questResponse,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -228,7 +144,8 @@ export class QuestRewardHelper {
|
||||
* @returns Updated quest
|
||||
*/
|
||||
public applyMoneyBoost(quest: IQuest, bonusPercent: number, questStatus: QuestStatus): IQuest {
|
||||
const rewards: IReward[] = quest.rewards?.[QuestStatus[questStatus]] ?? [];
|
||||
const clonedQuest = this.cloner.clone(quest);
|
||||
const rewards: IReward[] = clonedQuest.rewards?.[QuestStatus[questStatus]] ?? [];
|
||||
const currencyRewards = rewards.filter(
|
||||
(reward) => reward.type === "Item" && this.paymentHelper.isMoneyTpl(reward.items[0]._tpl),
|
||||
);
|
||||
@ -240,205 +157,6 @@ export class QuestRewardHelper {
|
||||
reward.value = newCurrencyAmount;
|
||||
}
|
||||
|
||||
return quest;
|
||||
}
|
||||
|
||||
/**
|
||||
* WIP - Find hideout craft id and add to unlockedProductionRecipe array in player profile
|
||||
* also update client response recipeUnlocked array with craft id
|
||||
* @param pmcData Player profile
|
||||
* @param craftUnlockReward Reward item from quest with craft unlock details
|
||||
* @param questDetails Quest with craft unlock reward
|
||||
* @param sessionID Session id
|
||||
* @param response Response to send back to client
|
||||
*/
|
||||
protected findAndAddHideoutProductionIdToProfile(
|
||||
pmcData: IPmcData,
|
||||
craftUnlockReward: IReward,
|
||||
questDetails: IQuest,
|
||||
sessionID: string,
|
||||
response: IItemEventRouterResponse,
|
||||
): void {
|
||||
const matchingProductions = this.getRewardProductionMatch(craftUnlockReward, questDetails);
|
||||
if (matchingProductions.length !== 1) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("quest-unable_to_find_matching_hideout_production", {
|
||||
questName: questDetails.QuestName,
|
||||
matchCount: matchingProductions.length,
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Add above match to pmc profile + client response
|
||||
const matchingCraftId = matchingProductions[0]._id;
|
||||
pmcData.UnlockedInfo.unlockedProductionRecipe.push(matchingCraftId);
|
||||
response.profileChanges[sessionID].recipeUnlocked[matchingCraftId] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find hideout craft for the specified quest reward
|
||||
* @param craftUnlockReward Reward item from quest with craft unlock details
|
||||
* @param questDetails Quest with craft unlock reward
|
||||
* @returns Hideout craft
|
||||
*/
|
||||
public getRewardProductionMatch(craftUnlockReward: IReward, questDetails: IQuest): IHideoutProduction[] {
|
||||
// Get hideout crafts and find those that match by areatype/required level/end product tpl - hope for just one match
|
||||
const craftingRecipes = this.databaseService.getHideout().production.recipes;
|
||||
|
||||
// Area that will be used to craft unlocked item
|
||||
const desiredHideoutAreaType = Number.parseInt(craftUnlockReward.traderId);
|
||||
|
||||
let matchingProductions = craftingRecipes.filter(
|
||||
(prod) =>
|
||||
prod.areaType === desiredHideoutAreaType &&
|
||||
//prod.requirements.some((requirement) => requirement.questId === questDetails._id) && // BSG dont store the quest id in requirement any more!
|
||||
prod.requirements.some((requirement) => requirement.type === "QuestComplete") &&
|
||||
prod.requirements.some((requirement) => requirement.requiredLevel === craftUnlockReward.loyaltyLevel) &&
|
||||
prod.endProduct === craftUnlockReward.items[0]._tpl,
|
||||
);
|
||||
|
||||
// More/less than single match, above filtering wasn't strict enough
|
||||
if (matchingProductions.length !== 1) {
|
||||
// Multiple matches were found, last ditch attempt to match by questid (value we add manually to production.json via `gen:productionquests` command)
|
||||
matchingProductions = matchingProductions.filter((prod) =>
|
||||
prod.requirements.some((requirement) => requirement.questId === questDetails._id),
|
||||
);
|
||||
}
|
||||
|
||||
return matchingProductions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a flat list of reward items for the given quest at a specific state for the specified game version (e.g. Fail/Success)
|
||||
* @param quest quest to get rewards for
|
||||
* @param status Quest status that holds the items (Started, Success, Fail)
|
||||
* @returns array of items with the correct maxStack
|
||||
*/
|
||||
protected getQuestRewardItems(quest: IQuest, status: QuestStatus, gameVersion: string): IItem[] {
|
||||
if (!quest.rewards[QuestStatus[status]]) {
|
||||
this.logger.warning(`Unable to find: ${status} reward for quest: ${quest.QuestName}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Iterate over all rewards with the desired status, flatten out items that have a type of Item
|
||||
const questRewards = quest.rewards[QuestStatus[status]].flatMap((reward: IReward) =>
|
||||
reward.type === "Item" && this.questRewardIsForGameEdition(reward, gameVersion)
|
||||
? this.processReward(reward)
|
||||
: [],
|
||||
);
|
||||
|
||||
return questRewards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take reward item from quest and set FiR status + fix stack sizes + fix mod Ids
|
||||
* @param questReward Reward item to fix
|
||||
* @returns Fixed rewards
|
||||
*/
|
||||
protected processReward(questReward: IReward): IItem[] {
|
||||
/** item with mods to return */
|
||||
let rewardItems: IItem[] = [];
|
||||
let targets: IItem[] = [];
|
||||
const mods: IItem[] = [];
|
||||
|
||||
// Is armor item that may need inserts / plates
|
||||
if (questReward.items.length === 1 && this.itemHelper.armorItemCanHoldMods(questReward.items[0]._tpl)) {
|
||||
// Only process items with slots
|
||||
if (this.itemHelper.itemHasSlots(questReward.items[0]._tpl)) {
|
||||
// Attempt to pull default preset from globals and add child items to reward (clones questReward.items)
|
||||
this.generateArmorRewardChildSlots(questReward.items[0], questReward);
|
||||
}
|
||||
}
|
||||
|
||||
for (const rewardItem of questReward.items) {
|
||||
this.itemHelper.addUpdObjectToItem(rewardItem);
|
||||
|
||||
// Reward items are granted Found in Raid status
|
||||
rewardItem.upd.SpawnedInSession = true;
|
||||
|
||||
// Is root item, fix stacks
|
||||
if (rewardItem._id === questReward.target) {
|
||||
// Is base reward item
|
||||
if (
|
||||
rewardItem.parentId !== undefined &&
|
||||
rewardItem.parentId === "hideout" && // Has parentId of hideout
|
||||
rewardItem.upd !== undefined &&
|
||||
rewardItem.upd.StackObjectsCount !== undefined && // Has upd with stackobject count
|
||||
rewardItem.upd.StackObjectsCount > 1 // More than 1 item in stack
|
||||
) {
|
||||
rewardItem.upd.StackObjectsCount = 1;
|
||||
}
|
||||
targets = this.itemHelper.splitStack(rewardItem);
|
||||
// splitStack created new ids for the new stacks. This would destroy the relation to possible children.
|
||||
// Instead, we reset the id to preserve relations and generate a new id in the downstream loop, where we are also reparenting if required
|
||||
for (const target of targets) {
|
||||
target._id = rewardItem._id;
|
||||
}
|
||||
} else {
|
||||
// Is child mod
|
||||
if (questReward.items[0].upd.SpawnedInSession) {
|
||||
// Propigate FiR status into child items
|
||||
rewardItem.upd.SpawnedInSession = questReward.items[0].upd.SpawnedInSession;
|
||||
}
|
||||
|
||||
mods.push(rewardItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Add mods to the base items, fix ids
|
||||
for (const target of targets) {
|
||||
// This has all the original id relations since we reset the id to the original after the splitStack
|
||||
const itemsClone = [this.cloner.clone(target)];
|
||||
// Here we generate a new id for the root item
|
||||
target._id = this.hashUtil.generate();
|
||||
|
||||
for (const mod of mods) {
|
||||
itemsClone.push(this.cloner.clone(mod));
|
||||
}
|
||||
|
||||
rewardItems = rewardItems.concat(this.itemHelper.reparentItemAndChildren(target, itemsClone));
|
||||
}
|
||||
|
||||
return rewardItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add missing mod items to a quest armor reward
|
||||
* @param originalRewardRootItem Original armor reward item from IReward.items object
|
||||
* @param questReward Armor reward from quest
|
||||
*/
|
||||
protected generateArmorRewardChildSlots(originalRewardRootItem: IItem, questReward: IReward): void {
|
||||
// Look for a default preset from globals for armor
|
||||
const defaultPreset = this.presetHelper.getDefaultPreset(originalRewardRootItem._tpl);
|
||||
if (defaultPreset) {
|
||||
// Found preset, use mods to hydrate reward item
|
||||
const presetAndMods: IItem[] = this.itemHelper.replaceIDs(defaultPreset._items);
|
||||
const newRootId = this.itemHelper.remapRootItemId(presetAndMods);
|
||||
|
||||
questReward.items = presetAndMods;
|
||||
|
||||
// Find root item and set its stack count
|
||||
const rootItem = questReward.items.find((item) => item._id === newRootId);
|
||||
|
||||
// Remap target id to the new presets root id
|
||||
questReward.target = rootItem._id;
|
||||
|
||||
// Copy over stack count otherwise reward shows as missing in client
|
||||
this.itemHelper.addUpdObjectToItem(rootItem);
|
||||
|
||||
rootItem.upd.StackObjectsCount = originalRewardRootItem.upd.StackObjectsCount;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.warning(
|
||||
`Unable to find default preset for armor ${originalRewardRootItem._tpl}, adding mods manually`,
|
||||
);
|
||||
const itemDbData = this.itemHelper.getItem(originalRewardRootItem._tpl)[1];
|
||||
|
||||
// Hydrate reward with only 'required' mods - necessary for things like helmets otherwise you end up with nvgs/visors etc
|
||||
questReward.items = this.itemHelper.addChildSlotItems(questReward.items, itemDbData, undefined, true);
|
||||
return clonedQuest;
|
||||
}
|
||||
}
|
||||
|
375
project/src/helpers/RewardHelper.ts
Normal file
375
project/src/helpers/RewardHelper.ts
Normal file
@ -0,0 +1,375 @@
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt/helpers/PresetHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage";
|
||||
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
||||
import { IReward } from "@spt/models/eft/common/tables/IReward";
|
||||
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
|
||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
|
||||
import { RewardType } from "@spt/models/enums/RewardType";
|
||||
import { SkillTypes } from "@spt/models/enums/SkillTypes";
|
||||
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { HashUtil } from "@spt/utils/HashUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class RewardHelper {
|
||||
constructor(
|
||||
@inject("PrimaryLogger") protected logger: ILogger,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Apply the given rewards to the passed in profile
|
||||
* @param rewards List of rewards to apply
|
||||
* @param source The source of the rewards (Achievement, quest)
|
||||
* @param fullProfile The full profile to apply the rewards to
|
||||
* @param questId The quest or achievement ID, used for finding production unlocks
|
||||
* @param questResponse Response to quest completion when a production is unlocked
|
||||
* @returns List of items that were rewarded
|
||||
*/
|
||||
public applyRewards(
|
||||
rewards: IReward[],
|
||||
source: CustomisationSource,
|
||||
fullProfile: ISptProfile,
|
||||
profileData: IPmcData,
|
||||
questId: string,
|
||||
questResponse?: IItemEventRouterResponse,
|
||||
): IItem[] {
|
||||
const sessionId = fullProfile?.info?.id;
|
||||
const pmcProfile = fullProfile?.characters.pmc;
|
||||
if (!pmcProfile) {
|
||||
this.logger.error(`Unable to get pmc profile for: ${sessionId}, no rewards given`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const gameVersion = pmcProfile.Info.GameVersion;
|
||||
|
||||
for (const reward of rewards) {
|
||||
// Handle reward availability for different game versions, notAvailableInGameEditions currently not used
|
||||
if (!this.rewardIsForGameEdition(reward, gameVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (reward.type) {
|
||||
case RewardType.SKILL:
|
||||
// This needs to use the passed in profileData, as it could be the scav profile
|
||||
this.profileHelper.addSkillPointsToPlayer(
|
||||
profileData,
|
||||
reward.target as SkillTypes,
|
||||
Number(reward.value),
|
||||
);
|
||||
break;
|
||||
case RewardType.EXPERIENCE:
|
||||
this.profileHelper.addExperienceToPmc(sessionId, Number.parseInt(<string>reward.value)); // this must occur first as the output object needs to take the modified profile exp value
|
||||
break;
|
||||
case RewardType.TRADER_STANDING:
|
||||
this.traderHelper.addStandingToTrader(
|
||||
sessionId,
|
||||
reward.target,
|
||||
Number.parseFloat(<string>reward.value),
|
||||
);
|
||||
break;
|
||||
case RewardType.TRADER_UNLOCK:
|
||||
this.traderHelper.setTraderUnlockedState(reward.target, true, sessionId);
|
||||
break;
|
||||
case RewardType.ITEM:
|
||||
// Item rewards are retrieved by getRewardItems() below, and returned to be handled by caller
|
||||
break;
|
||||
case RewardType.ASSORTMENT_UNLOCK:
|
||||
// Handled by getAssort(), locked assorts are stripped out by `assortHelper.stripLockedLoyaltyAssort()` before being sent to player
|
||||
break;
|
||||
case RewardType.ACHIEVEMENT:
|
||||
this.addAchievementToProfile(fullProfile, reward.target);
|
||||
break;
|
||||
case RewardType.STASH_ROWS:
|
||||
this.profileHelper.addStashRowsBonusToProfile(sessionId, Number.parseInt(<string>reward.value)); // Add specified stash rows from reward - requires client restart
|
||||
break;
|
||||
case RewardType.PRODUCTIONS_SCHEME:
|
||||
this.findAndAddHideoutProductionIdToProfile(pmcProfile, reward, questId, sessionId, questResponse);
|
||||
break;
|
||||
case RewardType.POCKETS:
|
||||
this.profileHelper.replaceProfilePocketTpl(pmcProfile, reward.target);
|
||||
break;
|
||||
case RewardType.CUSTOMIZATION_DIRECT:
|
||||
this.profileHelper.addHideoutCustomisationUnlock(fullProfile, reward, source);
|
||||
break;
|
||||
default:
|
||||
this.logger.error(
|
||||
this.localisationService.getText("reward-type_not_handled", {
|
||||
rewardType: reward.type,
|
||||
questId: questId,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.getRewardItems(rewards, gameVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the provided reward have a game version requirement to be given and does it match
|
||||
* @param reward Reward to check
|
||||
* @param gameVersion Version of game to check reward against
|
||||
* @returns True if it has requirement, false if it doesnt pass check
|
||||
*/
|
||||
public rewardIsForGameEdition(reward: IReward, gameVersion: string): boolean {
|
||||
if (reward.availableInGameEditions?.length > 0 && !reward.availableInGameEditions?.includes(gameVersion)) {
|
||||
// Reward has edition whitelist and game version isnt in it
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reward.notAvailableInGameEditions?.length > 0 && reward.notAvailableInGameEditions?.includes(gameVersion)) {
|
||||
// Reward has edition blacklist and game version is in it
|
||||
return false;
|
||||
}
|
||||
|
||||
// No whitelist/blacklist or reward isnt blacklisted/whitelisted
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* WIP - Find hideout craft id and add to unlockedProductionRecipe array in player profile
|
||||
* also update client response recipeUnlocked array with craft id
|
||||
* @param pmcData Player profile
|
||||
* @param craftUnlockReward Reward with craft unlock details
|
||||
* @param questId Quest or achievement ID with craft unlock reward
|
||||
* @param sessionID Session id
|
||||
* @param response Response to send back to client
|
||||
*/
|
||||
protected findAndAddHideoutProductionIdToProfile(
|
||||
pmcData: IPmcData,
|
||||
craftUnlockReward: IReward,
|
||||
questId: string,
|
||||
sessionID: string,
|
||||
response?: IItemEventRouterResponse,
|
||||
): void {
|
||||
const matchingProductions = this.getRewardProductionMatch(craftUnlockReward, questId);
|
||||
if (matchingProductions.length !== 1) {
|
||||
this.logger.error(
|
||||
this.localisationService.getText("reward-unable_to_find_matching_hideout_production", {
|
||||
questId: questId,
|
||||
matchCount: matchingProductions.length,
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Add above match to pmc profile + client response
|
||||
const matchingCraftId = matchingProductions[0]._id;
|
||||
pmcData.UnlockedInfo.unlockedProductionRecipe.push(matchingCraftId);
|
||||
if (response) {
|
||||
response.profileChanges[sessionID].recipeUnlocked[matchingCraftId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find hideout craft for the specified reward
|
||||
* @param craftUnlockReward Reward with craft unlock details
|
||||
* @param questId Quest or achievement ID with craft unlock reward
|
||||
* @returns Hideout craft
|
||||
*/
|
||||
public getRewardProductionMatch(craftUnlockReward: IReward, questId: string): IHideoutProduction[] {
|
||||
// Get hideout crafts and find those that match by areatype/required level/end product tpl - hope for just one match
|
||||
const craftingRecipes = this.databaseService.getHideout().production.recipes;
|
||||
|
||||
// Area that will be used to craft unlocked item
|
||||
const desiredHideoutAreaType = Number.parseInt(craftUnlockReward.traderId);
|
||||
|
||||
let matchingProductions = craftingRecipes.filter(
|
||||
(prod) =>
|
||||
prod.areaType === desiredHideoutAreaType &&
|
||||
//prod.requirements.some((requirement) => requirement.questId === questId) && // BSG dont store the quest id in requirement any more!
|
||||
prod.requirements.some((requirement) => requirement.type === "QuestComplete") &&
|
||||
prod.requirements.some((requirement) => requirement.requiredLevel === craftUnlockReward.loyaltyLevel) &&
|
||||
prod.endProduct === craftUnlockReward.items[0]._tpl,
|
||||
);
|
||||
|
||||
// More/less than single match, above filtering wasn't strict enough
|
||||
if (matchingProductions.length !== 1) {
|
||||
// Multiple matches were found, last ditch attempt to match by questid (value we add manually to production.json via `gen:productionquests` command)
|
||||
matchingProductions = matchingProductions.filter((prod) =>
|
||||
prod.requirements.some((requirement) => requirement.questId === questId),
|
||||
);
|
||||
}
|
||||
|
||||
return matchingProductions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a flat list of reward items from the given rewards for the specified game version
|
||||
* @param rewards Array of rewards to get the items from
|
||||
* @param gameVersion The game version of the profile
|
||||
* @returns array of items with the correct maxStack
|
||||
*/
|
||||
protected getRewardItems(rewards: IReward[], gameVersion: string): IItem[] {
|
||||
// Iterate over all rewards with the desired status, flatten out items that have a type of Item
|
||||
const rewardItems = rewards.flatMap((reward: IReward) =>
|
||||
reward.type === "Item" && this.rewardIsForGameEdition(reward, gameVersion)
|
||||
? this.processReward(reward)
|
||||
: [],
|
||||
);
|
||||
|
||||
return rewardItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take reward item and set FiR status + fix stack sizes + fix mod Ids
|
||||
* @param reward Reward item to fix
|
||||
* @returns Fixed rewards
|
||||
*/
|
||||
protected processReward(reward: IReward): IItem[] {
|
||||
/** item with mods to return */
|
||||
let rewardItems: IItem[] = [];
|
||||
let targets: IItem[] = [];
|
||||
const mods: IItem[] = [];
|
||||
|
||||
// Is armor item that may need inserts / plates
|
||||
if (reward.items.length === 1 && this.itemHelper.armorItemCanHoldMods(reward.items[0]._tpl)) {
|
||||
// Only process items with slots
|
||||
if (this.itemHelper.itemHasSlots(reward.items[0]._tpl)) {
|
||||
// Attempt to pull default preset from globals and add child items to reward (clones reward.items)
|
||||
this.generateArmorRewardChildSlots(reward.items[0], reward);
|
||||
}
|
||||
}
|
||||
|
||||
for (const rewardItem of reward.items) {
|
||||
this.itemHelper.addUpdObjectToItem(rewardItem);
|
||||
|
||||
// Reward items are granted Found in Raid status
|
||||
rewardItem.upd.SpawnedInSession = true;
|
||||
|
||||
// Is root item, fix stacks
|
||||
if (rewardItem._id === reward.target) {
|
||||
// Is base reward item
|
||||
if (
|
||||
rewardItem.parentId !== undefined &&
|
||||
rewardItem.parentId === "hideout" && // Has parentId of hideout
|
||||
rewardItem.upd !== undefined &&
|
||||
rewardItem.upd.StackObjectsCount !== undefined && // Has upd with stackobject count
|
||||
rewardItem.upd.StackObjectsCount > 1 // More than 1 item in stack
|
||||
) {
|
||||
rewardItem.upd.StackObjectsCount = 1;
|
||||
}
|
||||
targets = this.itemHelper.splitStack(rewardItem);
|
||||
// splitStack created new ids for the new stacks. This would destroy the relation to possible children.
|
||||
// Instead, we reset the id to preserve relations and generate a new id in the downstream loop, where we are also reparenting if required
|
||||
for (const target of targets) {
|
||||
target._id = rewardItem._id;
|
||||
}
|
||||
} else {
|
||||
// Is child mod
|
||||
if (reward.items[0].upd.SpawnedInSession) {
|
||||
// Propigate FiR status into child items
|
||||
rewardItem.upd.SpawnedInSession = reward.items[0].upd.SpawnedInSession;
|
||||
}
|
||||
|
||||
mods.push(rewardItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Add mods to the base items, fix ids
|
||||
for (const target of targets) {
|
||||
// This has all the original id relations since we reset the id to the original after the splitStack
|
||||
const itemsClone = [this.cloner.clone(target)];
|
||||
// Here we generate a new id for the root item
|
||||
target._id = this.hashUtil.generate();
|
||||
|
||||
for (const mod of mods) {
|
||||
itemsClone.push(this.cloner.clone(mod));
|
||||
}
|
||||
|
||||
rewardItems = rewardItems.concat(this.itemHelper.reparentItemAndChildren(target, itemsClone));
|
||||
}
|
||||
|
||||
return rewardItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add missing mod items to an armor reward
|
||||
* @param originalRewardRootItem Original armor reward item from IReward.items object
|
||||
* @param reward Armor reward
|
||||
*/
|
||||
protected generateArmorRewardChildSlots(originalRewardRootItem: IItem, reward: IReward): void {
|
||||
// Look for a default preset from globals for armor
|
||||
const defaultPreset = this.presetHelper.getDefaultPreset(originalRewardRootItem._tpl);
|
||||
if (defaultPreset) {
|
||||
// Found preset, use mods to hydrate reward item
|
||||
const presetAndMods: IItem[] = this.itemHelper.replaceIDs(defaultPreset._items);
|
||||
const newRootId = this.itemHelper.remapRootItemId(presetAndMods);
|
||||
|
||||
reward.items = presetAndMods;
|
||||
|
||||
// Find root item and set its stack count
|
||||
const rootItem = reward.items.find((item) => item._id === newRootId);
|
||||
|
||||
// Remap target id to the new presets root id
|
||||
reward.target = rootItem._id;
|
||||
|
||||
// Copy over stack count otherwise reward shows as missing in client
|
||||
this.itemHelper.addUpdObjectToItem(rootItem);
|
||||
|
||||
rootItem.upd.StackObjectsCount = originalRewardRootItem.upd.StackObjectsCount;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.warning(
|
||||
`Unable to find default preset for armor ${originalRewardRootItem._tpl}, adding mods manually`,
|
||||
);
|
||||
const itemDbData = this.itemHelper.getItem(originalRewardRootItem._tpl)[1];
|
||||
|
||||
// Hydrate reward with only 'required' mods - necessary for things like helmets otherwise you end up with nvgs/visors etc
|
||||
reward.items = this.itemHelper.addChildSlotItems(reward.items, itemDbData, undefined, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an achievement to player profile and handle any rewards for the achievement
|
||||
* Triggered from a quest, or another achievement
|
||||
* @param fullProfile Profile to add achievement to
|
||||
* @param achievementId Id of achievement to add
|
||||
*/
|
||||
public addAchievementToProfile(fullProfile: ISptProfile, achievementId: string): void {
|
||||
// Add achievement id to profile with timestamp it was unlocked
|
||||
fullProfile.characters.pmc.Achievements[achievementId] = this.timeUtil.getTimestamp();
|
||||
|
||||
// Check for any customisation unlocks
|
||||
const achievementDataDb = this.databaseService
|
||||
.getTemplates()
|
||||
.achievements.find((achievement) => achievement.id === achievementId);
|
||||
if (!achievementDataDb) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: At the moment, we don't know the exact quest and achievement data layout for an achievement
|
||||
// that is triggered by a quest, that gives an item, because BSG has only done this once. However
|
||||
// based on deduction, I am going to assume that the *quest* will handle the initial item reward,
|
||||
// and the achievement reward should only be handled post-wipe.
|
||||
// All of that is to say, we are going to ignore the list of returned reward items here
|
||||
const pmcProfile = fullProfile.characters.pmc;
|
||||
this.applyRewards(
|
||||
achievementDataDb.rewards,
|
||||
CustomisationSource.ACHIEVEMENT,
|
||||
fullProfile,
|
||||
pmcProfile,
|
||||
achievementDataDb.id,
|
||||
);
|
||||
}
|
||||
}
|
@ -51,7 +51,8 @@ import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { TransitionType } from "../models/enums/TransitionType";
|
||||
import { TransitionType } from "@spt/models/enums/TransitionType";
|
||||
import { RewardHelper } from "@spt/helpers/RewardHelper";
|
||||
|
||||
@injectable()
|
||||
export class LocationLifecycleService {
|
||||
@ -73,6 +74,7 @@ export class LocationLifecycleService {
|
||||
@inject("InRaidHelper") protected inRaidHelper: InRaidHelper,
|
||||
@inject("HealthHelper") protected healthHelper: HealthHelper,
|
||||
@inject("QuestHelper") protected questHelper: QuestHelper,
|
||||
@inject("RewardHelper") protected rewardHelper: RewardHelper,
|
||||
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
|
||||
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
|
||||
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
||||
@ -666,7 +668,7 @@ export class LocationLifecycleService {
|
||||
pmcProfile.SurvivorClass = postRaidProfile.SurvivorClass;
|
||||
|
||||
// MUST occur prior to profile achievements being overwritten by post-raid achievements
|
||||
this.processAchievementCustomisationRewards(fullProfile, postRaidProfile.Achievements);
|
||||
this.processAchievementRewards(fullProfile, postRaidProfile.Achievements);
|
||||
|
||||
pmcProfile.Achievements = postRaidProfile.Achievements;
|
||||
pmcProfile.Quests = this.processPostRaidQuests(postRaidProfile.Quests);
|
||||
@ -730,14 +732,13 @@ export class LocationLifecycleService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for and add any customisations found via the gained achievements this raid
|
||||
* Check for and add any rewards found via the gained achievements this raid
|
||||
* @param fullProfile Profile to add customisations to
|
||||
* @param postRaidAchievements Achievements gained this raid
|
||||
* @param postRaidAchievements All profile achievements at the end of the raid
|
||||
*/
|
||||
protected processAchievementCustomisationRewards(
|
||||
fullProfile: ISptProfile,
|
||||
postRaidAchievements: Record<string, number>,
|
||||
): void {
|
||||
protected processAchievementRewards(fullProfile: ISptProfile, postRaidAchievements: Record<string, number>): void {
|
||||
const sessionId = fullProfile.info.id;
|
||||
const pmcProfile = fullProfile.characters.pmc;
|
||||
const preRaidAchievementIds = Object.keys(fullProfile.characters.pmc.Achievements);
|
||||
const postRaidAchievementIds = Object.keys(postRaidAchievements);
|
||||
const achievementIdsAcquiredThisRaid = postRaidAchievementIds.filter(
|
||||
@ -756,14 +757,23 @@ export class LocationLifecycleService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get only customisation rewards from above achievements
|
||||
const customisationRewards = achievements
|
||||
.filter((achievement) => achievement?.rewards.some((reward) => reward.type === "CustomizationDirect"))
|
||||
.flatMap((achievement) => achievement?.rewards);
|
||||
|
||||
// Insert customisations into profile
|
||||
for (const reward of customisationRewards) {
|
||||
this.profileHelper.addHideoutCustomisationUnlock(fullProfile, reward, CustomisationSource.ACHIEVEMENT);
|
||||
for (const achievement of achievements) {
|
||||
const rewardItems = this.rewardHelper.applyRewards(
|
||||
achievement.rewards,
|
||||
CustomisationSource.ACHIEVEMENT,
|
||||
fullProfile,
|
||||
pmcProfile,
|
||||
achievement.id,
|
||||
);
|
||||
if (rewardItems?.length > 0) {
|
||||
this.mailSendService.sendLocalisedSystemMessageToPlayer(
|
||||
sessionId,
|
||||
"670547bb5fa0b1a7c30d5836 0",
|
||||
rewardItems,
|
||||
[],
|
||||
this.timeUtil.getHoursAsSeconds(24 * 7),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { HideoutHelper } from "@spt/helpers/HideoutHelper";
|
||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { QuestRewardHelper } from "@spt/helpers/QuestRewardHelper";
|
||||
import { RewardHelper } from "@spt/helpers/RewardHelper";
|
||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
import { IBonus, IHideoutSlot } from "@spt/models/eft/common/tables/IBotBase";
|
||||
@ -51,7 +51,7 @@ export class ProfileFixerService {
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
@inject("QuestRewardHelper") protected questRewardHelper: QuestRewardHelper,
|
||||
@inject("RewardHelper") protected rewardHelper: RewardHelper,
|
||||
) {
|
||||
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
@ -365,9 +365,9 @@ export class ProfileFixerService {
|
||||
productionUnlockReward: IReward,
|
||||
questDetails: IQuest,
|
||||
): void {
|
||||
const matchingProductions = this.questRewardHelper.getRewardProductionMatch(
|
||||
const matchingProductions = this.rewardHelper.getRewardProductionMatch(
|
||||
productionUnlockReward,
|
||||
questDetails,
|
||||
questDetails._id,
|
||||
);
|
||||
if (matchingProductions.length !== 1) {
|
||||
this.logger.error(
|
||||
|
Loading…
x
Reference in New Issue
Block a user