Removed unused code and repurposed save endpoint for scav profile saving

This commit is contained in:
CWX 2024-07-12 16:29:21 +01:00
parent 124e23b8f6
commit 4eeedc4257
7 changed files with 9 additions and 1382 deletions

View File

@ -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();
}

View File

@ -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))
{
(<BodyPartHealth>bodyPart).Health.Current = (<BodyPartHealth>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<string, boolean>)
{
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;
}
/**

View File

@ -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 = <string>(<unknown>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<string, TraderInfo>,
tradersClientProfile: Record<string, TraderInfo>,
): 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<string, number>): 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;
}
}

View File

@ -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[]
}

View File

@ -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
}

View File

@ -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<INullResponseData> =>
{
return this.inraidCallbacks.saveProgress(url, info, sessionID);

View File

@ -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<string, Record<string, Item[]>> = {};
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