Skip to content

Commit

Permalink
Add quest production unlocks to the PMC Profile fixer service
Browse files Browse the repository at this point in the history
  • Loading branch information
DrakiaXYZ committed Nov 30, 2024
1 parent 063d1ea commit 8b1a315
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 15 deletions.
45 changes: 30 additions & 15 deletions project/src/helpers/QuestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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 { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
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 @@ -1034,6 +1035,34 @@ export class QuestHelper {
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 id for the specified quest reward
* @param craftUnlockReward
* @param questDetails
* @returns
*/
public getRewardProductionMatch(
craftUnlockReward: IQuestReward,
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;

Expand All @@ -1055,23 +1084,9 @@ export class QuestHelper {
matchingProductions = matchingProductions.filter((prod) =>
prod.requirements.some((requirement) => requirement.questId === questDetails._id),
);

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;
return matchingProductions;
}

/**
Expand Down
72 changes: 72 additions & 0 deletions project/src/services/ProfileFixerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ 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 { QuestHelper } from "@spt/helpers/QuestHelper";
import { TraderHelper } from "@spt/helpers/TraderHelper";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { IBonus, IHideoutSlot } from "@spt/models/eft/common/tables/IBotBase";
import { IQuest, IQuestReward } from "@spt/models/eft/common/tables/IQuest";
import { IPmcDataRepeatableQuest, IRepeatableQuest } from "@spt/models/eft/common/tables/IRepeatableQuests";
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
import { IStageBonus } from "@spt/models/eft/hideout/IHideoutArea";
import { IEquipmentBuild, IMagazineBuild, ISptProfile, IWeaponBuild } from "@spt/models/eft/profile/ISptProfile";
import { BonusType } from "@spt/models/enums/BonusType";
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
import { HideoutAreas } from "@spt/models/enums/HideoutAreas";
import { QuestRewardType } from "@spt/models/enums/QuestRewardType";
import { QuestStatus } from "@spt/models/enums/QuestStatus";
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
import { ILogger } from "@spt/models/spt/utils/ILogger";
Expand Down Expand Up @@ -45,6 +49,7 @@ export class ProfileFixerService {
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("ConfigServer") protected configServer: ConfigServer,
@inject("PrimaryCloner") protected cloner: ICloner,
@inject("QuestHelper") protected questHelper: QuestHelper,
) {
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
Expand All @@ -58,6 +63,7 @@ export class ProfileFixerService {
this.removeDanglingConditionCounters(pmcProfile);
this.removeDanglingTaskConditionCounters(pmcProfile);
this.removeOrphanedQuests(pmcProfile);
this.verifyQuestProductionUnlocks(pmcProfile);

if (pmcProfile.Hideout) {
this.addHideoutEliteSlots(pmcProfile);
Expand Down Expand Up @@ -264,6 +270,72 @@ export class ProfileFixerService {
}
}

/**
* Verify that all quest production unlocks have been applied to the PMC Profile
* @param pmcProfile The profile to validate quest productions for
*/
protected verifyQuestProductionUnlocks(pmcProfile: IPmcData): void {
const start = performance.now();

const quests = this.databaseService.getQuests();
const profileQuests = pmcProfile.Quests;

for (const profileQuest of profileQuests)
{
const quest = quests[profileQuest.qid];

// For started or successful quests, check for unlocks in the `Started` rewards
if (profileQuest.status == QuestStatus.Started || profileQuest.status == QuestStatus.Success)
{
const productionRewards = quest.rewards.Started?.filter(reward => reward.type == QuestRewardType.PRODUCTIONS_SCHEME);
productionRewards?.forEach(reward => this.verifyQuestProductionUnlock(pmcProfile, reward, quest));
}

// For successful quests, check for unlocks in the `Success` rewards
if (profileQuest.status == QuestStatus.Success)
{
const productionRewards = quest.rewards.Success?.filter(reward => reward.type == QuestRewardType.PRODUCTIONS_SCHEME);
productionRewards?.forEach(reward => this.verifyQuestProductionUnlock(pmcProfile, reward, quest));
}
}

const validateTime = performance.now() - start
this.logger.debug(`Quest Production Unlock validation took: ${validateTime.toFixed(2)}ms`);
}

/**
* Validate that the given profile has the given quest reward production scheme unlocked, and add it if not
* @param pmcProfile Profile to check
* @param productionUnlockReward The quest reward to validate
* @param questDetails The quest the reward belongs to
* @returns
*/
protected verifyQuestProductionUnlock(
pmcProfile: IPmcData,
productionUnlockReward: IQuestReward,
questDetails: IQuest
): void {
const matchingProductions = this.questHelper.getRewardProductionMatch(productionUnlockReward, 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
const matchingProductionId = matchingProductions[0]._id;
if (!pmcProfile.UnlockedInfo.unlockedProductionRecipe.includes(matchingProductionId))
{
pmcProfile.UnlockedInfo.unlockedProductionRecipe.push(matchingProductionId);
this.logger.debug(`Added production ${matchingProductionId} to unlocked production recipes for ${questDetails.QuestName}`);
}
}

/**
* If the profile has elite Hideout Managment skill, add the additional slots from globals
* NOTE: This seems redundant, but we will leave it here just incase.
Expand Down

0 comments on commit 8b1a315

Please sign in to comment.