diff --git a/project/src/callbacks/InraidCallbacks.ts b/project/src/callbacks/InraidCallbacks.ts index eb859f37..daa01673 100644 --- a/project/src/callbacks/InraidCallbacks.ts +++ b/project/src/callbacks/InraidCallbacks.ts @@ -3,7 +3,7 @@ import { InraidController } from "@spt/controllers/InraidController"; import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData"; import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData"; import { IRegisterPlayerRequestData } from "@spt/models/eft/inRaid/IRegisterPlayerRequestData"; -import { ISaveProgressRequestData } from "@spt/models/eft/inRaid/ISaveProgressRequestData"; +import { IScavSaveRequestData } from "@spt/models/eft/inRaid/IScavSaveRequestData"; import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil"; /** @@ -39,9 +39,9 @@ export class InraidCallbacks * @param sessionID Session id * @returns Null http response */ - public saveProgress(url: string, info: ISaveProgressRequestData, sessionID: string): INullResponseData + public saveProgress(url: string, info: IScavSaveRequestData, sessionID: string): INullResponseData { - this.inraidController.savePostRaidProgressLegacy(info, sessionID); + this.inraidController.savePostRaidProfileForScav(info, sessionID); return this.httpResponse.nullResponse(); } diff --git a/project/src/controllers/InraidController.ts b/project/src/controllers/InraidController.ts index 43a52b31..1abe7158 100644 --- a/project/src/controllers/InraidController.ts +++ b/project/src/controllers/InraidController.ts @@ -1,41 +1,16 @@ import { inject, injectable } from "tsyringe"; import { ApplicationContext } from "@spt/context/ApplicationContext"; import { ContextVariableType } from "@spt/context/ContextVariableType"; -import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator"; -import { HealthHelper } from "@spt/helpers/HealthHelper"; -import { InRaidHelper } from "@spt/helpers/InRaidHelper"; -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 { ILocationBase } from "@spt/models/eft/common/ILocationBase"; -import { IPmcData } from "@spt/models/eft/common/IPmcData"; -import { BodyPartHealth } from "@spt/models/eft/common/tables/IBotBase"; import { IRegisterPlayerRequestData } from "@spt/models/eft/inRaid/IRegisterPlayerRequestData"; -import { ISaveProgressRequestData } from "@spt/models/eft/inRaid/ISaveProgressRequestData"; +import { IScavSaveRequestData } from "@spt/models/eft/inRaid/IScavSaveRequestData"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; -import { ItemTpl } from "@spt/models/enums/ItemTpl"; -import { PlayerRaidEndState } from "@spt/models/enums/PlayerRaidEndState"; -import { QuestStatus } from "@spt/models/enums/QuestStatus"; -import { SkillTypes } from "@spt/models/enums/SkillTypes"; -import { Traders } from "@spt/models/enums/Traders"; import { IBotConfig } from "@spt/models/spt/config/IBotConfig"; -import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig"; import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig"; -import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig"; -import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig"; -import { ITraderConfig } from "@spt/models/spt/config/ITraderConfig"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ConfigServer } from "@spt/servers/ConfigServer"; import { SaveServer } from "@spt/servers/SaveServer"; -import { DatabaseService } from "@spt/services/DatabaseService"; -import { InsuranceService } from "@spt/services/InsuranceService"; import { LocalisationService } from "@spt/services/LocalisationService"; -import { MailSendService } from "@spt/services/MailSendService"; -import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService"; -import { PmcChatResponseService } from "@spt/services/PmcChatResponseService"; -import { RandomUtil } from "@spt/utils/RandomUtil"; -import { TimeUtil } from "@spt/utils/TimeUtil"; /** * Logic for handling In Raid callbacks @@ -44,39 +19,18 @@ import { TimeUtil } from "@spt/utils/TimeUtil"; export class InraidController { protected inRaidConfig: IInRaidConfig; - protected traderConfig: ITraderConfig; - protected locationConfig: ILocationConfig; - protected ragfairConfig: IRagfairConfig; - protected hideoutConfig: IHideoutConfig; protected botConfig: IBotConfig; constructor( @inject("PrimaryLogger") protected logger: ILogger, @inject("SaveServer") protected saveServer: SaveServer, - @inject("TimeUtil") protected timeUtil: TimeUtil, - @inject("DatabaseService") protected databaseService: DatabaseService, - @inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService, - @inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService, - @inject("QuestHelper") protected questHelper: QuestHelper, - @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper, - @inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator, - @inject("HealthHelper") protected healthHelper: HealthHelper, - @inject("TraderHelper") protected traderHelper: TraderHelper, @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("InsuranceService") protected insuranceService: InsuranceService, - @inject("InRaidHelper") protected inRaidHelper: InRaidHelper, @inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("ConfigServer") protected configServer: ConfigServer, - @inject("MailSendService") protected mailSendService: MailSendService, - @inject("RandomUtil") protected randomUtil: RandomUtil, ) { this.inRaidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID); - this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); - this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION); - this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); - this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT); this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); } @@ -112,507 +66,11 @@ export class InraidController * @param offraidData post-raid request data * @param sessionID Session id */ - public savePostRaidProgressLegacy(offraidData: ISaveProgressRequestData, sessionID: string): void + public savePostRaidProfileForScav(offraidData: IScavSaveRequestData, sessionID: string): void { - this.logger.debug(`Raid outcome: ${offraidData.exit}`); - - if (!this.inRaidConfig.save.loot) - { - return; - } - - if (offraidData.isPlayerScav) - { - this.savePlayerScavProgress(sessionID, offraidData); - } - else - { - this.savePmcProgress(sessionID, offraidData); - } - - // Set flea interval time to out-of-raid value - this.ragfairConfig.runIntervalSeconds = this.ragfairConfig.runIntervalValues.outOfRaid; - this.hideoutConfig.runIntervalSeconds = this.hideoutConfig.runIntervalValues.outOfRaid; - } - - /** - * Handle updating player profile post-pmc raid - * @param sessionID Session id - * @param postRaidRequest Post-raid data - */ - protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void - { - const serverProfile = this.saveServer.getProfile(sessionID); - - const locationName = serverProfile.inraid.location.toLowerCase(); - - const map: ILocationBase = this.databaseService.getLocation(locationName).base; - - const serverPmcProfile = serverProfile.characters.pmc; - const serverScavProfile = serverProfile.characters.scav; - - const isDead = true;// this.isPlayerDead(postRaidRequest.exit); - const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcProfile.Inventory.items); - - serverProfile.inraid.character = "pmc"; - - this.inRaidHelper.updateProfileBaseStats(serverPmcProfile, postRaidRequest, sessionID); - this.inRaidHelper.updatePmcProfileDataPostRaid(serverPmcProfile, postRaidRequest, sessionID); - - this.mergePmcAndScavEncyclopedias(serverPmcProfile, serverScavProfile); - - // Check for exit status - this.markOrRemoveFoundInRaidItems(postRaidRequest); - - postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( - postRaidRequest.profile.Inventory.items, - postRaidRequest.profile, - serverPmcProfile.InsuredItems, - postRaidRequest.profile.Inventory.fastPanel, - ); - this.inRaidHelper.addStackCountToMoneyFromRaid(postRaidRequest.profile.Inventory.items); - - // Purge profile of equipment/container items - this.inRaidHelper.setInventory(sessionID, serverPmcProfile, postRaidRequest.profile); - - this.healthHelper.saveVitality(serverPmcProfile, postRaidRequest.health, sessionID); - - // Get array of insured items+child that were lost in raid - const gearToStore = this.insuranceService.getGearLostInRaid( - serverPmcProfile, - postRaidRequest, - preRaidGear, - sessionID, - isDead, - ); - - if (gearToStore.length > 0) - { - this.insuranceService.storeGearLostInRaidToSendLater(sessionID, gearToStore); - } - - // Edge case - Handle usec players leaving lighthouse with Rogues angry at them - if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec") - { - // Decrement counter if it exists, don't go below 0 - const remainingCounter = serverPmcProfile?.Stats.Eft.OverallCounters.Items.find((x) => - x.Key.includes("UsecRaidRemainKills"), - ); - if (remainingCounter?.Value > 0) - { - remainingCounter.Value--; - } - } - - if (isDead) - { - this.pmcChatResponseService.sendKillerResponse( - sessionID, - serverPmcProfile, - postRaidRequest.profile.Stats.Eft.Aggressor, - ); - this.matchBotDetailsCacheService.clearCache(); - - this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcProfile, sessionID); - } - else - { - // Not dead - - // Check for cultist amulets in special slot (only slot it can fit) - const sacredAmulet = this.itemHelper.getItemFromPoolByTpl( - serverPmcProfile.Inventory.items, - ItemTpl.CULTISTAMULET_SACRED_AMULET, - "SpecialSlot"); - if (sacredAmulet) - { - // No charges left, delete it - if (sacredAmulet.upd.CultistAmulet.NumberOfUsages <= 0) - { - serverPmcProfile.Inventory.items.splice( - serverPmcProfile.Inventory.items.indexOf(sacredAmulet), - 1, - ); - } - else if (sacredAmulet.upd.CultistAmulet.NumberOfUsages > 0) - { - // Charges left, reduce by 1 - sacredAmulet.upd.CultistAmulet.NumberOfUsages--; - } - } - } - - const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((victim) => - ["pmcbear", "pmcusec"].includes(victim.Role.toLowerCase()), - ); - if (victims?.length > 0) - { - this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcProfile); - } - - this.insuranceService.sendInsuredItems(serverPmcProfile, sessionID, map.Id); - } - - /** - * Make changes to PMC profile after they've died in raid, - * Alter body part hp, handle insurance, delete inventory items, remove carried quest items - * @param postRaidSaveRequest Post-raid save request - * @param pmcData Pmc profile - * @param sessionID Session id - * @returns Updated profile object - */ - protected performPostRaidActionsWhenDead( - postRaidSaveRequest: ISaveProgressRequestData, - pmcData: IPmcData, - sessionID: string, - ): IPmcData - { - this.updatePmcHealthPostRaid(postRaidSaveRequest, pmcData); - this.inRaidHelper.deleteInventory(pmcData, sessionID); - - if (this.inRaidHelper.shouldQuestItemsBeRemovedOnDeath()) - { - // Find and remove the completed condition from profile if player died, otherwise quest is stuck in limbo - // and quest items cannot be picked up again - const allQuests = this.questHelper.getQuestsFromDb(); - const activeQuestIdsInProfile = pmcData.Quests.filter( - (profileQuest) => - ![QuestStatus.AvailableForStart, QuestStatus.Success, QuestStatus.Expired].includes( - profileQuest.status, - ), - ).map((x) => x.qid); - for (const questItem of postRaidSaveRequest.profile.Stats.Eft.CarriedQuestItems) - { - // Get quest/find condition for carried quest item - const questAndFindItemConditionId = this.questHelper.getFindItemConditionByQuestItem( - questItem, - activeQuestIdsInProfile, - allQuests, - ); - if (Object.keys(questAndFindItemConditionId)?.length > 0) - { - this.profileHelper.removeQuestConditionFromProfile(pmcData, questAndFindItemConditionId); - } - } - - // Empty out stored quest items from player inventory - pmcData.Stats.Eft.CarriedQuestItems = []; - } - - return pmcData; - } - - /** - * Adjust player characters body part hp post-raid - * @param postRaidSaveRequest post raid data - * @param pmcData player profile - */ - protected updatePmcHealthPostRaid(postRaidSaveRequest: ISaveProgressRequestData, pmcData: IPmcData): void - { - switch (postRaidSaveRequest.exit) - { - case PlayerRaidEndState.LEFT.toString(): - // Naughty pmc left the raid early! - this.reducePmcHealthToPercent(pmcData, 0.01); // 1% - break; - case PlayerRaidEndState.MISSING_IN_ACTION.toString(): - // Didn't reach exit in time - this.reducePmcHealthToPercent(pmcData, 0.3); // 30% - break; - default: - // Left raid properly, don't make any adjustments - break; - } - } - - /** - * Reduce body part hp to % of max - * @param pmcData profile to edit - * @param multiplier multiplier to apply to max health - */ - protected reducePmcHealthToPercent(pmcData: IPmcData, multiplier: number): void - { - for (const bodyPart of Object.values(pmcData.Health.BodyParts)) - { - (bodyPart).Health.Current = (bodyPart).Health.Maximum * multiplier; - } - } - - /** - * Handle updating the profile post-pscav raid - * @param sessionID Session id - * @param postRaidRequest Post-raid data of raid - */ - protected savePlayerScavProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void - { - const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID); const serverScavProfile = this.profileHelper.getScavProfile(sessionID); - const isDead = true;// this.isPlayerDead(postRaidRequest.exit); - const preRaidScavCharismaProgress = this.profileHelper.getSkillFromProfile( - serverScavProfile, - SkillTypes.CHARISMA, - )?.Progress; - this.saveServer.getProfile(sessionID).inraid.character = "scav"; - - this.inRaidHelper.updateProfileBaseStats(serverScavProfile, postRaidRequest, sessionID); - this.inRaidHelper.updateScavProfileDataPostRaid(serverScavProfile, postRaidRequest, sessionID); - - this.mergePmcAndScavEncyclopedias(serverScavProfile, serverPmcProfile); - - this.updatePmcCharismaSkillPostScavRaid(serverScavProfile, serverPmcProfile, preRaidScavCharismaProgress); - - // Completing scav quests create ConditionCounters, these values need to be transported to the PMC profile - if (this.profileHasConditionCounters(serverScavProfile)) - { - // Scav quest progress needs to be moved to pmc so player can see it in menu / hand them in - this.migrateScavQuestProgressToPmcProfile(serverScavProfile, serverPmcProfile); - } - - // Change loot FiR status based on exit status - this.markOrRemoveFoundInRaidItems(postRaidRequest); - - postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( - postRaidRequest.profile.Inventory.items, - postRaidRequest.profile, - serverPmcProfile.InsuredItems, - postRaidRequest.profile.Inventory.fastPanel, - ); - - // Some items from client profile don't have upd objects when they're single stack items - this.inRaidHelper.addStackCountToMoneyFromRaid(postRaidRequest.profile.Inventory.items); - - // Reset hp/regenerate loot - this.handlePostRaidPlayerScavProcess(serverScavProfile, sessionID, postRaidRequest, serverPmcProfile, isDead); - } - - /** - * merge two dictionaries together - * Prioritise pair that has true as a value - * @param primary main dictionary - * @param secondary Secondary dictionary - */ - protected mergePmcAndScavEncyclopedias(primary: IPmcData, secondary: IPmcData): void - { - function extend(target: { [key: string]: boolean }, source: Record) - { - for (const key in source) - { - if (Object.hasOwn(source, key)) - { - target[key] = source[key]; - } - } - return target; - } - - const merged = extend(extend({}, primary.Encyclopedia), secondary.Encyclopedia); - primary.Encyclopedia = merged; - secondary.Encyclopedia = merged; - } - - /** - * Post-scav-raid any charisma increase must be propigated into PMC profile - * @param postRaidServerScavProfile Scav profile after adjustments made from raid - * @param postRaidServerPmcProfile Pmc profile after raid - * @param preRaidScavCharismaProgress charisma progress value pre-raid - */ - protected updatePmcCharismaSkillPostScavRaid( - postRaidServerScavProfile: IPmcData, - postRaidServerPmcProfile: IPmcData, - preRaidScavCharismaProgress: number, - ): void - { - const postRaidScavCharismaSkill = this.profileHelper.getSkillFromProfile( - postRaidServerScavProfile, - SkillTypes.CHARISMA, - ); - const pmcCharismaSkill = this.profileHelper.getSkillFromProfile(postRaidServerPmcProfile, SkillTypes.CHARISMA); - const postRaidScavCharismaGain = postRaidScavCharismaSkill?.Progress - preRaidScavCharismaProgress ?? 0; - - // Scav gained charisma, add to pmc - if (postRaidScavCharismaGain > 0) - { - this.logger.debug(`Applying ${postRaidScavCharismaGain} Charisma skill gained in scav raid to PMC profile`); - pmcCharismaSkill.Progress += postRaidScavCharismaGain; - } - } - - /** - * Does provided profile contain any condition counters - * @param profile Profile to check for condition counters - * @returns Profile has condition counters - */ - protected profileHasConditionCounters(profile: IPmcData): boolean - { - if (!profile.TaskConditionCounters) - { - return false; - } - - return Object.keys(profile.TaskConditionCounters).length > 0; - } - - /** - * Scav quest progress isnt transferred automatically from scav to pmc, we do this manually - * @param scavProfile Scav profile with quest progress post-raid - * @param pmcProfile Server pmc profile to copy scav quest progress into - */ - protected migrateScavQuestProgressToPmcProfile(scavProfile: IPmcData, pmcProfile: IPmcData): void - { - const achievements = this.databaseService.getAchievements(); - - for (const quest of scavProfile.Quests) - { - const pmcQuest = pmcProfile.Quests.find((x) => x.qid === quest.qid); - if (!pmcQuest) - { - this.logger.warning(this.localisationService.getText("inraid-unable_to_migrate_pmc_quest_not_found_in_profile", quest.qid)); - continue; - } - - // Status values mismatch or statusTimers counts mismatch - if ( - quest.status !== pmcQuest.status - || Object.keys(quest.statusTimers).length !== Object.keys(pmcQuest.statusTimers).length - ) - { - this.logger.debug( - `Quest: ${quest.qid} found in PMC profile has different status/statustimer. Scav: ${quest.status} vs PMC: ${pmcQuest.status}`, - ); - pmcQuest.status = quest.status; - - // Copy status timers over + fix bad enum key for each - pmcQuest.statusTimers = quest.statusTimers; - for (const statusTimerKey in quest.statusTimers) - { - if (Number.isNaN(Number.parseInt(statusTimerKey))) - { - quest.statusTimers[QuestStatus[statusTimerKey]] = quest.statusTimers[statusTimerKey]; - delete quest.statusTimers[statusTimerKey]; - } - } - } - } - - // Loop over all scav counters and add into pmc profile - for (const scavCounter of Object.values(scavProfile.TaskConditionCounters)) - { - // If this is an achievement that isn't for the scav, don't process it - const achievement = achievements.find((achievement) => achievement.id === scavCounter.sourceId); - if (achievement && achievement.side !== "Savage") - { - continue; - } - - this.logger.debug( - `Processing counter: ${scavCounter.id} value: ${scavCounter.value} quest: ${scavCounter.sourceId}`, - ); - const counterInPmcProfile = pmcProfile.TaskConditionCounters[scavCounter.id]; - if (!counterInPmcProfile) - { - // Doesn't exist yet, push it straight in - pmcProfile.TaskConditionCounters[scavCounter.id] = scavCounter; - continue; - } - - this.logger.debug( - `Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.id}`, - ); - - // Only adjust counter value if its changed - if (counterInPmcProfile.value !== scavCounter.value) - { - this.logger.debug(`OVERWRITING with values: ${scavCounter.value} quest: ${scavCounter.sourceId}`); - counterInPmcProfile.value = scavCounter.value; - } - } - } - - /** - * Mark inventory items as FiR if player survived raid, otherwise remove FiR from them - * @param offraidData Save Progress Request - */ - protected markOrRemoveFoundInRaidItems(offraidData: ISaveProgressRequestData): void - { - if (offraidData.exit !== PlayerRaidEndState.SURVIVED) - { - // Remove FIR status if the player hasn't survived - offraidData.profile = this.inRaidHelper.removeSpawnedInSessionPropertyFromItems(offraidData.profile); - } - } - - /** - * Update profile after player completes scav raid - * @param scavData Scav profile - * @param sessionID Session id - * @param offraidData Post-raid save request - * @param pmcData Pmc profile - * @param isDead Is player dead - */ - protected handlePostRaidPlayerScavProcess( - scavData: IPmcData, - sessionID: string, - offraidData: ISaveProgressRequestData, - pmcData: IPmcData, - isDead: boolean, - ): void - { - // Update scav profile inventory - this.inRaidHelper.setInventory(sessionID, scavData, offraidData.profile); - - // Reset scav hp and save to json - this.healthHelper.resetVitality(sessionID); - this.saveServer.getProfile(sessionID).characters.scav = scavData; - - // Scav karma - this.handlePostRaidPlayerScavKarmaChanges(pmcData, offraidData, scavData); - - // Scav died, regen scav loadout and set timer - if (isDead) - { - this.playerScavGenerator.generate(sessionID); - } - - // Update last played property - pmcData.Info.LastTimePlayedAsSavage = this.timeUtil.getTimestamp(); - - this.saveServer.saveProfile(sessionID); - } - - /** - * Update profile with scav karma values based on in-raid actions - * @param pmcData Pmc profile - * @param offraidData Post-raid save request - * @param scavData Scav profile - */ - protected handlePostRaidPlayerScavKarmaChanges( - pmcData: IPmcData, - offraidData: ISaveProgressRequestData, - scavData: IPmcData, - ): void - { - const fenceId = Traders.FENCE; - - let fenceStanding = Number(offraidData.profile.TradersInfo[fenceId].standing); - - // Client doesn't calcualte car extract rep changes, must be done manually - // Successful extracts give rep - if (offraidData.exit === PlayerRaidEndState.SURVIVED) - { - fenceStanding += this.inRaidConfig.scavExtractGain; - } - - // Make standing changes to pmc profile - pmcData.TradersInfo[fenceId].standing = Math.min(Math.max(fenceStanding, -7), 15); // Ensure it stays between -7 and 15 - this.logger.debug(`New fence standing: ${pmcData.TradersInfo[fenceId].standing}`); - - this.traderHelper.lvlUp(fenceId, pmcData); - pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1); - - // Copy updated fence rep values into scav profile to ensure consistency - scavData.TradersInfo[fenceId].standing = pmcData.TradersInfo[fenceId].standing; - scavData.TradersInfo[fenceId].loyaltyLevel = pmcData.TradersInfo[fenceId].loyaltyLevel; + serverScavProfile.Inventory.items = offraidData.profile.Inventory.items; } /** diff --git a/project/src/helpers/InRaidHelper.ts b/project/src/helpers/InRaidHelper.ts index b8c950a8..d807b532 100644 --- a/project/src/helpers/InRaidHelper.ts +++ b/project/src/helpers/InRaidHelper.ts @@ -1,118 +1,26 @@ import { inject, injectable } from "tsyringe"; import { InventoryHelper } from "@spt/helpers/InventoryHelper"; -import { ItemHelper } from "@spt/helpers/ItemHelper"; -import { PaymentHelper } from "@spt/helpers/PaymentHelper"; -import { ProfileHelper } from "@spt/helpers/ProfileHelper"; -import { QuestHelper } from "@spt/helpers/QuestHelper"; -import { IPmcData, IPostRaidPmcData } from "@spt/models/eft/common/IPmcData"; -import { IQuestStatus, TraderInfo } from "@spt/models/eft/common/tables/IBotBase"; +import { IPmcData } from "@spt/models/eft/common/IPmcData"; import { Item } from "@spt/models/eft/common/tables/IItem"; -import { ISaveProgressRequestData } from "@spt/models/eft/inRaid/ISaveProgressRequestData"; -import { IFailQuestRequestData } from "@spt/models/eft/quests/IFailQuestRequestData"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; -import { QuestStatus } from "@spt/models/enums/QuestStatus"; -import { Traders } from "@spt/models/enums/Traders"; -import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig"; import { ILostOnDeathConfig } from "@spt/models/spt/config/ILostOnDeathConfig"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ConfigServer } from "@spt/servers/ConfigServer"; -import { SaveServer } from "@spt/servers/SaveServer"; -import { DatabaseService } from "@spt/services/DatabaseService"; -import { LocalisationService } from "@spt/services/LocalisationService"; -import { ProfileFixerService } from "@spt/services/ProfileFixerService"; import { ICloner } from "@spt/utils/cloners/ICloner"; -import { RandomUtil } from "@spt/utils/RandomUtil"; -import { TimeUtil } from "@spt/utils/TimeUtil"; @injectable() export class InRaidHelper { protected lostOnDeathConfig: ILostOnDeathConfig; - protected inRaidConfig: IInRaidConfig; constructor( @inject("PrimaryLogger") protected logger: ILogger, - @inject("TimeUtil") protected timeUtil: TimeUtil, - @inject("SaveServer") protected saveServer: SaveServer, - @inject("ItemHelper") protected itemHelper: ItemHelper, - @inject("DatabaseService") protected databaseService: DatabaseService, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper, - @inject("ProfileHelper") protected profileHelper: ProfileHelper, - @inject("QuestHelper") protected questHelper: QuestHelper, - @inject("PaymentHelper") protected paymentHelper: PaymentHelper, - @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("ProfileFixerService") protected profileFixerService: ProfileFixerService, @inject("ConfigServer") protected configServer: ConfigServer, - @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("PrimaryCloner") protected cloner: ICloner, ) { this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH); - this.inRaidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID); - } - - /** - * Lookup quest item loss from lostOnDeath config - * @returns True if items should be removed from inventory - */ - public shouldQuestItemsBeRemovedOnDeath(): boolean - { - return this.lostOnDeathConfig.questItems; - } - - /** - * Check items array and add an upd object to money with a stack count of 1 - * Single stack money items have no upd object and thus no StackObjectsCount, causing issues - * @param items Items array to check - */ - public addStackCountToMoneyFromRaid(items: Item[]): void - { - for (const moneyItem of items.filter((item) => this.paymentHelper.isMoneyTpl(item._tpl))) - { - this.itemHelper.addUpdObjectToItem(moneyItem); - - if (!moneyItem.upd.StackObjectsCount) - { - moneyItem.upd.StackObjectsCount = 1; - } - } - } - - /** - * Reset a profile to a baseline, used post-raid - * Reset points earned during session property - * Increment exp - * @param profileData Profile to update - * @param saveProgressRequest post raid save data request data - * @param sessionID Session id - * @returns Reset profile object - */ - public updateProfileBaseStats( - profileData: IPmcData, - saveProgressRequest: ISaveProgressRequestData, - sessionID: string, - ): void - { - // Remove skill fatigue values - this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile); - - // Set profile data - profileData.Info.Level = saveProgressRequest.profile.Info.Level; - profileData.Skills = saveProgressRequest.profile.Skills; - profileData.Stats.Eft = saveProgressRequest.profile.Stats.Eft; - - profileData.Encyclopedia = saveProgressRequest.profile.Encyclopedia; - profileData.TaskConditionCounters = saveProgressRequest.profile.TaskConditionCounters; - - this.validateTaskConditionCounters(saveProgressRequest, profileData); - - profileData.SurvivorClass = saveProgressRequest.profile.SurvivorClass; - - // Add experience points - profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience; - profileData.Stats.Eft.TotalSessionExperience = 0; - - this.setPlayerInRaidLocationStatusToNone(sessionID); } /** @@ -128,362 +36,6 @@ export class InRaidHelper } } - /** Check counters are correct in profile */ - protected validateTaskConditionCounters( - saveProgressRequest: ISaveProgressRequestData, - profileData: IPmcData, - ): void - { - for (const backendCounterKey in saveProgressRequest.profile.TaskConditionCounters) - { - // Skip counters with no id - if (!saveProgressRequest.profile.TaskConditionCounters[backendCounterKey].id) - { - continue; - } - - const postRaidValue = saveProgressRequest.profile.TaskConditionCounters[backendCounterKey]?.value; - if (typeof postRaidValue === "undefined") - { - // No value, skip - continue; - } - - const matchingPreRaidCounter = profileData.TaskConditionCounters[backendCounterKey]; - if (!matchingPreRaidCounter) - { - this.logger.error(this.localisationService.getText("inraid-unable_to_find_key_in_taskconditioncounters", backendCounterKey)); - - continue; - } - - if (matchingPreRaidCounter.value !== postRaidValue) - { - this.logger.error(this.localisationService.getText("inraid-taskconditioncounter_keys_differ", - { key: backendCounterKey, oldValue: matchingPreRaidCounter.value, newValue: postRaidValue })); - } - } - } - - /** - * Update various serverPMC profile values; quests/limb hp/trader standing with values post-raic - * @param pmcData Server PMC profile - * @param saveProgressRequest Post-raid request data - * @param sessionId Session id - */ - public updatePmcProfileDataPostRaid( - pmcData: IPmcData, - saveProgressRequest: ISaveProgressRequestData, - sessionId: string, - ): void - { - // Process failed quests then copy everything - 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 - this.transferPostRaidLimbEffectsToProfile(saveProgressRequest, pmcData); - - // Trader standing only occur on pmc profile, scav kills are handled in handlePostRaidPlayerScavKarmaChanges() - // Scav client data has standing values of 0 for all traders, DO NOT RUN ON SCAV RAIDS - this.applyTraderStandingAdjustments(pmcData.TradersInfo, saveProgressRequest.profile.TradersInfo); - - this.updateProfileAchievements(pmcData, saveProgressRequest.profile.Achievements); - - this.profileFixerService.checkForAndFixPmcProfileIssues(pmcData); - } - - /** - * Update scav quest values on server profile with updated values post-raid - * @param scavData Server scav profile - * @param saveProgressRequest Post-raid request data - * @param sessionId Session id - */ - public updateScavProfileDataPostRaid( - scavData: IPmcData, - saveProgressRequest: ISaveProgressRequestData, - sessionId: string, - ): void - { - // Only copy active quests into scav profile // Progress will later to copied over to PMC profile - const existingActiveQuestIds = scavData.Quests - ?.filter((quest) => quest.status !== QuestStatus.AvailableForStart) - .map((x) => x.qid); - if (existingActiveQuestIds) - { - scavData.Quests = saveProgressRequest.profile.Quests - .filter((quest) => existingActiveQuestIds.includes(quest.qid)); - } - - this.profileFixerService.checkForAndFixScavProfileIssues(scavData); - } - - /** - * 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 with post-raid quests - */ - 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 - return; - } - - // Loop over all quests from post-raid profile - const newLockedQuests: IQuestStatus[] = []; - for (const postRaidQuest of postRaidProfile.Quests) - { - // postRaidQuest.status has a weird value, need to do some nasty casting to compare it - const postRaidQuestStatus = (postRaidQuest.status); - - // Find matching pre-raid quest, skip if we can't - const preRaidQuest = preRaidQuests?.find((preRaidQuest) => preRaidQuest.qid === postRaidQuest.qid); - if (!preRaidQuest) - { - continue; - } - - // Already completed/failed before raid, skip - if ([QuestStatus.Fail, QuestStatus.Success].includes(preRaidQuest.status)) - { - // Daily quests get their status altered in-raid to "AvailableForStart", - // Copy pre-raid status to post raid data - if (preRaidQuest.status === QuestStatus.Success) - { - postRaidQuest.status = QuestStatus.Success; - } - - if (preRaidQuest.status === QuestStatus.Fail) - { - postRaidQuest.status = QuestStatus.Fail; - } - - continue; - } - - // Quest with time-gate has unlocked - if ( - postRaidQuestStatus === "AvailableAfter" - && postRaidQuest.availableAfter <= this.timeUtil.getTimestamp() - ) - { - // Flag as ready to start - 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 - const failBody: IFailQuestRequestData = { - Action: "QuestFail", - qid: postRaidQuest.qid, - removeExcessItems: true, - }; - this.questHelper.failQuest(pmcData, failBody, sessionId); - } - // Restartable quests need special actions - else if (postRaidQuestStatus === "FailRestartable") - { - this.handleFailRestartableQuestStatus(pmcData, postRaidProfile, postRaidQuest); - } - } - } - - protected handleFailRestartableQuestStatus( - pmcData: IPmcData, - postRaidProfile: IPostRaidPmcData, - postRaidQuest: IQuestStatus): void - { - // Does failed quest have requirement to collect items from raid - const questDbData = this.questHelper.getQuestFromDb(postRaidQuest.qid, pmcData); - - // AvailableForFinish - const matchingAffFindConditions = questDbData.conditions.AvailableForFinish.filter( - (condition) => condition.conditionType === "FindItem", - ); - const itemsToCollect: string[] = []; - if (matchingAffFindConditions) - { - // Find all items the failed quest wanted - for (const condition of matchingAffFindConditions) - { - itemsToCollect.push(...condition.target); - } - } - - // Remove quest items from profile as quest has failed and may still be alive - // Required as restarting the quest from main menu does not remove value from CarriedQuestItems array - postRaidProfile.Stats.Eft.CarriedQuestItems = postRaidProfile.Stats.Eft.CarriedQuestItems.filter( - (carriedQuestItem) => !itemsToCollect.includes(carriedQuestItem), - ); - - // Remove quest item from profile now quest is failed - // updateProfileBaseStats() has already passed by ref EFT.Stats, all changes applied to postRaid profile also apply to server profile - for (const itemTpl of itemsToCollect) - { - // Look for sessioncounter and remove it - const counterIndex = postRaidProfile.Stats.Eft.SessionCounters.Items.findIndex( - (counter) => counter.Key.includes(itemTpl) && counter.Key.includes("LootItem"), - ); - if (counterIndex > -1) - { - postRaidProfile.Stats.Eft.SessionCounters.Items.splice(counterIndex, 1); - } - - // Look for quest item and remove it - const inventoryItemIndex = postRaidProfile.Inventory.items.findIndex((x) => x._tpl === itemTpl); - if (inventoryItemIndex > -1) - { - postRaidProfile.Inventory.items.splice(inventoryItemIndex, 1); - } - } - - // Clear out any completed conditions - postRaidQuest.completedConditions = []; - } - - /** - * Take body part effects from client profile and apply to server profile - * @param saveProgressRequest post-raid request - * @param profileData player profile on server - */ - protected transferPostRaidLimbEffectsToProfile( - saveProgressRequest: ISaveProgressRequestData, - profileData: IPmcData, - ): void - { - // Iterate over each body part - for (const bodyPartId in saveProgressRequest.profile.Health.BodyParts) - { - // Get effects on body part from profile - const bodyPartEffects = saveProgressRequest.profile.Health.BodyParts[bodyPartId].Effects; - for (const effect in bodyPartEffects) - { - const effectDetails = bodyPartEffects[effect]; - - // Null guard - if (!profileData.Health.BodyParts[bodyPartId].Effects) - { - profileData.Health.BodyParts[bodyPartId].Effects = {}; - } - - // Already exists on server profile, skip - const profileBodyPartEffects = profileData.Health.BodyParts[bodyPartId].Effects; - if (profileBodyPartEffects[effect]) - { - continue; - } - - // Add effect to server profile - profileBodyPartEffects[effect] = { Time: effectDetails.Time ?? -1 }; - } - } - } - - /** - * Adjust server trader settings if they differ from data sent by client - * @param tradersServerProfile Server - * @param tradersClientProfile Client - */ - protected applyTraderStandingAdjustments( - tradersServerProfile: Record, - tradersClientProfile: Record, - ): void - { - for (const traderId in tradersClientProfile) - { - if (traderId === Traders.FENCE) - { - // Taking a car extract adjusts fence rep values via client/match/offline/end, skip fence for this check - continue; - } - - const serverProfileTrader = tradersServerProfile[traderId]; - const clientProfileTrader = tradersClientProfile[traderId]; - if (!(serverProfileTrader && clientProfileTrader)) - { - continue; - } - - if (clientProfileTrader.standing !== serverProfileTrader.standing) - { - // Difference found, update server profile with values from client profile - tradersServerProfile[traderId].standing = clientProfileTrader.standing; - } - } - } - - /** - * Transfer client achievements into profile - * @param profile Player pmc profile - * @param clientAchievements Achievements from client - */ - protected updateProfileAchievements(profile: IPmcData, clientAchievements: Record): void - { - profile.Achievements ||= {}; - - for (const achievementId in clientAchievements) - { - profile.Achievements[achievementId] = clientAchievements[achievementId]; - } - } - - /** - * Set the SPT inraid location Profile property to 'none' - * @param sessionID Session id - */ - protected setPlayerInRaidLocationStatusToNone(sessionID: string): void - { - this.saveServer.getProfile(sessionID).inraid.location = "none"; - } - - /** - * Iterate over inventory items and remove the property that defines an item as Found in Raid - * Only removes property if item had FiR when entering raid - * @param postRaidProfile profile to update items for - * @returns Updated profile with SpawnedInSession removed - */ - public removeSpawnedInSessionPropertyFromItems(postRaidProfile: IPostRaidPmcData): IPostRaidPmcData - { - const dbItems = this.databaseService.getItems(); - const itemsToRemovePropertyFrom = postRaidProfile.Inventory.items.filter((item) => - { - // Has upd object + upd.SpawnedInSession property + not a quest item - return ( - "upd" in item - && "SpawnedInSession" in item.upd - && !dbItems[item._tpl]._props.QuestItem - && !( - this.inRaidConfig.keepFiRSecureContainerOnDeath - && this.itemHelper.itemIsInsideContainer(item, "SecuredContainer", postRaidProfile.Inventory.items) - ) - ); - }); - - for (const item of itemsToRemovePropertyFrom) - { - delete item.upd.SpawnedInSession; - } - - return postRaidProfile; - } - /** * Update a players inventory post-raid * Remove equipped items from pre-raid @@ -564,25 +116,6 @@ export class InRaidHelper }); } - /** - * Get items in vest/pocket/backpack inventory containers (excluding children) - * @param pmcData Player profile - * @returns Item array - */ - protected getBaseItemsInRigPocketAndBackpack(pmcData: IPmcData): Item[] - { - const items = pmcData.Inventory.items; - const rig = items.find((x) => x.slotId === "TacticalVest"); - const pockets = items.find((x) => x.slotId === "Pockets"); - const backpack = items.find((x) => x.slotId === "Backpack"); - - const baseItemsInRig = items.filter((x) => x.parentId === rig?._id); - const baseItemsInPockets = items.filter((x) => x.parentId === pockets?._id); - const baseItemsInBackpack = items.filter((x) => x.parentId === backpack?._id); - - return [...baseItemsInRig, ...baseItemsInPockets, ...baseItemsInBackpack]; - } - /** * Does the provided items slotId mean its kept on the player after death * @pmcData Player profile @@ -635,75 +168,4 @@ export class InRaidHelper // All other cases item is lost return false; } - - /** - * @deprecated - * Return the equipped items from a players inventory - * @param items Players inventory to search through - * @returns an array of equipped items - */ - public getPlayerGear(items: Item[]): Item[] - { - // Player Slots we care about - const inventorySlots = [ - "FirstPrimaryWeapon", - "SecondPrimaryWeapon", - "Holster", - "Scabbard", - "Compass", - "Headwear", - "Earpiece", - "Eyewear", - "FaceCover", - "ArmBand", - "ArmorVest", - "TacticalVest", - "Backpack", - "pocket1", - "pocket2", - "pocket3", - "pocket4", - "SpecialSlot1", - "SpecialSlot2", - "SpecialSlot3", - ]; - - let inventoryItems: Item[] = []; - - // Get an array of root player items - for (const item of items) - { - if (inventorySlots.includes(item.slotId)) - { - inventoryItems.push(item); - } - } - - // Loop through these items and get all of their children - let newItems = inventoryItems; - while (newItems.length > 0) - { - const foundItems = []; - - for (const item of newItems) - { - // Find children of this item - for (const newItem of items) - { - if (newItem.parentId === item._id) - { - foundItems.push(newItem); - } - } - } - - // Add these new found items to our list of inventory items - inventoryItems = [...inventoryItems, ...foundItems]; - - // Now find the children of these items - newItems = foundItems; - } - - return inventoryItems; - } } diff --git a/project/src/models/eft/inRaid/ISaveProgressRequestData.ts b/project/src/models/eft/inRaid/IScavSaveRequestData.ts similarity index 59% rename from project/src/models/eft/inRaid/ISaveProgressRequestData.ts rename to project/src/models/eft/inRaid/IScavSaveRequestData.ts index e79af2c1..a56f9c95 100644 --- a/project/src/models/eft/inRaid/ISaveProgressRequestData.ts +++ b/project/src/models/eft/inRaid/IScavSaveRequestData.ts @@ -3,11 +3,7 @@ import { ISyncHealthRequestData } from "@spt/models/eft/health/ISyncHealthReques import { IInsuredItemsData } from "@spt/models/eft/inRaid/IInsuredItemsData"; import { PlayerRaidEndState } from "@spt/models/enums/PlayerRaidEndState"; -export interface ISaveProgressRequestData +export interface IScavSaveRequestData { - exit: PlayerRaidEndState // survived" | "killed" | "left" | "runner" | "missinginaction profile: IPostRaidPmcData - isPlayerScav: boolean - health: ISyncHealthRequestData - insurance: IInsuredItemsData[] } diff --git a/project/src/models/spt/callbacks/IInraidCallbacks.ts b/project/src/models/spt/callbacks/IInraidCallbacks.ts deleted file mode 100644 index 3bbb3bca..00000000 --- a/project/src/models/spt/callbacks/IInraidCallbacks.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData"; -import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData"; -import { IRegisterPlayerRequestData } from "@spt/models/eft/inRaid/IRegisterPlayerRequestData"; -import { ISaveProgressRequestData } from "@spt/models/eft/inRaid/ISaveProgressRequestData"; -import { ISptProfile } from "@spt/models/eft/profile/ISptProfile"; - -export interface IInraidCallbacks -{ - onLoad(sessionID: string): ISptProfile - registerPlayer(url: string, info: IRegisterPlayerRequestData, sessionID: string): INullResponseData - saveProgress(url: string, info: ISaveProgressRequestData, sessionID: string): INullResponseData - getRaidEndState(): string - getRaidMenuSettings(url: string, info: IEmptyRequestData, sessionID: string): string - getWeaponDurability(url: string, info: any, sessionID: string): string - getAirdropConfig(url: string, info: any, sessionID: string): string -} diff --git a/project/src/routers/static/InraidStaticRouter.ts b/project/src/routers/static/InraidStaticRouter.ts index 5193fd38..42e50e4c 100644 --- a/project/src/routers/static/InraidStaticRouter.ts +++ b/project/src/routers/static/InraidStaticRouter.ts @@ -10,7 +10,7 @@ export class InraidStaticRouter extends StaticRouter { super([ new RouteAction( - "/raid/profile/save", + "/raid/profile/scavsave", async (url: string, info: any, sessionID: string, output: string): Promise => { return this.inraidCallbacks.saveProgress(url, info, sessionID); diff --git a/project/src/services/InsuranceService.ts b/project/src/services/InsuranceService.ts index 5d52cb6d..cdab105e 100644 --- a/project/src/services/InsuranceService.ts +++ b/project/src/services/InsuranceService.ts @@ -1,26 +1,19 @@ import { inject, injectable } from "tsyringe"; -import { DialogueHelper } from "@spt/helpers/DialogueHelper"; -import { HandbookHelper } from "@spt/helpers/HandbookHelper"; import { ItemHelper } from "@spt/helpers/ItemHelper"; -import { SecureContainerHelper } from "@spt/helpers/SecureContainerHelper"; import { TraderHelper } from "@spt/helpers/TraderHelper"; import { IPmcData } from "@spt/models/eft/common/IPmcData"; import { Item } from "@spt/models/eft/common/tables/IItem"; import { ITraderBase } from "@spt/models/eft/common/tables/ITrader"; -import { IInsuredItemsData } from "@spt/models/eft/inRaid/IInsuredItemsData"; -import { ISaveProgressRequestData } from "@spt/models/eft/inRaid/ISaveProgressRequestData"; import { BonusType } from "@spt/models/enums/BonusType"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { ItemTpl } from "@spt/models/enums/ItemTpl"; import { MessageType } from "@spt/models/enums/MessageType"; import { IInsuranceConfig } from "@spt/models/spt/config/IInsuranceConfig"; -import { ILostOnDeathConfig } from "@spt/models/spt/config/ILostOnDeathConfig"; import { IInsuranceEquipmentPkg } from "@spt/models/spt/services/IInsuranceEquipmentPkg"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ConfigServer } from "@spt/servers/ConfigServer"; import { SaveServer } from "@spt/servers/SaveServer"; import { DatabaseService } from "@spt/services/DatabaseService"; -import { LocaleService } from "@spt/services/LocaleService"; import { LocalisationService } from "@spt/services/LocalisationService"; import { MailSendService } from "@spt/services/MailSendService"; import { ICloner } from "@spt/utils/cloners/ICloner"; @@ -33,29 +26,23 @@ export class InsuranceService { protected insured: Record> = {}; protected insuranceConfig: IInsuranceConfig; - protected lostOnDeathConfig: ILostOnDeathConfig; constructor( @inject("PrimaryLogger") protected logger: ILogger, @inject("DatabaseService") protected databaseService: DatabaseService, - @inject("SecureContainerHelper") protected secureContainerHelper: SecureContainerHelper, @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("HashUtil") protected hashUtil: HashUtil, @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("SaveServer") protected saveServer: SaveServer, @inject("TraderHelper") protected traderHelper: TraderHelper, - @inject("DialogueHelper") protected dialogueHelper: DialogueHelper, - @inject("HandbookHelper") protected handbookHelper: HandbookHelper, @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("LocaleService") protected localeService: LocaleService, @inject("MailSendService") protected mailSendService: MailSendService, @inject("ConfigServer") protected configServer: ConfigServer, @inject("PrimaryCloner") protected cloner: ICloner, ) { this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE); - this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH); } /** @@ -78,17 +65,6 @@ export class InsuranceService return this.insured[sessionId]; } - /** - * Get insured items by profile id + trader id - * @param sessionId Profile id (session id) - * @param traderId Trader items were insured with - * @returns Item array - */ - public getInsuranceItems(sessionId: string, traderId: string): Item[] - { - return this.insured[sessionId][traderId]; - } - public resetInsurance(sessionId: string): void { this.insured[sessionId] = {}; @@ -158,27 +134,6 @@ export class InsuranceService this.resetInsurance(sessionID); } - /** - * Check all root insured items and remove location property + set slotId to 'hideout' - * @param sessionId Session id - * @param traderId Trader id - */ - protected removeLocationProperty(sessionId: string, traderId: string): void - { - const insuredItems = this.getInsurance(sessionId)[traderId]; - for (const insuredItem of this.getInsurance(sessionId)[traderId]) - { - // Find insured items parent - const insuredItemsParent = insuredItems.some((x) => x._id === insuredItem.parentId); - if (!insuredItemsParent) - { - // Remove location + set slotId of insured items parent - insuredItem.slotId = "hideout"; - delete insuredItem.location; - } - } - } - /** * Get a timestamp of when insurance items should be sent to player based on trader used to insure * Apply insurance return bonus if found in profile @@ -228,143 +183,6 @@ export class InsuranceService return this.timeUtil.getTimestamp() + randomisedReturnTimeSeconds * insuranceReturnTimeBonusPercent; } - /** - * @deprecated - Client now handles this - * Create insurance equipment packages that should be sent to the user. The packages should contain items that have - * been lost in a raid and should be returned to the player through the insurance system. - * - * NOTE: We do not have data on items that were dropped in a raid. This means we have to pull item data from the - * profile at the start of the raid to return to the player in insurance. Because of this, the item - * positioning may differ from the position the item was in when the player died. Apart from removing all - * positioning, this is the best we can do. >:{} - * - * @param pmcData Player profile - * @param offraidData Post-raid data - * @param preRaidGear Pre-raid data - * @param sessionID Session id - * @param playerDied Did player die in raid - * @returns Array of insured items lost in raid - */ - public getGearLostInRaid( - pmcData: IPmcData, - offraidData: ISaveProgressRequestData, - preRaidGear: Item[], - sessionID: string, - playerDied: boolean, - ): IInsuranceEquipmentPkg[] - { - const equipmentPkg: IInsuranceEquipmentPkg[] = []; - const preRaidGearMap = this.itemHelper.generateItemsMap(preRaidGear); - const offRaidGearMap = this.itemHelper.generateItemsMap(offraidData.profile.Inventory.items); - - for (const insuredItem of pmcData.InsuredItems) - { - // Skip insured items not on player when they started the raid. - if (!preRaidGearMap.has(insuredItem.itemId)) - { - continue; - } - - const preRaidItem = preRaidGearMap.get(insuredItem.itemId)!; - - // Skip slots we should never return as they're never lost on death - if (this.insuranceConfig.blacklistedEquipment.includes(preRaidItem.slotId!)) - { - continue; - } - - // Equipment slots can be flagged as never lost on death and shouldn't be saved in an insurance package. - // We need to check if the item is directly equipped to an equipment slot, or if it is a child Item of an - // equipment slot. - const equipmentParentItem = this.itemHelper.getEquipmentParent(preRaidItem._id, preRaidGearMap); - - const offraidDataitem = offraidData.insurance?.find( - (insuranceItem) => insuranceItem.id === insuredItem.itemId); - - if (offraidDataitem?.usedInQuest) - { - continue; - } - - // Now that we have the equipment parent item, we can check to see if that item is located in an equipment - // slot that is flagged as lost on death. If it is, then the itemShouldBeLostOnDeath. - const itemShouldBeLostOnDeath = equipmentParentItem?.slotId - ? this.lostOnDeathConfig.equipment[equipmentParentItem?.slotId] ?? true - : true; - - // Was the item found in the player inventory post-raid? - const itemOnPlayerPostRaid = offRaidGearMap.has(insuredItem.itemId); - - // Check if item missing in post-raid gear OR player died + item slot flagged as lost on death - // Catches both events: player died with item on + player survived but dropped item in raid - if (!itemOnPlayerPostRaid || (playerDied && itemShouldBeLostOnDeath)) - { - const inventoryInsuredItem = offraidData.insurance - ?.find((insuranceItem) => insuranceItem.id === insuredItem.itemId); - if (!inventoryInsuredItem) - { - throw new Error(this.localisationService.getText("insurance-item_not_found_in_post_raid_data", insuredItem.itemId)); - } - - equipmentPkg.push({ - pmcData: pmcData, - itemToReturnToPlayer: this.getInsuredItemDetails( - pmcData, - preRaidItem, - inventoryInsuredItem, - ), - traderId: insuredItem.tid, - sessionID: sessionID, - }); - - // Armor item with slots, we need to include soft_inserts as they can never be removed from armor items - if (this.itemHelper.armorItemCanHoldMods(preRaidItem._tpl)) - { - if (this.itemHelper.itemHasSlots(preRaidItem._tpl)) - { - // Get IDs of all soft insert child items on armor from pre raid gear data - const softInsertChildIds = preRaidGear - .filter( - (item) => - item.parentId === preRaidItem._id - && this.itemHelper.getSoftInsertSlotIds().includes(item.slotId!.toLowerCase()), - ) - .map((x) => x._id); - - // Add all items found above to return data - for (const softInsertChildModId of softInsertChildIds) - { - const preRaidInventoryItem = preRaidGear.find((item) => item._id === softInsertChildModId); - if (!preRaidInventoryItem) - { - throw new Error(this.localisationService.getText("insurance-pre_raid_item_not_found", softInsertChildModId)); - } - const inventoryInsuredItem = offraidData.insurance?.find( - (insuranceItem) => insuranceItem.id === softInsertChildModId, - ); - if (!inventoryInsuredItem) - { - throw new Error(this.localisationService.getText("insurance-post_raid_item_not_found", softInsertChildModId)); - } - equipmentPkg.push({ - pmcData: pmcData, - itemToReturnToPlayer: this.getInsuredItemDetails( - pmcData, - preRaidInventoryItem, - inventoryInsuredItem, - ), - traderId: insuredItem.tid, - sessionID: sessionID, - }); - } - } - } - } - } - - return equipmentPkg; - } - /** * Take the insurance item packages within a profile session and ensure that each of the items in that package are * not orphaned from their parent ID. @@ -398,75 +216,6 @@ export class InsuranceService this.adoptOrphanedInsEquipment(sessionID); } - /** - * Take preraid item and update properties to ensure its ready to be given to player in insurance return mail - * @param pmcData Player profile - * @param preRaidItemWithChildren Insured item (with children) as it was pre-raid - * @param allItemsFromClient Item data when player left raid (durability values) - * @returns Item (with children) to send to player - */ - protected getInsuredItemDetails( - pmcData: IPmcData, - preRaidItem: Item, - insuredItemFromClient: IInsuredItemsData, - ): Item - { - // Get baseline item to return, clone pre raid item - const itemToReturnClone: Item = this.cloner.clone(preRaidItem); - - // Add upd if it doesnt exist - this.itemHelper.addUpdObjectToItem(itemToReturnClone); - - // Check for slotid values that need to be updated and adjust - this.updateSlotIdValue(pmcData.Inventory.equipment, itemToReturnClone); - - // Remove location property - if (itemToReturnClone.slotId === "hideout" && "location" in itemToReturnClone) - { - delete itemToReturnClone.location; - } - - // Remove found in raid status when upd exists + SpawnedInSession value exists - if ("SpawnedInSession" in itemToReturnClone.upd!) - { - itemToReturnClone.upd.SpawnedInSession = false; - } - - // Client item has durability values, Ensure values persist into server data - if (insuredItemFromClient?.durability) - { - // Item didnt have Repairable object pre-raid, add it - if (!itemToReturnClone.upd?.Repairable) - { - itemToReturnClone.upd!.Repairable = { - Durability: insuredItemFromClient.durability, - MaxDurability: insuredItemFromClient.maxDurability!, - }; - } - else - { - itemToReturnClone.upd.Repairable.Durability = insuredItemFromClient.durability; - itemToReturnClone.upd.Repairable.MaxDurability = insuredItemFromClient.maxDurability!; - } - } - - // Client item has FaceShield values, Ensure values persist into server data - if (insuredItemFromClient?.hits) - { - // Item didnt have faceshield object pre-raid, add it - if (!itemToReturnClone.upd?.FaceShield) - { - itemToReturnClone.upd!.FaceShield = { Hits: insuredItemFromClient.hits }; - } - else - { - itemToReturnClone.upd.FaceShield.Hits = insuredItemFromClient.hits; - } - } - - return itemToReturnClone; - } - /** * For the passed in items, find the trader it was insured against * @param sessionId Session id @@ -503,28 +252,6 @@ export class InsuranceService return result; } - /** - * Reset slotId property to "hideout" when necessary (used to be in ) - * @param pmcData Players pmcData.Inventory.equipment value - * @param itemToReturn item we will send to player as insurance return - */ - protected updateSlotIdValue(playerBaseInventoryEquipmentId: string, itemToReturn: Item): void - { - const pocketSlots = ["pocket1", "pocket2", "pocket3", "pocket4"]; - - // Some pockets can lose items with player death, some don't - if (!("slotId" in itemToReturn) || pocketSlots.includes(itemToReturn.slotId!)) - { - itemToReturn.slotId = "hideout"; - } - - // Mark root-level items for later processing - if (itemToReturn.parentId === playerBaseInventoryEquipmentId) - { - itemToReturn.slotId = "hideout"; - } - } - /** * Add gear item to InsuredItems array in player profile * @param sessionID Session id