Added system to randomise the mods on equipment found as loose/static loot

controlled via config
This commit is contained in:
Dev 2023-12-28 21:31:31 +00:00
parent f227872ddc
commit e27f52c505
4 changed files with 90 additions and 21 deletions

View File

@ -1051,5 +1051,12 @@
"adjustWaves": true "adjustWaves": true
} }
} }
} },
"armorLootSettings": {
"modSpawnChancePercent": {
"mod_nvg": 20,
"front_plate": 50,
"back_plate": 50
}
}
} }

View File

@ -14,6 +14,7 @@ import {
IStaticForcedProps, IStaticForcedProps,
IStaticLootDetails, IStaticLootDetails,
} from "@spt-aki/models/eft/common/tables/ILootBase"; } from "@spt-aki/models/eft/common/tables/ILootBase";
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { Money } from "@spt-aki/models/enums/Money"; import { Money } from "@spt-aki/models/enums/Money";
@ -23,6 +24,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { MathUtil } from "@spt-aki/utils/MathUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil";
import { ObjectId } from "@spt-aki/utils/ObjectId"; import { ObjectId } from "@spt-aki/utils/ObjectId";
@ -52,6 +54,7 @@ export class LocationGenerator
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("ObjectId") protected objectId: ObjectId, @inject("ObjectId") protected objectId: ObjectId,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper, @inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
@ -812,6 +815,7 @@ export class LocationGenerator
{ {
const chosenItem = spawnPoint.template.Items.find((x) => x._id === chosenComposedKey); const chosenItem = spawnPoint.template.Items.find((x) => x._id === chosenComposedKey);
const chosenTpl = chosenItem._tpl; const chosenTpl = chosenItem._tpl;
const itemTemplate = this.itemHelper.getItem(chosenTpl)[1];
// Item array to return // Item array to return
const itemWithMods: Item[] = []; const itemWithMods: Item[] = [];
@ -822,7 +826,7 @@ export class LocationGenerator
|| this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO) || this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO)
) )
{ {
const itemTemplate = this.itemHelper.getItem(chosenTpl)[1];
const stackCount = itemTemplate._props.StackMaxSize === 1 const stackCount = itemTemplate._props.StackMaxSize === 1
? 1 ? 1
@ -837,25 +841,30 @@ export class LocationGenerator
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
{ {
// Fill with cartridges // Fill with cartridges
const ammoBoxTemplate = this.itemHelper.getItem(chosenTpl)[1];
const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }]; const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, ammoBoxTemplate); this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, itemTemplate); // ammo box template
itemWithMods.push(...ammoBoxItem); itemWithMods.push(...ammoBoxItem);
} }
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE)) else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
{ {
// Create array with just magazine + randomised amount of cartridges // Create array with just magazine + randomised amount of cartridges
const magazineTemplate = this.itemHelper.getItem(chosenTpl)[1];
const magazineItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }]; const magazineItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
this.itemHelper.fillMagazineWithRandomCartridge( this.itemHelper.fillMagazineWithRandomCartridge(
magazineItem, magazineItem,
magazineTemplate, itemTemplate, // Magazine template
staticAmmoDist, staticAmmoDist,
null, null,
this.locationConfig.minFillLooseMagazinePercent / 100, this.locationConfig.minFillLooseMagazinePercent / 100,
); );
itemWithMods.push(...magazineItem); itemWithMods.push(...magazineItem);
} }
else if (this.itemHelper.isOfBaseclasses(chosenTpl, [BaseClasses.VEST, BaseClasses.ARMOR, BaseClasses.HEADWEAR]))
{
if (itemTemplate._props.Slots?.length > 0)
{
this.addModsToEquipmentItem(itemWithMods, itemTemplate)
}
}
else else
{ {
// Get item + children and add into array we return // Get item + children and add into array we return
@ -917,15 +926,15 @@ export class LocationGenerator
// TODO: rewrite, BIG yikes // TODO: rewrite, BIG yikes
protected createStaticLootItem( protected createStaticLootItem(
tpl: string, chosenTpl: string,
staticAmmoDist: Record<string, IStaticAmmoDetails[]>, staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
parentId: string = undefined, parentId: string = undefined,
): IContainerItem ): IContainerItem
{ {
const itemTemplate = this.itemHelper.getItem(tpl)[1]; const itemTemplate = this.itemHelper.getItem(chosenTpl)[1];
let width = itemTemplate._props.Width; let width = itemTemplate._props.Width;
let height = itemTemplate._props.Height; let height = itemTemplate._props.Height;
let items: Item[] = [{ _id: this.objectId.generate(), _tpl: tpl }]; let items: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
// Use passed in parentId as override for new item // Use passed in parentId as override for new item
if (parentId) if (parentId)
@ -934,8 +943,8 @@ export class LocationGenerator
} }
if ( if (
this.itemHelper.isOfBaseclass(tpl, BaseClasses.MONEY) this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY)
|| this.itemHelper.isOfBaseclass(tpl, BaseClasses.AMMO) || this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO)
) )
{ {
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked // Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
@ -945,10 +954,10 @@ export class LocationGenerator
items[0].upd = { StackObjectsCount: stackCount }; items[0].upd = { StackObjectsCount: stackCount };
} }
// No spawn point, use default template // No spawn point, use default template
else if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.WEAPON)) else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON))
{ {
let children: Item[] = []; let children: Item[] = [];
const defaultPreset = this.jsonUtil.clone(this.presetHelper.getDefaultPreset(tpl)); const defaultPreset = this.jsonUtil.clone(this.presetHelper.getDefaultPreset(chosenTpl));
if (defaultPreset) if (defaultPreset)
{ {
try try
@ -962,7 +971,7 @@ export class LocationGenerator
// 5ba26383d4351e00334c93d9 //mp7_devgru // 5ba26383d4351e00334c93d9 //mp7_devgru
this.logger.warning( this.logger.warning(
this.localisationService.getText("location-preset_not_found", { this.localisationService.getText("location-preset_not_found", {
tpl: tpl, tpl: chosenTpl,
defaultId: defaultPreset._id, defaultId: defaultPreset._id,
defaultName: defaultPreset._name, defaultName: defaultPreset._name,
parentId: parentId, parentId: parentId,
@ -975,14 +984,14 @@ export class LocationGenerator
else else
{ {
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesnt have any default presets and kills this code below as it has no chidren to reparent // RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesnt have any default presets and kills this code below as it has no chidren to reparent
this.logger.debug(`createItem() No preset found for weapon: ${tpl}`); this.logger.debug(`createItem() No preset found for weapon: ${chosenTpl}`);
} }
const rootItem = items[0]; const rootItem = items[0];
if (!rootItem) if (!rootItem)
{ {
this.logger.error( this.logger.error(
this.localisationService.getText("location-missing_root_item", { tpl: tpl, parentId: parentId }), this.localisationService.getText("location-missing_root_item", { tpl: chosenTpl, parentId: parentId }),
); );
throw new Error(this.localisationService.getText("location-critical_error_see_log")); throw new Error(this.localisationService.getText("location-critical_error_see_log"));
@ -999,7 +1008,7 @@ export class LocationGenerator
{ {
this.logger.error( this.logger.error(
this.localisationService.getText("location-unable_to_reparent_item", { this.localisationService.getText("location-unable_to_reparent_item", {
tpl: tpl, tpl: chosenTpl,
parentId: parentId, parentId: parentId,
}), }),
); );
@ -1016,7 +1025,7 @@ export class LocationGenerator
if (magazine) if (magazine)
{ {
const magTemplate = this.itemHelper.getItem(magazine._tpl)[1]; const magTemplate = this.itemHelper.getItem(magazine._tpl)[1];
const weaponTemplate = this.itemHelper.getItem(tpl)[1]; const weaponTemplate = this.itemHelper.getItem(chosenTpl)[1];
// Create array with just magazine // Create array with just magazine
const magazineWithCartridges = [magazine]; const magazineWithCartridges = [magazine];
@ -1036,11 +1045,11 @@ export class LocationGenerator
height = size.height; height = size.height;
} }
// No spawnpoint to fall back on, generate manually // No spawnpoint to fall back on, generate manually
else if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.AMMO_BOX)) else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
{ {
this.itemHelper.addCartridgesToAmmoBox(items, itemTemplate); this.itemHelper.addCartridgesToAmmoBox(items, itemTemplate);
} }
else if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.MAGAZINE)) else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
{ {
// Create array with just magazine // Create array with just magazine
const magazineWithCartridges = [items[0]]; const magazineWithCartridges = [items[0]];
@ -1055,7 +1064,49 @@ export class LocationGenerator
// Replace existing magazine with above array // Replace existing magazine with above array
items.splice(items.indexOf(items[0]), 1, ...magazineWithCartridges); items.splice(items.indexOf(items[0]), 1, ...magazineWithCartridges);
} }
else if (this.itemHelper.isOfBaseclasses(chosenTpl, [BaseClasses.VEST, BaseClasses.ARMOR]))
{
if (itemTemplate._props.Slots?.length > 0)
{
this.addModsToEquipmentItem(items, itemTemplate)
}
}
return { items: items, width: width, height: height }; return {
items: items,
width: width,
height: height
};
}
/**
* Add mod componenets to an equipment item (head/rig/armor)
* @param modItem
* @param itemTemplate
*/
protected addModsToEquipmentItem(modItem: Item[], itemTemplate: ITemplateItem): void
{
// Add armor plates
for (const slot of itemTemplate._props.Slots)
{
// Check if mod has % chance to be added
const modSpawnChance = this.locationConfig.equipmentLootSettings.modSpawnChancePercent[slot._name.toLowerCase()];
if (modSpawnChance && !slot._required)
{
// only run chance to not add item if its not a required mod
if (this.randomUtil.getChance100(modSpawnChance))
{
continue;
}
}
modItem.push(
{
_id: this.hashUtil.generate(),
_tpl: this.randomUtil.getArrayValue(slot._props.filters[0].Filter), // Choose random tpl from array of compatible
parentId: modItem[0]._id,
slotId: slot._name
}
)
}
} }
} }

View File

@ -462,6 +462,9 @@ export interface SlotProps
export interface SlotFilter export interface SlotFilter
{ {
Shift?: number; Shift?: number;
locked?: boolean;
armorColliders?: any[];
armorPlateColliders?: string[];
Filter: string[]; Filter: string[];
AnimationIndex?: number; AnimationIndex?: number;
} }

View File

@ -40,6 +40,14 @@ export interface ILocationConfig extends IBaseConfig
looseLootBlacklist: Record<string, string[]>; looseLootBlacklist: Record<string, string[]>;
/** Key: map, value: settings to control how long scav raids are*/ /** Key: map, value: settings to control how long scav raids are*/
scavRaidTimeSettings: IScavRaidTimeSettings; scavRaidTimeSettings: IScavRaidTimeSettings;
/** Settings to adjust mods for lootable equipment in raid */
equipmentLootSettings: IEquipmentLootSettings
}
export interface IEquipmentLootSettings
{
// Percentage chance item will be added to equipment
modSpawnChancePercent: Record<string, number>
} }
export interface IFixEmptyBotWavesSettings export interface IFixEmptyBotWavesSettings