Improve mod item filtering code
This commit is contained in:
parent
8ca0c5d82b
commit
734d821630
@ -14,6 +14,7 @@ import { ITemplateItem, Slot } from "@spt-aki/models/eft/common/tables/ITemplate
|
||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { ModSpawn } from "@spt-aki/models/enums/ModSpawn";
|
||||
import { IChooseRandomCompatibleModResult } from "@spt-aki/models/spt/bots/IChooseRandomCompatibleModResult";
|
||||
import { EquipmentFilterDetails, IBotConfig } from "@spt-aki/models/spt/config/IBotConfig";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
@ -735,9 +736,6 @@ export class BotEquipmentModGenerator
|
||||
modSpawnResult: ModSpawn
|
||||
): [boolean, ITemplateItem]
|
||||
{
|
||||
/** Chosen mod tpl */
|
||||
let modTpl: string;
|
||||
let found = false;
|
||||
/** Slot mod will fill */
|
||||
const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot);
|
||||
const weaponTemplate = this.itemHelper.getItem(weapon[0]._tpl)[1];
|
||||
@ -745,93 +743,64 @@ export class BotEquipmentModGenerator
|
||||
// It's ammo, use predefined ammo parameter
|
||||
if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine")
|
||||
{
|
||||
modTpl = ammoTpl;
|
||||
return this.itemHelper.getItem(ammoTpl)
|
||||
}
|
||||
else
|
||||
|
||||
// Ensure there's a pool of mods to pick from
|
||||
let modPool = this.getModPoolForSlot(itemModPool, modSpawnResult, parentTemplate, weaponTemplate, modSlot, botEquipBlacklist, isRandomisableSlot);
|
||||
if (!(modPool || parentSlot._required))
|
||||
{
|
||||
// Ensure there's a pool of mods to pick from
|
||||
let modPool = this.getModPoolForSlot(itemModPool, modSpawnResult, parentTemplate, weaponTemplate, modSlot, botEquipBlacklist, isRandomisableSlot);
|
||||
if (!(modPool || parentSlot._required))
|
||||
// Nothing in mod pool + item not required
|
||||
this.logger.debug(
|
||||
`Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements
|
||||
if (modSlot.includes("mod_scope") && botWeaponSightWhitelist)
|
||||
{
|
||||
// scope pool has more than one scope
|
||||
if (modPool.length > 1)
|
||||
{
|
||||
// Nothing in mod pool + item not required
|
||||
this.logger.debug(
|
||||
`Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`,
|
||||
modPool = this.filterSightsByWeaponType(
|
||||
weapon[0],
|
||||
modPool,
|
||||
botWeaponSightWhitelist,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Pick random mod that's compatible
|
||||
const chosenModResult = this.chooseRandomCompatibleModTpl(modPool, parentSlot, modSpawnResult, weapon, modSlot);
|
||||
|
||||
// Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements
|
||||
if (modSlot.includes("mod_scope") && botWeaponSightWhitelist)
|
||||
{
|
||||
// scope pool has more than one scope
|
||||
if (modPool.length > 1)
|
||||
{
|
||||
modPool = this.filterSightsByWeaponType(
|
||||
weapon[0],
|
||||
modPool,
|
||||
botWeaponSightWhitelist,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pick random mod and check it's compatible
|
||||
const exhaustableModPool = new ExhaustableArray(modPool, this.randomUtil, this.jsonUtil);
|
||||
let modCompatibilityResult: { incompatible: boolean; reason: string; } = {
|
||||
incompatible: false,
|
||||
reason: "",
|
||||
};
|
||||
const modParentFilterList = parentSlot._props.filters[0].Filter;
|
||||
while (exhaustableModPool.hasValues())
|
||||
{
|
||||
modTpl = exhaustableModPool.getRandomValue();
|
||||
if (modSpawnResult === ModSpawn.DEFAULT_MOD && modPool.length === 1)
|
||||
{
|
||||
// default mod wanted and only one choice
|
||||
found = true;
|
||||
if (chosenModResult.slotBlocked)
|
||||
{
|
||||
// Don't bother trying to fit mod, slot is completely blocked
|
||||
return null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
modCompatibilityResult = this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(
|
||||
weapon,
|
||||
modTpl,
|
||||
modSlot,
|
||||
);
|
||||
const isOnModParentFilterList = modParentFilterList.includes(modTpl);
|
||||
if (!isOnModParentFilterList)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!modCompatibilityResult.incompatible && !this.weaponModComboIsIncompatible(weapon, modTpl))
|
||||
{
|
||||
found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Log mod chosen was incompatible
|
||||
if (modCompatibilityResult.incompatible && parentSlot._required)
|
||||
{
|
||||
this.logger.debug(modCompatibilityResult.reason);
|
||||
this.logger.debug(`Weapon: ${weapon.map(x => `${x._tpl} ${x.slotId ?? ""}`).join(",")}`)
|
||||
}
|
||||
// Log if mod chosen was incompatible
|
||||
if (chosenModResult.incompatible && parentSlot._required)
|
||||
{
|
||||
this.logger.debug(chosenModResult.reason);
|
||||
this.logger.debug(`Weapon: ${weapon.map(x => `${x._tpl} ${x.slotId ?? ""}`).join(",")}`)
|
||||
}
|
||||
|
||||
// Get random mod to attach from items db for required slots if none found above
|
||||
if (!found && parentSlot !== undefined && parentSlot._required)
|
||||
if (!chosenModResult.found && parentSlot !== undefined && parentSlot._required)
|
||||
{
|
||||
modTpl = this.getRandomModTplFromItemDb(modTpl, parentSlot, modSlot, weapon);
|
||||
found = !!modTpl;
|
||||
chosenModResult.chosenTpl = this.getRandomModTplFromItemDb("", parentSlot, modSlot, weapon);
|
||||
chosenModResult.found = true;
|
||||
}
|
||||
|
||||
// Compatible item not found + not required
|
||||
if (!found && parentSlot !== undefined && !parentSlot._required)
|
||||
if (!chosenModResult.found && parentSlot !== undefined && !parentSlot._required)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!found && parentSlot !== undefined)
|
||||
if (!chosenModResult.found && parentSlot !== undefined)
|
||||
{
|
||||
if (parentSlot._required)
|
||||
{
|
||||
@ -845,7 +814,65 @@ export class BotEquipmentModGenerator
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.itemHelper.getItem(modTpl);
|
||||
return this.itemHelper.getItem(chosenModResult.chosenTpl);
|
||||
}
|
||||
|
||||
protected chooseRandomCompatibleModTpl(
|
||||
modPool: string[],
|
||||
parentSlot: Slot,
|
||||
modSpawnResult: ModSpawn,
|
||||
weapon: Item[],
|
||||
modSlotname: string): IChooseRandomCompatibleModResult
|
||||
{
|
||||
let chosenTpl: string;
|
||||
const exhaustableModPool = new ExhaustableArray(modPool, this.randomUtil, this.jsonUtil);
|
||||
let chosenModResult: IChooseRandomCompatibleModResult = {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason: "unknown",
|
||||
};
|
||||
const modParentFilterList = parentSlot._props.filters[0].Filter;
|
||||
|
||||
while (exhaustableModPool.hasValues())
|
||||
{
|
||||
chosenTpl = exhaustableModPool.getRandomValue();
|
||||
if (modSpawnResult === ModSpawn.DEFAULT_MOD && modPool.length === 1)
|
||||
{
|
||||
// Default mod wanted and only one choice in pool
|
||||
chosenModResult.found = true;
|
||||
chosenModResult.chosenTpl = chosenTpl;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const isOnModParentFilterList = modParentFilterList.includes(chosenTpl);
|
||||
if (!isOnModParentFilterList)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chosenModResult = this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(
|
||||
weapon,
|
||||
chosenTpl,
|
||||
modSlotname,
|
||||
);
|
||||
|
||||
if (chosenModResult.slotBlocked)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Some mod combos will never work, make sure its not happened
|
||||
if (!chosenModResult.incompatible && !this.weaponModComboIsIncompatible(weapon, chosenTpl))
|
||||
{
|
||||
chosenModResult.found = true;
|
||||
chosenModResult.chosenTpl = chosenTpl;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chosenModResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -982,26 +1009,28 @@ export class BotEquipmentModGenerator
|
||||
/**
|
||||
* Log errors if mod is not compatible with slot
|
||||
* @param modToAdd template of mod to check
|
||||
* @param itemSlot slot the item will be placed in
|
||||
* @param slotAddedToTemplate slot the item will be placed in
|
||||
* @param modSlot slot the mod will fill
|
||||
* @param parentTemplate template of the mods parent item
|
||||
* @param parentTemplate template of the mods being added
|
||||
* @param botRole
|
||||
* @returns true if valid
|
||||
*/
|
||||
protected isModValidForSlot(
|
||||
modToAdd: [boolean, ITemplateItem],
|
||||
itemSlot: Slot,
|
||||
slotAddedToTemplate: Slot,
|
||||
modSlot: string,
|
||||
parentTemplate: ITemplateItem,
|
||||
botRole: string
|
||||
): boolean
|
||||
{
|
||||
const modBeingAddedTemplate = modToAdd[1];
|
||||
|
||||
// Mod lacks template item
|
||||
if (!modToAdd[1])
|
||||
if (!modBeingAddedTemplate)
|
||||
{
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-no_item_template_found_when_adding_mod", {
|
||||
modId: modToAdd[1]._id,
|
||||
modId: modBeingAddedTemplate._id,
|
||||
modSlot: modSlot,
|
||||
}),
|
||||
);
|
||||
@ -1014,13 +1043,14 @@ export class BotEquipmentModGenerator
|
||||
if (!modToAdd[0])
|
||||
{
|
||||
// Slot must be filled, show warning
|
||||
if (itemSlot._required)
|
||||
if (slotAddedToTemplate._required)
|
||||
{
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_add_mod_item_invalid", {
|
||||
itemName: modToAdd[1]._name,
|
||||
itemName: modBeingAddedTemplate._name,
|
||||
modSlot: modSlot,
|
||||
parentItemName: parentTemplate._name,
|
||||
botRole: botRole
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -1028,24 +1058,6 @@ export class BotEquipmentModGenerator
|
||||
return false;
|
||||
}
|
||||
|
||||
// If mod id doesn't exist in slots filter list and mod id doesn't have any of the slots filters as a base class, mod isn't valid for the slot
|
||||
if (
|
||||
!(itemSlot._props.filters[0].Filter.includes(modToAdd[1]._id)
|
||||
|| this.itemHelper.isOfBaseclasses(modToAdd[1]._id, itemSlot._props.filters[0].Filter))
|
||||
)
|
||||
{
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-mod_not_in_slot_filter_list", {
|
||||
modId: modToAdd[1]._id,
|
||||
modSlot: modSlot,
|
||||
parentName: parentTemplate._name,
|
||||
botRole: botRole
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -357,31 +357,31 @@ export class BotWeaponGenerator
|
||||
{
|
||||
for (const mod of weaponItemArray)
|
||||
{
|
||||
const modDbTemplate = this.itemHelper.getItem(mod._tpl)[1];
|
||||
if (!modDbTemplate._props.Slots?.length)
|
||||
const modTemplate = this.itemHelper.getItem(mod._tpl)[1];
|
||||
if (!modTemplate._props.Slots?.length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over slots in db item, if required, check tpl in that slot matches the filter list
|
||||
for (const modSlot of modDbTemplate._props.Slots)
|
||||
for (const modSlotTemplate of modTemplate._props.Slots)
|
||||
{
|
||||
// Ignore optional mods
|
||||
if (!modSlot._required)
|
||||
if (!modSlotTemplate._required)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const allowedTpls = modSlot._props.filters[0].Filter;
|
||||
const slotName = modSlot._name;
|
||||
const allowedTplsOnSlot = modSlotTemplate._props.filters[0].Filter;
|
||||
const slotName = modSlotTemplate._name;
|
||||
|
||||
const weaponSlotItem = weaponItemArray.find((x) => x.parentId === mod._id && x.slotId === slotName);
|
||||
const weaponSlotItem = weaponItemArray.find((weaponItem) => weaponItem.parentId === mod._id && weaponItem.slotId === slotName);
|
||||
if (!weaponSlotItem)
|
||||
{
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapons_required_slot_missing_item", {
|
||||
modSlot: modSlot._name,
|
||||
modName: modDbTemplate._name,
|
||||
modSlot: modSlotTemplate._name,
|
||||
modName: modTemplate._name,
|
||||
slotId: mod.slotId,
|
||||
botRole: botRole,
|
||||
}),
|
||||
@ -389,19 +389,6 @@ export class BotWeaponGenerator
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!allowedTpls.includes(weaponSlotItem._tpl))
|
||||
{
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapon_contains_invalid_item", {
|
||||
modSlot: modSlot._name,
|
||||
modName: modDbTemplate._name,
|
||||
weaponTpl: weaponSlotItem._tpl,
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||
import { IGetRaidConfigurationRequestData } from "@spt-aki/models/eft/match/IGetRaidConfigurationRequestData";
|
||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { IChooseRandomCompatibleModResult } from "@spt-aki/models/spt/bots/IChooseRandomCompatibleModResult";
|
||||
import { EquipmentFilters, IBotConfig, IRandomisedResourceValues } from "@spt-aki/models/spt/config/IBotConfig";
|
||||
import { IPmcConfig } from "@spt-aki/models/spt/config/IPmcConfig";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
@ -286,12 +287,12 @@ export class BotGeneratorHelper
|
||||
itemsEquipped: Item[],
|
||||
tplToCheck: string,
|
||||
equipmentSlot: string,
|
||||
): { incompatible: boolean; reason: string; slotBlocked?: boolean; }
|
||||
): IChooseRandomCompatibleModResult
|
||||
{
|
||||
// Skip slots that have no incompatibilities
|
||||
if (["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"].includes(equipmentSlot))
|
||||
{
|
||||
return { incompatible: false, reason: "" };
|
||||
return { incompatible: false, found: false, reason: "" };
|
||||
}
|
||||
|
||||
// TODO: Can probably be optimized to cache itemTemplates as items are added to inventory
|
||||
@ -308,7 +309,7 @@ export class BotGeneratorHelper
|
||||
}),
|
||||
);
|
||||
|
||||
return { incompatible: true, reason: `item: ${tplToCheck} does not exist in the database` };
|
||||
return { incompatible: true, found: false, reason: `item: ${tplToCheck} does not exist in the database` };
|
||||
}
|
||||
|
||||
if (!itemToEquip._props)
|
||||
@ -321,7 +322,11 @@ export class BotGeneratorHelper
|
||||
}),
|
||||
);
|
||||
|
||||
return { incompatible: true, reason: `item: ${tplToCheck} does not have a _props field` };
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason: `item: ${tplToCheck} does not have a _props field`
|
||||
};
|
||||
}
|
||||
|
||||
// Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover
|
||||
@ -331,6 +336,7 @@ export class BotGeneratorHelper
|
||||
// this.logger.warning(`1 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._name} - ${equipmentSlot}`);
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} ${itemToEquip._name} in slot: ${equipmentSlot} blocked by: ${blockingItem._id} ${blockingItem._name}`,
|
||||
slotBlocked: true,
|
||||
@ -344,6 +350,7 @@ export class BotGeneratorHelper
|
||||
// this.logger.warning(`2 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._props.Name} - ${equipmentSlot}`);
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} ${itemToEquip._name} in slot: ${equipmentSlot} blocked by: ${blockingItem._id} ${blockingItem._name}`,
|
||||
slotBlocked: true,
|
||||
@ -358,6 +365,7 @@ export class BotGeneratorHelper
|
||||
{
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} ${itemToEquip._name} is blocked by: ${existingHeadwear._tpl} in slot: ${existingHeadwear.slotId}`,
|
||||
slotBlocked: true,
|
||||
@ -373,6 +381,7 @@ export class BotGeneratorHelper
|
||||
{
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} ${itemToEquip._name} is blocked by: ${existingFaceCover._tpl} in slot: ${existingFaceCover.slotId}`,
|
||||
slotBlocked: true,
|
||||
@ -388,6 +397,7 @@ export class BotGeneratorHelper
|
||||
{
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} ${itemToEquip._name} is blocked by: ${existingEarpiece._tpl} in slot: ${existingEarpiece.slotId}`,
|
||||
slotBlocked: true,
|
||||
@ -403,6 +413,7 @@ export class BotGeneratorHelper
|
||||
{
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} ${itemToEquip._name} is blocked by: ${existingArmorVest._tpl} in slot: ${existingArmorVest.slotId}`,
|
||||
slotBlocked: true,
|
||||
@ -417,6 +428,7 @@ export class BotGeneratorHelper
|
||||
// this.logger.warning(`3 incompatibility found between - ${itemToEquip[1]._name} and ${blockingInventoryItem._tpl} - ${equipmentSlot}`)
|
||||
return {
|
||||
incompatible: true,
|
||||
found: false,
|
||||
reason:
|
||||
`${tplToCheck} blocks existing item ${blockingInventoryItem._tpl} in slot ${blockingInventoryItem.slotId}`,
|
||||
};
|
||||
|
@ -0,0 +1,8 @@
|
||||
export interface IChooseRandomCompatibleModResult
|
||||
{
|
||||
incompatible: boolean;
|
||||
found?: boolean;
|
||||
chosenTpl?: string;
|
||||
reason: string;
|
||||
slotBlocked?: boolean;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user