diff --git a/project/src/helpers/ProfileHelper.ts b/project/src/helpers/ProfileHelper.ts index 8215e4ae..9783e7d8 100644 --- a/project/src/helpers/ProfileHelper.ts +++ b/project/src/helpers/ProfileHelper.ts @@ -1,6 +1,7 @@ import { ItemHelper } from "@spt/helpers/ItemHelper"; import { IPmcData } from "@spt/models/eft/common/IPmcData"; import { BanType, Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase"; +import { IItem } from "@spt/models/eft/common/tables/IItem"; import { ISptProfile } from "@spt/models/eft/profile/ISptProfile"; import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData"; import { AccountTypes } from "@spt/models/enums/AccountTypes"; @@ -523,4 +524,13 @@ export class ProfileHelper { pocket._tpl = newPocketTpl; } } + + /** + * Return all quest items current in the supplied profile + * @param profile Profile to get quest items from + * @returns Array of item objects + */ + public getQuestItemsInProfile(profile: IPmcData): IItem[] { + return profile.Inventory.items.filter((item) => item.parentId === profile.Inventory.questRaidItems); + } } diff --git a/project/src/services/LocationLifecycleService.ts b/project/src/services/LocationLifecycleService.ts index b889b141..1877932a 100644 --- a/project/src/services/LocationLifecycleService.ts +++ b/project/src/services/LocationLifecycleService.ts @@ -639,6 +639,10 @@ export class LocationLifecycleService { const postRaidProfile = request.results.profile; const preRaidProfileQuestDataClone = this.cloner.clone(pmcProfile.Quests); + // MUST occur BEFORE inventory actions occur + // Player died, quest items presist in the post raid profile + const lostQuestItems = this.profileHelper.getQuestItemsInProfile(postRaidProfile); + // Update inventory this.inRaidHelper.setInventory(sessionId, pmcProfile, postRaidProfile, isSurvived, isTransfer); @@ -651,6 +655,12 @@ export class LocationLifecycleService { pmcProfile.Achievements = postRaidProfile.Achievements; pmcProfile.Quests = this.processPostRaidQuests(postRaidProfile.Quests); + if (lostQuestItems.length > 0) { + // MUST occur AFTER quests have post raid quest data has been merged + // Player is dead + had quest items, check and fix any broken find item quests + this.checkForAndFixPickupQuestsAfterDeath(sessionId, lostQuestItems, pmcProfile.Quests); + } + // Handle edge case - must occur AFTER processPostRaidQuests() this.lightkeeperQuestWorkaround(sessionId, postRaidProfile.Quests, preRaidProfileQuestDataClone, pmcProfile); @@ -672,7 +682,7 @@ export class LocationLifecycleService { // Copy fence values to Scav scavProfile.TradersInfo[fenceId] = pmcProfile.TradersInfo[fenceId]; - // Must occur after encyclopedia updated + // MUST occur AFTER encyclopedia updated this.mergePmcAndScavEncyclopedias(pmcProfile, scavProfile); // Remove skill fatigue values @@ -709,6 +719,58 @@ export class LocationLifecycleService { this.handleInsuredItemLostEvent(sessionId, pmcProfile, request, locationName); } + /** + * On death Quest items are lost, the client does not clean up completed conditions for picking up those quest items, + * If the completed conditions remain in the profile the player is unable to pick the item up again + * @param sessionId Session id + * @param lostQuestItems Quest items lost on player death + * @param profileQuests Quest status data from player profile + */ + protected checkForAndFixPickupQuestsAfterDeath( + sessionId: string, + lostQuestItems: IItem[], + profileQuests: IQuestStatus[], + ) { + // Exclude completed quests + const activeQuestIdsInProfile = profileQuests + .filter((quest) => quest.status !== QuestStatus.Success) + .map((status) => status.qid); + + // Get db details of quests we found above + const questDb = Object.values(this.databaseService.getQuests()).filter((x) => + activeQuestIdsInProfile.includes(x._id), + ); + + for (const lostItem of lostQuestItems) { + for (const quest of questDb) { + // Find a quest in the db that has the lost item in one of its conditions that is also a 'find' type of condition + const matchingCondition = quest.conditions.AvailableForFinish.find( + (questCondition) => + questCondition.conditionType === "FindItem" && questCondition.target.includes(lostItem._tpl), + ); + if (!matchingCondition) { + // Quest doesnt have a matching condition + continue; + } + + // We have a match, remove the condition id from profile to reset progress and let player pick item up again + const profileQuestToUpdate = profileQuests.find((questStatus) => questStatus.qid === quest._id); + if (!profileQuestToUpdate) { + // Profile doesnt have a matching quest + continue; + } + + // Filter out the matching condition we found + profileQuestToUpdate.completedConditions = profileQuestToUpdate.completedConditions.filter( + (conditionId) => conditionId !== matchingCondition.id, + ); + + // We found and updated the relevant quest, no more work to do with this lost item + break; + } + } + } + /** * In 0.15 Lightkeeper quests do not give rewards in PvE, this issue also occurs in spt * We check for newly completed Lk quests and run them through the servers `CompleteQuest` process