Skip to content

Commit

Permalink
Added more questHelper fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Chomp committed Jan 7, 2025
1 parent 2e8cdce commit 86d303f
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 114 deletions.
122 changes: 8 additions & 114 deletions project/src/helpers/QuestHelper.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ItemHelper } from "@spt/helpers/ItemHelper";
import { PresetHelper } from "@spt/helpers/PresetHelper";
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
import { QuestConditionHelper } from "@spt/helpers/QuestConditionHelper";
import { QuestRewardHelper } from "@spt/helpers/QuestRewardHelper";
import { TraderHelper } from "@spt/helpers/TraderHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { Common, IQuestStatus } from "@spt/models/eft/common/tables/IBotBase";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { IQuest, IQuestCondition, IQuestReward } from "@spt/models/eft/common/tables/IQuest";
import { IQuest, IQuestCondition } from "@spt/models/eft/common/tables/IQuest";
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
import { IAcceptQuestRequestData } from "@spt/models/eft/quests/IAcceptQuestRequestData";
import { ICompleteQuestRequestData } from "@spt/models/eft/quests/ICompleteQuestRequestData";
Expand Down Expand Up @@ -49,7 +48,6 @@ export class QuestHelper {
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("PresetHelper") protected presetHelper: PresetHelper,
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("PlayerService") protected playerService: PlayerService,
@inject("ConfigServer") protected configServer: ConfigServer,
Expand Down Expand Up @@ -234,116 +232,6 @@ export class QuestHelper {
}
}

/**
* 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: IQuestReward): 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 IQuestReward.items object
* @param questReward Armor reward from quest
*/
protected generateArmorRewardChildSlots(originalRewardRootItem: IItem, questReward: IQuestReward): 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);
}

/**
* Look up quest in db by accepted quest id and construct a profile-ready object ready to store in profile
* @param pmcData Player profile
Expand Down Expand Up @@ -975,7 +863,13 @@ export class QuestHelper {

const newQuestState = QuestStatus.Success;
this.updateQuestState(pmcData, newQuestState, completedQuestId);
const questRewards = this.applyQuestReward(pmcData, body.qid, newQuestState, sessionID, completeQuestResponse);
const questRewards = this.questRewardHelper.applyQuestReward(
pmcData,
body.qid,
newQuestState,
sessionID,
completeQuestResponse,
);

// Check for linked failed + unrestartable quests (only get quests not already failed
const questsToFail = this.getQuestsFromProfileFailedByCompletingQuest(completedQuestId, pmcData);
Expand Down
114 changes: 114 additions & 0 deletions project/src/helpers/QuestRewardHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ItemHelper } from "@spt/helpers/ItemHelper";
import { PaymentHelper } from "@spt/helpers/PaymentHelper";
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";
Expand All @@ -14,20 +15,23 @@ import type { ILogger } from "@spt/models/spt/utils/ILogger";
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
import { DatabaseService } from "@spt/services/DatabaseService";
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";

@injectable()
export class QuestRewardHelper {
constructor(
@inject("PrimaryLogger") protected logger: ILogger,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("DatabaseService") protected databaseService: DatabaseService,
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("PresetHelper") protected presetHelper: PresetHelper,
@inject("PrimaryCloner") protected cloner: ICloner,
) {}

Expand Down Expand Up @@ -323,4 +327,114 @@ export class QuestRewardHelper {

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: IQuestReward): 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 IQuestReward.items object
* @param questReward Armor reward from quest
*/
protected generateArmorRewardChildSlots(originalRewardRootItem: IItem, questReward: IQuestReward): 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);
}
}

0 comments on commit 86d303f

Please sign in to comment.