Refactor airdrop loot generation to account for armor plate changes

This commit is contained in:
Dev 2024-01-18 23:25:08 +00:00
parent ea0d8224e6
commit 310762d66d
5 changed files with 81 additions and 28 deletions

View File

@ -30,9 +30,13 @@
}, },
"loot": { "loot": {
"mixed": { "mixed": {
"presetCount": { "weaponPresetCount": {
"min": 3, "min": 3,
"max": 5 "max": 5
},
"armorPresetCount": {
"min": 1,
"max": 3
}, },
"itemCount": { "itemCount": {
"min": 12, "min": 12,
@ -155,9 +159,13 @@
"allowBossItems": false "allowBossItems": false
}, },
"weaponArmor": { "weaponArmor": {
"presetCount": { "weaponPresetCount": {
"min": 6, "min": 6,
"max": 8 "max": 8
},
"armorPresetCount": {
"min": 3,
"max": 5
}, },
"itemCount": { "itemCount": {
"min": 10, "min": 10,
@ -251,9 +259,13 @@
"allowBossItems": false "allowBossItems": false
}, },
"foodMedical": { "foodMedical": {
"presetCount": { "weaponPresetCount": {
"min": 3, "min": 0,
"max": 5 "max": 0
},
"armorPresetCount": {
"min": 0,
"max": 0
}, },
"itemCount": { "itemCount": {
"min": 17, "min": 17,
@ -334,9 +346,13 @@
"allowBossItems": false "allowBossItems": false
}, },
"barter": { "barter": {
"presetCount": { "weaponPresetCount": {
"min": 3, "min": 0,
"max": 5 "max": 0
},
"armorPresetCount": {
"min": 0,
"max": 0
}, },
"itemCount": { "itemCount": {
"min": 16, "min": 16,

View File

@ -208,7 +208,8 @@ export class LocationController
} }
return { return {
presetCount: lootSettingsByType.presetCount, weaponPresetCount: lootSettingsByType.weaponPresetCount,
armorPresetCount: lootSettingsByType.armorPresetCount,
itemCount: lootSettingsByType.itemCount, itemCount: lootSettingsByType.itemCount,
weaponCrateCount: lootSettingsByType.weaponCrateCount, weaponCrateCount: lootSettingsByType.weaponCrateCount,
itemBlacklist: lootSettingsByType.itemBlacklist, itemBlacklist: lootSettingsByType.itemBlacklist,

View File

@ -105,14 +105,27 @@ export class LootGenerator
} }
} }
const globalDefaultPresets = Object.entries(tables.globals.ItemPresets).filter((x) => const globalDefaultPresets = Object.values(this.presetHelper.getDefaultPresets());
x[1]._encyclopedia !== undefined
); // Filter default presets to just weapons
const randomisedPresetCount = this.randomUtil.getInt(options.presetCount.min, options.presetCount.max); const weaponDefaultPresets = globalDefaultPresets.filter(preset => this.itemHelper.isOfBaseclass(preset._encyclopedia, BaseClasses.WEAPON));
const randomisedWeaponPresetCount = this.randomUtil.getInt(options.weaponPresetCount.min, options.weaponPresetCount.max);
const itemBlacklistArray = Array.from(itemBlacklist); const itemBlacklistArray = Array.from(itemBlacklist);
for (let index = 0; index < randomisedPresetCount; index++) for (let index = 0; index < randomisedWeaponPresetCount; index++)
{ {
if (!this.findAndAddRandomPresetToLoot(globalDefaultPresets, itemTypeCounts, itemBlacklistArray, result)) if (!this.findAndAddRandomPresetToLoot(weaponDefaultPresets, itemTypeCounts, itemBlacklistArray, result))
{
index--;
}
}
// Filter default presets to just armors and then filter again by protection level
const armorDefaultPresets = globalDefaultPresets.filter(preset => this.itemHelper.armorItemCanHoldMods(preset._encyclopedia));
const levelFilteredArmorPresets = armorDefaultPresets.filter(armor => this.armorIsDesiredProtectionLevel(armor, options));
const randomisedArmorPresetCount = this.randomUtil.getInt(options.armorPresetCount.min, options.armorPresetCount.max);
for (let index = 0; index < randomisedArmorPresetCount; index++)
{
if (!this.findAndAddRandomPresetToLoot(levelFilteredArmorPresets, itemTypeCounts, itemBlacklistArray, result))
{ {
index--; index--;
} }
@ -121,6 +134,29 @@ export class LootGenerator
return result; return result;
} }
/**
* Filter armor items by their main plates protection level
* @param armor Armor preset
* @param options Loot request options
* @returns True item passes checks
*/
protected armorIsDesiredProtectionLevel(armor: IPreset, options: LootRequest): boolean
{
const frontPlate = armor._items.find(mod => mod?.slotId?.toLowerCase() === "front_plate");
if (frontPlate)
{
const plateDb = this.itemHelper.getItem(frontPlate._tpl);
return options.armorLevelWhitelist.includes(Number.parseInt(plateDb[1]._props.armorClass as any));
}
const helmetTop = armor._items.find(mod => mod?.slotId?.toLowerCase() === "helmet_top");
if (helmetTop)
{
const plateDb = this.itemHelper.getItem(helmetTop._tpl);
return options.armorLevelWhitelist.includes(Number.parseInt(plateDb[1]._props.armorClass as any));
}
}
/** /**
* Construct item limit record to hold max and current item count for each item type * Construct item limit record to hold max and current item count for each item type
* @param limits limits as defined in config * @param limits limits as defined in config
@ -160,6 +196,12 @@ export class LootGenerator
return false; return false;
} }
// Skip armors as they need to come from presets
if (this.itemHelper.armorItemCanHoldMods(randomItem._id))
{
return false;
}
const newLootItem: LootItem = { const newLootItem: LootItem = {
id: this.hashUtil.generate(), id: this.hashUtil.generate(),
tpl: randomItem._id, tpl: randomItem._id,
@ -167,15 +209,6 @@ export class LootGenerator
stackCount: 1, stackCount: 1,
}; };
// Check if armor has level in allowed whitelist
if (randomItem._parent === BaseClasses.ARMOR || randomItem._parent === BaseClasses.VEST)
{
if (!options.armorLevelWhitelist.includes(Number(randomItem._props.armorClass)))
{
return false;
}
}
// Special case - handle items that need a stackcount > 1 // Special case - handle items that need a stackcount > 1
if (randomItem._props.StackMaxSize > 1) if (randomItem._props.StackMaxSize > 1)
{ {
@ -224,14 +257,14 @@ export class LootGenerator
* @returns true if preset was valid and added to pool * @returns true if preset was valid and added to pool
*/ */
protected findAndAddRandomPresetToLoot( protected findAndAddRandomPresetToLoot(
globalDefaultPresets: [string, IPreset][], globalDefaultPresets: IPreset[],
itemTypeCounts: Record<string, { current: number; max: number; }>, itemTypeCounts: Record<string, { current: number; max: number; }>,
itemBlacklist: string[], itemBlacklist: string[],
result: LootItem[], result: LootItem[],
): boolean ): boolean
{ {
// Choose random preset and get details from item.json using encyclopedia value (encyclopedia === tplId) // Choose random preset and get details from item.json using encyclopedia value (encyclopedia === tplId)
const randomPreset = this.randomUtil.getArrayValue(globalDefaultPresets)[1]; const randomPreset = this.randomUtil.getArrayValue(globalDefaultPresets);
if (!randomPreset?._encyclopedia) if (!randomPreset?._encyclopedia)
{ {
this.logger.debug(`Airdrop - preset with id: ${randomPreset._id} lacks encyclopedia property, skipping`); this.logger.debug(`Airdrop - preset with id: ${randomPreset._id} lacks encyclopedia property, skipping`);

View File

@ -44,7 +44,9 @@ export interface AirdropChancePercent
export interface AirdropLoot export interface AirdropLoot
{ {
/** Min/max of weapons inside crate */ /** Min/max of weapons inside crate */
presetCount?: MinMax; weaponPresetCount?: MinMax;
/** Min/max of armors (head/chest/rig) inside crate */
armorPresetCount?: MinMax;
/** Min/max of items inside crate */ /** Min/max of items inside crate */
itemCount: MinMax; itemCount: MinMax;
/** Min/max of sealed weapon boxes inside crate */ /** Min/max of sealed weapon boxes inside crate */

View File

@ -2,7 +2,8 @@ import { MinMax } from "@spt-aki/models/common/MinMax";
export interface LootRequest export interface LootRequest
{ {
presetCount: MinMax; weaponPresetCount: MinMax;
armorPresetCount: MinMax;
itemCount: MinMax; itemCount: MinMax;
weaponCrateCount: MinMax; weaponCrateCount: MinMax;
itemBlacklist: string[]; itemBlacklist: string[];