From ddb9917c6b03cea4dc7d27f27d1c1b356f64edcd Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 20 Nov 2023 16:33:04 +0000 Subject: [PATCH] Rework of post-raid scav/pmc profile handling: Moved logic out of `updateProfileBaseStats()` and into separate functions for pmc/scav, left profile-agnostic code alone new functions `updatePmcProfileDataPostRaid` and `updateScavProfileDataPostRaid` scav - Only copy active quest progress from client profile to server scav profile scav - dont attempt to update trader standings, none exist on scav profile scav - dont transfer psot-raid limb damage to server profile Update quest status values similarly to PMC quests post raid to ensure they're consistent with existing quest data in profile Simplifies `migrateScavQuestProgressToPmcProfile` made various warnings debug instead --- project/src/controllers/InraidController.ts | 71 +++++++------ project/src/helpers/InRaidHelper.ts | 111 +++++++++++++------- project/src/servers/SaveServer.ts | 2 +- project/src/services/ProfileFixerService.ts | 9 ++ 4 files changed, 121 insertions(+), 72 deletions(-) diff --git a/project/src/controllers/InraidController.ts b/project/src/controllers/InraidController.ts index e0910ff1..4dc71241 100644 --- a/project/src/controllers/InraidController.ts +++ b/project/src/controllers/InraidController.ts @@ -107,19 +107,20 @@ export class InraidController */ protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void { - const serverProfile = this.saveServer.getProfile(sessionID); - const locationName = serverProfile.inraid.location.toLowerCase(); + const serveProfile = this.saveServer.getProfile(sessionID); + const locationName = serveProfile.inraid.location.toLowerCase(); const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base; const mapHasInsuranceEnabled = map.Insurance; - let serverPmcData = serverProfile.characters.pmc; + let serverPmcProfile = serveProfile.characters.pmc; const isDead = this.isPlayerDead(postRaidRequest.exit); - const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcData.Inventory.items); + const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcProfile.Inventory.items); - serverProfile.inraid.character = "pmc"; + serveProfile.inraid.character = "pmc"; - serverPmcData = this.inRaidHelper.updateProfileBaseStats(serverPmcData, postRaidRequest, sessionID); + this.inRaidHelper.updateProfileBaseStats(serverPmcProfile, postRaidRequest, sessionID); + this.inRaidHelper.updatePmcProfileDataPostRaid(serverPmcProfile, postRaidRequest, sessionID); // Check for exit status this.markOrRemoveFoundInRaidItems(postRaidRequest); @@ -127,20 +128,20 @@ export class InraidController postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( postRaidRequest.profile, postRaidRequest.profile.Inventory.items, - serverPmcData.InsuredItems, + serverPmcProfile.InsuredItems, postRaidRequest.profile.Inventory.fastPanel, ); this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items); // Purge profile of equipment/container items - serverPmcData = this.inRaidHelper.setInventory(sessionID, serverPmcData, postRaidRequest.profile); + serverPmcProfile = this.inRaidHelper.setInventory(sessionID, serverPmcProfile, postRaidRequest.profile); - this.healthHelper.saveVitality(serverPmcData, postRaidRequest.health, sessionID); + this.healthHelper.saveVitality(serverPmcProfile, postRaidRequest.health, sessionID); // Remove inventory if player died and send insurance items if (mapHasInsuranceEnabled) { - this.insuranceService.storeLostGear(serverPmcData, postRaidRequest, preRaidGear, sessionID, isDead); + this.insuranceService.storeLostGear(serverPmcProfile, postRaidRequest, preRaidGear, sessionID, isDead); } else { @@ -151,7 +152,7 @@ export class InraidController if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec") { // Decrement counter if it exists, don't go below 0 - const remainingCounter = serverPmcData?.Stats.Eft.OverallCounters.Items.find((x) => + const remainingCounter = serverPmcProfile?.Stats.Eft.OverallCounters.Items.find((x) => x.Key.includes("UsecRaidRemainKills") ); if (remainingCounter?.Value > 0) @@ -164,12 +165,12 @@ export class InraidController { this.pmcChatResponseService.sendKillerResponse( sessionID, - serverPmcData, + serverPmcProfile, postRaidRequest.profile.Stats.Eft.Aggressor, ); this.matchBotDetailsCacheService.clearCache(); - serverPmcData = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcData, sessionID); + serverPmcProfile = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcProfile, sessionID); } const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) => @@ -177,12 +178,12 @@ export class InraidController ); if (victims?.length > 0) { - this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcData); + this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcProfile); } if (mapHasInsuranceEnabled) { - this.insuranceService.sendInsuredItems(serverPmcData, sessionID, map.Id); + this.insuranceService.sendInsuredItems(serverPmcProfile, sessionID, map.Id); } } @@ -274,39 +275,43 @@ export class InraidController */ protected savePlayerScavProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void { - const pmcData = this.profileHelper.getPmcProfile(sessionID); - let scavData = this.profileHelper.getScavProfile(sessionID); + const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID); + const serverScavProfile = this.profileHelper.getScavProfile(sessionID); const isDead = this.isPlayerDead(postRaidRequest.exit); this.saveServer.getProfile(sessionID).inraid.character = "scav"; - scavData = this.inRaidHelper.updateProfileBaseStats(scavData, postRaidRequest, sessionID); + this.inRaidHelper.updateProfileBaseStats(serverScavProfile, postRaidRequest, sessionID); + this.inRaidHelper.updateScavProfileDataPostRaid(serverScavProfile, postRaidRequest, sessionID); // Completing scav quests create ConditionCounters, these values need to be transported to the PMC profile - if (this.profileHasConditionCounters(scavData)) + 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(scavData, pmcData); + this.migrateScavQuestProgressToPmcProfile(serverScavProfile, serverPmcProfile); } - // Check for exit status + // Change loot FiR status based on exit status this.markOrRemoveFoundInRaidItems(postRaidRequest); postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( postRaidRequest.profile, postRaidRequest.profile.Inventory.items, - pmcData.InsuredItems, + serverPmcProfile.InsuredItems, postRaidRequest.profile.Inventory.fastPanel, ); + + // Some items from client profile don't have upd objects when they're single stack items this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items); - this.handlePostRaidPlayerScavProcess(scavData, sessionID, postRaidRequest, pmcData, isDead); + // Reset hp/regenerate loot + this.handlePostRaidPlayerScavProcess(serverScavProfile, sessionID, postRaidRequest, serverPmcProfile, isDead); } /** * Does provided profile contain any condition counters * @param profile Profile to check for condition counters - * @returns + * @returns Profile has condition counters */ protected profileHasConditionCounters(profile: IPmcData): boolean { @@ -318,6 +323,11 @@ export class InraidController return profile.ConditionCounters.Counters.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 { for (const quest of scavProfile.Quests) @@ -329,17 +339,16 @@ export class InraidController continue; } - // Post-raid status is enum word e.g. `Started` but pmc quest status is number e.g. 2 // Status values mismatch or statusTimers counts mismatch if ( - quest.status !== QuestStatus[pmcQuest.status] + quest.status !== pmcQuest.status || Object.keys(quest.statusTimers).length !== Object.keys(pmcQuest.statusTimers).length ) { - this.logger.warning( + this.logger.debug( `Quest: ${quest.qid} found in PMC profile has different status/statustimer. Scav: ${quest.status} vs PMC: ${pmcQuest.status}`, ); - pmcQuest.status = QuestStatus[quest.status]; + pmcQuest.status = quest.status; // Copy status timers over + fix bad enum key for each pmcQuest.statusTimers = quest.statusTimers; @@ -357,7 +366,7 @@ export class InraidController // Loop over all scav counters and add into pmc profile for (const scavCounter of scavProfile.ConditionCounters.Counters) { - this.logger.warning( + this.logger.debug( `Processing counter: ${scavCounter.id} value:${scavCounter.value} quest:${scavCounter.qid}`, ); const counterInPmcProfile = pmcProfile.ConditionCounters.Counters.find((x) => x.id === scavCounter.id); @@ -369,14 +378,14 @@ export class InraidController continue; } - this.logger.warning( + this.logger.debug( `Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.qid}`, ); // Only adjust counter value if its changed if (counterInPmcProfile.value !== scavCounter.value) { - this.logger.warning(`OVERWRITING with values: ${scavCounter.value} quest: ${scavCounter.qid}`); + this.logger.debug(`OVERWRITING with values: ${scavCounter.value} quest: ${scavCounter.qid}`); counterInPmcProfile.value = scavCounter.value; } } diff --git a/project/src/helpers/InRaidHelper.ts b/project/src/helpers/InRaidHelper.ts index 880e4a26..686165c8 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 { ProfileHelper } from "./ProfileHelper"; @injectable() export class InRaidHelper @@ -35,6 +36,7 @@ export class InRaidHelper @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @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, @@ -62,19 +64,16 @@ export class InRaidHelper */ public addUpdToMoneyFromRaid(items: Item[]): void { - for (const item of items) + for (const item of items.filter(x => this.paymentHelper.isMoneyTpl(x._tpl))) { - if (this.paymentHelper.isMoneyTpl(item._tpl)) + if (!item.upd) { - if (!item.upd) - { - item.upd = {}; - } + item.upd = {}; + } - if (!item.upd.StackObjectsCount) - { - item.upd.StackObjectsCount = 1; - } + if (!item.upd.StackObjectsCount) + { + item.upd.StackObjectsCount = 1; } } } @@ -140,18 +139,44 @@ export class InRaidHelper profileData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionID: string, - ): IPmcData - { - // remove old skill fatigue + ): void + { + // Remove skill fatigue values this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile); - // set profile data + // 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.ConditionCounters = saveProgressRequest.profile.ConditionCounters; + this.validateBackendCounters(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); + } + + /** + * Reset the skill points earned in a raid to 0, ready for next raid + * @param profile Profile to update + */ + protected resetSkillPointsEarnedDuringRaid(profile: IPmcData): void + { + for (const skill of profile.Skills.Common) + { + skill.PointsEarnedDuringSession = 0.0; + } + } + + /** Check counters are correct in profile */ + protected validateBackendCounters(saveProgressRequest: ISaveProgressRequestData, profileData: IPmcData): void + { for (const backendCounterKey in saveProgressRequest.profile.BackendCounters) { // Skip counters with no id @@ -178,33 +203,47 @@ export class InRaidHelper if (matchingPreRaidCounter.value !== postRaidValue) { this.logger.error( - `Backendcounter: ${backendCounterKey} value is different post raid, old: ${matchingPreRaidCounter.value} new: ${postRaidValue}`, + `Backendcounter: ${backendCounterKey} value is different post raid, old: ${matchingPreRaidCounter.value} new: ${postRaidValue}` ); } } + } - this.processFailedQuests(sessionID, profileData, profileData.Quests, saveProgressRequest.profile.Quests); - profileData.Quests = saveProgressRequest.profile.Quests; + /** + * 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.processFailedQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile.Quests); + pmcData.Quests = saveProgressRequest.profile.Quests; - // Transfer effects from request to profile - this.transferPostRaidLimbEffectsToProfile(saveProgressRequest, profileData); + // No need to do this for scav, old scav is deleted and new one generated + this.transferPostRaidLimbEffectsToProfile(saveProgressRequest, pmcData); - this.applyTraderStandingAdjustments(profileData.TradersInfo, saveProgressRequest.profile.TradersInfo); + // 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); - profileData.SurvivorClass = saveProgressRequest.profile.SurvivorClass; + this.profileFixerService.checkForAndFixPmcProfileIssues(pmcData); + } - // Add experience points - profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience; - profileData.Stats.Eft.TotalSessionExperience = 0; + /** + * 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(x => x.status !== QuestStatus.AvailableForStart).map(x => x.qid); + scavData.Quests = saveProgressRequest.profile.Quests.filter(x => existingActiveQuestIds.includes(x.qid)); - this.setPlayerInRaidLocationStatusToNone(sessionID); - - if (!saveProgressRequest.isPlayerScav) - { - this.profileFixerService.checkForAndFixPmcProfileIssues(profileData); - } - - return profileData; + this.profileFixerService.checkForAndFixScavProfileIssues(scavData); } /** @@ -250,14 +289,6 @@ export class InRaidHelper } } - protected resetSkillPointsEarnedDuringRaid(profile: IPmcData): void - { - for (const skill of profile.Skills.Common) - { - skill.PointsEarnedDuringSession = 0.0; - } - } - /** * Take body part effects from client profile and apply to server profile * @param saveProgressRequest post-raid request diff --git a/project/src/servers/SaveServer.ts b/project/src/servers/SaveServer.ts index 95152dee..d5c2beba 100644 --- a/project/src/servers/SaveServer.ts +++ b/project/src/servers/SaveServer.ts @@ -185,7 +185,7 @@ export class SaveServer { const filePath = `${this.profileFilepath}${sessionID}.json`; - // run callbacks + // Run pre-save callbacks before we save into json for (const callback in this.onBeforeSaveCallbacks) { const previous = this.profiles[sessionID]; diff --git a/project/src/services/ProfileFixerService.ts b/project/src/services/ProfileFixerService.ts index 82c7ed8b..ae5bef13 100644 --- a/project/src/services/ProfileFixerService.ts +++ b/project/src/services/ProfileFixerService.ts @@ -162,6 +162,15 @@ export class ProfileFixerService } } + /** + * Find issues in the scav profile data that may cause issues + * @param scavProfile profile to check and fix + */ + public checkForAndFixScavProfileIssues(scavProfile: IPmcData): void + { + this.updateProfileQuestDataValues(scavProfile); + } + protected addMissingGunStandContainerImprovements(pmcProfile: IPmcData): void { const weaponStandArea = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WEAPON_STAND);