Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add quest production unlocks to the PMC Profile fixer service #959

Merged
merged 1 commit into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading