2024-07-06 14:36:48 +01:00
import { ApplicationContext } from "@spt/context/ApplicationContext" ;
import { ContextVariableType } from "@spt/context/ContextVariableType" ;
import { LocationLootGenerator } from "@spt/generators/LocationLootGenerator" ;
import { LootGenerator } from "@spt/generators/LootGenerator" ;
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator" ;
import { HealthHelper } from "@spt/helpers/HealthHelper" ;
import { InRaidHelper } from "@spt/helpers/InRaidHelper" ;
import { ProfileHelper } from "@spt/helpers/ProfileHelper" ;
2024-10-24 22:45:42 +01:00
import { QuestHelper } from "@spt/helpers/QuestHelper" ;
2024-07-06 14:36:48 +01:00
import { TraderHelper } from "@spt/helpers/TraderHelper" ;
import { ILocationBase } from "@spt/models/eft/common/ILocationBase" ;
import { IPmcData } from "@spt/models/eft/common/IPmcData" ;
2024-09-24 11:26:45 +01:00
import { Common , IQuestStatus , ITraderInfo } from "@spt/models/eft/common/tables/IBotBase" ;
2024-09-24 12:47:29 +01:00
import { IItem } from "@spt/models/eft/common/tables/IItem" ;
2024-10-07 12:48:49 +01:00
import {
IEndLocalRaidRequestData ,
IEndRaidResult ,
ILocationTransit ,
} from "@spt/models/eft/match/IEndLocalRaidRequestData" ;
2024-07-06 14:36:48 +01:00
import { IStartLocalRaidRequestData } from "@spt/models/eft/match/IStartLocalRaidRequestData" ;
import { IStartLocalRaidResponseData } from "@spt/models/eft/match/IStartLocalRaidResponseData" ;
import { ConfigTypes } from "@spt/models/enums/ConfigTypes" ;
2024-10-17 14:49:39 +01:00
import { ExitStatus } from "@spt/models/enums/ExitStatis" ;
2024-07-06 14:36:48 +01:00
import { MessageType } from "@spt/models/enums/MessageType" ;
2024-09-03 17:09:56 +01:00
import { QuestStatus } from "@spt/models/enums/QuestStatus" ;
2024-07-06 14:36:48 +01:00
import { Traders } from "@spt/models/enums/Traders" ;
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig" ;
import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig" ;
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig" ;
2024-09-18 11:36:45 +01:00
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig" ;
2024-07-06 14:36:48 +01:00
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig" ;
import { ITraderConfig } from "@spt/models/spt/config/ITraderConfig" ;
import { IRaidChanges } from "@spt/models/spt/location/IRaidChanges" ;
import { ILogger } from "@spt/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt/servers/ConfigServer" ;
import { SaveServer } from "@spt/servers/SaveServer" ;
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService" ;
import { BotLootCacheService } from "@spt/services/BotLootCacheService" ;
2024-09-07 12:08:37 +01:00
import { BotNameService } from "@spt/services/BotNameService" ;
2024-07-06 14:36:48 +01:00
import { DatabaseService } from "@spt/services/DatabaseService" ;
import { InsuranceService } from "@spt/services/InsuranceService" ;
import { LocalisationService } from "@spt/services/LocalisationService" ;
import { MailSendService } from "@spt/services/MailSendService" ;
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService" ;
import { PmcChatResponseService } from "@spt/services/PmcChatResponseService" ;
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService" ;
2024-07-07 17:06:45 +01:00
import { HashUtil } from "@spt/utils/HashUtil" ;
2024-07-06 14:36:48 +01:00
import { RandomUtil } from "@spt/utils/RandomUtil" ;
import { TimeUtil } from "@spt/utils/TimeUtil" ;
2024-07-23 11:12:53 -04:00
import { ICloner } from "@spt/utils/cloners/ICloner" ;
import { inject , injectable } from "tsyringe" ;
2024-07-06 14:36:48 +01:00
@injectable ( )
2024-07-23 11:12:53 -04:00
export class LocationLifecycleService {
2024-07-06 14:36:48 +01:00
protected inRaidConfig : IInRaidConfig ;
protected traderConfig : ITraderConfig ;
protected ragfairConfig : IRagfairConfig ;
protected hideoutConfig : IHideoutConfig ;
protected locationConfig : ILocationConfig ;
2024-09-18 11:36:45 +01:00
protected pmcConfig : IPmcConfig ;
2024-07-06 14:36:48 +01:00
constructor (
@inject ( "PrimaryLogger" ) protected logger : ILogger ,
2024-07-07 17:06:45 +01:00
@inject ( "HashUtil" ) protected hashUtil : HashUtil ,
2024-07-06 14:36:48 +01:00
@inject ( "SaveServer" ) protected saveServer : SaveServer ,
@inject ( "TimeUtil" ) protected timeUtil : TimeUtil ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "ProfileHelper" ) protected profileHelper : ProfileHelper ,
@inject ( "DatabaseService" ) protected databaseService : DatabaseService ,
@inject ( "InRaidHelper" ) protected inRaidHelper : InRaidHelper ,
@inject ( "HealthHelper" ) protected healthHelper : HealthHelper ,
2024-10-24 22:45:42 +01:00
@inject ( "QuestHelper" ) protected questHelper : QuestHelper ,
2024-07-06 14:36:48 +01:00
@inject ( "MatchBotDetailsCacheService" ) protected matchBotDetailsCacheService : MatchBotDetailsCacheService ,
@inject ( "PmcChatResponseService" ) protected pmcChatResponseService : PmcChatResponseService ,
@inject ( "PlayerScavGenerator" ) protected playerScavGenerator : PlayerScavGenerator ,
@inject ( "TraderHelper" ) protected traderHelper : TraderHelper ,
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
@inject ( "InsuranceService" ) protected insuranceService : InsuranceService ,
@inject ( "BotLootCacheService" ) protected botLootCacheService : BotLootCacheService ,
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
@inject ( "BotGenerationCacheService" ) protected botGenerationCacheService : BotGenerationCacheService ,
@inject ( "MailSendService" ) protected mailSendService : MailSendService ,
@inject ( "RaidTimeAdjustmentService" ) protected raidTimeAdjustmentService : RaidTimeAdjustmentService ,
2024-09-07 12:08:37 +01:00
@inject ( "BotNameService" ) protected botNameService : BotNameService ,
2024-07-06 14:36:48 +01:00
@inject ( "LootGenerator" ) protected lootGenerator : LootGenerator ,
@inject ( "ApplicationContext" ) protected applicationContext : ApplicationContext ,
@inject ( "LocationLootGenerator" ) protected locationLootGenerator : LocationLootGenerator ,
@inject ( "PrimaryCloner" ) protected cloner : ICloner ,
2024-07-23 11:12:53 -04:00
) {
2024-07-07 17:06:45 +01:00
this . inRaidConfig = this . configServer . getConfig ( ConfigTypes . IN_RAID ) ;
2024-07-06 14:36:48 +01:00
this . traderConfig = this . configServer . getConfig ( ConfigTypes . TRADER ) ;
this . ragfairConfig = this . configServer . getConfig ( ConfigTypes . RAGFAIR ) ;
this . hideoutConfig = this . configServer . getConfig ( ConfigTypes . HIDEOUT ) ;
this . locationConfig = this . configServer . getConfig ( ConfigTypes . LOCATION ) ;
2024-09-18 11:36:45 +01:00
this . pmcConfig = this . configServer . getConfig ( ConfigTypes . PMC ) ;
2024-07-06 14:36:48 +01:00
}
2024-09-13 20:51:22 +01:00
/** Handle client/match/local/start */
2024-07-23 11:12:53 -04:00
public startLocalRaid ( sessionId : string , request : IStartLocalRaidRequestData ) : IStartLocalRaidResponseData {
2024-11-15 19:22:46 +00:00
this . logger . debug ( ` Starting: ${ request . location } ` ) ;
2024-07-06 14:36:48 +01:00
const playerProfile = this . profileHelper . getPmcProfile ( sessionId ) ;
const result : IStartLocalRaidResponseData = {
serverId : ` ${ request . location } . ${ request . playerSide } . ${ this . timeUtil . getTimestamp ( ) } ` , // TODO - does this need to be more verbose - investigate client?
serverSettings : this.databaseService.getLocationServices ( ) , // TODO - is this per map or global?
profile : { insuredItems : playerProfile.InsuredItems } ,
2024-10-03 15:17:00 +01:00
locationLoot : this.generateLocationAndLoot ( request . location , ! request . sptSkipLootGeneration ) ,
2024-09-26 20:49:08 +01:00
transition : {
isLocationTransition : false ,
2024-11-11 18:26:58 +00:00
transitionRaidId : this.hashUtil.generate ( ) ,
2024-09-26 20:49:08 +01:00
transitionCount : 0 ,
visitedLocations : [ ] ,
} ,
2024-07-06 14:36:48 +01:00
} ;
2024-09-27 08:45:26 +01:00
// Only has value when transitioning into map from previous one
if ( request . transition ) {
2024-11-11 20:19:53 +00:00
// TODO - why doesnt the raid after transit have any transit data?
2024-09-27 08:45:26 +01:00
result . transition = request . transition ;
}
2024-10-07 12:48:49 +01:00
// Get data stored at end of previous raid (if any)
const transitionData = this . applicationContext
. getLatestValue ( ContextVariableType . TRANSIT_INFO )
? . getValue < ILocationTransit > ( ) ;
if ( transitionData ) {
2024-11-22 18:46:57 +00:00
this . logger . success ( ` Player: ${ sessionId } is in transit to ${ request . location } ` ) ;
2024-10-07 12:48:49 +01:00
result . transition . isLocationTransition = true ;
result . transition . transitionRaidId = transitionData . transitionRaidId ;
result . transition . transitionCount += 1 ;
2024-11-11 20:19:53 +00:00
// Used by client to determine infil location) - client adds the map player is transiting to later
result . transition . visitedLocations . push ( transitionData . sptLastVisitedLocation ) ;
// Complete, clean up as no longer needed
2024-10-07 12:48:49 +01:00
this . applicationContext . clearValues ( ContextVariableType . TRANSIT_INFO ) ;
}
2024-09-18 11:36:45 +01:00
// Apply changes from pmcConfig to bot hostility values
this . adjustBotHostilitySettings ( result . locationLoot ) ;
2024-09-18 13:23:06 +01:00
this . adjustExtracts ( request . playerSide , request . location , result . locationLoot ) ;
2024-07-06 14:36:48 +01:00
// Clear bot cache ready for a fresh raid
this . botGenerationCacheService . clearStoredBots ( ) ;
2024-09-07 12:08:37 +01:00
this . botNameService . clearNameCache ( ) ;
2024-07-06 14:36:48 +01:00
return result ;
}
2024-09-18 13:23:06 +01:00
/ * *
* Replace map exits with scav exits when player is scavving
* @param playerSide Playders side ( savage / usec / bear )
* @param location id of map being loaded
* @param locationData Maps locationbase data
* /
protected adjustExtracts ( playerSide : string , location : string , locationData : ILocationBase ) : void {
const playerIsScav = playerSide . toLowerCase ( ) === "savage" ;
if ( playerIsScav ) {
// Get relevant extract data for map
const mapExtracts = this . databaseService . getLocation ( location ) ? . allExtracts ;
if ( ! mapExtracts ) {
this . logger . warning ( ` Unable to find map: ${ location } extract data, no adjustments made ` ) ;
return ;
}
// Find only scav extracts and overwrite existing exits with them
2024-09-20 11:07:27 +01:00
const scavExtracts = mapExtracts . filter ( ( extract ) = > [ "scav" ] . includes ( extract . Side . toLowerCase ( ) ) ) ;
2024-09-18 13:23:06 +01:00
if ( scavExtracts . length > 0 ) {
// Scav extracts found, use them
2024-09-20 11:07:27 +01:00
locationData . exits . push ( . . . scavExtracts ) ;
2024-09-18 13:23:06 +01:00
}
}
}
2024-07-06 14:36:48 +01:00
/ * *
2024-09-18 11:36:45 +01:00
* Adjust the bot hostility values prior to entering a raid
* @param location map to adjust values of
* /
protected adjustBotHostilitySettings ( location : ILocationBase ) : void {
for ( const botId in this . pmcConfig . hostilitySettings ) {
const configHostilityChanges = this . pmcConfig . hostilitySettings [ botId ] ;
const locationBotHostilityDetails = location . BotLocationModifier . AdditionalHostilitySettings . find (
( botSettings ) = > botSettings . BotRole . toLowerCase ( ) === botId ,
) ;
// No matching bot in config, skip
if ( ! locationBotHostilityDetails ) {
this . logger . warning (
` No bot: ${ botId } hostility values found on: ${ location . Id } , can only edit existing. Skipping ` ,
) ;
continue ;
}
// Add new permanent enemies if they don't already exist
if ( configHostilityChanges . additionalEnemyTypes ) {
for ( const enemyTypeToAdd of configHostilityChanges . additionalEnemyTypes ) {
if ( ! locationBotHostilityDetails . AlwaysEnemies . includes ( enemyTypeToAdd ) ) {
locationBotHostilityDetails . AlwaysEnemies . push ( enemyTypeToAdd ) ;
}
}
}
// Add/edit chance settings
2024-09-19 12:28:33 +01:00
if ( configHostilityChanges . chancedEnemies ) {
2024-11-01 13:15:38 +00:00
locationBotHostilityDetails . ChancedEnemies || = [ ] ;
2024-09-19 12:28:33 +01:00
for ( const chanceDetailsToApply of configHostilityChanges . chancedEnemies ) {
2024-09-18 11:36:45 +01:00
const locationBotDetails = locationBotHostilityDetails . ChancedEnemies . find (
( botChance ) = > botChance . Role === chanceDetailsToApply . Role ,
) ;
if ( locationBotDetails ) {
// Existing
locationBotDetails . EnemyChance = chanceDetailsToApply . EnemyChance ;
} else {
// Add new
locationBotHostilityDetails . ChancedEnemies . push ( chanceDetailsToApply ) ;
}
}
}
// Add new permanent friends if they don't already exist
if ( configHostilityChanges . additionalFriendlyTypes ) {
2024-11-01 13:15:38 +00:00
locationBotHostilityDetails . AlwaysFriends || = [ ] ;
2024-09-18 11:36:45 +01:00
for ( const friendlyTypeToAdd of configHostilityChanges . additionalFriendlyTypes ) {
if ( ! locationBotHostilityDetails . AlwaysFriends . includes ( friendlyTypeToAdd ) ) {
locationBotHostilityDetails . AlwaysFriends . push ( friendlyTypeToAdd ) ;
}
}
}
2024-09-19 14:02:57 +01:00
// Adjust vs bear hostility chance
2024-09-19 12:28:33 +01:00
if ( typeof configHostilityChanges . bearEnemyChance !== "undefined" ) {
locationBotHostilityDetails . BearEnemyChance = configHostilityChanges . bearEnemyChance ;
2024-09-18 11:36:45 +01:00
}
2024-09-19 14:02:57 +01:00
// Adjust vs usec hostility chance
2024-09-19 12:28:33 +01:00
if ( typeof configHostilityChanges . usecEnemyChance !== "undefined" ) {
locationBotHostilityDetails . UsecEnemyChance = configHostilityChanges . usecEnemyChance ;
}
2024-09-19 14:02:57 +01:00
// Adjust vs savage hostility chance
2024-09-19 12:28:33 +01:00
if ( typeof configHostilityChanges . savageEnemyChance !== "undefined" ) {
locationBotHostilityDetails . SavageEnemyChance = configHostilityChanges . savageEnemyChance ;
2024-09-18 11:36:45 +01:00
}
2024-09-18 11:39:32 +01:00
2024-09-19 14:02:57 +01:00
// Adjust vs scav hostility behaviour
2024-09-18 11:39:32 +01:00
if ( typeof configHostilityChanges . savagePlayerBehaviour !== "undefined" ) {
locationBotHostilityDetails . SavagePlayerBehaviour = configHostilityChanges . savagePlayerBehaviour ;
}
2024-09-18 11:36:45 +01:00
}
}
/ * *
* Generate a maps base location ( cloned ) and loot
2024-07-06 14:36:48 +01:00
* @param name Map name
2024-10-03 15:17:00 +01:00
* @param generateLoot OPTIONAL - Should loot be generated for the map before being returned
2024-07-06 14:36:48 +01:00
* @returns ILocationBase
* /
2024-10-03 15:17:00 +01:00
protected generateLocationAndLoot ( name : string , generateLoot = true ) : ILocationBase {
2024-07-06 14:36:48 +01:00
const location = this . databaseService . getLocation ( name ) ;
const locationBaseClone = this . cloner . clone ( location . base ) ;
// Update datetime property to now
locationBaseClone . UnixDateTime = this . timeUtil . getTimestamp ( ) ;
// Don't generate loot for hideout
2024-07-23 11:12:53 -04:00
if ( name === "hideout" ) {
2024-07-06 14:36:48 +01:00
return locationBaseClone ;
}
2024-11-20 09:20:59 +00:00
// If new spawn system is enabled, clear the spawn waves
2024-11-22 15:10:45 +00:00
if ( locationBaseClone . NewSpawn ) {
2024-11-20 09:20:59 +00:00
locationBaseClone . waves = [ ] ;
}
2024-10-03 15:17:00 +01:00
// We only need the base data
if ( ! generateLoot ) {
return locationBaseClone ;
}
2024-07-06 14:36:48 +01:00
// Check for a loot multipler adjustment in app context and apply if one is found
let locationConfigClone : ILocationConfig ;
const raidAdjustments = this . applicationContext
. getLatestValue ( ContextVariableType . RAID_ADJUSTMENTS )
? . getValue < IRaidChanges > ( ) ;
2024-07-23 11:12:53 -04:00
if ( raidAdjustments ) {
2024-07-06 14:36:48 +01:00
locationConfigClone = this . cloner . clone ( this . locationConfig ) ; // Clone values so they can be used to reset originals later
this . raidTimeAdjustmentService . makeAdjustmentsToMap ( raidAdjustments , locationBaseClone ) ;
}
const staticAmmoDist = this . cloner . clone ( location . staticAmmo ) ;
// Create containers and add loot to them
const staticLoot = this . locationLootGenerator . generateStaticContainers ( locationBaseClone , staticAmmoDist ) ;
locationBaseClone . Loot . push ( . . . staticLoot ) ;
// Add dynamic loot to output loot
const dynamicLootDistClone = this . cloner . clone ( location . looseLoot ) ;
const dynamicSpawnPoints = this . locationLootGenerator . generateDynamicLoot (
dynamicLootDistClone ,
staticAmmoDist ,
2024-09-13 22:50:59 +01:00
name . toLowerCase ( ) ,
2024-07-06 14:36:48 +01:00
) ;
2024-07-23 11:12:53 -04:00
for ( const spawnPoint of dynamicSpawnPoints ) {
2024-07-06 14:36:48 +01:00
locationBaseClone . Loot . push ( spawnPoint ) ;
}
// Done generating, log results
this . logger . success (
this . localisationService . getText ( "location-dynamic_items_spawned_success" , dynamicSpawnPoints . length ) ,
) ;
this . logger . success ( this . localisationService . getText ( "location-generated_success" , name ) ) ;
// Reset loot multipliers back to original values
2024-07-23 11:12:53 -04:00
if ( raidAdjustments ) {
2024-07-06 14:36:48 +01:00
this . logger . debug ( "Resetting loot multipliers back to their original values" ) ;
this . locationConfig . staticLootMultiplier = locationConfigClone . staticLootMultiplier ;
this . locationConfig . looseLootMultiplier = locationConfigClone . looseLootMultiplier ;
this . applicationContext . clearValues ( ContextVariableType . RAID_ADJUSTMENTS ) ;
}
return locationBaseClone ;
}
2024-10-07 12:48:49 +01:00
/** Handle client/match/local/end */
2024-07-23 11:12:53 -04:00
public endLocalRaid ( sessionId : string , request : IEndLocalRaidRequestData ) : void {
2024-07-06 14:36:48 +01:00
// Clear bot loot cache
this . botLootCacheService . clearCache ( ) ;
const fullProfile = this . profileHelper . getFullProfile ( sessionId ) ;
const pmcProfile = fullProfile . characters . pmc ;
const scavProfile = fullProfile . characters . scav ;
// TODO:
// Quest status?
// stats/eft/aggressor - weird values (EFT.IProfileDataContainer.Nickname)
2024-11-16 20:22:24 +00:00
this . logger . debug ( ` Raid: ${ request . serverId } outcome: ${ request . results . result } ` ) ;
2024-07-06 14:36:48 +01:00
2024-09-24 17:25:32 +01:00
// Reset flea interval time to out-of-raid value
2024-07-06 14:36:48 +01:00
this . ragfairConfig . runIntervalSeconds = this . ragfairConfig . runIntervalValues . outOfRaid ;
this . hideoutConfig . runIntervalSeconds = this . hideoutConfig . runIntervalValues . outOfRaid ;
// ServerId has various info stored in it, delimited by a period
const serverDetails = request . serverId . split ( "." ) ;
const locationName = serverDetails [ 0 ] . toLowerCase ( ) ;
const isPmc = serverDetails [ 1 ] . toLowerCase ( ) === "pmc" ;
const mapBase = this . databaseService . getLocation ( locationName ) . base ;
const isDead = this . isPlayerDead ( request . results ) ;
2024-09-29 10:30:44 +01:00
const isTransfer = this . isMapToMapTransfer ( request . results ) ;
2024-08-23 19:20:44 +01:00
const isSurvived = this . isPlayerSurvived ( request . results ) ;
2024-07-06 14:36:48 +01:00
2024-10-07 12:48:49 +01:00
// Handle items transferred via BTR or transit to player mailbox
2024-09-27 07:19:20 +00:00
this . handleItemTransferEvent ( sessionId , request ) ;
2024-09-26 17:28:23 +01:00
2024-10-07 12:48:49 +01:00
// Player is moving between maps
2024-11-22 15:10:45 +00:00
if ( isTransfer && request . locationTransit ) {
2024-11-11 20:19:53 +00:00
// Manually store the map player just left
request . locationTransit . sptLastVisitedLocation = locationName ;
// TODO - Persist each players last visited location history over multiple transits, e.g using InMemoryCacheService, need to take care to not let data get stored forever
2024-10-07 12:48:49 +01:00
// Store transfer data for later use in `startLocalRaid()` when next raid starts
this . applicationContext . addValue ( ContextVariableType . TRANSIT_INFO , request . locationTransit ) ;
}
2024-07-23 11:12:53 -04:00
if ( ! isPmc ) {
2024-09-29 10:30:44 +01:00
this . handlePostRaidPlayerScav ( sessionId , pmcProfile , scavProfile , isDead , isTransfer , request ) ;
2024-07-06 14:36:48 +01:00
return ;
}
2024-08-23 19:20:44 +01:00
this . handlePostRaidPmc (
sessionId ,
pmcProfile ,
scavProfile ,
isDead ,
isSurvived ,
2024-09-29 10:30:44 +01:00
isTransfer ,
2024-08-23 19:20:44 +01:00
request ,
locationName ,
) ;
2024-07-07 17:06:45 +01:00
// Handle car extracts
2024-07-23 11:12:53 -04:00
if ( this . extractWasViaCar ( request . results . exitName ) ) {
2024-07-07 17:06:45 +01:00
this . handleCarExtract ( request . results . exitName , pmcProfile , sessionId ) ;
}
// Handle coop exit
2024-07-23 11:12:53 -04:00
if (
request . results . exitName &&
this . extractTakenWasCoop ( request . results . exitName ) &&
this . traderConfig . fence . coopExtractGift . sendGift
) {
2024-07-07 17:06:45 +01:00
this . handleCoopExtract ( sessionId , pmcProfile , request . results . exitName ) ;
this . sendCoopTakenFenceMessage ( sessionId ) ;
}
}
/ * *
* Was extract by car
* @param extractName name of extract
* @returns True if extract was by car
* /
2024-07-23 11:12:53 -04:00
protected extractWasViaCar ( extractName : string ) : boolean {
2024-07-07 17:06:45 +01:00
// exit name is undefined on death
2024-07-23 11:12:53 -04:00
if ( ! extractName ) {
2024-07-07 17:06:45 +01:00
return false ;
}
2024-07-23 11:12:53 -04:00
if ( extractName . toLowerCase ( ) . includes ( "v-ex" ) ) {
2024-07-07 17:06:45 +01:00
return true ;
}
return this . inRaidConfig . carExtracts . includes ( extractName . trim ( ) ) ;
}
/ * *
* Handle when a player extracts using a car - Add rep to fence
* @param extractName name of the extract used
* @param pmcData Player profile
* @param sessionId Session id
* /
2024-07-23 11:12:53 -04:00
protected handleCarExtract ( extractName : string , pmcData : IPmcData , sessionId : string ) : void {
2024-07-07 17:06:45 +01:00
// Ensure key exists for extract
2024-07-23 11:12:53 -04:00
if ( ! ( extractName in pmcData . CarExtractCounts ) ) {
2024-07-07 17:06:45 +01:00
pmcData . CarExtractCounts [ extractName ] = 0 ;
}
// Increment extract count value
pmcData . CarExtractCounts [ extractName ] += 1 ;
// Not exact replica of Live behaviour
// Simplified for now, no real reason to do the whole (unconfirmed) extra 0.01 standing per day regeneration mechanic
const newFenceStanding = this . getFenceStandingAfterExtract (
pmcData ,
this . inRaidConfig . carExtractBaseStandingGain ,
pmcData . CarExtractCounts [ extractName ] ,
) ;
const fenceId : string = Traders . FENCE ;
pmcData . TradersInfo [ fenceId ] . standing = newFenceStanding ;
// Check if new standing has leveled up trader
this . traderHelper . lvlUp ( fenceId , pmcData ) ;
pmcData . TradersInfo [ fenceId ] . loyaltyLevel = Math . max ( pmcData . TradersInfo [ fenceId ] . loyaltyLevel , 1 ) ;
this . logger . debug (
` Car extract: ${ extractName } used, total times taken: ${ pmcData . CarExtractCounts [ extractName ] } ` ,
) ;
// Copy updated fence rep values into scav profile to ensure consistency
const scavData : IPmcData = this . profileHelper . getScavProfile ( sessionId ) ;
scavData . TradersInfo [ fenceId ] . standing = pmcData . TradersInfo [ fenceId ] . standing ;
scavData . TradersInfo [ fenceId ] . loyaltyLevel = pmcData . TradersInfo [ fenceId ] . loyaltyLevel ;
}
/ * *
* Handle when a player extracts using a coop extract - add rep to fence
* @param sessionId Session / player id
* @param pmcData Profile
* @param extractName Name of extract taken
* /
2024-07-23 11:12:53 -04:00
protected handleCoopExtract ( sessionId : string , pmcData : IPmcData , extractName : string ) : void {
2024-07-07 17:06:45 +01:00
pmcData . CoopExtractCounts || = { } ;
// Ensure key exists for extract
2024-07-23 11:12:53 -04:00
if ( ! ( extractName in pmcData . CoopExtractCounts ) ) {
2024-07-07 17:06:45 +01:00
pmcData . CoopExtractCounts [ extractName ] = 0 ;
}
// Increment extract count value
pmcData . CoopExtractCounts [ extractName ] += 1 ;
// Get new fence standing value
const newFenceStanding = this . getFenceStandingAfterExtract (
pmcData ,
this . inRaidConfig . coopExtractBaseStandingGain ,
pmcData . CoopExtractCounts [ extractName ] ,
) ;
const fenceId : string = Traders . FENCE ;
pmcData . TradersInfo [ fenceId ] . standing = newFenceStanding ;
// Check if new standing has leveled up trader
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
const scavData : IPmcData = this . profileHelper . getScavProfile ( sessionId ) ;
scavData . TradersInfo [ fenceId ] . standing = pmcData . TradersInfo [ fenceId ] . standing ;
scavData . TradersInfo [ fenceId ] . loyaltyLevel = pmcData . TradersInfo [ fenceId ] . loyaltyLevel ;
}
/ * *
* Get the fence rep gain from using a car or coop extract
* @param pmcData Profile
* @param baseGain amount gained for the first extract
* @param extractCount Number of times extract was taken
* @returns Fence standing after taking extract
* /
2024-07-23 11:12:53 -04:00
protected getFenceStandingAfterExtract ( pmcData : IPmcData , baseGain : number , extractCount : number ) : number {
2024-07-07 17:06:45 +01:00
// Get current standing
const fenceId : string = Traders . FENCE ;
let fenceStanding = Number ( pmcData . TradersInfo [ fenceId ] . standing ) ;
// get standing after taking extract x times, x.xx format, gain from extract can be no smaller than 0.01
fenceStanding += Math . max ( baseGain / extractCount , 0.01 ) ;
// Ensure fence loyalty level is not above/below the range -7 to 15
const newFenceStanding = Math . min ( Math . max ( fenceStanding , - 7 ) , 15 ) ;
this . logger . debug ( ` Old vs new fence standing: ${ pmcData . TradersInfo [ fenceId ] . standing } , ${ newFenceStanding } ` ) ;
return Number ( newFenceStanding . toFixed ( 2 ) ) ;
}
2024-07-23 11:12:53 -04:00
protected sendCoopTakenFenceMessage ( sessionId : string ) : void {
2024-07-07 17:06:45 +01:00
// Generate reward for taking coop extract
const loot = this . lootGenerator . createRandomLoot ( this . traderConfig . fence . coopExtractGift ) ;
2024-09-24 12:47:29 +01:00
const mailableLoot : IItem [ ] = [ ] ;
2024-07-07 17:06:45 +01:00
const parentId = this . hashUtil . generate ( ) ;
2024-07-23 11:12:53 -04:00
for ( const item of loot ) {
2024-07-07 17:06:45 +01:00
item . parentId = parentId ;
mailableLoot . push ( item ) ;
}
// Send message from fence giving player reward generated above
this . mailSendService . sendLocalisedNpcMessageToPlayer (
sessionId ,
this . traderHelper . getTraderById ( Traders . FENCE ) ,
MessageType . MESSAGE_WITH_ITEMS ,
this . randomUtil . getArrayValue ( this . traderConfig . fence . coopExtractGift . messageLocaleIds ) ,
mailableLoot ,
this . timeUtil . getHoursAsSeconds ( this . traderConfig . fence . coopExtractGift . giftExpiryHours ) ,
) ;
}
/ * *
* Did player take a COOP extract
* @param extractName Name of extract player took
* @returns True if coop extract
* /
2024-07-23 11:12:53 -04:00
protected extractTakenWasCoop ( extractName : string ) : boolean {
2024-07-07 17:06:45 +01:00
// No extract name, not a coop extract
2024-07-23 11:12:53 -04:00
if ( ! extractName ) {
2024-07-07 17:06:45 +01:00
return false ;
}
return this . inRaidConfig . coopExtracts . includes ( extractName . trim ( ) ) ;
2024-07-06 14:36:48 +01:00
}
protected handlePostRaidPlayerScav (
sessionId : string ,
pmcProfile : IPmcData ,
scavProfile : IPmcData ,
isDead : boolean ,
2024-09-29 10:30:44 +01:00
isTransfer : boolean ,
2024-07-07 21:51:24 +01:00
request : IEndLocalRaidRequestData ,
2024-07-23 11:12:53 -04:00
) : void {
2024-09-29 10:30:44 +01:00
const postRaidProfile = request . results . profile ;
if ( isTransfer ) {
// We want scav inventory to persist into next raid when pscav is moving between maps
this . inRaidHelper . setInventory ( sessionId , scavProfile , postRaidProfile , true , isTransfer ) ;
}
2024-07-07 21:51:24 +01:00
scavProfile . Info . Level = request . results . profile . Info . Level ;
scavProfile . Skills = request . results . profile . Skills ;
2024-09-04 23:01:33 +01:00
scavProfile . Stats = request . results . profile . Stats ;
2024-07-07 21:51:24 +01:00
scavProfile . Encyclopedia = request . results . profile . Encyclopedia ;
scavProfile . TaskConditionCounters = request . results . profile . TaskConditionCounters ;
scavProfile . SurvivorClass = request . results . profile . SurvivorClass ;
2024-09-29 10:30:44 +01:00
2024-07-07 21:51:24 +01:00
// Scavs dont have achievements, but copy anyway
scavProfile . Achievements = request . results . profile . Achievements ;
scavProfile . Info . Experience = request . results . profile . Info . Experience ;
// Must occur after experience is set and stats copied over
scavProfile . Stats . Eft . TotalSessionExperience = 0 ;
2024-07-08 16:06:59 +01:00
this . applyTraderStandingAdjustments ( scavProfile . TradersInfo , request . results . profile . TradersInfo ) ;
2024-10-17 14:49:39 +01:00
// Clamp fence standing within -7 to 15 range
2024-11-16 20:22:24 +00:00
const fenceMax = this . traderConfig . fence . playerRepMax ; // 15
const fenceMin = this . traderConfig . fence . playerRepMin ; //-7
2024-10-17 14:49:39 +01:00
const currentFenceStanding = request . results . profile . TradersInfo [ Traders . FENCE ] . standing ;
2024-11-16 19:57:27 +00:00
scavProfile . TradersInfo [ Traders . FENCE ] . standing = Math . min ( Math . max ( currentFenceStanding , fenceMin ) , fenceMax ) ;
2024-07-08 16:06:59 +01:00
2024-10-17 14:49:39 +01:00
// Successful extract as scav, give some rep
2024-11-16 20:22:24 +00:00
if ( this . isPlayerSurvived ( request . results ) && scavProfile . TradersInfo [ Traders . FENCE ] . standing < fenceMax ) {
2024-10-17 14:49:39 +01:00
scavProfile . TradersInfo [ Traders . FENCE ] . standing += this . inRaidConfig . scavExtractStandingGain ;
}
// Copy scav fence values to PMC profile
pmcProfile . TradersInfo [ Traders . FENCE ] = scavProfile . TradersInfo [ Traders . FENCE ] ;
2024-07-08 16:06:59 +01:00
2024-07-07 21:51:24 +01:00
// Must occur after encyclopedia updated
this . mergePmcAndScavEncyclopedias ( scavProfile , pmcProfile ) ;
// Remove skill fatigue values
this . resetSkillPointsEarnedDuringRaid ( scavProfile . Skills . Common ) ;
2024-09-04 23:01:33 +01:00
// Scav died, regen scav loadout and reset timer
if ( isDead ) {
this . playerScavGenerator . generate ( sessionId ) ;
}
2024-07-06 14:36:48 +01:00
// Update last played property
pmcProfile . Info . LastTimePlayedAsSavage = this . timeUtil . getTimestamp ( ) ;
// Force a profile save
this . saveServer . saveProfile ( sessionId ) ;
}
2024-09-29 10:30:44 +01:00
/ * *
*
* @param sessionId Player id
* @param pmcProfile Pmc profile
* @param scavProfile Scav profile
* @param isDead Player died / got left behind in raid
* @param isSurvived Not same as opposite of ` isDead ` , specific status
* @param request
* @param locationName
* /
2024-07-06 14:36:48 +01:00
protected handlePostRaidPmc (
sessionId : string ,
pmcProfile : IPmcData ,
scavProfile : IPmcData ,
isDead : boolean ,
2024-08-23 19:20:44 +01:00
isSurvived : boolean ,
2024-09-29 10:30:44 +01:00
isTransfer : boolean ,
2024-07-06 14:36:48 +01:00
request : IEndLocalRaidRequestData ,
2024-07-07 20:57:41 +01:00
locationName : string ,
2024-07-23 11:12:53 -04:00
) : void {
2024-09-29 10:30:44 +01:00
const postRaidProfile = request . results . profile ;
2024-10-24 22:45:42 +01:00
const preRaidProfileQuestDataClone = this . cloner . clone ( pmcProfile . Quests ) ;
2024-09-29 10:30:44 +01:00
2024-11-24 18:33:23 +00:00
// MUST occur BEFORE inventory actions (setInventory()) occur
// Player died, get quest items they lost for use later
2024-11-24 15:47:03 +00:00
const lostQuestItems = this . profileHelper . getQuestItemsInProfile ( postRaidProfile ) ;
2024-07-06 14:36:48 +01:00
// Update inventory
2024-09-29 10:30:44 +01:00
this . inRaidHelper . setInventory ( sessionId , pmcProfile , postRaidProfile , isSurvived , isTransfer ) ;
2024-07-06 14:36:48 +01:00
pmcProfile . Info . Level = postRaidProfile . Info . Level ;
pmcProfile . Skills = postRaidProfile . Skills ;
pmcProfile . Stats . Eft = postRaidProfile . Stats . Eft ;
2024-07-07 21:51:24 +01:00
pmcProfile . Encyclopedia = postRaidProfile . Encyclopedia ;
pmcProfile . TaskConditionCounters = postRaidProfile . TaskConditionCounters ;
pmcProfile . SurvivorClass = postRaidProfile . SurvivorClass ;
pmcProfile . Achievements = postRaidProfile . Achievements ;
2024-10-24 22:56:02 +01:00
pmcProfile . Quests = this . processPostRaidQuests ( postRaidProfile . Quests ) ;
2024-10-24 22:45:42 +01:00
// Handle edge case - must occur AFTER processPostRaidQuests()
this . lightkeeperQuestWorkaround ( sessionId , postRaidProfile . Quests , preRaidProfileQuestDataClone , pmcProfile ) ;
2024-09-24 16:47:55 +01:00
pmcProfile . WishList = postRaidProfile . WishList ;
2024-07-07 21:51:24 +01:00
pmcProfile . Info . Experience = postRaidProfile . Info . Experience ;
2024-07-06 14:36:48 +01:00
2024-07-08 16:06:59 +01:00
this . applyTraderStandingAdjustments ( pmcProfile . TradersInfo , postRaidProfile . TradersInfo ) ;
2024-10-24 22:45:42 +01:00
// Must occur AFTER experience is set and stats copied over
2024-07-06 14:36:48 +01:00
pmcProfile . Stats . Eft . TotalSessionExperience = 0 ;
2024-07-08 16:06:59 +01:00
const fenceId = Traders . FENCE ;
// Clamp fence standing
const currentFenceStanding = postRaidProfile . TradersInfo [ fenceId ] . standing ;
pmcProfile . TradersInfo [ fenceId ] . standing = Math . min ( Math . max ( currentFenceStanding , - 7 ) , 15 ) ; // Ensure it stays between -7 and 15
// Copy fence values to Scav
scavProfile . TradersInfo [ fenceId ] = pmcProfile . TradersInfo [ fenceId ] ;
2024-11-24 15:47:03 +00:00
// MUST occur AFTER encyclopedia updated
2024-07-07 21:51:24 +01:00
this . mergePmcAndScavEncyclopedias ( pmcProfile , scavProfile ) ;
2024-07-06 14:36:48 +01:00
// Remove skill fatigue values
this . resetSkillPointsEarnedDuringRaid ( pmcProfile . Skills . Common ) ;
// Handle temp, hydration, limb hp/effects
2024-07-23 11:12:53 -04:00
this . healthHelper . updateProfileHealthPostRaid ( pmcProfile , postRaidProfile . Health , sessionId , isDead ) ;
2024-07-06 14:36:48 +01:00
2024-07-23 11:12:53 -04:00
if ( isDead ) {
2024-11-24 18:33:23 +00:00
if ( lostQuestItems . length > 0 ) {
// MUST occur AFTER quests have post raid quest data has been merged "processPostRaidQuests()"
// Player is dead + had quest items, check and fix any broken find item quests
this . checkForAndFixPickupQuestsAfterDeath ( sessionId , lostQuestItems , pmcProfile . Quests ) ;
}
2024-07-23 11:12:53 -04:00
this . pmcChatResponseService . sendKillerResponse ( sessionId , pmcProfile , postRaidProfile . Stats . Eft . Aggressor ) ;
2024-07-06 14:36:48 +01:00
this . inRaidHelper . deleteInventory ( pmcProfile , sessionId ) ;
2024-08-02 15:54:39 +01:00
this . inRaidHelper . removeFiRStatusFromItemsInContainer ( sessionId , pmcProfile , "SecuredContainer" ) ;
2024-07-06 14:36:48 +01:00
}
2024-07-07 21:51:24 +01:00
// Must occur AFTER killer messages have been sent
this . matchBotDetailsCacheService . clearCache ( ) ;
2024-09-29 10:30:44 +01:00
const victims = postRaidProfile . Stats . Eft . Victims . filter (
( victim ) = > [ "pmcbear" , "pmcusec" ] . includes ( victim . Role . toLowerCase ( ) ) , // TODO replace with enum
2024-07-06 14:36:48 +01:00
) ;
2024-07-23 11:12:53 -04:00
if ( victims ? . length > 0 ) {
2024-09-29 10:30:44 +01:00
// Player killed PMCs, send some mail responses to them
2024-07-06 14:36:48 +01:00
this . pmcChatResponseService . sendVictimResponse ( sessionId , victims , pmcProfile ) ;
}
2024-08-23 10:35:41 +01:00
this . handleInsuredItemLostEvent ( sessionId , pmcProfile , request , locationName ) ;
2024-07-07 20:57:41 +01:00
}
2024-11-24 15:47:03 +00:00
/ * *
* On death Quest items are lost , the client does not clean up completed conditions for picking up those quest items ,
* If the completed conditions remain in the profile the player is unable to pick the item up again
* @param sessionId Session id
* @param lostQuestItems Quest items lost on player death
* @param profileQuests Quest status data from player profile
* /
protected checkForAndFixPickupQuestsAfterDeath (
sessionId : string ,
lostQuestItems : IItem [ ] ,
profileQuests : IQuestStatus [ ] ,
) {
// Exclude completed quests
const activeQuestIdsInProfile = profileQuests
2024-11-24 17:00:10 +00:00
. filter ( ( quest ) = > ! [ QuestStatus . Success , QuestStatus . AvailableForStart ] . includes ( quest . status ) )
2024-11-24 15:47:03 +00:00
. map ( ( status ) = > status . qid ) ;
// Get db details of quests we found above
2024-11-24 17:00:10 +00:00
const questDb = Object . values ( this . databaseService . getQuests ( ) ) . filter ( ( quest ) = >
activeQuestIdsInProfile . includes ( quest . _id ) ,
2024-11-24 15:47:03 +00:00
) ;
for ( const lostItem of lostQuestItems ) {
2024-11-24 17:00:10 +00:00
let matchingConditionId : string ;
// Find a quest that has a FindItem condition that has the list items tpl as a target
const matchingQuests = questDb . filter ( ( quest ) = > {
2024-11-24 15:47:03 +00:00
const matchingCondition = quest . conditions . AvailableForFinish . find (
( questCondition ) = >
questCondition . conditionType === "FindItem" && questCondition . target . includes ( lostItem . _tpl ) ,
) ;
if ( ! matchingCondition ) {
// Quest doesnt have a matching condition
2024-11-24 17:00:10 +00:00
return false ;
2024-11-24 15:47:03 +00:00
}
2024-11-24 17:00:10 +00:00
// We found a condition, save id for later
matchingConditionId = matchingCondition . id ;
return true ;
} ) ;
2024-11-24 15:47:03 +00:00
2024-11-24 17:00:10 +00:00
// Fail if multiple were found
if ( matchingQuests . length !== 1 ) {
this . logger . error (
` Unable to fix quest item: ${ lostItem } , ${ matchingQuests . length } matching quests found, expected 1 ` ,
2024-11-24 15:47:03 +00:00
) ;
2024-11-24 17:00:10 +00:00
continue ;
2024-11-24 15:47:03 +00:00
}
2024-11-24 17:00:10 +00:00
const matchingQuest = matchingQuests [ 0 ] ;
// We have a match, remove the condition id from profile to reset progress and let player pick item up again
const profileQuestToUpdate = profileQuests . find ( ( questStatus ) = > questStatus . qid === matchingQuest . _id ) ;
if ( ! profileQuestToUpdate ) {
// Profile doesnt have a matching quest
continue ;
}
// Filter out the matching condition we found
profileQuestToUpdate . completedConditions = profileQuestToUpdate . completedConditions . filter (
( conditionId ) = > conditionId !== matchingConditionId ,
) ;
2024-11-24 15:47:03 +00:00
}
}
2024-10-24 22:45:42 +01:00
/ * *
* In 0.15 Lightkeeper quests do not give rewards in PvE , this issue also occurs in spt
* We check for newly completed Lk quests and run them through the servers ` CompleteQuest ` process
* This rewards players with items + craft unlocks + new trader assorts
* @param sessionId Session id
* @param postRaidQuests Quest statuses post - raid
* @param preRaidQuests Quest statuses pre - raid
* @param pmcProfile Players profile
* /
protected lightkeeperQuestWorkaround (
sessionId : string ,
postRaidQuests : IQuestStatus [ ] ,
preRaidQuests : IQuestStatus [ ] ,
pmcProfile : IPmcData ,
) : void {
// LK quests that were not completed before raid but now are
const newlyCompletedLightkeeperQuests = postRaidQuests . filter (
( postRaidQuest ) = >
postRaidQuest . status === QuestStatus . Success &&
preRaidQuests . find (
( preRaidQuest ) = >
preRaidQuest . qid === postRaidQuest . qid && preRaidQuest . status !== QuestStatus . Success ,
) &&
2024-10-25 19:19:49 +01:00
this . databaseService . getQuests ( ) [ postRaidQuest . qid ] ? . traderId === Traders . LIGHTHOUSEKEEPER ,
2024-10-24 22:45:42 +01:00
) ;
// Run server complete quest process to ensure player gets rewards
for ( const questToComplete of newlyCompletedLightkeeperQuests ) {
this . questHelper . completeQuest (
pmcProfile ,
{ Action : "CompleteQuest" , qid : questToComplete.qid , removeExcessItems : false } ,
sessionId ,
) ;
}
}
2024-09-03 17:09:56 +01:00
/ * *
* Convert post - raid quests into correct format
2024-09-03 18:38:32 +01:00
* Quest status comes back as a string version of the enum ` Success ` , not the expected value of 1
2024-10-24 22:45:42 +01:00
* @param questsToProcess quests data from client
* @param preRaidQuestStatuses quest data from before raid
2024-09-03 17:09:56 +01:00
* @returns IQuestStatus
* /
2024-10-24 22:56:02 +01:00
protected processPostRaidQuests ( questsToProcess : IQuestStatus [ ] ) : IQuestStatus [ ] {
2024-09-03 17:09:56 +01:00
for ( const quest of questsToProcess ) {
2024-09-03 19:12:23 +01:00
quest . status = Number ( QuestStatus [ quest . status ] ) ;
2024-09-03 17:09:56 +01:00
// Iterate over each status timer key and convert from a string into the enums number value
for ( const statusTimerKey in quest . statusTimers ) {
if ( Number . isNaN ( Number . parseInt ( statusTimerKey ) ) ) {
// Is a string, convert
quest . statusTimers [ QuestStatus [ statusTimerKey ] ] = quest . statusTimers [ statusTimerKey ] ;
// Delete the old string key/value
quest . statusTimers [ statusTimerKey ] = undefined ;
}
}
}
2024-09-10 13:41:54 +01:00
// Find marked as failed quests + flagged as restartable and re-status them as 'failed' so they can be restarted by player
const failedQuests = questsToProcess . filter ( ( quest ) = > quest . status === QuestStatus . MarkedAsFailed ) ;
for ( const failedQuest of failedQuests ) {
const dbQuest = this . databaseService . getQuests ( ) [ failedQuest . qid ] ;
if ( ! dbQuest ) {
continue ;
}
if ( dbQuest . restartable ) {
failedQuest . status = QuestStatus . Fail ;
}
}
2024-09-03 17:09:56 +01:00
return questsToProcess ;
}
2024-07-08 16:06:59 +01:00
/ * *
* Adjust server trader settings if they differ from data sent by client
* @param tradersServerProfile Server
* @param tradersClientProfile Client
* /
protected applyTraderStandingAdjustments (
2024-09-24 11:26:45 +01:00
tradersServerProfile : Record < string , ITraderInfo > ,
tradersClientProfile : Record < string , ITraderInfo > ,
2024-07-23 11:12:53 -04:00
) : void {
for ( const traderId in tradersClientProfile ) {
2024-07-08 16:06:59 +01:00
const serverProfileTrader = tradersServerProfile [ traderId ] ;
const clientProfileTrader = tradersClientProfile [ traderId ] ;
2024-07-23 11:12:53 -04:00
if ( ! ( serverProfileTrader && clientProfileTrader ) ) {
2024-07-08 16:06:59 +01:00
continue ;
}
2024-07-23 11:12:53 -04:00
if ( clientProfileTrader . standing !== serverProfileTrader . standing ) {
2024-07-08 16:06:59 +01:00
// Difference found, update server profile with values from client profile
tradersServerProfile [ traderId ] . standing = clientProfileTrader . standing ;
}
}
}
2024-07-07 21:51:24 +01:00
/ * *
2024-09-27 07:19:20 +00:00
* Check if player used BTR or transit item sending service and send items to player via mail if found
2024-07-07 21:51:24 +01:00
* @param sessionId Session id
* @param request End raid request
* /
2024-09-27 07:19:20 +00:00
protected handleItemTransferEvent ( sessionId : string , request : IEndLocalRaidRequestData ) : void {
const transferTypes = [ "btr" , "transit" ] ;
for ( const trasferType of transferTypes ) {
const rootId = ` ${ Traders . BTR } _ ${ trasferType } ` ;
let itemsToSend = request . transferItems [ rootId ] ? ? [ ] ;
// Filter out the btr container item from transferred items before delivering
itemsToSend = itemsToSend . filter ( ( item ) = > item . _id !== Traders . BTR ) ;
if ( itemsToSend . length === 0 ) {
continue ;
}
2024-08-31 20:48:32 +01:00
2024-09-27 07:19:20 +00:00
this . transferItemDelivery ( sessionId , Traders . BTR , itemsToSend ) ;
}
2024-07-07 21:51:24 +01:00
}
2024-09-27 07:19:20 +00:00
protected transferItemDelivery ( sessionId : string , traderId : string , items : IItem [ ] ) : void {
2024-07-07 21:51:24 +01:00
const serverProfile = this . saveServer . getProfile ( sessionId ) ;
const pmcData = serverProfile . characters . pmc ;
const dialogueTemplates = this . databaseService . getTrader ( traderId ) . dialogue ;
2024-07-23 11:12:53 -04:00
if ( ! dialogueTemplates ) {
this . logger . error (
this . localisationService . getText ( "inraid-unable_to_deliver_item_no_trader_found" , traderId ) ,
) ;
2024-07-07 21:51:24 +01:00
return ;
}
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 item delivery and insurance
const deliveredItemIds = items . map ( ( item ) = > item . _id ) ;
2024-07-23 11:12:53 -04:00
pmcData . InsuredItems = pmcData . InsuredItems . filter (
( insuredItem ) = > ! deliveredItemIds . includes ( insuredItem . itemId ) ,
) ;
2024-07-07 21:51:24 +01:00
// Send the items to the player
this . mailSendService . sendLocalisedNpcMessageToPlayer (
sessionId ,
this . traderHelper . getTraderById ( traderId ) ,
MessageType . BTR_ITEMS_DELIVERY ,
messageId ,
items ,
messageStoreTime ,
) ;
}
2024-08-23 10:35:41 +01:00
protected handleInsuredItemLostEvent (
sessionId : string ,
preRaidPmcProfile : IPmcData ,
request : IEndLocalRaidRequestData ,
locationName : string ,
2024-09-17 16:24:23 +01:00
) : void {
2024-08-23 10:35:41 +01:00
if ( request . lostInsuredItems ? . length > 0 ) {
const mappedItems = this . insuranceService . mapInsuredItemsToTrader (
sessionId ,
request . lostInsuredItems ,
request . results . profile ,
) ;
2024-09-17 16:24:23 +01:00
// Is possible to have items in lostInsuredItems but removed before reaching mappedItems
if ( mappedItems . length === 0 ) {
return ;
}
2024-08-23 10:35:41 +01:00
this . insuranceService . storeGearLostInRaidToSendLater ( sessionId , mappedItems ) ;
2024-09-20 16:43:00 +01:00
this . insuranceService . startPostRaidInsuranceLostProcess ( preRaidPmcProfile , sessionId , locationName ) ;
2024-08-23 10:35:41 +01:00
}
}
2024-07-07 20:57:41 +01:00
/ * *
* Return the equipped items from a players inventory
* @param items Players inventory to search through
* @returns an array of equipped items
* /
2024-09-24 12:47:29 +01:00
protected getEquippedGear ( items : IItem [ ] ) : IItem [ ] {
2024-07-07 20:57:41 +01:00
// 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" ,
] ;
2024-09-24 12:47:29 +01:00
let inventoryItems : IItem [ ] = [ ] ;
2024-07-07 20:57:41 +01:00
// Get an array of root player items
2024-07-23 11:12:53 -04:00
for ( const item of items ) {
if ( inventorySlots . includes ( item . slotId ) ) {
2024-07-07 20:57:41 +01:00
inventoryItems . push ( item ) ;
}
2024-07-06 14:36:48 +01:00
}
2024-07-07 20:57:41 +01:00
// Loop through these items and get all of their children
let newItems = inventoryItems ;
2024-07-23 11:12:53 -04:00
while ( newItems . length > 0 ) {
2024-07-07 20:57:41 +01:00
const foundItems = [ ] ;
2024-07-23 11:12:53 -04:00
for ( const item of newItems ) {
2024-07-07 20:57:41 +01:00
// Find children of this item
2024-07-23 11:12:53 -04:00
for ( const newItem of items ) {
if ( newItem . parentId === item . _id ) {
2024-07-07 20:57:41 +01:00
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 ;
2024-07-06 14:36:48 +01:00
}
2024-08-23 19:20:44 +01:00
/ * *
* Checks to see if player survives . run through will return false
* @param statusOnExit Exit value from offraidData object
* @returns true if Survived
* /
protected isPlayerSurvived ( results : IEndRaidResult ) : boolean {
2024-11-16 20:22:24 +00:00
return results . result === ExitStatus . SURVIVED ;
2024-08-23 19:20:44 +01:00
}
2024-07-06 14:36:48 +01:00
/ * *
* Is the player dead after a raid - dead = anything other than "survived" / "runner"
2024-09-29 10:30:44 +01:00
* @param results Post raid request
2024-07-06 14:36:48 +01:00
* @returns true if dead
* /
2024-07-23 11:12:53 -04:00
protected isPlayerDead ( results : IEndRaidResult ) : boolean {
2024-11-16 20:22:24 +00:00
return [ ExitStatus . KILLED , ExitStatus . MISSINGINACTION , ExitStatus . LEFT ] . includes ( results . result ) ;
2024-07-06 14:36:48 +01:00
}
2024-09-29 10:30:44 +01:00
/ * *
* Has the player moved from one map to another
* @param results Post raid request
* @returns True if players transfered
* /
protected isMapToMapTransfer ( results : IEndRaidResult ) {
2024-11-16 20:22:24 +00:00
return results . result === ExitStatus . TRANSIT ;
2024-09-29 10:30:44 +01:00
}
2024-07-06 14:36:48 +01:00
/ * *
* Reset the skill points earned in a raid to 0 , ready for next raid
* @param commonSkills Profile common skills to update
* /
2024-07-23 11:12:53 -04:00
protected resetSkillPointsEarnedDuringRaid ( commonSkills : Common [ ] ) : void {
for ( const skill of commonSkills ) {
2024-07-06 14:36:48 +01:00
skill . PointsEarnedDuringSession = 0.0 ;
}
}
/ * *
* merge two dictionaries together
* Prioritise pair that has true as a value
* @param primary main dictionary
* @param secondary Secondary dictionary
* /
2024-07-23 11:12:53 -04:00
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 ) ) {
2024-07-06 14:36:48 +01:00
target [ key ] = source [ key ] ;
}
}
return target ;
}
const merged = extend ( extend ( { } , primary . Encyclopedia ) , secondary . Encyclopedia ) ;
primary . Encyclopedia = merged ;
secondary . Encyclopedia = merged ;
}
}