diff --git a/project/src/helpers/InRaidHelper.ts b/project/src/helpers/InRaidHelper.ts index c779e42e..227457bd 100644 --- a/project/src/helpers/InRaidHelper.ts +++ b/project/src/helpers/InRaidHelper.ts @@ -21,6 +21,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; +import { TimeUtil } from "@spt-aki/utils/TimeUtil"; import { ProfileHelper } from "./ProfileHelper"; @injectable() @@ -31,6 +32,7 @@ export class InRaidHelper constructor( @inject("WinstonLogger") protected logger: ILogger, + @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("SaveServer") protected saveServer: SaveServer, @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("ItemHelper") protected itemHelper: ItemHelper, @@ -129,7 +131,6 @@ export class InRaidHelper * Reset a profile to a baseline, used post-raid * Reset points earned during session property * Increment exp - * Remove Labs keycard * @param profileData Profile to update * @param saveProgressRequest post raid save data request data * @param sessionID Session id @@ -218,7 +219,7 @@ export class InRaidHelper public updatePmcProfileDataPostRaid(pmcData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void { // Process failed quests then copy everything - this.processFailedQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile); + this.processAlteredQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile); pmcData.Quests = saveProgressRequest.profile.Quests; // No need to do this for scav, old scav is deleted and new one generated @@ -250,19 +251,20 @@ export class InRaidHelper } /** - * Look for quests with status = fail that were not failed pre-raid and run the failQuest() function + * Look for quests with a status different from what it began the raid with * @param sessionId Player id * @param pmcData Player profile * @param preRaidQuests Quests prior to starting raid - * @param postRaidProfile Profile sent by client + * @param postRaidProfile Profile sent by client with post-raid quests */ - protected processFailedQuests( + protected processAlteredQuests( sessionId: string, pmcData: IPmcData, preRaidQuests: IQuestStatus[], postRaidProfile: IPostRaidPmcData, ): void { + // TODO: this may break when locked quests are added to profile but player has completed no quests prior to raid if (!preRaidQuests) { // No quests to compare against, skip @@ -270,29 +272,45 @@ export class InRaidHelper } // Loop over all quests from post-raid profile + const newLockedQuests: IQuestStatus[] = []; for (const postRaidQuest of postRaidProfile.Quests) { - // Find matching pre-raid quest + not already failed + // postRaidQuest.status has a weird value, need to do some nasty casting to compare it + const postRaidQuestStatus = postRaidQuest.status; + + // Find matching pre-raid quest const preRaidQuest = preRaidQuests?.find((x) => x.qid === postRaidQuest.qid); if (!preRaidQuest) { + // Some traders gives locked quests (LightKeeper) due to time-gating + if (postRaidQuestStatus === "Locked") + { + // Store new locked quest for future processing + newLockedQuests.push(postRaidQuest); + } + continue; } - // Already failed before raid, skip - if (preRaidQuest.status === QuestStatus.Fail) - { - continue; - } - - if (preRaidQuest.status === QuestStatus.Success) + // Already completed/failed before raid, skip + if ([QuestStatus.Fail, QuestStatus.Success].includes(preRaidQuest.status) ) { continue; } - // Quest failed inside raid, need to handle - // postRaidQuest.status has a weird value, need to do some nasty casting to compare it - const postRaidQuestStatus = postRaidQuest.status; + // Quest with time-gate has unlocked + if (postRaidQuestStatus === "AvailableAfter" && postRaidQuest.availableAfter <= this.timeUtil.getTimestamp()) + { + // Flag as ready to complete + postRaidQuest.status = QuestStatus.AvailableForStart; + postRaidQuest.statusTimers[QuestStatus.AvailableForStart] = this.timeUtil.getTimestamp(); + + this.logger.debug(`Time-locked quest ${postRaidQuest.qid} is now ready to start`); + + continue; + } + + // Quest failed inside raid if (postRaidQuestStatus === "Fail") { // Send failed message @@ -347,6 +365,34 @@ export class InRaidHelper postRaidQuest.completedConditions = []; } } + + // Reclassify time-gated quests as time gated until a specific date + if (newLockedQuests.length > 0) + { + for (const lockedQuest of newLockedQuests) + { + // Get the quest from Db + const dbQuest = this.questHelper.getQuestFromDb(lockedQuest.qid, null); + if (!dbQuest) + { + this.logger.warning(`Unable to adjust locked quest: ${lockedQuest.qid} as it wasnt found in db. It may not become available later on`); + + continue; + } + + // Find the time requirement in AvailableForStart array (assuming there is one as quest in locked state === its time-gated) + const afsRequirement = dbQuest.conditions.AvailableForStart.find(x => x._parent === "Quest"); + if (afsRequirement && afsRequirement._props.availableAfter > 0) + { + // Prereq quest has a wait + // Set quest as AvailableAfter and set timer + const timestamp = this.timeUtil.getTimestamp() + afsRequirement._props.availableAfter; + lockedQuest.availableAfter = timestamp; + lockedQuest.statusTimers.AvailableAfter = timestamp; + lockedQuest.status = 9; + } + } + } } /** diff --git a/project/src/services/ProfileFixerService.ts b/project/src/services/ProfileFixerService.ts index 4cef1a91..f6ab00f9 100644 --- a/project/src/services/ProfileFixerService.ts +++ b/project/src/services/ProfileFixerService.ts @@ -524,7 +524,7 @@ export class ProfileFixerService { // Old profiles had quests with a bad status of 0 (invalid) added to profile, remove them // E.g. compensation for damage showing before standing check was added to getClientQuests() - if (quest.status === 0 && !isDevProfile) + if (quest.status === 0 && quest.availableAfter === 0 && !isDevProfile) { questsToDelete.push(quest);