2023-03-03 16:23:46 +01:00
import { inject , injectable } from "tsyringe" ;
2023-10-19 19:21:17 +02:00
import { ContainerHelper } from "@spt-aki/helpers/ContainerHelper" ;
import { ItemHelper } from "@spt-aki/helpers/ItemHelper" ;
import { PresetHelper } from "@spt-aki/helpers/PresetHelper" ;
import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper" ;
import { IContainerMinMax , IStaticContainer } from "@spt-aki/models/eft/common/ILocation" ;
import { ILocationBase } from "@spt-aki/models/eft/common/ILocationBase" ;
import { ILooseLoot , Spawnpoint , SpawnpointTemplate , SpawnpointsForced } from "@spt-aki/models/eft/common/ILooseLoot" ;
import { Item } from "@spt-aki/models/eft/common/tables/IItem" ;
2023-11-16 22:42:06 +01:00
import {
IStaticAmmoDetails ,
IStaticContainerData ,
IStaticForcedProps ,
IStaticLootDetails ,
} from "@spt-aki/models/eft/common/tables/ILootBase" ;
2023-12-28 22:31:31 +01:00
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem" ;
2023-10-19 19:21:17 +02:00
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses" ;
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes" ;
import { Money } from "@spt-aki/models/enums/Money" ;
import { ILocationConfig } from "@spt-aki/models/spt/config/ILocationConfig" ;
import { ILogger } from "@spt-aki/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt-aki/servers/ConfigServer" ;
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer" ;
import { LocalisationService } from "@spt-aki/services/LocalisationService" ;
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService" ;
2023-12-28 22:31:31 +01:00
import { HashUtil } from "@spt-aki/utils/HashUtil" ;
2023-10-19 19:21:17 +02:00
import { JsonUtil } from "@spt-aki/utils/JsonUtil" ;
import { MathUtil } from "@spt-aki/utils/MathUtil" ;
import { ObjectId } from "@spt-aki/utils/ObjectId" ;
import { ProbabilityObject , ProbabilityObjectArray , RandomUtil } from "@spt-aki/utils/RandomUtil" ;
2023-03-03 16:23:46 +01:00
export interface IContainerItem
{
2023-11-16 22:42:06 +01:00
items : Item [ ] ;
width : number ;
height : number ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
export interface IContainerGroupCount
{
/** Containers this group has + probabilty to spawn */
2023-11-16 22:42:06 +01:00
containerIdsWithProbability : Record < string , number > ;
2023-10-10 13:03:20 +02:00
/** How many containers the map should spawn with this group id */
2023-11-16 22:42:06 +01:00
chosenCount : number ;
2023-10-10 13:03:20 +02:00
}
2023-03-03 16:23:46 +01:00
@injectable ( )
export class LocationGenerator
{
protected locationConfig : ILocationConfig ;
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
2023-10-10 13:03:20 +02:00
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
2023-03-03 16:23:46 +01:00
@inject ( "JsonUtil" ) protected jsonUtil : JsonUtil ,
2023-12-28 22:31:31 +01:00
@inject ( "HashUtil" ) protected hashUtil : HashUtil ,
2023-03-03 16:23:46 +01:00
@inject ( "ObjectId" ) protected objectId : ObjectId ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "RagfairServerHelper" ) protected ragfairServerHelper : RagfairServerHelper ,
@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 ,
2023-11-16 22:42:06 +01:00
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
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
* @param staticAmmoDist Static ammo distribution - database . loot . staticAmmo
* @returns Array of container objects
* /
2023-11-16 22:42:06 +01:00
public generateStaticContainers (
locationBase : ILocationBase ,
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
) : SpawnpointTemplate [ ]
2023-10-10 13:03:20 +02:00
{
2023-12-11 15:41:30 +01:00
let staticLootItemCount = 0 ;
2023-10-10 13:03:20 +02:00
const result : SpawnpointTemplate [ ] = [ ] ;
const locationId = locationBase . Id . toLowerCase ( ) ;
const db = this . databaseServer . getTables ( ) ;
const staticWeaponsOnMap = this . jsonUtil . clone ( db . loot . staticContainers [ locationBase . Name ] ? . staticWeapons ) ;
if ( ! staticWeaponsOnMap )
{
this . logger . error ( ` Unable to find static weapon data for map: ${ locationBase . Name } ` ) ;
}
// Add mounted weapons to output loot
result . push ( . . . staticWeaponsOnMap ? ? [ ] ) ;
2023-11-16 22:42:06 +01:00
const allStaticContainersOnMap = this . jsonUtil . clone (
db . loot . staticContainers [ locationBase . Name ] ? . staticContainers ,
) ;
2023-10-10 13:03:20 +02:00
if ( ! allStaticContainersOnMap )
{
this . logger . error ( ` Unable to find static container data for map: ${ locationBase . Name } ` ) ;
}
const staticRandomisableContainersOnMap = this . getRandomisableContainersOnMap ( allStaticContainersOnMap ) ;
// Containers that MUST be added to map (quest containers etc)
const staticForcedOnMap = this . jsonUtil . clone ( db . loot . staticContainers [ locationBase . Name ] ? . staticForced ) ;
if ( ! staticForcedOnMap )
{
this . logger . error ( ` Unable to find forced static data for map: ${ locationBase . Name } ` ) ;
}
// Keep track of static loot count
let staticContainerCount = 0 ;
// Find all 100% spawn containers
const staticLootDist = db . loot . staticLoot ;
const guaranteedContainers = this . getGuaranteedContainers ( allStaticContainersOnMap ) ;
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
for ( const container of guaranteedContainers )
{
2023-11-16 22:42:06 +01:00
const containerWithLoot = this . addLootToContainer (
container ,
staticForcedOnMap ,
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 (
! ( this . locationConfig . containerRandomisationSettings . enabled
&& this . locationConfig . containerRandomisationSettings . maps [ locationId ] )
)
2023-10-10 13:03:20 +02:00
{
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Container randomisation disabled, Adding ${ staticRandomisableContainersOnMap . length } containers to ${ locationBase . Name } ` ,
) ;
2023-10-10 13:03:20 +02:00
for ( const container of staticRandomisableContainersOnMap )
{
2023-11-16 22:42:06 +01:00
const containerWithLoot = this . addLootToContainer (
container ,
staticForcedOnMap ,
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
2023-11-16 22:42:06 +01:00
const staticContainerGroupData : IStaticContainer = db . locations [ locationId ] . statics ;
2023-12-28 16:13:29 +01:00
if ( ! staticContainerGroupData )
{
this . logger . warning ( ` Map: ${ locationId } lacks a statics file, skipping container generation. ` )
return result ;
}
2023-10-10 13:03:20 +02:00
const mapping = this . getGroupIdToContainerMappings ( staticContainerGroupData , staticRandomisableContainersOnMap ) ;
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
for ( const groupId in mapping )
{
const data = mapping [ groupId ] ;
// Count chosen was 0, skip
if ( data . chosenCount === 0 )
{
continue ;
}
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%
if ( groupId === "" )
{
const containerIdsCopy = this . jsonUtil . clone ( data . containerIdsWithProbability ) ;
// Roll each containers probability, if it passes, it gets added
data . containerIdsWithProbability = { } ;
for ( const containerId in containerIdsCopy )
{
if ( this . randomUtil . getChance100 ( containerIdsCopy [ containerId ] * 100 ) )
{
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
if ( data . chosenCount === 0 )
{
continue ;
}
}
// Pass possible containers into function to choose some
const chosenContainerIds = this . getContainersByProbabilty ( groupId , data ) ;
for ( const chosenContainerId of chosenContainerIds )
{
// Look up container object from full list of containers on map
2023-11-16 22:42:06 +01:00
const containerObject = staticRandomisableContainersOnMap . find ( ( x ) = >
x . template . Id === chosenContainerId
) ;
2023-10-10 13:03:20 +02:00
if ( ! containerObject )
{
2023-11-16 22:42:06 +01:00
this . logger . debug (
` Container: ${
chosenContainerIds [ chosenContainerId ]
} not found in staticRandomisableContainersOnMap , this is bad ` ,
) ;
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 ,
staticForcedOnMap ,
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
* /
protected getRandomisableContainersOnMap ( staticContainers : IStaticContainerData [ ] ) : IStaticContainerData [ ]
{
2023-11-16 22:42:06 +01:00
return staticContainers . filter ( ( x ) = >
x . probability !== 1 && ! x . template . IsAlwaysSpawn
&& ! this . locationConfig . containerRandomisationSettings . containerTypesToNotRandomise . includes (
x . template . Items [ 0 ] . _tpl ,
)
) ;
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
* /
protected getGuaranteedContainers ( staticContainersOnMap : IStaticContainerData [ ] ) : IStaticContainerData [ ]
{
2023-11-16 22:42:06 +01:00
return staticContainersOnMap . filter ( ( x ) = >
x . probability === 1 || x . template . IsAlwaysSpawn
|| this . locationConfig . containerRandomisationSettings . containerTypesToNotRandomise . includes (
x . template . Items [ 0 ] . _tpl ,
)
) ;
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
* /
protected getContainersByProbabilty ( groupId : string , containerData : IContainerGroupCount ) : string [ ]
{
const chosenContainerIds : string [ ] = [ ] ;
const containerIds = Object . keys ( containerData . containerIdsWithProbability ) ;
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
const containerDistribution = new ProbabilityObjectArray < string > ( this . mathUtil , this . jsonUtil ) ;
2023-11-16 22:42:06 +01:00
containerIds . forEach ( ( x ) = >
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 [ ] ,
) : 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 > = { } ;
for ( const groupId in staticContainerGroupData . containersGroups )
{
const groupData = staticContainerGroupData . containersGroups [ groupId ] ;
if ( ! mapping [ groupId ] )
{
mapping [ groupId ] = {
containerIdsWithProbability : { } ,
chosenCount : this.randomUtil.getInt (
2023-11-16 22:42:06 +01:00
Math . round (
groupData . minContainers
* this . locationConfig . containerRandomisationSettings . containerGroupMinSizeMultiplier ,
) ,
Math . round (
groupData . maxContainers
* this . locationConfig . containerRandomisationSettings . containerGroupMaxSizeMultiplier ,
) ,
) ,
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 ""
for ( const container of staticContainersOnMap )
{
const groupData = staticContainerGroupData . containers [ container . template . Id ] ;
if ( ! groupData )
{
this . logger . error ( ` Container ${ container . template . Id } not found in statics.json, this is bad ` ) ;
continue ;
}
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 ` ,
) ;
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 ,
) : IStaticContainerData
2023-03-03 16:23:46 +01:00
{
2023-07-02 16:05:32 +02:00
const container = this . jsonUtil . clone ( staticContainer ) ;
2023-10-10 13:03:20 +02:00
const containerTpl = container . 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 ( ) ;
2023-10-10 13:03:20 +02:00
container . template . Root = parentId ;
container . template . Items [ 0 ] . _id = parentId ;
2023-03-03 16:23:46 +01:00
2023-07-02 16:05:32 +02:00
let 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)
2023-11-16 22:42:06 +01:00
const tplsForced = staticForced . filter ( ( x ) = > x . containerId === container . template . Id ) . map ( ( x ) = > x . itemTpl ) ;
2023-03-03 16:23:46 +01:00
// Draw random loot
2023-07-02 16:05:32 +02:00
// Money spawn more than once in container
2023-03-03 18:53:28 +01:00
let failedToFitCount = 0 ;
2023-03-03 16:23:46 +01:00
const locklist = [ Money . ROUBLES , Money . DOLLARS , Money . EUROS ] ;
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
2023-11-16 22:42:06 +01:00
const chosenTpls = containerLootPool . draw (
itemCountToAdd ,
this . locationConfig . allowDuplicateItemsInStaticContainers ,
locklist ,
2024-01-07 21:17:07 +01:00
) . filter ( x = > ! tplsForced . includes ( x ) ) ;
2023-07-02 16:05:32 +02:00
// Add forced loot to chosen item pool
const tplsToAddToContainer = tplsForced . concat ( chosenTpls ) ;
for ( const tplToAdd of tplsToAddToContainer )
2023-03-03 16:23:46 +01:00
{
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 ) ;
2023-03-03 16:23:46 +01:00
if ( ! result . success )
{
2023-03-03 18:53:28 +01:00
// 2 attempts to fit an item, container is probably full, stop trying to add more
if ( failedToFitCount >= this . locationConfig . fitLootIntoContainerAttempts )
{
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
}
2023-11-16 22:42:06 +01:00
containerMap = this . containerHelper . fillContainerMapWithItem (
containerMap ,
result . x ,
result . y ,
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" ;
2023-11-16 22:42:06 +01: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
2023-03-03 16:23:46 +01:00
for ( const item of items )
{
2023-10-10 13:03:20 +02:00
container . template . Items . push ( item ) ;
2023-03-03 16:23:46 +01:00
}
}
2023-03-03 18:53:28 +01:00
2023-03-03 16:23:46 +01:00
return container ;
}
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 [ ] [ ]
* /
protected getContainerMapping ( containerTpl : string ) : number [ ] [ ]
{
// Get template from db
const containerTemplate = this . itemHelper . getItem ( containerTpl ) [ 1 ] ;
// Get height/width
const height = containerTemplate . _props . Grids [ 0 ] . _props . cellsV ;
const width = containerTemplate . _props . Grids [ 0 ] . _props . cellsH ;
// Calcualte 2d array and return
return Array ( height ) . fill ( 0 ) . map ( ( ) = > Array ( width ) . fill ( 0 ) ) ;
}
/ * *
* 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 ,
) : number
2023-07-02 16:05:32 +02:00
{
// Create probability array to calcualte the total count of lootable items inside container
2023-08-09 12:49:45 +02:00
const itemCountArray = new ProbabilityObjectArray < number > ( this . mathUtil , this . jsonUtil ) ;
2023-12-28 16:13:29 +01:00
const countDistribution = staticLootDist [ containerTypeId ] ? . itemcountDistribution ;
if ( ! countDistribution )
{
this . logger . warning ( ` Unable to acquire count distrubution for container: ${ containerTypeId } on: ${ locationName } . defaulting to 0 ` ) ;
return 0 ;
}
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 > ,
) : 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
2023-08-09 12:49:45 +02:00
const itemDistribution = new ProbabilityObjectArray < string > ( this . mathUtil , this . jsonUtil ) ;
2023-12-28 16:13:29 +01:00
const itemContainerDistribution = staticLootDist [ containerTypeId ] ? . itemDistribution ;
if ( ! itemContainerDistribution )
{
this . logger . warning ( ` Unable to acquire item distrubution for container: ${ containerTypeId } ` ) ;
return itemDistribution ;
}
for ( const icd of itemContainerDistribution )
2023-07-02 16:05:32 +02:00
{
if ( ! seasonalEventActive && seasonalItemTplBlacklist . includes ( icd . tpl ) )
{
// Skip seasonal event items if they're not enabled
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 ;
}
2023-03-03 16:23:46 +01:00
protected getLooseLootMultiplerForLocation ( location : string ) : number
{
return this . locationConfig . looseLootMultiplier [ location ] ;
}
protected getStaticLootMultiplerForLocation ( location : string ) : number
{
return this . locationConfig . staticLootMultiplier [ location ] ;
}
/ * *
* 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 ,
) : SpawnpointTemplate [ ]
2023-03-03 16:23:46 +01:00
{
const loot : SpawnpointTemplate [ ] = [ ] ;
2023-10-10 13:03:20 +02:00
// Add all forced loot to return array
this . addForcedLoot ( loot , dynamicLootDist . spawnpointsForced , 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 (
2023-11-16 22:42:06 +01:00
this . getLooseLootMultiplerForLocation ( locationName )
2024-01-17 11:45:25 +01:00
* 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
const guaranteedLoosePoints : Spawnpoint [ ] = [ ] ;
2023-10-24 12:15:23 +02:00
const blacklistedSpawnpoints = this . locationConfig . looseLootBlacklist [ locationName ] ;
2023-08-09 12:49:45 +02:00
const spawnpointArray = new ProbabilityObjectArray < string , Spawnpoint > ( this . mathUtil , this . jsonUtil ) ;
2023-10-10 13:03:20 +02:00
for ( const spawnpoint of allDynamicSpawnpoints )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
// Point is blacklsited, skip
2023-10-24 12:15:23 +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 ;
}
if ( spawnpoint . probability === 1 )
{
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
2023-10-24 12:15:23 +02:00
let chosenSpawnpoints : Spawnpoint [ ] = [ . . . guaranteedLoosePoints ] ;
// Add randomly chosen spawn points
2023-10-10 13:03:20 +02:00
for ( const si of spawnpointArray . draw ( desiredSpawnpointCount , false ) )
2023-03-03 16:23:46 +01:00
{
2023-10-10 13:03:20 +02:00
chosenSpawnpoints . push ( spawnpointArray . data ( si ) ) ;
2023-03-03 16:23:46 +01:00
}
// Filter out duplicate locationIds
2023-11-16 22:42:06 +01:00
chosenSpawnpoints = [ . . . new Map ( chosenSpawnpoints . map ( ( x ) = > [ x . locationId , x ] ) ) . values ( ) ] ;
2023-10-24 12:15:23 +02:00
// Do we have enough items in pool to fulfill requirement
const tooManySpawnPointsRequested = ( desiredSpawnpointCount - chosenSpawnpoints . length ) > 0 ;
if ( tooManySpawnPointsRequested )
2023-03-03 16:23:46 +01:00
{
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 ( ) ;
2023-10-10 13:03:20 +02:00
for ( const spawnPoint of chosenSpawnpoints )
2023-03-03 16:23:46 +01:00
{
2023-10-24 12:15:23 +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 ;
}
if ( ! spawnPoint . template . Items || spawnPoint . template . Items . length === 0 )
{
2023-11-16 22:42:06 +01:00
this . logger . error (
this . localisationService . getText ( "location-spawnpoint_missing_items" , spawnPoint . template . Id ) ,
) ;
2023-10-24 12:15:23 +02:00
continue ;
}
2023-08-09 12:49:45 +02:00
const itemArray = new ProbabilityObjectArray < string > ( this . mathUtil , this . jsonUtil ) ;
2023-03-03 16:23:46 +01:00
for ( const itemDist of spawnPoint . itemDistribution )
{
2023-11-16 22:42:06 +01:00
if (
! seasonalEventActive && seasonalItemTplBlacklist . includes (
spawnPoint . template . Items . find ( ( x ) = > x . _id === itemDist . composedKey . key ) . _tpl ,
)
)
2023-03-03 16:23:46 +01:00
{
// Skip seasonal event items if they're not enabled
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-01-02 20:46:21 +01:00
if ( itemArray . length === 0 )
{
this . logger . warning ( ` Loot pool for position: ${ spawnPoint . template . Id } is empty. Skipping ` ) ;
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
// Root id can change when generating a weapon
spawnPoint . template . Root = createItemResult . items [ 0 ] . _id ;
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 (
2023-12-04 17:00:44 +01:00
lootLocationTemplates : SpawnpointTemplate [ ] ,
2023-11-16 22:42:06 +01:00
forcedSpawnPoints : SpawnpointsForced [ ] ,
locationName : string ,
) : void
2023-03-03 16:23:46 +01:00
{
const lootToForceSingleAmountOnMap = this . locationConfig . forcedLootSingleSpawnById [ locationName ] ;
if ( lootToForceSingleAmountOnMap )
{
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
for ( const itemTpl of lootToForceSingleAmountOnMap )
{
// Get all spawn positions for item tpl in forced loot array
2023-11-16 22:42:06 +01:00
const items = forcedSpawnPoints . filter ( ( x ) = > x . template . Items [ 0 ] . _tpl === itemTpl ) ;
2023-03-03 16:23:46 +01: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
2023-11-16 22:42:06 +01:00
const spawnpointArray = new ProbabilityObjectArray < string , SpawnpointsForced > (
this . mathUtil ,
this . jsonUtil ,
) ;
2023-03-03 16:23:46 +01:00
for ( const si of items )
{
// 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
for ( const spawnPointLocationId of spawnpointArray . draw ( 1 , false ) )
{
2023-11-16 22:42:06 +01:00
const itemToAdd = items . find ( ( x ) = > x . locationId === spawnPointLocationId ) ;
2023-03-03 16:23:46 +01:00
const lootItem = itemToAdd . template ;
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
2023-12-04 17:00:44 +01:00
for ( const forcedLootLocation of forcedSpawnPoints )
2023-03-03 16:23:46 +01:00
{
2023-12-04 17:00:44 +01:00
const firstLootItemTpl = forcedLootLocation . template . Items [ 0 ] . _tpl ;
// Skip spawn positions processed already
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
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
2023-12-15 15:10:33 +01:00
const existingLocation = lootLocationTemplates . find ( ( x ) = > x . Id === locationTemplateToAdd . Id ) ;
2023-12-04 17:00:44 +01:00
if ( ! existingLocation )
{
lootLocationTemplates . push ( locationTemplateToAdd ) ;
}
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 ,
spawnPoint : Spawnpoint ,
staticAmmoDist : Record < string , IStaticAmmoDetails [ ] > ,
) : IContainerItem
2023-03-03 16:23:46 +01:00
{
2023-11-16 22:42:06 +01:00
const chosenItem = spawnPoint . template . Items . find ( ( x ) = > x . _id === chosenComposedKey ) ;
2023-03-03 16:23:46 +01:00
const chosenTpl = chosenItem . _tpl ;
2023-12-28 22:31:31 +01:00
const itemTemplate = this . itemHelper . getItem ( chosenTpl ) [ 1 ] ;
2023-03-03 16:23:46 +01:00
// Item array to return
2024-01-09 00:27:18 +01:00
let itemWithMods : Item [ ] = [ ] ;
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-01-01 12:33:19 +01:00
if ( this . itemHelper . isOfBaseclasses ( chosenTpl , [ BaseClasses . MONEY , BaseClasses . AMMO ] ) )
2023-03-03 16:23:46 +01:00
{
2023-10-28 20:39:45 +02:00
const stackCount = itemTemplate . _props . StackMaxSize === 1
? 1
: this . randomUtil . getInt ( itemTemplate . _props . StackMinRandom , itemTemplate . _props . StackMaxRandom ) ;
2023-11-16 22:42:06 +01:00
itemWithMods . push ( {
_id : this.objectId.generate ( ) ,
_tpl : chosenTpl ,
upd : { StackObjectsCount : stackCount } ,
} ) ;
2023-03-03 16:23:46 +01:00
}
2023-10-10 13:03:20 +02:00
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . AMMO_BOX ) )
{
2023-11-29 12:36:20 +01:00
// Fill with cartridges
2023-11-16 22:42:06 +01:00
const ammoBoxItem : Item [ ] = [ { _id : this.objectId.generate ( ) , _tpl : chosenTpl } ] ;
2023-12-28 22:31:31 +01:00
this . itemHelper . addCartridgesToAmmoBox ( ammoBoxItem , itemTemplate ) ; // ammo box template
2023-10-10 13:03:20 +02:00
itemWithMods . push ( . . . ammoBoxItem ) ;
}
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . MAGAZINE ) )
{
2024-01-13 13:30:15 +01:00
// Create array with just magazine
2023-11-16 22:42:06 +01:00
const magazineItem : Item [ ] = [ { _id : this.objectId.generate ( ) , _tpl : chosenTpl } ] ;
2024-01-13 13:30:15 +01:00
if ( this . randomUtil . getChance100 ( this . locationConfig . magazineLootHasAmmoChancePercent ) )
{
// Add randomised amount of cartridges
this . itemHelper . fillMagazineWithRandomCartridge (
magazineItem ,
itemTemplate , // Magazine template
staticAmmoDist ,
null ,
this . locationConfig . minFillLooseMagazinePercent / 100 ,
) ;
}
2023-10-10 13:03:20 +02:00
itemWithMods . push ( . . . magazineItem ) ;
}
2024-01-11 18:42:58 +01:00
else if ( this . itemHelper . armorItemCanHoldMods ( chosenTpl ) )
2023-12-28 22:31:31 +01:00
{
2023-12-29 18:13:48 +01:00
itemWithMods . push ( {
_id : this.objectId.generate ( ) ,
_tpl : chosenTpl ,
} ) ;
2023-12-28 22:31:31 +01:00
if ( itemTemplate . _props . Slots ? . length > 0 )
{
2024-01-09 00:27:18 +01:00
itemWithMods = this . itemHelper . addChildSlotItems ( itemWithMods , itemTemplate , this . locationConfig . equipmentLootSettings . modSpawnChancePercent ) ;
2023-12-28 22:31:31 +01:00
}
}
2023-03-03 16:23:46 +01:00
else
{
// Get item + children and add into array we return
2023-11-16 22:42:06 +01:00
const itemWithChildren = this . itemHelper . findAndReturnChildrenAsItems (
spawnPoint . template . Items ,
chosenItem . _id ,
) ;
2023-03-03 16:23:46 +01:00
// We need to reparent to ensure ids are unique
this . reparentItemAndChildren ( itemWithChildren ) ;
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
}
/ * *
* Replace the _id value for base item + all children items parentid value
* @param itemWithChildren Item with mods to update
* @param newId new id to add on chidren of base item
* /
protected reparentItemAndChildren ( itemWithChildren : Item [ ] , newId = this . objectId . generate ( ) ) : void
{
// original id on base item
const oldId = itemWithChildren [ 0 ] . _id ;
// Update base item to use new id
itemWithChildren [ 0 ] . _id = newId ;
// Update all parentIds of items attached to base item to use new id
for ( const item of itemWithChildren )
{
if ( item . parentId === oldId )
{
item . parentId = newId ;
}
}
}
/ * *
* 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
* /
protected getItemInArray ( items : Item [ ] , chosenTpl : string ) : Item
{
if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . WEAPON ) )
{
2023-11-16 22:42:06 +01:00
return items . find ( ( v ) = > v . _tpl === chosenTpl && v . parentId === undefined ) ;
2023-03-03 16:23:46 +01:00
}
2023-11-16 22:42:06 +01:00
return items . find ( ( x ) = > x . _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 [ ] > ,
parentId : string = undefined ,
) : IContainerItem
2023-03-03 16:23:46 +01:00
{
2023-12-28 22:31:31 +01:00
const itemTemplate = this . itemHelper . getItem ( chosenTpl ) [ 1 ] ;
2023-03-03 16:23:46 +01:00
let width = itemTemplate . _props . Width ;
let height = itemTemplate . _props . Height ;
2023-12-28 22:31:31 +01:00
let items : Item [ ] = [ { _id : this.objectId.generate ( ) , _tpl : chosenTpl } ] ;
2023-03-03 16:23:46 +01:00
// Use passed in parentId as override for new item
if ( parentId )
{
items [ 0 ] . parentId = parentId ;
}
2023-11-16 22:42:06 +01:00
if (
2023-12-28 22:31:31 +01:00
this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . MONEY )
|| this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . AMMO )
2023-11-16 22:42:06 +01:00
)
2023-03-03 16:23:46 +01:00
{
2023-10-28 20:39:45 +02:00
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
const stackCount = itemTemplate . _props . StackMaxSize === 1
? 1
: this . randomUtil . getInt ( itemTemplate . _props . StackMinRandom , itemTemplate . _props . StackMaxRandom ) ;
2023-10-24 12:15:23 +02:00
items [ 0 ] . upd = { StackObjectsCount : stackCount } ;
2023-03-03 16:23:46 +01:00
}
// No spawn point, use default template
2023-12-28 22:31:31 +01:00
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . WEAPON ) )
2023-03-03 16:23:46 +01:00
{
let children : Item [ ] = [ ] ;
2023-12-28 22:31:31 +01:00
const defaultPreset = this . jsonUtil . clone ( this . presetHelper . getDefaultPreset ( chosenTpl ) ) ;
2023-03-03 16:23:46 +01:00
if ( defaultPreset )
{
try
{
2024-01-14 22:12:56 +01:00
children = this . itemHelper . reparentItemAndChildren ( defaultPreset . _items [ 0 ] , defaultPreset . _items ) ;
2023-03-03 16:23:46 +01:00
}
catch ( error )
{
// 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 ;
}
}
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
2023-12-28 22:31:31 +01:00
this . logger . debug ( ` createItem() No preset found for weapon: ${ chosenTpl } ` ) ;
2023-03-03 16:23:46 +01:00
}
const rootItem = items [ 0 ] ;
if ( ! rootItem )
{
2023-11-16 22:42:06 +01:00
this . logger . error (
2023-12-28 22:31:31 +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" ) ) ;
}
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
}
2023-03-03 16:23:46 +01: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
2023-11-16 22:42:06 +01:00
const magazine = items . filter ( ( x ) = > x . 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
2023-03-03 18:53:28 +01:00
if ( magazine )
2023-03-03 16:23:46 +01:00
{
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
const magazineWithCartridges = [ magazine ] ;
2023-11-16 22:42:06 +01:00
this . itemHelper . fillMagazineWithRandomCartridge (
magazineWithCartridges ,
magTemplate ,
staticAmmoDist ,
weaponTemplate . _props . ammoCaliber ,
) ;
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
2023-12-28 22:31:31 +01:00
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . AMMO_BOX ) )
2023-03-03 16:23:46 +01:00
{
this . itemHelper . addCartridgesToAmmoBox ( items , itemTemplate ) ;
}
2023-12-28 22:31:31 +01:00
else if ( this . itemHelper . isOfBaseclass ( chosenTpl , BaseClasses . MAGAZINE ) )
2023-03-03 16:23:46 +01:00
{
2024-01-13 13:30:15 +01:00
if ( this . randomUtil . getChance100 ( this . locationConfig . magazineLootHasAmmoChancePercent ) )
{
// Create array with just magazine
const magazineWithCartridges = [ items [ 0 ] ] ;
this . itemHelper . fillMagazineWithRandomCartridge (
magazineWithCartridges ,
itemTemplate ,
staticAmmoDist ,
null ,
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
items . splice ( items . indexOf ( items [ 0 ] ) , 1 , . . . magazineWithCartridges ) ;
}
2023-03-03 16:23:46 +01:00
}
2024-01-11 18:42:58 +01:00
else if ( this . itemHelper . armorItemCanHoldMods ( chosenTpl ) )
2023-12-28 22:31:31 +01:00
{
2024-01-11 18:42:58 +01:00
// We make base item above, at start of function, no need to do it here
2023-12-28 22:31:31 +01:00
if ( itemTemplate . _props . Slots ? . length > 0 )
{
2024-01-09 00:27:18 +01:00
items = this . itemHelper . addChildSlotItems ( items , itemTemplate , this . locationConfig . equipmentLootSettings . modSpawnChancePercent ) ;
2023-12-28 22:31:31 +01:00
}
}
return {
items : items ,
width : width ,
height : height
} ;
}
2023-11-16 22:42:06 +01:00
}