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
This commit is contained in:
Dev 2023-11-20 16:33:04 +00:00
parent d2209114c9
commit ddb9917c6b
4 changed files with 121 additions and 72 deletions

View File

@ -107,19 +107,20 @@ export class InraidController
*/ */
protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
{ {
const serverProfile = this.saveServer.getProfile(sessionID); const serveProfile = this.saveServer.getProfile(sessionID);
const locationName = serverProfile.inraid.location.toLowerCase(); const locationName = serveProfile.inraid.location.toLowerCase();
const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base; const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base;
const mapHasInsuranceEnabled = map.Insurance; const mapHasInsuranceEnabled = map.Insurance;
let serverPmcData = serverProfile.characters.pmc; let serverPmcProfile = serveProfile.characters.pmc;
const isDead = this.isPlayerDead(postRaidRequest.exit); 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 // Check for exit status
this.markOrRemoveFoundInRaidItems(postRaidRequest); this.markOrRemoveFoundInRaidItems(postRaidRequest);
@ -127,20 +128,20 @@ export class InraidController
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile, postRaidRequest.profile,
postRaidRequest.profile.Inventory.items, postRaidRequest.profile.Inventory.items,
serverPmcData.InsuredItems, serverPmcProfile.InsuredItems,
postRaidRequest.profile.Inventory.fastPanel, postRaidRequest.profile.Inventory.fastPanel,
); );
this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items); this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items);
// Purge profile of equipment/container 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 // Remove inventory if player died and send insurance items
if (mapHasInsuranceEnabled) if (mapHasInsuranceEnabled)
{ {
this.insuranceService.storeLostGear(serverPmcData, postRaidRequest, preRaidGear, sessionID, isDead); this.insuranceService.storeLostGear(serverPmcProfile, postRaidRequest, preRaidGear, sessionID, isDead);
} }
else else
{ {
@ -151,7 +152,7 @@ export class InraidController
if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec") if (locationName === "lighthouse" && postRaidRequest.profile.Info.Side.toLowerCase() === "usec")
{ {
// Decrement counter if it exists, don't go below 0 // 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") x.Key.includes("UsecRaidRemainKills")
); );
if (remainingCounter?.Value > 0) if (remainingCounter?.Value > 0)
@ -164,12 +165,12 @@ export class InraidController
{ {
this.pmcChatResponseService.sendKillerResponse( this.pmcChatResponseService.sendKillerResponse(
sessionID, sessionID,
serverPmcData, serverPmcProfile,
postRaidRequest.profile.Stats.Eft.Aggressor, postRaidRequest.profile.Stats.Eft.Aggressor,
); );
this.matchBotDetailsCacheService.clearCache(); this.matchBotDetailsCacheService.clearCache();
serverPmcData = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcData, sessionID); serverPmcProfile = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcProfile, sessionID);
} }
const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) => const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) =>
@ -177,12 +178,12 @@ export class InraidController
); );
if (victims?.length > 0) if (victims?.length > 0)
{ {
this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcData); this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcProfile);
} }
if (mapHasInsuranceEnabled) 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 protected savePlayerScavProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
{ {
const pmcData = this.profileHelper.getPmcProfile(sessionID); const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID);
let scavData = this.profileHelper.getScavProfile(sessionID); const serverScavProfile = this.profileHelper.getScavProfile(sessionID);
const isDead = this.isPlayerDead(postRaidRequest.exit); const isDead = this.isPlayerDead(postRaidRequest.exit);
this.saveServer.getProfile(sessionID).inraid.character = "scav"; 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 // 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 // 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); this.markOrRemoveFoundInRaidItems(postRaidRequest);
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs( postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile, postRaidRequest.profile,
postRaidRequest.profile.Inventory.items, postRaidRequest.profile.Inventory.items,
pmcData.InsuredItems, serverPmcProfile.InsuredItems,
postRaidRequest.profile.Inventory.fastPanel, 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.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 * Does provided profile contain any condition counters
* @param profile Profile to check for condition counters * @param profile Profile to check for condition counters
* @returns * @returns Profile has condition counters
*/ */
protected profileHasConditionCounters(profile: IPmcData): boolean protected profileHasConditionCounters(profile: IPmcData): boolean
{ {
@ -318,6 +323,11 @@ export class InraidController
return profile.ConditionCounters.Counters.length > 0; 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 protected migrateScavQuestProgressToPmcProfile(scavProfile: IPmcData, pmcProfile: IPmcData): void
{ {
for (const quest of scavProfile.Quests) for (const quest of scavProfile.Quests)
@ -329,17 +339,16 @@ export class InraidController
continue; 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 // Status values mismatch or statusTimers counts mismatch
if ( if (
quest.status !== <any>QuestStatus[pmcQuest.status] quest.status !== pmcQuest.status
|| Object.keys(quest.statusTimers).length !== Object.keys(pmcQuest.statusTimers).length || 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}`, `Quest: ${quest.qid} found in PMC profile has different status/statustimer. Scav: ${quest.status} vs PMC: ${pmcQuest.status}`,
); );
pmcQuest.status = <any>QuestStatus[quest.status]; pmcQuest.status = quest.status;
// Copy status timers over + fix bad enum key for each // Copy status timers over + fix bad enum key for each
pmcQuest.statusTimers = quest.statusTimers; pmcQuest.statusTimers = quest.statusTimers;
@ -357,7 +366,7 @@ export class InraidController
// Loop over all scav counters and add into pmc profile // Loop over all scav counters and add into pmc profile
for (const scavCounter of scavProfile.ConditionCounters.Counters) for (const scavCounter of scavProfile.ConditionCounters.Counters)
{ {
this.logger.warning( this.logger.debug(
`Processing counter: ${scavCounter.id} value:${scavCounter.value} quest:${scavCounter.qid}`, `Processing counter: ${scavCounter.id} value:${scavCounter.value} quest:${scavCounter.qid}`,
); );
const counterInPmcProfile = pmcProfile.ConditionCounters.Counters.find((x) => x.id === scavCounter.id); const counterInPmcProfile = pmcProfile.ConditionCounters.Counters.find((x) => x.id === scavCounter.id);
@ -369,14 +378,14 @@ export class InraidController
continue; continue;
} }
this.logger.warning( this.logger.debug(
`Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.qid}`, `Counter id: ${scavCounter.id} already exists in pmc profile! with value: ${counterInPmcProfile.value} for quest: ${counterInPmcProfile.qid}`,
); );
// Only adjust counter value if its changed // Only adjust counter value if its changed
if (counterInPmcProfile.value !== scavCounter.value) 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; counterInPmcProfile.value = scavCounter.value;
} }
} }

View File

@ -21,6 +21,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { ProfileHelper } from "./ProfileHelper";
@injectable() @injectable()
export class InRaidHelper export class InRaidHelper
@ -35,6 +36,7 @@ export class InRaidHelper
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("QuestHelper") protected questHelper: QuestHelper, @inject("QuestHelper") protected questHelper: QuestHelper,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper, @inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@ -62,9 +64,7 @@ export class InRaidHelper
*/ */
public addUpdToMoneyFromRaid(items: Item[]): void 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)
{ {
@ -77,7 +77,6 @@ export class InRaidHelper
} }
} }
} }
}
/** /**
* Add karma changes up and return the new value * Add karma changes up and return the new value
@ -140,18 +139,44 @@ export class InRaidHelper
profileData: IPmcData, profileData: IPmcData,
saveProgressRequest: ISaveProgressRequestData, saveProgressRequest: ISaveProgressRequestData,
sessionID: string, sessionID: string,
): IPmcData ): void
{ {
// remove old skill fatigue // Remove skill fatigue values
this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile); this.resetSkillPointsEarnedDuringRaid(saveProgressRequest.profile);
// set profile data // Set profile data
profileData.Info.Level = saveProgressRequest.profile.Info.Level; profileData.Info.Level = saveProgressRequest.profile.Info.Level;
profileData.Skills = saveProgressRequest.profile.Skills; profileData.Skills = saveProgressRequest.profile.Skills;
profileData.Stats.Eft = saveProgressRequest.profile.Stats.Eft; profileData.Stats.Eft = saveProgressRequest.profile.Stats.Eft;
profileData.Encyclopedia = saveProgressRequest.profile.Encyclopedia; profileData.Encyclopedia = saveProgressRequest.profile.Encyclopedia;
profileData.ConditionCounters = saveProgressRequest.profile.ConditionCounters; 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) for (const backendCounterKey in saveProgressRequest.profile.BackendCounters)
{ {
// Skip counters with no id // Skip counters with no id
@ -178,33 +203,47 @@ export class InRaidHelper
if (matchingPreRaidCounter.value !== postRaidValue) if (matchingPreRaidCounter.value !== postRaidValue)
{ {
this.logger.error( 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;
// Transfer effects from request to profile
this.transferPostRaidLimbEffectsToProfile(saveProgressRequest, profileData);
this.applyTraderStandingAdjustments(profileData.TradersInfo, saveProgressRequest.profile.TradersInfo);
profileData.SurvivorClass = saveProgressRequest.profile.SurvivorClass;
// Add experience points
profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience;
profileData.Stats.Eft.TotalSessionExperience = 0;
this.setPlayerInRaidLocationStatusToNone(sessionID);
if (!saveProgressRequest.isPlayerScav)
{
this.profileFixerService.checkForAndFixPmcProfileIssues(profileData);
} }
return profileData; /**
* 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;
// 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.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(x => x.status !== QuestStatus.AvailableForStart).map(x => x.qid);
scavData.Quests = saveProgressRequest.profile.Quests.filter(x => existingActiveQuestIds.includes(x.qid));
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 * Take body part effects from client profile and apply to server profile
* @param saveProgressRequest post-raid request * @param saveProgressRequest post-raid request

View File

@ -185,7 +185,7 @@ export class SaveServer
{ {
const filePath = `${this.profileFilepath}${sessionID}.json`; const filePath = `${this.profileFilepath}${sessionID}.json`;
// run callbacks // Run pre-save callbacks before we save into json
for (const callback in this.onBeforeSaveCallbacks) for (const callback in this.onBeforeSaveCallbacks)
{ {
const previous = this.profiles[sessionID]; const previous = this.profiles[sessionID];

View File

@ -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 protected addMissingGunStandContainerImprovements(pmcProfile: IPmcData): void
{ {
const weaponStandArea = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WEAPON_STAND); const weaponStandArea = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WEAPON_STAND);