Server/project/src/controllers/InraidController.ts

629 lines
25 KiB
TypeScript
Raw Normal View History

2023-03-03 15:23:46 +00:00
import { inject, injectable } from "tsyringe";
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
import { PlayerScavGenerator } from "@spt-aki/generators/PlayerScavGenerator";
import { HealthHelper } from "@spt-aki/helpers/HealthHelper";
import { InRaidHelper } from "@spt-aki/helpers/InRaidHelper";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
import { QuestHelper } from "@spt-aki/helpers/QuestHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { ILocationBase } from "@spt-aki/models/eft/common/ILocationBase";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { BodyPartHealth } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { IRegisterPlayerRequestData } from "@spt-aki/models/eft/inRaid/IRegisterPlayerRequestData";
import { ISaveProgressRequestData } from "@spt-aki/models/eft/inRaid/ISaveProgressRequestData";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { MessageType } from "@spt-aki/models/enums/MessageType";
import { PlayerRaidEndState } from "@spt-aki/models/enums/PlayerRaidEndState";
import { QuestStatus } from "@spt-aki/models/enums/QuestStatus";
import { Traders } from "@spt-aki/models/enums/Traders";
import { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig";
2024-01-20 18:47:57 +00:00
import { IBTRConfig } from "@spt-aki/models/spt/config/IBTRConfig";
import { IInRaidConfig } from "@spt-aki/models/spt/config/IInRaidConfig";
import { ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
import { ITraderServiceModel } from "@spt-aki/models/spt/services/ITraderServiceModel";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { SaveServer } from "@spt-aki/servers/SaveServer";
import { InsuranceService } from "@spt-aki/services/InsuranceService";
import { MailSendService } from "@spt-aki/services/MailSendService";
import { MatchBotDetailsCacheService } from "@spt-aki/services/MatchBotDetailsCacheService";
import { PmcChatResponseService } from "@spt-aki/services/PmcChatResponseService";
import { TraderServicesService } from "@spt-aki/services/TraderServicesService";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
2023-03-03 15:23:46 +00:00
/**
* Logic for handling In Raid callbacks
*/
@injectable()
export class InraidController
{
protected airdropConfig: IAirdropConfig;
2024-01-20 18:47:57 +00:00
protected btrConfig: IBTRConfig;
protected inRaidConfig: IInRaidConfig;
protected traderConfig: ITraderConfig;
2023-03-03 15:23:46 +00:00
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("SaveServer") protected saveServer: SaveServer,
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
2023-03-03 15:23:46 +00:00
@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("TraderServicesService") protected traderServicesService: TraderServicesService,
2023-03-03 15:23:46 +00:00
@inject("InsuranceService") protected insuranceService: InsuranceService,
@inject("InRaidHelper") protected inRaidHelper: InRaidHelper,
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
2023-11-10 16:49:29 -05:00
@inject("ConfigServer") protected configServer: ConfigServer,
@inject("MailSendService") protected mailSendService: MailSendService,
@inject("RandomUtil") protected randomUtil: RandomUtil,
2023-03-03 15:23:46 +00:00
)
{
this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP);
2024-01-20 18:47:57 +00:00
this.btrConfig = this.configServer.getConfig(ConfigTypes.BTR);
this.inRaidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID);
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
2023-03-03 15:23:46 +00:00
}
/**
* Save locationId to active profiles inraid object AND app context
* @param sessionID Session id
* @param info Register player request
*/
public addPlayer(sessionID: string, info: IRegisterPlayerRequestData): void
{
this.applicationContext.addValue(ContextVariableType.REGISTER_PLAYER_REQUEST, info);
this.saveServer.getProfile(sessionID).inraid.location = info.locationId;
}
/**
2023-07-15 14:49:25 +01:00
* Handle raid/profile/save
2023-03-03 15:23:46 +00:00
* Save profile state to disk
* Handles pmc/pscav
* @param offraidData post-raid request data
* @param sessionID Session id
*/
public savePostRaidProgress(offraidData: ISaveProgressRequestData, sessionID: string): void
{
2023-10-11 10:47:54 +01:00
this.logger.debug(`Raid outcome: ${offraidData.exit}`);
if (!this.inRaidConfig.save.loot)
2023-03-03 15:23:46 +00:00
{
return;
}
if (offraidData.isPlayerScav)
{
this.savePlayerScavProgress(sessionID, offraidData);
}
else
{
this.savePmcProgress(sessionID, offraidData);
}
}
/**
* Handle updating player profile post-pmc raid
2023-10-11 10:47:54 +01:00
* @param sessionID Session id
* @param postRaidRequest Post-raid data
2023-03-03 15:23:46 +00:00
*/
2023-10-11 10:47:54 +01:00
protected savePmcProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
2023-03-03 15:23:46 +00:00
{
const serverProfile = this.saveServer.getProfile(sessionID);
const locationName = serverProfile.inraid.location.toLowerCase();
2023-03-03 15:23:46 +00:00
const map: ILocationBase = this.databaseServer.getTables().locations[locationName].base;
const mapHasInsuranceEnabled = map.Insurance;
2023-03-03 15:23:46 +00:00
let serverPmcProfile = serverProfile.characters.pmc;
2023-10-11 10:47:54 +01:00
const isDead = this.isPlayerDead(postRaidRequest.exit);
const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcProfile.Inventory.items);
2023-10-11 10:47:54 +01:00
serverProfile.inraid.character = "pmc";
2023-03-03 15:23:46 +00:00
this.inRaidHelper.updateProfileBaseStats(serverPmcProfile, postRaidRequest, sessionID);
this.inRaidHelper.updatePmcProfileDataPostRaid(serverPmcProfile, postRaidRequest, sessionID);
2023-03-03 15:23:46 +00:00
// Check for exit status
2023-10-11 10:47:54 +01:00
this.markOrRemoveFoundInRaidItems(postRaidRequest);
2023-03-03 15:23:46 +00:00
2023-11-10 16:49:29 -05:00
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile.Inventory.items,
postRaidRequest.profile,
serverPmcProfile.InsuredItems,
2023-11-10 16:49:29 -05:00
postRaidRequest.profile.Inventory.fastPanel,
);
2023-10-11 10:47:54 +01:00
this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items);
2023-03-03 15:23:46 +00:00
2023-10-11 10:47:54 +01:00
// Purge profile of equipment/container items
serverPmcProfile = this.inRaidHelper.setInventory(sessionID, serverPmcProfile, postRaidRequest.profile);
2023-10-11 10:47:54 +01:00
this.healthHelper.saveVitality(serverPmcProfile, postRaidRequest.health, sessionID);
2023-03-03 15:23:46 +00:00
// 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)
{
mapHasInsuranceEnabled
Refactor Insurance Processing for Gear Lost in Raids Notable coding Changes: - Added `getRootItemParentID` method in `InsuranceService` to standardize the determination of the root insurance container. - Added `IInsuranceEquipmentPkg` model for structuring insurance packages, a type used to store insurance item data before it's saved in the profile. - Added `HashUtil` in `InsuranceController` and `InsuranceService` for generating an ID for the root insurance container in the case that the root ID cannot be found. - Updated and normalized item map generation and usage across `InsuranceService` and `InsuranceController`. - Updated `ItemHelper` with new methods `adoptOrphanedItems` and `generateItemsMap`, facilitating better management of item relationships and efficient item look-ups. - Updated `InsuranceController.findItemsToDelete` and related methods to use the new `rootItemParentID` parameter to ensure that all root level items share the same parent ID. - Updated logic in `InsuranceService` for creating insurance packages and handling orphaned items. Uh-huh, but what would you say you do here? - Resolves an issue that arose when `lostondeath.json` equipment configuration options were set to `false`. On death, the equipment's children items would be sent back to the player through insurance, duplicating them. - Resolves an issue that prevented items from appearing in an insurance return even though they passed an insurance roll. - Improved debug logging. Remaining Oopses: - 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. Resolves #425
2024-02-08 15:56:45 -05:00
? this.insuranceService.storeGearLostInRaidToSendLater(sessionID, gearToStore)
: this.insuranceService.sendLostInsuranceMessage(sessionID, locationName);
2023-03-03 15:23:46 +00:00
}
// Edge case - Handle usec players leaving lighthouse with Rogues angry at them
2023-10-11 10:47:54 +01:00
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) =>
2023-11-10 16:49:29 -05:00
x.Key.includes("UsecRaidRemainKills")
);
if (remainingCounter?.Value > 0)
{
2023-11-10 16:49:29 -05:00
remainingCounter.Value--;
}
}
2023-03-03 15:23:46 +00:00
if (isDead)
{
2023-11-10 16:49:29 -05:00
this.pmcChatResponseService.sendKillerResponse(
sessionID,
serverPmcProfile,
2023-11-10 16:49:29 -05:00
postRaidRequest.profile.Stats.Eft.Aggressor,
);
this.matchBotDetailsCacheService.clearCache();
serverPmcProfile = this.performPostRaidActionsWhenDead(postRaidRequest, serverPmcProfile, sessionID);
2023-03-03 15:23:46 +00:00
}
else
{
// Not dead
2024-02-12 15:10:45 +00:00
// Check for cultist amulets in special slot (only slot it can fit)
const amuletOnPlayer = serverPmcProfile.Inventory.items.filter((item) =>
item.slotId?.startsWith("SpecialSlot")
).find((item) => item._tpl === "64d0b40fbe2eed70e254e2d4");
if (amuletOnPlayer)
{
// No charges left, delete it
if (amuletOnPlayer.upd.CultistAmulet.NumberOfUsages <= 0)
{
serverPmcProfile.Inventory.items.splice(
serverPmcProfile.Inventory.items.indexOf(amuletOnPlayer),
1,
);
}
else if (amuletOnPlayer.upd.CultistAmulet.NumberOfUsages > 0)
{
// Charges left, reduce by 1
amuletOnPlayer.upd.CultistAmulet.NumberOfUsages--;
}
}
}
2023-03-03 15:23:46 +00:00
2023-11-10 16:49:29 -05:00
const victims = postRaidRequest.profile.Stats.Eft.Victims.filter((x) =>
["sptbear", "sptusec"].includes(x.Role.toLowerCase())
);
if (victims?.length > 0)
{
this.pmcChatResponseService.sendVictimResponse(sessionID, victims, serverPmcProfile);
}
if (mapHasInsuranceEnabled)
2023-03-03 15:23:46 +00:00
{
this.insuranceService.sendInsuredItems(serverPmcProfile, sessionID, map.Id);
2023-03-03 15:23:46 +00:00
}
}
/**
2024-02-06 09:49:51 +00:00
* Make changes to PMC profile after they've died in raid,
2023-11-10 16:49:29 -05:00
* Alter body part hp, handle insurance, delete inventory items, remove carried quest items
* @param postRaidSaveRequest Post-raid save request
2023-03-03 15:23:46 +00:00
* @param pmcData Pmc profile
* @param sessionID Session id
* @returns Updated profile object
*/
2023-11-10 16:49:29 -05:00
protected performPostRaidActionsWhenDead(
postRaidSaveRequest: ISaveProgressRequestData,
pmcData: IPmcData,
sessionID: string,
): IPmcData
2023-03-03 15:23:46 +00:00
{
this.updatePmcHealthPostRaid(postRaidSaveRequest, pmcData);
this.inRaidHelper.deleteInventory(pmcData, sessionID);
if (this.inRaidHelper.removeQuestItemsOnDeath())
{
2023-11-10 16:49:29 -05:00
// 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();
2024-02-06 09:49:51 +00:00
const activeQuestIdsInProfile = pmcData.Quests.filter((profileQuest) =>
![QuestStatus.AvailableForStart, QuestStatus.Success, QuestStatus.Expired].includes(profileQuest.status)
2023-11-10 16:49:29 -05:00
).map((x) => x.qid);
for (const questItem of postRaidSaveRequest.profile.Stats.Eft.CarriedQuestItems)
2023-03-03 15:23:46 +00:00
{
// Get quest/find condition for carried quest item
2023-11-10 16:49:29 -05:00
const questAndFindItemConditionId = this.questHelper.getFindItemConditionByQuestItem(
questItem,
activeQuestIdsInProfile,
allQuests,
);
if (Object.keys(questAndFindItemConditionId)?.length > 0)
{
this.profileHelper.removeQuestConditionFromProfile(pmcData, questAndFindItemConditionId);
}
2023-03-03 15:23:46 +00:00
}
// Empty out stored quest items from player inventory
pmcData.Stats.Eft.CarriedQuestItems = [];
2023-03-03 15:23:46 +00:00
}
return pmcData;
}
/**
2023-11-10 16:49:29 -05:00
* Adjust player characters body part hp post-raid
2023-03-03 15:23:46 +00:00
* @param postRaidSaveRequest post raid data
* @param pmcData player profile
*/
protected updatePmcHealthPostRaid(postRaidSaveRequest: ISaveProgressRequestData, pmcData: IPmcData): void
{
switch (postRaidSaveRequest.exit)
{
case PlayerRaidEndState.LEFT.toString():
2023-03-03 15:23:46 +00:00
// Naughty pmc left the raid early!
this.reducePmcHealthToPercent(pmcData, 0.01); // 1%
break;
case PlayerRaidEndState.MISSING_IN_ACTION.toString():
2023-03-03 15:23:46 +00:00
// 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
2023-11-10 16:49:29 -05:00
* @param multiplier multiplier to apply to max health
2023-03-03 15:23:46 +00:00
*/
2023-11-10 16:49:29 -05:00
protected reducePmcHealthToPercent(pmcData: IPmcData, multiplier: number): void
2023-03-03 15:23:46 +00:00
{
for (const bodyPart of Object.values(pmcData.Health.BodyParts))
{
2023-11-10 16:49:29 -05:00
(<BodyPartHealth>bodyPart).Health.Current = (<BodyPartHealth>bodyPart).Health.Maximum * multiplier;
2023-03-03 15:23:46 +00:00
}
}
/**
* Handle updating the profile post-pscav raid
2023-10-11 10:47:54 +01:00
* @param sessionID Session id
* @param postRaidRequest Post-raid data of raid
2023-03-03 15:23:46 +00:00
*/
2023-10-11 10:47:54 +01:00
protected savePlayerScavProgress(sessionID: string, postRaidRequest: ISaveProgressRequestData): void
2023-03-03 15:23:46 +00:00
{
const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID);
const serverScavProfile = this.profileHelper.getScavProfile(sessionID);
2023-10-11 10:47:54 +01:00
const isDead = this.isPlayerDead(postRaidRequest.exit);
2023-03-03 15:23:46 +00:00
this.saveServer.getProfile(sessionID).inraid.character = "scav";
this.inRaidHelper.updateProfileBaseStats(serverScavProfile, postRaidRequest, sessionID);
this.inRaidHelper.updateScavProfileDataPostRaid(serverScavProfile, postRaidRequest, sessionID);
2023-03-03 15:23:46 +00:00
this.mergePmcAndScavEncyclopedias(serverScavProfile.Encyclopedia, serverPmcProfile.Encyclopedia);
// 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
2023-10-11 10:47:54 +01:00
this.markOrRemoveFoundInRaidItems(postRaidRequest);
2023-03-03 15:23:46 +00:00
2023-11-10 16:49:29 -05:00
postRaidRequest.profile.Inventory.items = this.itemHelper.replaceIDs(
postRaidRequest.profile.Inventory.items,
postRaidRequest.profile,
serverPmcProfile.InsuredItems,
2023-11-10 16:49:29 -05:00
postRaidRequest.profile.Inventory.fastPanel,
);
// Some items from client profile don't have upd objects when they're single stack items
2023-10-11 10:47:54 +01:00
this.inRaidHelper.addUpdToMoneyFromRaid(postRaidRequest.profile.Inventory.items);
2023-03-03 15:23:46 +00:00
// Reset hp/regenerate loot
this.handlePostRaidPlayerScavProcess(serverScavProfile, sessionID, postRaidRequest, serverPmcProfile, isDead);
2023-03-03 15:23:46 +00:00
}
/**
* merge two dictionaries together
* Prioritise pair that has true as a value
* @param primary main dictionary
* @param secondary Secondary dictionary
*/
protected mergePmcAndScavEncyclopedias(primary: Record<string, boolean>, secondary: Record<string, boolean>): 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), secondary);
primary = merged;
secondary = merged;
}
/**
* 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
{
for (const quest of scavProfile.Quests)
{
const pmcQuest = pmcProfile.Quests.find((x) => x.qid === quest.qid);
if (!pmcQuest)
{
this.logger.warning(`No PMC quest found for ID: ${quest.qid}`);
continue;
}
// Status values mismatch or statusTimers counts mismatch
2023-11-10 16:49:29 -05:00
if (
quest.status !== pmcQuest.status
|| Object.keys(quest.statusTimers).length !== Object.keys(pmcQuest.statusTimers).length
2023-11-10 16:49:29 -05:00
)
{
this.logger.debug(
2023-11-10 16:49:29 -05:00
`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(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))
{
this.logger.debug(
`Processing counter: ${scavCounter.id} value: ${scavCounter.value} quest: ${scavCounter.sourceId}`,
2023-11-10 16:49:29 -05:00
);
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}`,
2023-11-10 16:49:29 -05:00
);
// 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;
}
}
}
2023-03-03 15:23:46 +00:00
/**
* Is the player dead after a raid - dead is anything other than "survived" / "runner"
* @param statusOnExit exit value from offraidData object
* @returns true if dead
*/
protected isPlayerDead(statusOnExit: PlayerRaidEndState): boolean
2023-03-03 15:23:46 +00:00
{
return (statusOnExit !== PlayerRaidEndState.SURVIVED && statusOnExit !== PlayerRaidEndState.RUNNER);
2023-03-03 15:23:46 +00:00
}
/**
* Mark inventory items as FiR if player survived raid, otherwise remove FiR from them
* @param offraidData Save Progress Request
*/
protected markOrRemoveFoundInRaidItems(offraidData: ISaveProgressRequestData): void
2023-03-03 15:23:46 +00:00
{
if (offraidData.exit !== PlayerRaidEndState.SURVIVED)
2023-03-03 15:23:46 +00:00
{
2023-11-10 16:49:29 -05:00
// Remove FIR status if the player hasn't survived
2023-03-03 15:23:46 +00:00
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
*/
2023-11-10 16:49:29 -05:00
protected handlePostRaidPlayerScavProcess(
scavData: IPmcData,
sessionID: string,
offraidData: ISaveProgressRequestData,
pmcData: IPmcData,
isDead: boolean,
): void
2023-03-03 15:23:46 +00:00
{
// Update scav profile inventory
const updatedScavData = this.inRaidHelper.setInventory(sessionID, scavData, offraidData.profile);
2023-03-03 15:23:46 +00:00
// Reset scav hp and save to json
this.healthHelper.resetVitality(sessionID);
this.saveServer.getProfile(sessionID).characters.scav = updatedScavData;
2023-03-03 15:23:46 +00:00
// Scav karma
this.handlePostRaidPlayerScavKarmaChanges(pmcData, offraidData);
2023-03-03 15:23:46 +00:00
// 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
*/
protected handlePostRaidPlayerScavKarmaChanges(pmcData: IPmcData, offraidData: ISaveProgressRequestData): void
2023-03-03 15:23:46 +00:00
{
const fenceId = Traders.FENCE;
2024-02-11 10:39:31 +00:00
const postRaidFenceRep = offraidData.profile.TradersInfo[fenceId].standing;
this.logger.debug(`post-raid standing: ${postRaidFenceRep}`);
2023-03-03 15:23:46 +00:00
let fenceStanding = Number(pmcData.TradersInfo[fenceId].standing);
2024-02-11 10:39:31 +00:00
this.logger.debug(`pre-raid fence standing: ${fenceStanding}`);
fenceStanding = this.inRaidHelper.calculateFenceStandingChangeFromKillsAsScav(
2023-11-10 16:49:29 -05:00
fenceStanding,
offraidData.profile.Stats.Eft.Victims,
);
2023-03-03 15:23:46 +00:00
// Successful extract with scav adds 0.01 standing
if (offraidData.exit === PlayerRaidEndState.SURVIVED)
2023-03-03 15:23:46 +00:00
{
fenceStanding += this.inRaidConfig.scavExtractGain;
2023-03-03 15:23:46 +00:00
}
2023-11-10 16:49:29 -05:00
2023-03-03 15:23:46 +00:00
// 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);
2023-03-03 15:23:46 +00:00
pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1);
}
/**
* Get the inraid config from configs/inraid.json
* @returns InRaid Config
*/
public getInraidConfig(): IInRaidConfig
{
return this.inRaidConfig;
2023-03-03 15:23:46 +00:00
}
/**
* Get airdrop config from configs/airdrop.json
* @returns Airdrop config
*/
public getAirdropConfig(): IAirdropConfig
{
return this.airdropConfig;
}
2024-01-20 18:47:57 +00:00
/**
* Get BTR config from configs/btr.json
* @returns Airdrop config
*/
public getBTRConfig(): IBTRConfig
{
return this.btrConfig;
}
/**
* Handle singleplayer/traderServices/getTraderServices
* @returns Trader services data
*/
public getTraderServices(sessionId: string, traderId: string): ITraderServiceModel[]
{
return this.traderServicesService.getTraderServices(traderId);
}
/**
* Handle singleplayer/traderServices/itemDelivery
*/
public itemDelivery(sessionId: string, traderId: string, items: Item[]): void
{
const serverProfile = this.saveServer.getProfile(sessionId);
const pmcData = serverProfile.characters.pmc;
const dialogueTemplates = this.databaseServer.getTables().traders[traderId].dialogue;
const messageId = this.randomUtil.getArrayValue(dialogueTemplates.itemsDelivered);
const messageStoreTime = this.timeUtil.getHoursAsSeconds(this.traderConfig.fence.btrDeliveryExpireHours);
// Remove any items that were returned by the item delivery, but also insured, from the player's insurance list
// This is to stop items being duplicated by being returned from both the item delivery, and insurance
const deliveredItemIds = items.map((x) => x._id);
pmcData.InsuredItems = pmcData.InsuredItems.filter((x) => !deliveredItemIds.includes(x.itemId));
// Send the items to the player
this.mailSendService.sendLocalisedNpcMessageToPlayer(
sessionId,
this.traderHelper.getTraderById(traderId),
MessageType.BTR_ITEMS_DELIVERY,
messageId,
items,
messageStoreTime,
);
}
public getTraitorScavHostileChance(url: string, sessionID: string): number
{
return this.inRaidConfig.playerScavHostileChancePercent;
}
2023-11-10 16:49:29 -05:00
}