2024-05-21 19:59:04 +02:00
import { ContainerHelper } from "@spt/helpers/ContainerHelper" ;
import { ItemHelper } from "@spt/helpers/ItemHelper" ;
import { PresetHelper } from "@spt/helpers/PresetHelper" ;
2023-11-16 22:42:06 +01:00
import {
2024-04-23 11:27:09 +02:00
IContainerMinMax ,
2023-11-16 22:42:06 +01:00
IStaticAmmoDetails ,
2024-04-23 11:27:09 +02:00
IStaticContainer ,
2023-11-16 22:42:06 +01:00
IStaticContainerData ,
IStaticForcedProps ,
IStaticLootDetails ,
2024-05-21 19:59:04 +02:00
} from "@spt/models/eft/common/ILocation" ;
import { ILocationBase } from "@spt/models/eft/common/ILocationBase" ;
2024-09-24 12:26:45 +02:00
import { ILooseLoot , ISpawnpoint , ISpawnpointTemplate , ISpawnpointsForced } from "@spt/models/eft/common/ILooseLoot" ;
2024-09-24 13:47:29 +02:00
import { IItem } from "@spt/models/eft/common/tables/IItem" ;
2024-05-21 19:59:04 +02:00
import { BaseClasses } from "@spt/models/enums/BaseClasses" ;
import { ConfigTypes } from "@spt/models/enums/ConfigTypes" ;
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig" ;
import { ILogger } from "@spt/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt/servers/ConfigServer" ;
2024-05-28 15:52:22 +02:00
import { DatabaseService } from "@spt/services/DatabaseService" ;
2024-06-04 18:06:50 +02:00
import { ItemFilterService } from "@spt/services/ItemFilterService" ;
2024-05-21 19:59:04 +02:00
import { LocalisationService } from "@spt/services/LocalisationService" ;
import { SeasonalEventService } from "@spt/services/SeasonalEventService" ;
import { MathUtil } from "@spt/utils/MathUtil" ;
import { ObjectId } from "@spt/utils/ObjectId" ;
import { ProbabilityObject , ProbabilityObjectArray , RandomUtil } from "@spt/utils/RandomUtil" ;
2024-07-23 17:12:53 +02:00
import { ICloner } from "@spt/utils/cloners/ICloner" ;
import { inject , injectable } from "tsyringe" ;
2023-03-03 16:23:46 +01:00
2024-07-23 17:12:53 +02:00
export interface IContainerItem {
2024-09-24 13:47:29 +02:00
items : IItem [ ] ;
2024-07-23 17:12:53 +02:00
width : number ;
height : number ;
2023-03-03 16:23:46 +01:00
}
2024-07-23 17:12:53 +02:00
export interface IContainerGroupCount {
2023-10-10 13:03:20 +02:00
/** Containers this group has + probabilty to spawn */
2024-07-23 17:12:53 +02:00
containerIdsWithProbability : Record < string , number > ;
2023-10-10 13:03:20 +02:00
/** How many containers the map should spawn with this group id */
2024-07-23 17:12:53 +02:00
chosenCount : number ;
2023-10-10 13:03:20 +02:00
}
2023-03-03 16:23:46 +01:00
@injectable ( )
2024-07-23 17:12:53 +02:00
export class LocationLootGenerator {
2023-03-03 16:23:46 +01:00
protected locationConfig : ILocationConfig ;
constructor (
2024-05-28 16:04:20 +02:00
@inject ( "PrimaryLogger" ) protected logger : ILogger ,
2024-05-28 15:52:22 +02:00
@inject ( "DatabaseService" ) protected databaseService : DatabaseService ,
2023-03-03 16:23:46 +01:00
@inject ( "ObjectId" ) protected objectId : ObjectId ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "ItemHelper" ) protected itemHelper : ItemHelper ,
@inject ( "MathUtil" ) protected mathUtil : MathUtil ,
@inject ( "SeasonalEventService" ) protected seasonalEventService : SeasonalEventService ,
@inject ( "ContainerHelper" ) protected containerHelper : ContainerHelper ,
@inject ( "PresetHelper" ) protected presetHelper : PresetHelper ,
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
2024-06-04 18:06:50 +02:00
@inject ( "ItemFilterService" ) protected itemFilterService : ItemFilterService ,
2023-11-16 22:42:06 +01:00
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
2024-05-28 16:04:20 +02:00
@inject ( "PrimaryCloner" ) protected cloner : ICloner ,
2024-07-23 17:12:53 +02:00
) {
2023-03-03 16:23:46 +01:00
this . locationConfig = this . configServer . getConfig ( ConfigTypes . LOCATION ) ;
}
2023-10-10 13:03:20 +02:00
/ * *
* Create an array of container objects with randomised loot
* @param locationBase Map base to generate containers for
2024-06-11 20:58:30 +02:00
* @param staticAmmoDist Static ammo distribution
2023-10-10 13:03:20 +02:00
* @returns Array of container objects
* /
2023-11-16 22:42:06 +01:00
public generateStaticContainers (
locationBase : ILocationBase ,
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
2024-09-24 12:26:45 +02:00
) : ISpawnpointTemplate [ ] {
2023-12-11 15:41:30 +01:00
let staticLootItemCount = 0 ;
2024-09-24 12:26:45 +02:00
const result : ISpawnpointTemplate [ ] = [ ] ;
2023-10-10 13:03:20 +02:00
const locationId = locationBase . Id . toLowerCase ( ) ;
2024-05-28 15:52:22 +02:00
const mapData = this . databaseService . getLocation ( locationId ) ;
2023-10-10 13:03:20 +02:00
2024-05-13 19:58:17 +02:00
const staticWeaponsOnMapClone = this . cloner . clone ( mapData . staticContainers . staticWeapons ) ;
2024-07-23 17:12:53 +02:00
if ( ! staticWeaponsOnMapClone ) {
this . logger . error (
this . localisationService . getText ( "location-unable_to_find_static_weapon_for_map" , locationBase . Name ) ,
) ;
2023-10-10 13:03:20 +02:00
}
// Add mounted weapons to output loot
2024-05-17 21:32:41 +02:00
result . push ( . . . ( staticWeaponsOnMapClone ? ? [ ] ) ) ;
2023-10-10 13:03:20 +02:00
2024-05-13 19:58:17 +02:00
const allStaticContainersOnMapClone = this . cloner . clone ( mapData . staticContainers . staticContainers ) ;
2024-07-23 17:12:53 +02:00
if ( ! allStaticContainersOnMapClone ) {
this . logger . error (
this . localisationService . getText ( "location-unable_to_find_static_container_for_map" , locationBase . Name ) ,
) ;
2023-10-10 13:03:20 +02:00
}
2024-02-05 15:43:46 +01:00
const staticRandomisableContainersOnMap = this . getRandomisableContainersOnMap ( allStaticContainersOnMapClone ) ;
2023-10-10 13:03:20 +02:00
// Containers that MUST be added to map (quest containers etc)
2024-05-13 19:58:17 +02:00
const staticForcedOnMapClone = this . cloner . clone ( mapData . staticContainers . staticForced ) ;
2024-07-23 17:12:53 +02:00
if ( ! staticForcedOnMapClone ) {
this . logger . error (
this . localisationService . getText (
"location-unable_to_find_forced_static_data_for_map" ,
locationBase . Name ,
) ,
) ;
2023-10-10 13:03:20 +02:00
}
// Keep track of static loot count
let staticContainerCount = 0 ;
// Find all 100% spawn containers
2024-04-23 11:27:09 +02:00
const staticLootDist = mapData . staticLoot ;
2024-02-05 15:43:46 +01:00
const guaranteedContainers = this . getGuaranteedContainers ( allStaticContainersOnMapClone ) ;
2023-10-10 13:03:20 +02:00
staticContainerCount += guaranteedContainers . length ;
2023-11-16 22:42:06 +01:00
2023-10-10 13:03:20 +02:00
// Add loot to guaranteed containers and add to result
2024-07-23 17:12:53 +02:00
for ( const container of guaranteedContainers ) {
2023-11-16 22:42:06 +01:00
const containerWithLoot = this . addLootToContainer (
container ,
2024-02-05 15:43:46 +01:00
staticForcedOnMapClone ,
2023-11-16 22:42:06 +01:00
staticLootDist ,
staticAmmoDist ,
locationId ,
) ;
2023-10-10 13:03:20 +02:00
result . push ( containerWithLoot . template ) ;
2023-12-11 15:41:30 +01:00
staticLootItemCount += containerWithLoot . template . Items . length ;
2023-10-10 13:03:20 +02:00
}
2023-12-11 15:41:30 +01:00
this . logger . debug ( ` Added ${ guaranteedContainers . length } guaranteed containers ` ) ;
2023-10-10 13:03:20 +02:00
2023-12-11 15:41:30 +01:00
// Randomisation is turned off globally or just turned off for this map
2023-11-16 22:42:06 +01:00
if (
2024-05-17 21:32:41 +02:00
! (
2024-07-23 17:12:53 +02:00
this . locationConfig . containerRandomisationSettings . enabled &&
this . locationConfig . containerRandomisationSettings . maps [ locationId ]
2024-05-17 21:32:41 +02:00
)
2024-07-23 17:12:53 +02:00
) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Container randomisation disabled, Adding ${ staticRandomisableContainersOnMap . length } containers to ${ locationBase . Name } ` ,
) ;
2024-07-23 17:12:53 +02:00
for ( const container of staticRandomisableContainersOnMap ) {
2023-11-16 22:42:06 +01:00
const containerWithLoot = this . addLootToContainer (
container ,
2024-02-05 15:43:46 +01:00
staticForcedOnMapClone ,
2023-11-16 22:42:06 +01:00
staticLootDist ,
staticAmmoDist ,
locationId ,
) ;
2023-10-10 13:03:20 +02:00
result . push ( containerWithLoot . template ) ;
2023-12-11 15:41:30 +01:00
staticLootItemCount += containerWithLoot . template . Items . length ;
2023-10-10 13:03:20 +02:00
}
2023-12-11 15:41:30 +01:00
this . logger . success ( ` A total of ${ staticLootItemCount } static items spawned ` ) ;
2023-10-10 13:03:20 +02:00
return result ;
}
// Group containers by their groupId
2024-07-23 17:12:53 +02:00
if ( ! mapData . statics ) {
this . logger . warning (
this . localisationService . getText ( "location-unable_to_generate_static_loot" , locationId ) ,
) ;
2023-12-28 16:13:29 +01:00
return result ;
}
2024-05-28 15:52:22 +02:00
const mapping = this . getGroupIdToContainerMappings ( mapData . statics , staticRandomisableContainersOnMap ) ;
2023-10-10 13:03:20 +02:00
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
2024-07-23 17:12:53 +02:00
for ( const groupId in mapping ) {
2023-10-10 13:03:20 +02:00
const data = mapping [ groupId ] ;
// Count chosen was 0, skip
2024-07-23 17:12:53 +02:00
if ( data . chosenCount === 0 ) {
2023-10-10 13:03:20 +02:00
continue ;
}
2024-07-23 17:12:53 +02:00
if ( Object . keys ( data . containerIdsWithProbability ) . length === 0 ) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Group: ${ groupId } has no containers with < 100% spawn chance to choose from, skipping ` ,
) ;
2023-10-10 13:03:20 +02:00
continue ;
}
// EDGE CASE: These are containers without a group and have a probability < 100%
2024-07-23 17:12:53 +02:00
if ( groupId === "" ) {
2024-05-13 19:58:17 +02:00
const containerIdsCopy = this . cloner . clone ( data . containerIdsWithProbability ) ;
2023-10-10 13:03:20 +02:00
// Roll each containers probability, if it passes, it gets added
data . containerIdsWithProbability = { } ;
2024-07-23 17:12:53 +02:00
for ( const containerId in containerIdsCopy ) {
if ( this . randomUtil . getChance100 ( containerIdsCopy [ containerId ] * 100 ) ) {
2023-10-10 13:03:20 +02:00
data . containerIdsWithProbability [ containerId ] = containerIdsCopy [ containerId ] ;
}
}
// Set desired count to size of array (we want all containers chosen)
data . chosenCount = Object . keys ( data . containerIdsWithProbability ) . length ;
// EDGE CASE: chosen container count could be 0
2024-07-23 17:12:53 +02:00
if ( data . chosenCount === 0 ) {
2023-10-10 13:03:20 +02:00
continue ;
}
}
// Pass possible containers into function to choose some
const chosenContainerIds = this . getContainersByProbabilty ( groupId , data ) ;
2024-07-23 17:12:53 +02:00
for ( const chosenContainerId of chosenContainerIds ) {
2023-10-10 13:03:20 +02:00
// Look up container object from full list of containers on map
2024-05-17 21:32:41 +02:00
const containerObject = staticRandomisableContainersOnMap . find (
( staticContainer ) = > staticContainer . template . Id === chosenContainerId ,
2023-11-16 22:42:06 +01:00
) ;
2024-07-23 17:12:53 +02:00
if ( ! containerObject ) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
2024-05-17 21:32:41 +02:00
` Container: ${ chosenContainerIds [ chosenContainerId ] } not found in staticRandomisableContainersOnMap, this is bad ` ,
2023-11-16 22:42:06 +01:00
) ;
2023-10-10 13:03:20 +02:00
continue ;
}
// Add loot to container and push into result object
2023-11-16 22:42:06 +01:00
const containerWithLoot = this . addLootToContainer (
containerObject ,
2024-02-05 15:43:46 +01:00
staticForcedOnMapClone ,
2023-11-16 22:42:06 +01:00
staticLootDist ,
staticAmmoDist ,
locationId ,
) ;
2023-10-10 13:03:20 +02:00
result . push ( containerWithLoot . template ) ;
staticContainerCount ++ ;
2023-12-11 15:41:30 +01:00
staticLootItemCount += containerWithLoot . template . Items . length ;
2023-10-10 13:03:20 +02:00
}
}
2023-12-11 15:41:30 +01:00
this . logger . success ( ` A total of ${ staticLootItemCount } static items spawned ` ) ;
2023-11-16 22:42:06 +01:00
this . logger . success (
this . localisationService . getText ( "location-containers_generated_success" , staticContainerCount ) ,
) ;
2023-10-10 13:03:20 +02:00
return result ;
}
/ * *
* Get containers with a non - 100 % chance to spawn OR are NOT on the container type randomistion blacklist
2023-11-16 22:42:06 +01:00
* @param staticContainers
2023-10-10 13:03:20 +02:00
* @returns IStaticContainerData array
* /
2024-07-23 17:12:53 +02:00
protected getRandomisableContainersOnMap ( staticContainers : IStaticContainerData [ ] ) : IStaticContainerData [ ] {
2024-05-17 21:32:41 +02:00
return staticContainers . filter (
( staticContainer ) = >
2024-07-23 17:12:53 +02:00
staticContainer . probability !== 1 &&
! staticContainer . template . IsAlwaysSpawn &&
! this . locationConfig . containerRandomisationSettings . containerTypesToNotRandomise . includes (
2024-05-17 21:32:41 +02:00
staticContainer . template . Items [ 0 ] . _tpl ,
) ,
2023-11-16 22:42:06 +01:00
) ;
2023-10-10 13:03:20 +02:00
}
/ * *
* Get containers with 100 % spawn rate or have a type on the randomistion ignore list
2023-11-16 22:42:06 +01:00
* @param staticContainersOnMap
2023-10-10 13:03:20 +02:00
* @returns IStaticContainerData array
* /
2024-07-23 17:12:53 +02:00
protected getGuaranteedContainers ( staticContainersOnMap : IStaticContainerData [ ] ) : IStaticContainerData [ ] {
2024-05-17 21:32:41 +02:00
return staticContainersOnMap . filter (
( staticContainer ) = >
2024-07-23 17:12:53 +02:00
staticContainer . probability === 1 ||
staticContainer . template . IsAlwaysSpawn ||
this . locationConfig . containerRandomisationSettings . containerTypesToNotRandomise . includes (
2024-05-17 21:32:41 +02:00
staticContainer . template . Items [ 0 ] . _tpl ,
) ,
2023-11-16 22:42:06 +01:00
) ;
2023-10-10 13:03:20 +02:00
}
/ * *
* Choose a number of containers based on their probabilty value to fulfil the desired count in containerData . chosenCount
* @param groupId Name of the group the containers are being collected for
* @param containerData Containers and probability values for a groupId
* @returns List of chosen container Ids
* /
2024-07-23 17:12:53 +02:00
protected getContainersByProbabilty ( groupId : string , containerData : IContainerGroupCount ) : string [ ] {
2023-10-10 13:03:20 +02:00
const chosenContainerIds : string [ ] = [ ] ;
const containerIds = Object . keys ( containerData . containerIdsWithProbability ) ;
2024-07-23 17:12:53 +02:00
if ( containerData . chosenCount > containerIds . length ) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Group: ${ groupId } wants ${ containerData . chosenCount } containers but pool only has ${ containerIds . length } , add what's available ` ,
) ;
2023-10-10 13:03:20 +02:00
return containerIds ;
}
// Create probability array with all possible container ids in this group and their relataive probability of spawning
2024-05-13 19:58:17 +02:00
const containerDistribution = new ProbabilityObjectArray < string > ( this . mathUtil , this . cloner ) ;
2024-07-23 17:12:53 +02:00
for ( const x of containerIds ) {
2024-02-02 21:00:12 +01:00
containerDistribution . push ( new ProbabilityObject ( x , containerData . containerIdsWithProbability [ x ] ) ) ;
}
2023-10-10 13:03:20 +02:00
chosenContainerIds . push ( . . . containerDistribution . draw ( containerData . chosenCount ) ) ;
return chosenContainerIds ;
}
/ * *
* Get a mapping of each groupid and the containers in that group + count of containers to spawn on map
* @param containersGroups Container group values
* @returns dictionary keyed by groupId
* /
protected getGroupIdToContainerMappings (
staticContainerGroupData : IStaticContainer | Record < string , IContainerMinMax > ,
2023-11-16 22:42:06 +01:00
staticContainersOnMap : IStaticContainerData [ ] ,
2024-07-23 17:12:53 +02:00
) : Record < string , IContainerGroupCount > {
2023-10-10 13:03:20 +02:00
// Create dictionary of all group ids and choose a count of containers the map will spawn of that group
const mapping : Record < string , IContainerGroupCount > = { } ;
2024-07-23 17:12:53 +02:00
for ( const groupId in staticContainerGroupData . containersGroups ) {
2023-10-10 13:03:20 +02:00
const groupData = staticContainerGroupData . containersGroups [ groupId ] ;
2024-07-23 17:12:53 +02:00
if ( ! mapping [ groupId ] ) {
2023-10-10 13:03:20 +02:00
mapping [ groupId ] = {
containerIdsWithProbability : { } ,
chosenCount : this.randomUtil.getInt (
2023-11-16 22:42:06 +01:00
Math . round (
2024-07-23 17:12:53 +02:00
groupData . minContainers *
this . locationConfig . containerRandomisationSettings . containerGroupMinSizeMultiplier ,
2023-11-16 22:42:06 +01:00
) ,
Math . round (
2024-07-23 17:12:53 +02:00
groupData . maxContainers *
this . locationConfig . containerRandomisationSettings . containerGroupMaxSizeMultiplier ,
2023-11-16 22:42:06 +01:00
) ,
) ,
2023-10-10 13:03:20 +02:00
} ;
}
}
// Add an empty group for containers without a group id but still have a < 100% chance to spawn
// Likely bad BSG data, will be fixed...eventually, example of the groupids: `NEED_TO_BE_FIXED1`,`NEED_TO_BE_FIXED_SE02`, `NEED_TO_BE_FIXED_NW_01`
2023-11-16 22:42:06 +01:00
mapping [ "" ] = { containerIdsWithProbability : { } , chosenCount : - 1 } ;
2023-10-10 13:03:20 +02:00
// Iterate over all containers and add to group keyed by groupId
// Containers without a group go into a group with empty key ""
2024-07-23 17:12:53 +02:00
for ( const container of staticContainersOnMap ) {
2023-10-10 13:03:20 +02:00
const groupData = staticContainerGroupData . containers [ container . template . Id ] ;
2024-07-23 17:12:53 +02:00
if ( ! groupData ) {
this . logger . error (
this . localisationService . getText (
"location-unable_to_find_container_in_statics_json" ,
container . template . Id ,
) ,
) ;
2024-05-21 13:40:16 +02:00
2023-10-10 13:03:20 +02:00
continue ;
}
2024-07-23 17:12:53 +02:00
if ( container . probability === 1 ) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Container ${ container . template . Id } with group ${ groupData . groupId } had 100% chance to spawn was picked as random container, skipping ` ,
) ;
2024-05-21 13:40:16 +02:00
2023-10-10 13:03:20 +02:00
continue ;
}
mapping [ groupData . groupId ] . containerIdsWithProbability [ container . template . Id ] = container . probability ;
}
return mapping ;
}
2023-03-03 18:53:28 +01:00
/ * *
2023-07-02 16:05:32 +02:00
* Choose loot to put into a static container based on weighting
* Handle forced items + seasonal item removal when not in season
* @param staticContainer The container itself we will add loot to
* @param staticForced Loot we need to force into the container
* @param staticLootDist staticLoot . json
* @param staticAmmoDist staticAmmo . json
2023-03-03 18:53:28 +01:00
* @param locationName Name of the map to generate static loot for
* @returns IStaticContainerProps
* /
2023-10-10 13:03:20 +02:00
protected addLootToContainer (
staticContainer : IStaticContainerData ,
2023-03-03 16:23:46 +01:00
staticForced : IStaticForcedProps [ ] ,
staticLootDist : Record < string , IStaticLootDetails > ,
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
2023-11-16 22:42:06 +01:00
locationName : string ,
2024-07-23 17:12:53 +02:00
) : IStaticContainerData {
2024-05-13 19:58:17 +02:00
const containerClone = this . cloner . clone ( staticContainer ) ;
2024-02-05 15:43:46 +01:00
const containerTpl = containerClone . template . Items [ 0 ] . _tpl ;
2023-07-02 16:05:32 +02:00
// Create new unique parent id to prevent any collisions
2023-03-03 16:23:46 +01:00
const parentId = this . objectId . generate ( ) ;
2024-02-05 15:43:46 +01:00
containerClone . template . Root = parentId ;
containerClone . template . Items [ 0 ] . _id = parentId ;
2023-03-03 16:23:46 +01:00
2024-02-11 13:16:08 +01:00
const containerMap = this . getContainerMapping ( containerTpl ) ;
2023-03-03 16:23:46 +01:00
2023-07-02 16:05:32 +02:00
// Choose count of items to add to container
const itemCountToAdd = this . getWeightedCountOfContainerItems ( containerTpl , staticLootDist , locationName ) ;
2023-03-03 16:23:46 +01:00
2023-07-02 16:05:32 +02:00
// Get all possible loot items for container
const containerLootPool = this . getPossibleLootItemsForContainer ( containerTpl , staticLootDist ) ;
2023-03-03 16:23:46 +01:00
2023-07-02 16:05:32 +02:00
// Some containers need to have items forced into it (quest keys etc)
2024-05-17 21:32:41 +02:00
const tplsForced = staticForced
. filter ( ( forcedStaticProp ) = > forcedStaticProp . containerId === containerClone . template . Id )
. map ( ( x ) = > x . itemTpl ) ;
2023-03-03 16:23:46 +01:00
// Draw random loot
2024-07-07 20:43:32 +02:00
// Allow money to spawn more than once in container
2023-03-03 18:53:28 +01:00
let failedToFitCount = 0 ;
2024-07-07 20:43:32 +02:00
const locklist = this . itemHelper . getMoneyTpls ( ) ;
2023-07-02 16:05:32 +02:00
// Choose items to add to container, factor in weighting + lock money down
2024-01-07 21:20:25 +01:00
// Filter out items picked that're already in the above `tplsForced` array
2024-05-17 21:32:41 +02:00
const chosenTpls = containerLootPool
. draw ( itemCountToAdd , this . locationConfig . allowDuplicateItemsInStaticContainers , locklist )
. filter ( ( tpl ) = > ! tplsForced . includes ( tpl ) ) ;
2023-07-02 16:05:32 +02:00
// Add forced loot to chosen item pool
const tplsToAddToContainer = tplsForced . concat ( chosenTpls ) ;
2024-07-23 17:12:53 +02:00
for ( const tplToAdd of tplsToAddToContainer ) {
2023-07-02 16:05:32 +02:00
const chosenItemWithChildren = this . createStaticLootItem ( tplToAdd , staticAmmoDist , parentId ) ;
2023-03-06 10:57:04 +01:00
const items = chosenItemWithChildren . items ;
const width = chosenItemWithChildren . width ;
const height = chosenItemWithChildren . height ;
2023-03-03 16:23:46 +01:00
2023-03-03 18:53:28 +01:00
// look for open slot to put chosen item into
2023-07-02 16:05:32 +02:00
const result = this . containerHelper . findSlotForItem ( containerMap , width , height ) ;
2024-07-23 17:12:53 +02:00
if ( ! result . success ) {
if ( failedToFitCount >= this . locationConfig . fitLootIntoContainerAttempts ) {
2024-02-09 16:13:49 +01:00
// x attempts to fit an item, container is probably full, stop trying to add more
2023-03-03 18:53:28 +01:00
break ;
}
// Can't fit item, skip
failedToFitCount ++ ;
2023-07-02 16:05:32 +02:00
2023-03-03 18:53:28 +01:00
continue ;
2023-03-03 16:23:46 +01:00
}
2024-02-09 16:13:49 +01:00
this . containerHelper . fillContainerMapWithItem (
2023-11-16 22:42:06 +01:00
containerMap ,
2024-07-23 18:30:20 +02:00
result . x ,
result . y ,
2023-11-16 22:42:06 +01:00
width ,
height ,
result . rotation ,
) ;
2023-07-02 16:05:32 +02:00
const rotation = result . rotation ? 1 : 0 ;
2023-03-03 16:23:46 +01:00
items [ 0 ] . slotId = "main" ;
2024-07-23 18:30:20 +02:00
items [ 0 ] . location = { x : result.x , y : result.y , r : rotation } ;
2023-03-03 16:23:46 +01:00
2023-07-02 16:05:32 +02:00
// Add loot to container before returning
2024-07-23 17:12:53 +02:00
for ( const item of items ) {
2024-02-05 15:43:46 +01:00
containerClone . template . Items . push ( item ) ;
2023-03-03 16:23:46 +01:00
}
}
2023-03-03 18:53:28 +01:00
2024-02-05 15:43:46 +01:00
return containerClone ;
2023-03-03 16:23:46 +01:00
}
2023-07-02 16:05:32 +02:00
/ * *
* Get a 2 d grid of a containers item slots
* @param containerTpl Tpl id of the container
* @returns number [ ] [ ]
* /
2024-07-23 17:12:53 +02:00
protected getContainerMapping ( containerTpl : string ) : number [ ] [ ] {
2023-07-02 16:05:32 +02:00
// Get template from db
const containerTemplate = this . itemHelper . getItem ( containerTpl ) [ 1 ] ;
// Get height/width
2024-07-23 18:30:20 +02:00
const height = containerTemplate . _props . Grids [ 0 ] . _props . cellsV ;
const width = containerTemplate . _props . Grids [ 0 ] . _props . cellsH ;
2023-07-02 16:05:32 +02:00
// Calcualte 2d array and return
2024-05-17 21:32:41 +02:00
return Array ( height )
. fill ( 0 )
. map ( ( ) = > Array ( width ) . fill ( 0 ) ) ;
2023-07-02 16:05:32 +02:00
}
/ * *
* Look up a containers itemcountDistribution data and choose an item count based on the found weights
* @param containerTypeId Container to get item count for
* @param staticLootDist staticLoot . json
* @param locationName Map name ( to get per - map multiplier for from config )
* @returns item count
* /
2023-11-16 22:42:06 +01:00
protected getWeightedCountOfContainerItems (
containerTypeId : string ,
staticLootDist : Record < string , IStaticLootDetails > ,
locationName : string ,
2024-07-23 17:12:53 +02:00
) : number {
2023-07-02 16:05:32 +02:00
// Create probability array to calcualte the total count of lootable items inside container
2024-05-13 19:58:17 +02:00
const itemCountArray = new ProbabilityObjectArray < number > ( this . mathUtil , this . cloner ) ;
2023-12-28 16:13:29 +01:00
const countDistribution = staticLootDist [ containerTypeId ] ? . itemcountDistribution ;
2024-07-23 17:12:53 +02:00
if ( ! countDistribution ) {
this . logger . warning (
this . localisationService . getText ( "location-unable_to_find_count_distribution_for_container" , {
2024-05-24 17:42:42 +02:00
containerId : containerTypeId ,
locationName : locationName ,
2024-07-23 17:12:53 +02:00
} ) ,
) ;
2023-12-28 16:13:29 +01:00
return 0 ;
}
2024-07-23 17:12:53 +02:00
for ( const itemCountDistribution of countDistribution ) {
2023-07-02 16:05:32 +02:00
// Add each count of items into array
itemCountArray . push (
2023-11-16 22:42:06 +01:00
new ProbabilityObject ( itemCountDistribution . count , itemCountDistribution . relativeProbability ) ,
2023-07-02 16:05:32 +02:00
) ;
}
return Math . round ( this . getStaticLootMultiplerForLocation ( locationName ) * itemCountArray . draw ( ) [ 0 ] ) ;
}
/ * *
* Get all possible loot items that can be placed into a container
* Do not add seasonal items if found + current date is inside seasonal event
* @param containerTypeId Contianer to get possible loot for
* @param staticLootDist staticLoot . json
* @returns ProbabilityObjectArray of item tpls + probabilty
* /
2023-11-16 22:42:06 +01:00
protected getPossibleLootItemsForContainer (
containerTypeId : string ,
staticLootDist : Record < string , IStaticLootDetails > ,
2024-07-23 17:12:53 +02:00
) : ProbabilityObjectArray < string , number > {
2023-07-02 16:05:32 +02:00
const seasonalEventActive = this . seasonalEventService . seasonalEventEnabled ( ) ;
2023-12-15 15:10:33 +01:00
const seasonalItemTplBlacklist = this . seasonalEventService . getInactiveSeasonalEventItems ( ) ;
2023-07-02 16:05:32 +02:00
2024-05-28 16:35:38 +02:00
const itemDistribution = new ProbabilityObjectArray < string , number > ( this . mathUtil , this . cloner ) ;
2023-12-28 16:13:29 +01:00
const itemContainerDistribution = staticLootDist [ containerTypeId ] ? . itemDistribution ;
2024-07-23 17:12:53 +02:00
if ( ! itemContainerDistribution ) {
this . logger . warning (
this . localisationService . getText ( "location-missing_item_distribution_data" , containerTypeId ) ,
) ;
2023-12-28 16:13:29 +01:00
return itemDistribution ;
}
2024-07-23 17:12:53 +02:00
for ( const icd of itemContainerDistribution ) {
if ( ! seasonalEventActive && seasonalItemTplBlacklist . includes ( icd . tpl ) ) {
2023-07-02 16:05:32 +02:00
// Skip seasonal event items if they're not enabled
continue ;
}
2024-06-04 19:57:36 +02:00
// Ensure no blacklisted lootable items are in pool
2024-07-23 17:12:53 +02:00
if ( this . itemFilterService . isLootableItemBlacklisted ( icd . tpl ) ) {
2024-06-04 18:06:50 +02:00
continue ;
}
2023-11-16 22:42:06 +01:00
itemDistribution . push ( new ProbabilityObject ( icd . tpl , icd . relativeProbability ) ) ;
2023-07-02 16:05:32 +02:00
}
return itemDistribution ;
}
2024-07-23 17:12:53 +02:00
protected getLooseLootMultiplerForLocation ( location : string ) : number {
2024-09-13 23:50:59 +02:00
return this . locationConfig . looseLootMultiplier [ location ] ;
2023-03-03 16:23:46 +01:00
}
2024-07-23 17:12:53 +02:00
protected getStaticLootMultiplerForLocation ( location : string ) : number {
2024-09-13 23:50:59 +02:00
return this . locationConfig . staticLootMultiplier [ location ] ;
2023-03-03 16:23:46 +01:00
}
/ * *
* Create array of loose + forced loot using probability system
2023-11-16 22:42:06 +01:00
* @param dynamicLootDist
* @param staticAmmoDist
2023-03-03 16:23:46 +01:00
* @param locationName Location to generate loot for
* @returns Array of spawn points with loot in them
* /
2023-11-16 22:42:06 +01:00
public generateDynamicLoot (
dynamicLootDist : ILooseLoot ,
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
locationName : string ,
2024-09-24 12:26:45 +02:00
) : ISpawnpointTemplate [ ] {
const loot : ISpawnpointTemplate [ ] = [ ] ;
const dynamicForcedSpawnPoints : ISpawnpointsForced [ ] = [ ] ;
2023-03-03 16:23:46 +01:00
2024-02-18 10:03:37 +01:00
// Build the list of forced loot from both `spawnpointsForced` and any point marked `IsAlwaysSpawn`
dynamicForcedSpawnPoints . push ( . . . dynamicLootDist . spawnpointsForced ) ;
2024-05-17 21:32:41 +02:00
dynamicForcedSpawnPoints . push ( . . . dynamicLootDist . spawnpoints . filter ( ( point ) = > point . template . IsAlwaysSpawn ) ) ;
2024-02-18 10:03:37 +01:00
// Add forced loot
this . addForcedLoot ( loot , dynamicForcedSpawnPoints , locationName ) ;
2023-03-03 16:23:46 +01:00
2023-10-10 13:03:20 +02:00
const allDynamicSpawnpoints = dynamicLootDist . spawnpoints ;
2023-11-16 22:42:06 +01:00
// Draw from random distribution
2023-10-10 13:03:20 +02:00
const desiredSpawnpointCount = Math . round (
2024-07-23 17:12:53 +02:00
this . getLooseLootMultiplerForLocation ( locationName ) *
this . randomUtil . getNormallyDistributedRandomNumber (
dynamicLootDist . spawnpointCount . mean ,
dynamicLootDist . spawnpointCount . std ,
) ,
2023-03-03 16:23:46 +01:00
) ;
2023-10-10 13:03:20 +02:00
// Positions not in forced but have 100% chance to spawn
2024-09-24 12:26:45 +02:00
const guaranteedLoosePoints : ISpawnpoint [ ] = [ ] ;
2023-10-10 13:03:20 +02:00
2023-10-24 12:15:23 +02:00
const blacklistedSpawnpoints = this . locationConfig . looseLootBlacklist [ locationName ] ;
2024-09-24 12:26:45 +02:00
const spawnpointArray = new ProbabilityObjectArray < string , ISpawnpoint > ( this . mathUtil , this . cloner ) ;
2023-10-10 13:03:20 +02:00
2024-07-23 17:12:53 +02:00
for ( const spawnpoint of allDynamicSpawnpoints ) {
2023-10-10 13:03:20 +02:00
// Point is blacklsited, skip
2024-07-23 17:12:53 +02:00
if ( blacklistedSpawnpoints ? . includes ( spawnpoint . template . Id ) ) {
2023-10-10 13:03:20 +02:00
this . logger . debug ( ` Ignoring loose loot location: ${ spawnpoint . template . Id } ` ) ;
continue ;
}
2024-02-18 10:03:37 +01:00
// We've handled IsAlwaysSpawn above, so skip them
2024-07-23 17:12:53 +02:00
if ( spawnpoint . template . IsAlwaysSpawn ) {
2024-02-18 10:03:37 +01:00
continue ;
}
2024-07-07 19:55:36 +02:00
// 100%, add it to guaranteed
2024-07-23 17:12:53 +02:00
if ( spawnpoint . probability === 1 ) {
2023-10-10 13:03:20 +02:00
guaranteedLoosePoints . push ( spawnpoint ) ;
continue ;
}
2023-11-16 22:42:06 +01:00
spawnpointArray . push ( new ProbabilityObject ( spawnpoint . template . Id , spawnpoint . probability , spawnpoint ) ) ;
2023-03-03 16:23:46 +01:00
}
// Select a number of spawn points to add loot to
2023-10-10 13:03:20 +02:00
// Add ALL loose loot with 100% chance to pool
2024-09-24 12:26:45 +02:00
let chosenSpawnpoints : ISpawnpoint [ ] = [ . . . guaranteedLoosePoints ] ;
2023-10-24 12:15:23 +02:00
2024-03-30 19:29:08 +01:00
const randomSpawnpointCount = desiredSpawnpointCount - chosenSpawnpoints . length ;
2024-07-07 19:55:36 +02:00
// Only draw random spawn points if needed
2024-07-23 17:12:53 +02:00
if ( randomSpawnpointCount > 0 && spawnpointArray . length > 0 ) {
2024-02-15 10:09:35 +01:00
// Add randomly chosen spawn points
2024-07-23 17:12:53 +02:00
for ( const si of spawnpointArray . draw ( randomSpawnpointCount , false ) ) {
2024-07-23 18:30:20 +02:00
chosenSpawnpoints . push ( spawnpointArray . data ( si ) ) ;
2024-02-15 10:09:35 +01:00
}
2023-03-03 16:23:46 +01:00
}
// Filter out duplicate locationIds
2024-04-23 22:07:34 +02:00
chosenSpawnpoints = [
2024-05-17 21:32:41 +02:00
. . . new Map ( chosenSpawnpoints . map ( ( spawnPoint ) = > [ spawnPoint . locationId , spawnPoint ] ) ) . values ( ) ,
2024-04-23 22:07:34 +02:00
] ;
2023-10-24 12:15:23 +02:00
// Do we have enough items in pool to fulfill requirement
2024-05-08 05:57:08 +02:00
const tooManySpawnPointsRequested = desiredSpawnpointCount - chosenSpawnpoints . length > 0 ;
2024-07-23 17:12:53 +02:00
if ( tooManySpawnPointsRequested ) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
this . localisationService . getText ( "location-spawn_point_count_requested_vs_found" , {
requested : desiredSpawnpointCount + guaranteedLoosePoints . length ,
found : chosenSpawnpoints.length ,
mapName : locationName ,
} ) ,
) ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
// Iterate over spawnpoints
2023-03-03 16:23:46 +01:00
const seasonalEventActive = this . seasonalEventService . seasonalEventEnabled ( ) ;
2023-12-15 15:10:33 +01:00
const seasonalItemTplBlacklist = this . seasonalEventService . getInactiveSeasonalEventItems ( ) ;
2024-07-23 17:12:53 +02:00
for ( const spawnPoint of chosenSpawnpoints ) {
2024-07-07 19:55:36 +02:00
// Spawnpoint is invalid, skip it
2024-07-23 17:12:53 +02:00
if ( ! spawnPoint . template ) {
2023-11-16 22:42:06 +01:00
this . logger . warning (
this . localisationService . getText ( "location-missing_dynamic_template" , spawnPoint . locationId ) ,
) ;
2023-10-24 12:15:23 +02:00
continue ;
}
2024-06-04 19:57:36 +02:00
// Ensure no blacklisted lootable items are in pool
2024-07-23 17:12:53 +02:00
spawnPoint . template . Items = spawnPoint . template . Items . filter (
( item ) = > ! this . itemFilterService . isLootableItemBlacklisted ( item . _tpl ) ,
) ;
2024-06-04 18:06:50 +02:00
2024-07-07 19:55:36 +02:00
// Ensure no seasonal items are in pool if not in-season
2024-07-23 17:12:53 +02:00
if ( ! seasonalEventActive ) {
spawnPoint . template . Items = spawnPoint . template . Items . filter (
( item ) = > ! seasonalItemTplBlacklist . includes ( item . _tpl ) ,
) ;
2024-06-04 18:06:50 +02:00
}
2024-07-07 19:55:36 +02:00
// Spawn point has no items after filtering, skip
2024-07-23 17:12:53 +02:00
if ( ! spawnPoint . template . Items || spawnPoint . template . Items . length === 0 ) {
2024-06-04 18:06:50 +02:00
this . logger . warning (
2023-11-16 22:42:06 +01:00
this . localisationService . getText ( "location-spawnpoint_missing_items" , spawnPoint . template . Id ) ,
) ;
2023-10-24 12:15:23 +02:00
continue ;
}
2024-07-07 19:55:36 +02:00
// Get an array of allowed IDs after above filtering has occured
const validItemIds = spawnPoint . template . Items . map ( ( item ) = > item . _id ) ;
2024-06-04 18:06:50 +02:00
// Construct container to hold above filtered items, letting us pick an item for the spot
2024-05-13 19:58:17 +02:00
const itemArray = new ProbabilityObjectArray < string > ( this . mathUtil , this . cloner ) ;
2024-07-23 17:12:53 +02:00
for ( const itemDist of spawnPoint . itemDistribution ) {
if ( ! validItemIds . includes ( itemDist . composedKey . key ) ) {
2024-07-07 19:55:36 +02:00
continue ;
}
2023-11-16 22:42:06 +01:00
itemArray . push ( new ProbabilityObject ( itemDist . composedKey . key , itemDist . relativeProbability ) ) ;
2023-03-03 16:23:46 +01:00
}
2024-07-23 17:12:53 +02:00
if ( itemArray . length === 0 ) {
this . logger . warning (
this . localisationService . getText ( "location-loot_pool_is_empty_skipping" , spawnPoint . template . Id ) ,
) ;
2024-01-02 20:46:21 +01:00
continue ;
}
2023-03-03 16:23:46 +01:00
// Draw a random item from spawn points possible items
const chosenComposedKey = itemArray . draw ( 1 ) [ 0 ] ;
2023-10-10 13:03:20 +02:00
const createItemResult = this . createDynamicLootItem ( chosenComposedKey , spawnPoint , staticAmmoDist ) ;
2023-03-03 16:23:46 +01:00
2024-06-04 18:06:50 +02:00
// Root id can change when generating a weapon, ensure ids match
2023-03-03 16:23:46 +01:00
spawnPoint . template . Root = createItemResult . items [ 0 ] . _id ;
2024-06-04 18:06:50 +02:00
// Overwrite entire pool with chosen item
2023-03-03 16:23:46 +01:00
spawnPoint . template . Items = createItemResult . items ;
loot . push ( spawnPoint . template ) ;
}
return loot ;
}
/ * *
* Add forced spawn point loot into loot parameter array
2023-12-04 17:00:44 +01:00
* @param lootLocationTemplates array to add forced loot spawn locations to
* @param forcedSpawnPoints forced Forced loot locations that must be added
* @param locationName Name of map currently having force loot created for
2023-03-03 16:23:46 +01:00
* /
2023-11-16 22:42:06 +01:00
protected addForcedLoot (
2024-09-24 12:26:45 +02:00
lootLocationTemplates : ISpawnpointTemplate [ ] ,
forcedSpawnPoints : ISpawnpointsForced [ ] ,
2023-11-16 22:42:06 +01:00
locationName : string ,
2024-07-23 17:12:53 +02:00
) : void {
2023-03-03 16:23:46 +01:00
const lootToForceSingleAmountOnMap = this . locationConfig . forcedLootSingleSpawnById [ locationName ] ;
2024-07-23 17:12:53 +02:00
if ( lootToForceSingleAmountOnMap ) {
2023-03-03 16:23:46 +01:00
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
2024-07-23 17:12:53 +02:00
for ( const itemTpl of lootToForceSingleAmountOnMap ) {
2023-03-03 16:23:46 +01:00
// Get all spawn positions for item tpl in forced loot array
2024-05-17 21:32:41 +02:00
const items = forcedSpawnPoints . filter (
( forcedSpawnPoint ) = > forcedSpawnPoint . template . Items [ 0 ] . _tpl === itemTpl ,
2024-04-23 22:07:34 +02:00
) ;
2024-07-23 17:12:53 +02:00
if ( ! items || items . length === 0 ) {
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Unable to adjust loot item ${ itemTpl } as it does not exist inside ${ locationName } forced loot. ` ,
) ;
2023-03-03 16:23:46 +01:00
continue ;
}
// Create probability array of all spawn positions for this spawn id
2024-09-24 12:26:45 +02:00
const spawnpointArray = new ProbabilityObjectArray < string , ISpawnpointsForced > (
2023-11-16 22:42:06 +01:00
this . mathUtil ,
2024-05-13 19:58:17 +02:00
this . cloner ,
2023-11-16 22:42:06 +01:00
) ;
2024-07-23 17:12:53 +02:00
for ( const si of items ) {
2023-03-03 16:23:46 +01:00
// use locationId as template.Id is the same across all items
2023-11-16 22:42:06 +01:00
spawnpointArray . push ( new ProbabilityObject ( si . locationId , si . probability , si ) ) ;
2023-03-03 16:23:46 +01:00
}
2023-11-16 22:42:06 +01:00
2023-03-03 16:23:46 +01:00
// Choose 1 out of all found spawn positions for spawn id and add to loot array
2024-07-23 17:12:53 +02:00
for ( const spawnPointLocationId of spawnpointArray . draw ( 1 , false ) ) {
2024-05-17 21:32:41 +02:00
const itemToAdd = items . find ( ( item ) = > item . locationId === spawnPointLocationId ) ;
2024-05-28 16:35:38 +02:00
const lootItem = itemToAdd ? . template ;
2024-07-23 17:12:53 +02:00
if ( ! lootItem ) {
this . logger . warning (
` Item with spawn point id ${ spawnPointLocationId } could not be found, skipping ` ,
) ;
2024-05-28 16:35:38 +02:00
continue ;
}
2023-03-03 16:23:46 +01:00
lootItem . Root = this . objectId . generate ( ) ;
lootItem . Items [ 0 ] . _id = lootItem . Root ;
2023-12-04 17:00:44 +01:00
lootLocationTemplates . push ( lootItem ) ;
2023-03-03 16:23:46 +01:00
}
}
}
const seasonalEventActive = this . seasonalEventService . seasonalEventEnabled ( ) ;
2023-12-15 15:10:33 +01:00
const seasonalItemTplBlacklist = this . seasonalEventService . getInactiveSeasonalEventItems ( ) ;
2023-03-03 16:23:46 +01:00
// Add remaining forced loot to array
2024-07-23 17:12:53 +02:00
for ( const forcedLootLocation of forcedSpawnPoints ) {
2023-12-04 17:00:44 +01:00
const firstLootItemTpl = forcedLootLocation . template . Items [ 0 ] . _tpl ;
// Skip spawn positions processed already
2024-07-23 17:12:53 +02:00
if ( lootToForceSingleAmountOnMap ? . includes ( firstLootItemTpl ) ) {
2023-03-03 16:23:46 +01:00
continue ;
}
2023-12-04 17:00:44 +01:00
// Skip adding seasonal items when seasonal event is not active
2024-07-23 17:12:53 +02:00
if ( ! seasonalEventActive && seasonalItemTplBlacklist . includes ( firstLootItemTpl ) ) {
2023-03-03 16:23:46 +01:00
continue ;
}
2023-12-04 17:00:44 +01:00
const locationTemplateToAdd = forcedLootLocation . template ;
2023-12-15 15:10:33 +01:00
2023-12-04 17:00:44 +01:00
// Ensure root id matches the first items id
locationTemplateToAdd . Root = this . objectId . generate ( ) ;
locationTemplateToAdd . Items [ 0 ] . _id = locationTemplateToAdd . Root ;
// Push forced location into array as long as it doesnt exist already
2024-06-13 14:41:29 +02:00
const existingLocation = lootLocationTemplates . some (
2024-05-17 21:32:41 +02:00
( spawnPoint ) = > spawnPoint . Id === locationTemplateToAdd . Id ,
2024-04-23 22:07:34 +02:00
) ;
2024-07-23 17:12:53 +02:00
if ( ! existingLocation ) {
2023-12-04 17:00:44 +01:00
lootLocationTemplates . push ( locationTemplateToAdd ) ;
2024-07-23 17:12:53 +02:00
} else {
2023-12-15 15:10:33 +01:00
this . logger . debug (
` Attempted to add a forced loot location with Id: ${ locationTemplateToAdd . Id } to map ${ locationName } that already has that id in use, skipping ` ,
) ;
2023-12-04 17:00:44 +01:00
}
2023-03-03 16:23:46 +01:00
}
}
/ * *
* Create array of item ( with child items ) and return
* @param chosenComposedKey Key we want to look up items for
* @param spawnPoint Dynamic spawn point item we want will be placed in
2023-10-10 13:03:20 +02:00
* @param staticAmmoDist ammo distributions
2023-03-03 16:23:46 +01:00
* @returns IContainerItem
* /
2023-11-16 22:42:06 +01:00
protected createDynamicLootItem (
chosenComposedKey : string ,
2024-09-24 12:26:45 +02:00
spawnPoint : ISpawnpoint ,
2023-11-16 22:42:06 +01:00
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
2024-07-23 17:12:53 +02:00
) : IContainerItem {
2024-05-17 21:32:41 +02:00
const chosenItem = spawnPoint . template . Items . find ( ( item ) = > item . _id === chosenComposedKey ) ;
2024-05-28 16:35:38 +02:00
const chosenTpl = chosenItem ? . _tpl ;
2024-07-23 17:12:53 +02:00
if ( ! chosenTpl ) {
2024-05-28 16:35:38 +02:00
throw new Error ( ` Item for tpl ${ chosenComposedKey } was not found in the spawn point ` ) ;
}
2024-02-02 19:54:07 +01:00
const itemTemplate = this . itemHelper . getItem ( chosenTpl ) [ 1 ] ;
2023-03-03 16:23:46 +01:00
// Item array to return
2024-09-24 13:47:29 +02:00
const itemWithMods : IItem [ ] = [ ] ;
2023-10-28 20:39:45 +02:00
2023-03-03 16:23:46 +01:00
// Money/Ammo - don't rely on items in spawnPoint.template.Items so we can randomise it ourselves
2024-07-23 17:12:53 +02:00
if ( this . itemHelper . isOfBaseclasses ( chosenTpl , [ BaseClasses . MONEY , BaseClasses . AMMO ] ) ) {
const stackCount =
itemTemplate . _props . StackMaxSize === 1
2024-05-17 21:32:41 +02:00
? 1
2024-07-23 18:30:20 +02:00
: this . randomUtil . getInt ( itemTemplate . _props . StackMinRandom , itemTemplate . _props . StackMaxRandom ) ;
2023-10-28 20:39:45 +02:00
2023-11-16 22:42:06 +01:00
itemWithMods . push ( {
_id : this.objectId.generate ( ) ,
_tpl : chosenTpl ,
upd : { StackObjectsCount : stackCount } ,
} ) ;
2024-07-23 17:12:53 +02:00
} else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . AMMO_BOX ) ) {
2023-11-29 12:36:20 +01:00
// Fill with cartridges
2024-09-24 13:47:29 +02:00
const ammoBoxItem : IItem [ ] = [ { _id : this.objectId.generate ( ) , _tpl : chosenTpl } ] ;
2024-02-11 13:12:27 +01:00
this . itemHelper . addCartridgesToAmmoBox ( ammoBoxItem , itemTemplate ) ;
2023-10-10 13:03:20 +02:00
itemWithMods . push ( . . . ammoBoxItem ) ;
2024-07-23 17:12:53 +02:00
} else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . MAGAZINE ) ) {
2024-01-13 13:30:15 +01:00
// Create array with just magazine
2024-09-24 13:47:29 +02:00
const magazineItem : IItem [ ] = [ { _id : this.objectId.generate ( ) , _tpl : chosenTpl } ] ;
2024-01-13 13:30:15 +01:00
2024-07-23 17:12:53 +02:00
if ( this . randomUtil . getChance100 ( this . locationConfig . staticMagazineLootHasAmmoChancePercent ) ) {
2024-01-13 13:30:15 +01:00
// Add randomised amount of cartridges
this . itemHelper . fillMagazineWithRandomCartridge (
magazineItem ,
itemTemplate , // Magazine template
staticAmmoDist ,
2024-05-27 22:06:07 +02:00
undefined ,
2024-01-13 13:30:15 +01:00
this . locationConfig . minFillLooseMagazinePercent / 100 ,
) ;
}
2024-02-02 19:54:07 +01:00
2023-10-10 13:03:20 +02:00
itemWithMods . push ( . . . magazineItem ) ;
2024-07-23 17:12:53 +02:00
} else {
2024-02-07 00:17:41 +01:00
// Also used by armors to get child mods
2023-03-03 16:23:46 +01:00
// Get item + children and add into array we return
2024-09-13 21:51:22 +02:00
let itemWithChildren = this . itemHelper . findAndReturnChildrenAsItems (
2023-11-16 22:42:06 +01:00
spawnPoint . template . Items ,
chosenItem . _id ,
) ;
2023-03-03 16:23:46 +01:00
2024-09-13 21:51:22 +02:00
// Ensure all IDs are unique
itemWithChildren = this . itemHelper . replaceIDs ( itemWithChildren ) ;
2023-03-03 16:23:46 +01:00
itemWithMods . push ( . . . itemWithChildren ) ;
}
// Get inventory size of item
const size = this . itemHelper . getItemSize ( itemWithMods , itemWithMods [ 0 ] . _id ) ;
2023-11-16 22:42:06 +01:00
return { items : itemWithMods , width : size.width , height : size.height } ;
2023-03-03 16:23:46 +01:00
}
/ * *
* Find an item in array by its _tpl , handle differently if chosenTpl is a weapon
* @param items Items array to search
* @param chosenTpl Tpl we want to get item with
* @returns Item object
* /
2024-09-24 13:47:29 +02:00
protected getItemInArray ( items : IItem [ ] , chosenTpl : string ) : IItem | undefined {
2024-07-23 17:12:53 +02:00
if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . WEAPON ) ) {
2024-05-17 21:32:41 +02:00
return items . find ( ( v ) = > v . _tpl === chosenTpl && v . parentId === undefined ) ;
2023-03-03 16:23:46 +01:00
}
2024-05-17 21:32:41 +02:00
return items . find ( ( item ) = > item . _tpl === chosenTpl ) ;
2023-03-03 16:23:46 +01:00
}
// TODO: rewrite, BIG yikes
2023-11-16 22:42:06 +01:00
protected createStaticLootItem (
2023-12-28 22:31:31 +01:00
chosenTpl : string ,
2023-11-16 22:42:06 +01:00
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
2024-05-28 16:35:38 +02:00
parentId? : string ,
2024-07-23 17:12:53 +02:00
) : IContainerItem {
2023-12-28 22:31:31 +01:00
const itemTemplate = this . itemHelper . getItem ( chosenTpl ) [ 1 ] ;
2024-07-23 18:30:20 +02:00
let width = itemTemplate . _props . Width ;
let height = itemTemplate . _props . Height ;
2024-09-24 13:47:29 +02:00
let items : IItem [ ] = [ { _id : this.objectId.generate ( ) , _tpl : chosenTpl } ] ;
2024-02-11 16:06:03 +01:00
const rootItem = items [ 0 ] ;
2023-03-03 16:23:46 +01:00
// Use passed in parentId as override for new item
2024-07-23 17:12:53 +02:00
if ( parentId ) {
2024-02-11 16:06:03 +01:00
rootItem . parentId = parentId ;
2023-03-03 16:23:46 +01:00
}
2023-11-16 22:42:06 +01:00
if (
2024-07-23 17:12:53 +02:00
this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . MONEY ) ||
this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . AMMO )
) {
2023-10-28 20:39:45 +02:00
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
2024-07-23 17:12:53 +02:00
const stackCount =
itemTemplate . _props . StackMaxSize === 1
2024-05-17 21:32:41 +02:00
? 1
2024-07-23 18:30:20 +02:00
: this . randomUtil . getInt ( itemTemplate . _props . StackMinRandom , itemTemplate . _props . StackMaxRandom ) ;
2024-02-11 16:06:03 +01:00
rootItem . upd = { StackObjectsCount : stackCount } ;
2023-03-03 16:23:46 +01:00
}
// No spawn point, use default template
2024-07-23 17:12:53 +02:00
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . WEAPON ) ) {
2024-09-24 13:47:29 +02:00
let children : IItem [ ] = [ ] ;
2024-05-13 19:58:17 +02:00
const defaultPreset = this . cloner . clone ( this . presetHelper . getDefaultPreset ( chosenTpl ) ) ;
2024-07-23 17:12:53 +02:00
if ( defaultPreset ? . _items ) {
try {
2024-01-14 22:12:56 +01:00
children = this . itemHelper . reparentItemAndChildren ( defaultPreset . _items [ 0 ] , defaultPreset . _items ) ;
2024-07-23 17:12:53 +02:00
} catch ( error ) {
2023-03-03 16:23:46 +01:00
// this item already broke it once without being reproducible tpl = "5839a40f24597726f856b511"; AKS-74UB Default
// 5ea03f7400685063ec28bfa8 // ppsh default
// 5ba26383d4351e00334c93d9 //mp7_devgru
2023-11-16 22:42:06 +01:00
this . logger . warning (
this . localisationService . getText ( "location-preset_not_found" , {
2023-12-28 22:31:31 +01:00
tpl : chosenTpl ,
2023-11-16 22:42:06 +01:00
defaultId : defaultPreset._id ,
defaultName : defaultPreset._name ,
parentId : parentId ,
} ) ,
) ;
2023-03-03 16:23:46 +01:00
throw error ;
}
2024-07-23 17:12:53 +02:00
} else {
2023-10-10 13:03:20 +02:00
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesnt have any default presets and kills this code below as it has no chidren to reparent
2024-09-13 21:51:22 +02:00
this . logger . debug ( ` createStaticLootItem() No preset found for weapon: ${ chosenTpl } ` ) ;
2023-03-03 16:23:46 +01:00
}
const rootItem = items [ 0 ] ;
2024-07-23 17:12:53 +02:00
if ( ! rootItem ) {
2023-11-16 22:42:06 +01:00
this . logger . error (
2024-02-02 19:54:07 +01:00
this . localisationService . getText ( "location-missing_root_item" , {
tpl : chosenTpl ,
parentId : parentId ,
} ) ,
2023-11-16 22:42:06 +01:00
) ;
2023-03-03 16:23:46 +01:00
throw new Error ( this . localisationService . getText ( "location-critical_error_see_log" ) ) ;
}
2024-07-23 17:12:53 +02:00
try {
if ( children ? . length > 0 ) {
2024-01-14 22:12:56 +01:00
items = this . itemHelper . reparentItemAndChildren ( rootItem , children ) ;
2023-11-16 22:42:06 +01:00
}
2024-07-23 17:12:53 +02:00
} catch ( error ) {
2023-11-16 22:42:06 +01:00
this . logger . error (
this . localisationService . getText ( "location-unable_to_reparent_item" , {
2023-12-28 22:31:31 +01:00
tpl : chosenTpl ,
2023-11-16 22:42:06 +01:00
parentId : parentId ,
} ) ,
) ;
2023-03-03 16:23:46 +01:00
throw error ;
}
// Here we should use generalized BotGenerators functions e.g. fillExistingMagazines in the future since
// it can handle revolver ammo (it's not restructured to be used here yet.)
// General: Make a WeaponController for Ragfair preset stuff and the generating weapons and ammo stuff from
// BotGenerator
2024-05-17 21:32:41 +02:00
const magazine = items . filter ( ( item ) = > item . slotId === "mod_magazine" ) [ 0 ] ;
2023-03-03 16:23:46 +01:00
// some weapon presets come without magazine; only fill the mag if it exists
2024-07-23 17:12:53 +02:00
if ( magazine ) {
2023-03-03 18:53:28 +01:00
const magTemplate = this . itemHelper . getItem ( magazine . _tpl ) [ 1 ] ;
2023-12-28 22:31:31 +01:00
const weaponTemplate = this . itemHelper . getItem ( chosenTpl ) [ 1 ] ;
2023-03-06 10:57:04 +01:00
2023-03-03 18:53:28 +01:00
// Create array with just magazine
2024-05-01 00:23:00 +02:00
const defaultWeapon = this . itemHelper . getItem ( rootItem . _tpl ) [ 1 ] ;
2023-03-03 18:53:28 +01:00
const magazineWithCartridges = [ magazine ] ;
2023-11-16 22:42:06 +01:00
this . itemHelper . fillMagazineWithRandomCartridge (
magazineWithCartridges ,
magTemplate ,
staticAmmoDist ,
weaponTemplate . _props . ammoCaliber ,
2024-02-07 15:45:43 +01:00
0.25 ,
2024-05-01 00:23:00 +02:00
defaultWeapon . _props . defAmmo ,
defaultWeapon ,
2023-11-16 22:42:06 +01:00
) ;
2023-03-03 18:53:28 +01:00
// Replace existing magazine with above array
items . splice ( items . indexOf ( magazine ) , 1 , . . . magazineWithCartridges ) ;
2023-03-03 16:23:46 +01:00
}
const size = this . itemHelper . getItemSize ( items , rootItem . _id ) ;
width = size . width ;
height = size . height ;
}
// No spawnpoint to fall back on, generate manually
2024-07-23 17:12:53 +02:00
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . AMMO_BOX ) ) {
2024-02-11 13:12:27 +01:00
this . itemHelper . addCartridgesToAmmoBox ( items , itemTemplate ) ;
2024-07-23 17:12:53 +02:00
} else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . MAGAZINE ) ) {
if ( this . randomUtil . getChance100 ( this . locationConfig . magazineLootHasAmmoChancePercent ) ) {
2024-01-13 13:30:15 +01:00
// Create array with just magazine
2024-02-11 16:06:03 +01:00
const magazineWithCartridges = [ rootItem ] ;
2024-01-13 13:30:15 +01:00
this . itemHelper . fillMagazineWithRandomCartridge (
magazineWithCartridges ,
itemTemplate ,
staticAmmoDist ,
2024-05-27 22:06:07 +02:00
undefined ,
2024-01-13 13:30:15 +01:00
this . locationConfig . minFillStaticMagazinePercent / 100 ,
) ;
2023-03-03 18:53:28 +01:00
2024-01-13 13:30:15 +01:00
// Replace existing magazine with above array
2024-02-11 16:06:03 +01:00
items . splice ( items . indexOf ( rootItem ) , 1 , . . . magazineWithCartridges ) ;
2024-01-13 13:30:15 +01:00
}
2024-07-23 17:12:53 +02:00
} else if ( this . itemHelper . armorItemCanHoldMods ( chosenTpl ) ) {
2024-02-07 00:00:39 +01:00
const defaultPreset = this . presetHelper . getDefaultPreset ( chosenTpl ) ;
2024-07-23 17:12:53 +02:00
if ( defaultPreset ) {
2024-09-24 13:47:29 +02:00
const presetAndMods : IItem [ ] = this . itemHelper . replaceIDs ( defaultPreset . _items ) ;
2024-02-07 00:00:39 +01:00
this . itemHelper . remapRootItemId ( presetAndMods ) ;
2024-02-11 16:06:03 +01:00
// Use original items parentId otherwise item doesnt get added to container correctly
presetAndMods [ 0 ] . parentId = rootItem . parentId ;
2024-02-07 00:00:39 +01:00
items = presetAndMods ;
2024-07-23 17:12:53 +02:00
} else {
2024-02-07 00:00:39 +01:00
// We make base item above, at start of function, no need to do it here
2024-07-23 17:12:53 +02:00
if ( ( itemTemplate . _props . Slots ? . length ? ? 0 ) > 0 ) {
2024-02-07 00:00:39 +01:00
items = this . itemHelper . addChildSlotItems (
items ,
itemTemplate ,
this . locationConfig . equipmentLootSettings . modSpawnChancePercent ,
) ;
}
2023-12-28 22:31:31 +01:00
}
}
2024-02-02 19:54:07 +01:00
return { items : items , width : width , height : height } ;
2023-12-28 22:31:31 +01:00
}
2023-11-16 22:42:06 +01:00
}