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
This commit is contained in:
Dev 2023-11-24 16:05:58 +00:00
parent 8c7b5da9ff
commit 3c0e6a34f2
5 changed files with 127 additions and 23 deletions

View File

@ -352,13 +352,21 @@ export class BotLootGenerator
itemsToAdd, itemsToAdd,
inventoryToAddItemsTo, 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++; fitItemIntoContainerAttempts++;
if (fitItemIntoContainerAttempts >= 4) if (fitItemIntoContainerAttempts >= 4)
{ {
this.logger.debug( 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; break;
@ -436,7 +444,7 @@ export class BotLootGenerator
if (result !== ItemAddedResult.SUCCESS) 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]}`);
} }
} }
} }

View File

@ -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 { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { AccountTypes } from "@spt-aki/models/enums/AccountTypes"; import { AccountTypes } from "@spt-aki/models/enums/AccountTypes";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; 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 { MemberCategory } from "@spt-aki/models/enums/MemberCategory";
import { Traders } from "@spt-aki/models/enums/Traders"; import { Traders } from "@spt-aki/models/enums/Traders";
import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig"; import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig";
@ -125,13 +126,18 @@ export class PlayerScavGenerator
_tpl: labsCard._id, _tpl: labsCard._id,
...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard), ...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard),
}]; }];
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
["TacticalVest", "Pockets", "Backpack"], ["TacticalVest", "Pockets", "Backpack"],
itemsToAdd[0]._id, itemsToAdd[0]._id,
labsCard._id, labsCard._id,
itemsToAdd, itemsToAdd,
scavData.Inventory, scavData.Inventory,
); );
if (result !== ItemAddedResult.SUCCESS)
{
this.logger.debug(`Unable to add keycard to bot. Reason: ${ItemAddedResult[result]}`);
}
} }
// Remove secure container // Remove secure container

View File

@ -4,10 +4,12 @@ import { IInventoryMagGen } from "@spt-aki/generators/weapongen/IInventoryMagGen
import { InventoryMagGen } from "@spt-aki/generators/weapongen/InventoryMagGen"; import { InventoryMagGen } from "@spt-aki/generators/weapongen/InventoryMagGen";
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper"; import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; 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 { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots";
import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult"; import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@injectable() @injectable()
export class ExternalInventoryMagGen implements IInventoryMagGen export class ExternalInventoryMagGen implements IInventoryMagGen
@ -17,6 +19,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
@inject("RandomUtil") protected randomUtil: RandomUtil
) )
{} {}
@ -33,8 +36,15 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
process(inventoryMagGen: InventoryMagGen): void 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 magTemplate = inventoryMagGen.getMagazineTemplate();
let magazineTpl = magTemplate._id; let magazineTpl = magTemplate._id;
const weapon = inventoryMagGen.getWeaponTemplate();
const attemptedMagBlacklist: string[] = [];
const defaultMagazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weapon);
const randomizedMagazineCount = Number( const randomizedMagazineCount = Number(
this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()), this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()),
); );
@ -46,7 +56,7 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
magTemplate, magTemplate,
); );
const ableToFitMagazinesIntoBotInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( const fitsIntoInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
[EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS], [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS],
magazineWithAmmo[0]._id, magazineWithAmmo[0]._id,
magazineTpl, magazineTpl,
@ -54,43 +64,104 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
inventoryMagGen.getPmcInventory(), 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, /* We were unable to fit at least the minimum amount of magazines,
* so we fallback to default magazine and try again. * 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 ( if (magazineTpl === defaultMagazineTpl)
magazineTpl
=== this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(
inventoryMagGen.getWeaponTemplate(),
)
)
{ {
// We were already on default - stop here to prevent infinite looping // We were already on default - stop here to prevent infinite looping
break; break;
} }
// Get default magazine tpl, reset loop counter by 1 and try again // Add failed magazine tpl to blacklist
magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl( attemptedMagBlacklist.push(magazineTpl);
inventoryMagGen.getWeaponTemplate(),
); // 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]; magTemplate = this.itemHelper.getItem(magazineTpl)[1];
if (!magTemplate) if (!magTemplate)
{ {
this.logger.error( this.logger.error(
this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl), this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl),
); );
break; 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") 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--; 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);
}
} }

View File

@ -134,7 +134,7 @@ export class BotWeaponGeneratorHelper
if (result !== ItemAddedResult.SUCCESS) 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) if (result === ItemAddedResult.NO_SPACE)
{ {
@ -173,6 +173,7 @@ export class BotWeaponGeneratorHelper
inventory: Inventory, inventory: Inventory,
): ItemAddedResult ): ItemAddedResult
{ {
let missingContainerCount = 0;
for (const slot of equipmentSlots) for (const slot of equipmentSlots)
{ {
// Get container to put item into // Get container to put item into
@ -183,8 +184,15 @@ export class BotWeaponGeneratorHelper
this.logger.debug( this.logger.debug(
`Unable to add item: ${ `Unable to add item: ${
itemWithChildren[0]._tpl 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; continue;
} }
@ -205,6 +213,9 @@ export class BotWeaponGeneratorHelper
// Get x/y grid size of item // Get x/y grid size of item
const itemSize = this.inventoryHelper.getItemSize(parentTpl, parentId, itemWithChildren); 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) for (const slotGrid of containerTemplate[1]._props.Grids)
{ {
// Grid is empty, skip // Grid is empty, skip
@ -267,11 +278,18 @@ export class BotWeaponGeneratorHelper
return ItemAddedResult.SUCCESS; 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 // Start loop again in next grid of container
} }
} }
return ItemAddedResult.NO_SPACE; return ItemAddedResult.UNKNOWN;
} }
/** /**

View File

@ -1,5 +1,6 @@
export enum ItemAddedResult export enum ItemAddedResult {
{ UNKNOWN = -1,
SUCCESS = 1, SUCCESS = 1,
NO_SPACE = 2, NO_SPACE = 2,
NO_CONTAINERS = 3
} }