Removed unused code and repurposed save endpoint for scav profile saving
This commit is contained in:
parent
124e23b8f6
commit
4eeedc4257
@ -3,7 +3,7 @@ import { InraidController } from "@spt/controllers/InraidController";
|
|||||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||||
import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData";
|
import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData";
|
||||||
import { IRegisterPlayerRequestData } from "@spt/models/eft/inRaid/IRegisterPlayerRequestData";
|
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";
|
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,9 +39,9 @@ export class InraidCallbacks
|
|||||||
* @param sessionID Session id
|
* @param sessionID Session id
|
||||||
* @returns Null http response
|
* @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();
|
return this.httpResponse.nullResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,41 +1,16 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
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 { 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 { 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 { 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 { IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
||||||
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
|
||||||
import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig";
|
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 { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
import { SaveServer } from "@spt/servers/SaveServer";
|
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 { 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
|
* Logic for handling In Raid callbacks
|
||||||
@ -44,39 +19,18 @@ import { TimeUtil } from "@spt/utils/TimeUtil";
|
|||||||
export class InraidController
|
export class InraidController
|
||||||
{
|
{
|
||||||
protected inRaidConfig: IInRaidConfig;
|
protected inRaidConfig: IInRaidConfig;
|
||||||
protected traderConfig: ITraderConfig;
|
|
||||||
protected locationConfig: ILocationConfig;
|
|
||||||
protected ragfairConfig: IRagfairConfig;
|
|
||||||
protected hideoutConfig: IHideoutConfig;
|
|
||||||
protected botConfig: IBotConfig;
|
protected botConfig: IBotConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("PrimaryLogger") protected logger: ILogger,
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@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("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("LocalisationService") protected localisationService: LocalisationService,
|
||||||
@inject("InsuranceService") protected insuranceService: InsuranceService,
|
|
||||||
@inject("InRaidHelper") protected inRaidHelper: InRaidHelper,
|
|
||||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
|
||||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.inRaidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID);
|
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);
|
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,507 +66,11 @@ export class InraidController
|
|||||||
* @param offraidData post-raid request data
|
* @param offraidData post-raid request data
|
||||||
* @param sessionID Session id
|
* @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 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";
|
serverScavProfile.Inventory.items = offraidData.profile.Inventory.items;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,118 +1,26 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
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 { Item } from "@spt/models/eft/common/tables/IItem";
|
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 { 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 { ILostOnDeathConfig } from "@spt/models/spt/config/ILostOnDeathConfig";
|
||||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
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 { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
|
||||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class InRaidHelper
|
export class InRaidHelper
|
||||||
{
|
{
|
||||||
protected lostOnDeathConfig: ILostOnDeathConfig;
|
protected lostOnDeathConfig: ILostOnDeathConfig;
|
||||||
protected inRaidConfig: IInRaidConfig;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("PrimaryLogger") protected logger: ILogger,
|
@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("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("ConfigServer") protected configServer: ConfigServer,
|
||||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
||||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH);
|
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
|
* Update a players inventory post-raid
|
||||||
* Remove equipped items from pre-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
|
* Does the provided items slotId mean its kept on the player after death
|
||||||
* @pmcData Player profile
|
* @pmcData Player profile
|
||||||
@ -635,75 +168,4 @@ export class InRaidHelper
|
|||||||
// All other cases item is lost
|
// All other cases item is lost
|
||||||
return false;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,7 @@ import { ISyncHealthRequestData } from "@spt/models/eft/health/ISyncHealthReques
|
|||||||
import { IInsuredItemsData } from "@spt/models/eft/inRaid/IInsuredItemsData";
|
import { IInsuredItemsData } from "@spt/models/eft/inRaid/IInsuredItemsData";
|
||||||
import { PlayerRaidEndState } from "@spt/models/enums/PlayerRaidEndState";
|
import { PlayerRaidEndState } from "@spt/models/enums/PlayerRaidEndState";
|
||||||
|
|
||||||
export interface ISaveProgressRequestData
|
export interface IScavSaveRequestData
|
||||||
{
|
{
|
||||||
exit: PlayerRaidEndState // survived" | "killed" | "left" | "runner" | "missinginaction
|
|
||||||
profile: IPostRaidPmcData
|
profile: IPostRaidPmcData
|
||||||
isPlayerScav: boolean
|
|
||||||
health: ISyncHealthRequestData
|
|
||||||
insurance: IInsuredItemsData[]
|
|
||||||
}
|
}
|
@ -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
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ export class InraidStaticRouter extends StaticRouter
|
|||||||
{
|
{
|
||||||
super([
|
super([
|
||||||
new RouteAction(
|
new RouteAction(
|
||||||
"/raid/profile/save",
|
"/raid/profile/scavsave",
|
||||||
async (url: string, info: any, sessionID: string, output: string): Promise<INullResponseData> =>
|
async (url: string, info: any, sessionID: string, output: string): Promise<INullResponseData> =>
|
||||||
{
|
{
|
||||||
return this.inraidCallbacks.saveProgress(url, info, sessionID);
|
return this.inraidCallbacks.saveProgress(url, info, sessionID);
|
||||||
|
@ -1,26 +1,19 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
|
||||||
import { HandbookHelper } from "@spt/helpers/HandbookHelper";
|
|
||||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
import { SecureContainerHelper } from "@spt/helpers/SecureContainerHelper";
|
|
||||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
import { Item } from "@spt/models/eft/common/tables/IItem";
|
import { Item } from "@spt/models/eft/common/tables/IItem";
|
||||||
import { ITraderBase } from "@spt/models/eft/common/tables/ITrader";
|
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 { BonusType } from "@spt/models/enums/BonusType";
|
||||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
import { ItemTpl } from "@spt/models/enums/ItemTpl";
|
import { ItemTpl } from "@spt/models/enums/ItemTpl";
|
||||||
import { MessageType } from "@spt/models/enums/MessageType";
|
import { MessageType } from "@spt/models/enums/MessageType";
|
||||||
import { IInsuranceConfig } from "@spt/models/spt/config/IInsuranceConfig";
|
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 { IInsuranceEquipmentPkg } from "@spt/models/spt/services/IInsuranceEquipmentPkg";
|
||||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
import { SaveServer } from "@spt/servers/SaveServer";
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
import { LocaleService } from "@spt/services/LocaleService";
|
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
import { MailSendService } from "@spt/services/MailSendService";
|
import { MailSendService } from "@spt/services/MailSendService";
|
||||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
@ -33,29 +26,23 @@ export class InsuranceService
|
|||||||
{
|
{
|
||||||
protected insured: Record<string, Record<string, Item[]>> = {};
|
protected insured: Record<string, Record<string, Item[]>> = {};
|
||||||
protected insuranceConfig: IInsuranceConfig;
|
protected insuranceConfig: IInsuranceConfig;
|
||||||
protected lostOnDeathConfig: ILostOnDeathConfig;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("PrimaryLogger") protected logger: ILogger,
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
@inject("SecureContainerHelper") protected secureContainerHelper: SecureContainerHelper,
|
|
||||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
|
||||||
@inject("HandbookHelper") protected handbookHelper: HandbookHelper,
|
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
@inject("LocaleService") protected localeService: LocaleService,
|
|
||||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE);
|
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];
|
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
|
public resetInsurance(sessionId: string): void
|
||||||
{
|
{
|
||||||
this.insured[sessionId] = {};
|
this.insured[sessionId] = {};
|
||||||
@ -158,27 +134,6 @@ export class InsuranceService
|
|||||||
this.resetInsurance(sessionID);
|
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
|
* 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
|
* Apply insurance return bonus if found in profile
|
||||||
@ -228,143 +183,6 @@ export class InsuranceService
|
|||||||
return this.timeUtil.getTimestamp() + randomisedReturnTimeSeconds * insuranceReturnTimeBonusPercent;
|
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
|
* 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.
|
* not orphaned from their parent ID.
|
||||||
@ -398,75 +216,6 @@ export class InsuranceService
|
|||||||
this.adoptOrphanedInsEquipment(sessionID);
|
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
|
* For the passed in items, find the trader it was insured against
|
||||||
* @param sessionId Session id
|
* @param sessionId Session id
|
||||||
@ -503,28 +252,6 @@ export class InsuranceService
|
|||||||
return result;
|
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
|
* Add gear item to InsuredItems array in player profile
|
||||||
* @param sessionID Session id
|
* @param sessionID Session id
|
||||||
|
Loading…
x
Reference in New Issue
Block a user