From 3c0e6a34f2bf3e360bd8c33f21ce7da521e9dedc Mon Sep 17 00:00:00 2001 From: Dev Date: Fri, 24 Nov 2023 16:05:58 +0000 Subject: [PATCH] Improve `addItemWithChildrenToEquipmentSlot()`, Make it return different result when: There are no containers to add item to Unknown reason Improved handling of when container has no space for item Improve `ExternalInventoryMagGen.process()`, Handle edge case when a weapon with a default internal magazine but weapon uses external magazine Improve most locations that call `addItemWithChildrenToEquipmentSlot()` to log failure reason --- project/src/generators/BotLootGenerator.ts | 14 ++- project/src/generators/PlayerScavGenerator.ts | 8 +- .../ExternalInventoryMagGen.ts | 99 ++++++++++++++++--- .../src/helpers/BotWeaponGeneratorHelper.ts | 24 ++++- project/src/models/enums/ItemAddedResult.ts | 5 +- 5 files changed, 127 insertions(+), 23 deletions(-) diff --git a/project/src/generators/BotLootGenerator.ts b/project/src/generators/BotLootGenerator.ts index 83038ed3..8e69be4c 100644 --- a/project/src/generators/BotLootGenerator.ts +++ b/project/src/generators/BotLootGenerator.ts @@ -352,13 +352,21 @@ export class BotLootGenerator itemsToAdd, inventoryToAddItemsTo, ); - if (itemAddedResult === ItemAddedResult.NO_SPACE) + if (itemAddedResult !== ItemAddedResult.SUCCESS) { + + if (itemAddedResult === ItemAddedResult.NO_CONTAINERS) + { + // Bot has no container to put item in, exit + this.logger.debug(`Unable to add ${totalItemCount} items to bot as it lacks a container to include them`); + break; + } + fitItemIntoContainerAttempts++; if (fitItemIntoContainerAttempts >= 4) { this.logger.debug( - `Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times. No space, skipping`, + `Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times. ${ItemAddedResult[itemAddedResult]}, skipping`, ); break; @@ -436,7 +444,7 @@ export class BotLootGenerator if (result !== ItemAddedResult.SUCCESS) { - this.logger.debug(`Failed to add additional weapon ${generatedWeapon.weapon[0]._id} to bot backpack, reason: ${result}`); + this.logger.debug(`Failed to add additional weapon ${generatedWeapon.weapon[0]._id} to bot backpack, reason: ${ItemAddedResult[result]}`); } } } diff --git a/project/src/generators/PlayerScavGenerator.ts b/project/src/generators/PlayerScavGenerator.ts index ce557c24..41ee9483 100644 --- a/project/src/generators/PlayerScavGenerator.ts +++ b/project/src/generators/PlayerScavGenerator.ts @@ -12,6 +12,7 @@ import { IBotType } from "@spt-aki/models/eft/common/tables/IBotType"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { AccountTypes } from "@spt-aki/models/enums/AccountTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; +import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult"; import { MemberCategory } from "@spt-aki/models/enums/MemberCategory"; import { Traders } from "@spt-aki/models/enums/Traders"; import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig"; @@ -125,13 +126,18 @@ export class PlayerScavGenerator _tpl: labsCard._id, ...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard), }]; - this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( + const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( ["TacticalVest", "Pockets", "Backpack"], itemsToAdd[0]._id, labsCard._id, itemsToAdd, scavData.Inventory, ); + + if (result !== ItemAddedResult.SUCCESS) + { + this.logger.debug(`Unable to add keycard to bot. Reason: ${ItemAddedResult[result]}`); + } } // Remove secure container diff --git a/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts b/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts index 0926a4ef..53b69b18 100644 --- a/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts +++ b/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts @@ -4,10 +4,12 @@ import { IInventoryMagGen } from "@spt-aki/generators/weapongen/IInventoryMagGen import { InventoryMagGen } from "@spt-aki/generators/weapongen/InventoryMagGen"; import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; +import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots"; import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @injectable() export class ExternalInventoryMagGen implements IInventoryMagGen @@ -17,6 +19,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, + @inject("RandomUtil") protected randomUtil: RandomUtil ) {} @@ -33,8 +36,15 @@ export class ExternalInventoryMagGen implements IInventoryMagGen process(inventoryMagGen: InventoryMagGen): void { + // Cout of attempts to fit a magazine into bot inventory + let fitAttempts = 0; + + // Magazine Db template let magTemplate = inventoryMagGen.getMagazineTemplate(); let magazineTpl = magTemplate._id; + const weapon = inventoryMagGen.getWeaponTemplate(); + const attemptedMagBlacklist: string[] = []; + const defaultMagazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weapon); const randomizedMagazineCount = Number( this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()), ); @@ -46,7 +56,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen magTemplate, ); - const ableToFitMagazinesIntoBotInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( + const fitsIntoInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS], magazineWithAmmo[0]._id, magazineTpl, @@ -54,43 +64,104 @@ export class ExternalInventoryMagGen implements IInventoryMagGen inventoryMagGen.getPmcInventory(), ); - if (ableToFitMagazinesIntoBotInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount) + if (fitsIntoInventory === ItemAddedResult.NO_CONTAINERS) { + // No containers to fit magazines, stop trying + break; + } + // No space for magazine and we haven't reached desired magazine count + else if (fitsIntoInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount) + { + // Prevent infinite loop by only allowing 5 attempts at fitting a magazine into inventory + if (fitAttempts > 5) + { + this.logger.debug(`Failed ${fitAttempts} times to add magazine ${magazineTpl} to bot inventory, stopping`); + + break; + } + /* We were unable to fit at least the minimum amount of magazines, * so we fallback to default magazine and try again. - * Temporary workaround to Killa spawning with no extras if he spawns with a drum mag */ + * Temporary workaround to Killa spawning with no extra mags if he spawns with a drum mag */ - if ( - magazineTpl - === this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl( - inventoryMagGen.getWeaponTemplate(), - ) - ) + if (magazineTpl === defaultMagazineTpl) { // We were already on default - stop here to prevent infinite looping break; } - // Get default magazine tpl, reset loop counter by 1 and try again - magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl( - inventoryMagGen.getWeaponTemplate(), - ); + // Add failed magazine tpl to blacklist + attemptedMagBlacklist.push(magazineTpl); + + // Set chosen magazine tpl to the weapons default magazine tpl and try to fit into inventory next loop + magazineTpl = defaultMagazineTpl; magTemplate = this.itemHelper.getItem(magazineTpl)[1]; if (!magTemplate) { this.logger.error( this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl), ); + break; } + // Edge case - some weapons (SKS) have an internal magazine as default, choose random non-internal magazine to add to bot instead if (magTemplate._props.ReloadMagType === "InternalMagazine") { - break; + const result = this.getRandomExternalMagazineForInternalMagazineGun(inventoryMagGen.getWeaponTemplate()._id, attemptedMagBlacklist); + if (!result?._id) + { + this.logger.debug(`Unable to add additional magazine into bot inventory for weapon: ${weapon._name}, attempted: ${fitAttempts} times`); + + break; + } + + magazineTpl = result._id; + magTemplate = result; + fitAttempts++; } + // Reduce loop counter by 1 to ensure we get full cout of desired magazines i--; } + + if (fitsIntoInventory === ItemAddedResult.SUCCESS) + { + // Reset fit counter now it succeeded + fitAttempts = 0; + } } } + + /** + * Get a random compatible external magazine for a weapon, excluses internal magazines from possible pool + * @param weaponTpl Weapon to get mag for + * @returns tpl of magazine + */ + protected getRandomExternalMagazineForInternalMagazineGun(weaponTpl: string, magazineBlacklist: string[]): ITemplateItem + { + // The mag Slot data for the weapon + const magSlot = this.itemHelper.getItem(weaponTpl)[1]._props.Slots.find(x => x._name === "mod_magazine"); + if (!magSlot) + { + return null; + } + + // All possible mags that fit into the weapon excluding blacklisted + const magazinePool = magSlot._props.filters[0].Filter.filter(x => !magazineBlacklist.includes(x)).map((x) => this.itemHelper.getItem(x)[1]); + if (!magazinePool) + { + return null; + } + + // Non-internal magazines that fit into the weapon + const externalMagazineOnlyPool = magazinePool.filter(x => x._props.ReloadMagType !== "InternalMagazine"); + if (!externalMagazineOnlyPool || externalMagazineOnlyPool?.length === 0) + { + return null; + } + + // Randomly chosen external magazine + return this.randomUtil.getArrayValue(externalMagazineOnlyPool); + } } diff --git a/project/src/helpers/BotWeaponGeneratorHelper.ts b/project/src/helpers/BotWeaponGeneratorHelper.ts index 1628bcf8..fa576dfd 100644 --- a/project/src/helpers/BotWeaponGeneratorHelper.ts +++ b/project/src/helpers/BotWeaponGeneratorHelper.ts @@ -134,7 +134,7 @@ export class BotWeaponGeneratorHelper if (result !== ItemAddedResult.SUCCESS) { - this.logger.debug(`Unable to add ammo: ${ammoItem._tpl} to bot equipment, ${ItemAddedResult[result]}`); + this.logger.debug(`Unable to add ammo: ${ammoItem._tpl} to bot inventory, ${ItemAddedResult[result]}`); if (result === ItemAddedResult.NO_SPACE) { @@ -173,6 +173,7 @@ export class BotWeaponGeneratorHelper inventory: Inventory, ): ItemAddedResult { + let missingContainerCount = 0; for (const slot of equipmentSlots) { // Get container to put item into @@ -183,8 +184,15 @@ export class BotWeaponGeneratorHelper this.logger.debug( `Unable to add item: ${ itemWithChildren[0]._tpl - } to: ${slot}, slot missing/bot generated without equipment`, + } to: ${slot}, slot missing/bot generated without item in slot`, ); + + missingContainerCount++; + if (missingContainerCount === equipmentSlots.length) + { + return ItemAddedResult.NO_CONTAINERS + } + continue; } @@ -205,6 +213,9 @@ export class BotWeaponGeneratorHelper // Get x/y grid size of item const itemSize = this.inventoryHelper.getItemSize(parentTpl, parentId, itemWithChildren); + // Iterate over each grid in the container and look for a big enough space for the item to be placed in + let currentGridCount = 1; + const slotGridCount = containerTemplate[1]._props.Grids.length; for (const slotGrid of containerTemplate[1]._props.Grids) { // Grid is empty, skip @@ -267,11 +278,18 @@ export class BotWeaponGeneratorHelper return ItemAddedResult.SUCCESS; } + // If we've checked all grids in container and reached this point, there's no space for item + if (slotGridCount >= currentGridCount) + { + return ItemAddedResult.NO_SPACE; + } + currentGridCount++; + // Start loop again in next grid of container } } - return ItemAddedResult.NO_SPACE; + return ItemAddedResult.UNKNOWN; } /** diff --git a/project/src/models/enums/ItemAddedResult.ts b/project/src/models/enums/ItemAddedResult.ts index 72ad247d..32a15a40 100644 --- a/project/src/models/enums/ItemAddedResult.ts +++ b/project/src/models/enums/ItemAddedResult.ts @@ -1,5 +1,6 @@ -export enum ItemAddedResult -{ +export enum ItemAddedResult { + UNKNOWN = -1, SUCCESS = 1, NO_SPACE = 2, + NO_CONTAINERS = 3 }