Fix issue with weapon/equipment mod selection where it would ignore settings when mod slot was required

Fix JSON errors in bot.json
This commit is contained in:
Dev 2024-01-25 13:45:42 +00:00
parent fc9e3f9f59
commit fd7050b0ac
6 changed files with 104 additions and 41 deletions

View File

@ -891,6 +891,7 @@
"mod_foregrip": 10, "mod_foregrip": 10,
"mod_handguard": 10, "mod_handguard": 10,
"mod_launcher": 0, "mod_launcher": 0,
"mod_reciever": 15,
"mod_magazine": 10, "mod_magazine": 10,
"mod_mount": 15, "mod_mount": 15,
"mod_mount_000": 10, "mod_mount_000": 10,
@ -1120,7 +1121,7 @@
"TacticalVest": { "TacticalVest": {
"6034d0230ca681766b6a0fb5": 120, "6034d0230ca681766b6a0fb5": 120,
"6034cf5fffd42c541047f72e": 150, "6034cf5fffd42c541047f72e": 150,
"597867e986f7741b265c6bd3": 120 "59e7635f86f7742cbf2c1095": 120
}, },
"Backpack": { "Backpack": {
}, },
@ -1131,11 +1132,9 @@
"edit": { "edit": {
"ArmorVest": { "ArmorVest": {
"5648a7494bdc2d9d488b4583": 290, "5648a7494bdc2d9d488b4583": 290,
"5c0e446786f7742013381639": 210,
"5b44d22286f774172b0c9de8": 280, "5b44d22286f774172b0c9de8": 280,
"5c0e5bab86f77461f55ed1f3": 280, "5c0e5bab86f77461f55ed1f3": 280,
"5c0e5edb86f77461f55ed1f7": 270, "5c0e5edb86f77461f55ed1f7": 270,
"5ab8e4ed86f7742d8e50c7fa": 260, "5ab8e4ed86f7742d8e50c7fa": 260,
"5df8a2ca86f7740bfe6df777": 185, "5df8a2ca86f7740bfe6df777": 185,
"64be79c487d1510151095552": 270, "64be79c487d1510151095552": 270,
@ -1144,7 +1143,6 @@
"5e4abb5086f77406975c9342": 4, "5e4abb5086f77406975c9342": 4,
"6038b4ca92ec1c3103795a0d": 4, "6038b4ca92ec1c3103795a0d": 4,
"6038b4b292ec1c3103795a0b": 4, "6038b4b292ec1c3103795a0b": 4,
"628dc750b910320f4c27a732": 4,
"5b44cd8b86f774503d30cba2": 4, "5b44cd8b86f774503d30cba2": 4,
"5ca21c6986f77479963115a7": 4, "5ca21c6986f77479963115a7": 4,
"5b44d0de86f774503d30cba8": 4, "5b44d0de86f774503d30cba8": 4,
@ -1154,6 +1152,7 @@
"545cdb794bdc2d3a198b456a": 4 "545cdb794bdc2d3a198b456a": 4
}, },
"TacticalVest": { "TacticalVest": {
"5c0e446786f7742013381639": 210,
"5e4abfed86f77406a2713cf7": 200, "5e4abfed86f77406a2713cf7": 200,
"5d5d8ca986f7742798716522": 200, "5d5d8ca986f7742798716522": 200,
"5d5d646386f7742797261fd9": 230, "5d5d646386f7742797261fd9": 230,
@ -1167,6 +1166,7 @@
"60a3c68c37ea821725773ef5": 1, "60a3c68c37ea821725773ef5": 1,
"5e4ac41886f77406a511c9a8": 1, "5e4ac41886f77406a511c9a8": 1,
"5b44cad286f77402a54ae7e5": 1, "5b44cad286f77402a54ae7e5": 1,
"628dc750b910320f4c27a732": 1,
"5c0e3eb886f7742015526062": 230, "5c0e3eb886f7742015526062": 230,
"572b7adb24597762ae139821": 250, "572b7adb24597762ae139821": 250,
"544a5caa4bdc2d1a388b4568": 50, "544a5caa4bdc2d1a388b4568": 50,

View File

@ -5615,7 +5615,8 @@
"5926dad986f7741f82604363" "5926dad986f7741f82604363"
], ],
"mod_muzzle": [ "mod_muzzle": [
"5926e16e86f7742f5a0f7ecb" "5926e16e86f7742f5a0f7ecb",
"5c0000c00db834001a6697fc"
], ],
"mod_sight_rear": [ "mod_sight_rear": [
"5926d2be86f774134d668e4e" "5926d2be86f774134d668e4e"

View File

@ -5612,7 +5612,8 @@
"5926dad986f7741f82604363" "5926dad986f7741f82604363"
], ],
"mod_muzzle": [ "mod_muzzle": [
"5926e16e86f7742f5a0f7ecb" "5926e16e86f7742f5a0f7ecb",
"5c0000c00db834001a6697fc"
], ],
"mod_sight_rear": [ "mod_sight_rear": [
"5926d2be86f774134d668e4e" "5926d2be86f774134d668e4e"

View File

@ -13,6 +13,7 @@ import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { ITemplateItem, Slot } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { ITemplateItem, Slot } 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 { ModSpawn } from "@spt-aki/models/enums/ModSpawn";
import { EquipmentFilterDetails, IBotConfig } from "@spt-aki/models/spt/config/IBotConfig"; import { EquipmentFilterDetails, IBotConfig } from "@spt-aki/models/spt/config/IBotConfig";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { ConfigServer } from "@spt-aki/servers/ConfigServer";
@ -97,8 +98,8 @@ export class BotEquipmentModGenerator
); );
continue; continue;
} }
const modSpawnResult = this.shouldModBeSpawned(itemSlotTemplate, modSlotName.toLowerCase(), settings.spawnChances.equipmentMods);
if (!(this.shouldModBeSpawned(itemSlotTemplate, modSlotName.toLowerCase(), settings.spawnChances.equipmentMods) || forceSpawn)) if (modSpawnResult === ModSpawn.SKIP && !forceSpawn)
{ {
continue; continue;
} }
@ -151,7 +152,7 @@ export class BotEquipmentModGenerator
// Compatible item not found but slot REQUIRES item, get random item from db // Compatible item not found but slot REQUIRES item, get random item from db
if (!found && itemSlotTemplate._required) if (!found && itemSlotTemplate._required)
{ {
modTpl = this.getModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment); modTpl = this.getRandomModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment);
found = !!modTpl; found = !!modTpl;
} }
@ -251,7 +252,8 @@ export class BotEquipmentModGenerator
this.logger.debug(`Plate filter was too restrictive for armor: ${armorItem._id}, unable to find plates of level: ${chosenArmorPlateLevel}. Using mod items default plate`); this.logger.debug(`Plate filter was too restrictive for armor: ${armorItem._id}, unable to find plates of level: ${chosenArmorPlateLevel}. Using mod items default plate`);
const relatedItemDbModSlot = armorItem._props.Slots.find(slot => slot._name.toLowerCase() === modSlot); const relatedItemDbModSlot = armorItem._props.Slots.find(slot => slot._name.toLowerCase() === modSlot);
if (!relatedItemDbModSlot._props.filters[0].Plate) const defaultPlate = relatedItemDbModSlot._props.filters[0].Plate
if (!defaultPlate)
{ {
// No relevant plate found after filtering AND no default plate // No relevant plate found after filtering AND no default plate
@ -275,7 +277,7 @@ export class BotEquipmentModGenerator
} }
result.result = Result.SUCCESS; result.result = Result.SUCCESS;
result.plateModTpls = [relatedItemDbModSlot._props.filters[0].Plate]; result.plateModTpls = [defaultPlate];
return result; return result;
} }
@ -292,7 +294,7 @@ export class BotEquipmentModGenerator
* @param sessionId session id * @param sessionId session id
* @param weapon Weapon to add mods to * @param weapon Weapon to add mods to
* @param modPool Pool of compatible mods to attach to weapon * @param modPool Pool of compatible mods to attach to weapon
* @param weaponParentId parentId of weapon * @param weaponId parentId of weapon
* @param parentTemplate Weapon which mods will be generated on * @param parentTemplate Weapon which mods will be generated on
* @param modSpawnChances Mod spawn chances * @param modSpawnChances Mod spawn chances
* @param ammoTpl Ammo tpl to use when generating magazines/cartridges * @param ammoTpl Ammo tpl to use when generating magazines/cartridges
@ -306,7 +308,7 @@ export class BotEquipmentModGenerator
sessionId: string, sessionId: string,
weapon: Item[], weapon: Item[],
modPool: Mods, modPool: Mods,
weaponParentId: string, weaponId: string,
parentTemplate: ITemplateItem, parentTemplate: ITemplateItem,
modSpawnChances: ModsChances, modSpawnChances: ModsChances,
ammoTpl: string, ammoTpl: string,
@ -366,7 +368,8 @@ export class BotEquipmentModGenerator
} }
// Check spawn chance of mod // Check spawn chance of mod
if (!this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances)) const modSpawnResult = this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances);
if (modSpawnResult === ModSpawn.SKIP)
{ {
continue; continue;
} }
@ -381,6 +384,7 @@ export class BotEquipmentModGenerator
weapon, weapon,
ammoTpl, ammoTpl,
parentTemplate, parentTemplate,
modSpawnResult
); );
// Compatible mod not found // Compatible mod not found
@ -465,7 +469,7 @@ export class BotEquipmentModGenerator
const modId = this.hashUtil.generate(); const modId = this.hashUtil.generate();
weapon.push( weapon.push(
this.createModItem(modId, modToAddTemplate._id, weaponParentId, modSlot, modToAddTemplate, botRole), this.createModItem(modId, modToAddTemplate._id, weaponId, modSlot, modToAddTemplate, botRole),
); );
// I first thought we could use the recursive generateModsForItems as previously for cylinder magazines. // I first thought we could use the recursive generateModsForItems as previously for cylinder magazines.
@ -684,24 +688,28 @@ export class BotEquipmentModGenerator
/** /**
* Randomly choose if a mod should be spawned, 100% for required mods OR mod is ammo slot * Randomly choose if a mod should be spawned, 100% for required mods OR mod is ammo slot
* never return true for an item that has 0% spawn chance
* @param itemSlot slot the item sits in * @param itemSlot slot the item sits in
* @param modSlot slot the mod sits in * @param modSlot slot the mod sits in
* @param modSpawnChances Chances for various mod spawns * @param modSpawnChances Chances for various mod spawns
* @returns boolean true if it should spawn * @returns ModSpawn.SPAWN when mod should be spawned, ModSpawn.DEFAULT_MOD when default mod should spawn, ModSpawn.SKIP when mod is skipped
*/ */
protected shouldModBeSpawned(itemSlot: Slot, modSlot: string, modSpawnChances: ModsChances): boolean protected shouldModBeSpawned(itemSlot: Slot, modSlot: string, modSpawnChances: ModsChances): ModSpawn
{ {
const modSpawnChance = itemSlot._required || this.getAmmoContainers().includes(modSlot) // Required OR it is ammo const slotRequired = itemSlot._required;
? 100 if (this.getAmmoContainers().includes(modSlot))
: modSpawnChances[modSlot];
if (modSpawnChance === 100)
{ {
return true; return ModSpawn.SPAWN
}
const spawnMod = this.probabilityHelper.rollChance(modSpawnChances[modSlot]);
if (!spawnMod && slotRequired)
{
// Mod is required but spawn chance roll failed, choose default mod spawn for slot
return ModSpawn.DEFAULT_MOD;
} }
return this.probabilityHelper.rollChance(modSpawnChance); return spawnMod
? ModSpawn.SPAWN
: ModSpawn.SKIP;
} }
/** /**
@ -713,7 +721,7 @@ export class BotEquipmentModGenerator
* @param weapon array with only weapon tpl in it, ready for mods to be added * @param weapon array with only weapon tpl in it, ready for mods to be added
* @param ammoTpl ammo tpl to use if slot requires a cartridge to be added (e.g. mod_magazine) * @param ammoTpl ammo tpl to use if slot requires a cartridge to be added (e.g. mod_magazine)
* @param parentTemplate Parent item the mod will go into * @param parentTemplate Parent item the mod will go into
* @returns ITemplateItem * @returns itemHelper.getItem() result
*/ */
protected chooseModToPutIntoSlot( protected chooseModToPutIntoSlot(
modSlot: string, modSlot: string,
@ -724,11 +732,15 @@ export class BotEquipmentModGenerator
weapon: Item[], weapon: Item[],
ammoTpl: string, ammoTpl: string,
parentTemplate: ITemplateItem, parentTemplate: ITemplateItem,
modSpawnResult: ModSpawn
): [boolean, ITemplateItem] ): [boolean, ITemplateItem]
{ {
/** Chosen mod tpl */
let modTpl: string; let modTpl: string;
let found = false; let found = false;
/** Slot mod will fill */
const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot); const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot);
const weaponTemplate = this.itemHelper.getItem(weapon[0]._tpl)[1];
// It's ammo, use predefined ammo parameter // It's ammo, use predefined ammo parameter
if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine") if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine")
@ -737,15 +749,11 @@ export class BotEquipmentModGenerator
} }
else else
{ {
// Get randomised pool of mods if this is a slot we randomise
if (isRandomisableSlot)
{
itemModPool[modSlot] = this.getDynamicModPool(parentTemplate._id, modSlot, botEquipBlacklist);
}
// Ensure there's a pool of mods to pick from // Ensure there's a pool of mods to pick from
if (!(itemModPool[modSlot] || parentSlot._required)) 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( this.logger.debug(
`Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`, `Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`,
); );
@ -756,18 +764,18 @@ export class BotEquipmentModGenerator
if (modSlot.includes("mod_scope") && botWeaponSightWhitelist) if (modSlot.includes("mod_scope") && botWeaponSightWhitelist)
{ {
// scope pool has more than one scope // scope pool has more than one scope
if (itemModPool[modSlot].length > 1) if (modPool.length > 1)
{ {
itemModPool[modSlot] = this.filterSightsByWeaponType( modPool = this.filterSightsByWeaponType(
weapon[0], weapon[0],
itemModPool[modSlot], modPool,
botWeaponSightWhitelist, botWeaponSightWhitelist,
); );
} }
} }
// Pick random mod and check it's compatible // Pick random mod and check it's compatible
const exhaustableModPool = new ExhaustableArray(itemModPool[modSlot], this.randomUtil, this.jsonUtil); const exhaustableModPool = new ExhaustableArray(modPool, this.randomUtil, this.jsonUtil);
let modCompatibilityResult: { incompatible: boolean; reason: string; } = { let modCompatibilityResult: { incompatible: boolean; reason: string; } = {
incompatible: false, incompatible: false,
reason: "", reason: "",
@ -799,7 +807,7 @@ export class BotEquipmentModGenerator
// Get random mod to attach from items db for required slots if none found above // Get random mod to attach from items db for required slots if none found above
if (!found && parentSlot !== undefined && parentSlot._required) if (!found && parentSlot !== undefined && parentSlot._required)
{ {
modTpl = this.getModTplFromItemDb(modTpl, parentSlot, modSlot, weapon); modTpl = this.getRandomModTplFromItemDb(modTpl, parentSlot, modSlot, weapon);
found = !!modTpl; found = !!modTpl;
} }
@ -826,6 +834,54 @@ export class BotEquipmentModGenerator
return this.itemHelper.getItem(modTpl); return this.itemHelper.getItem(modTpl);
} }
/**
* Filter mod pool down based on various criteria:
* Is slot flagged as randomisable
* Is slot required
* Is slot flagged as default mod only
* @param itemModPool Existing pool of mods to choose
* @param modSpawnResult outcome of random roll to select if mod should be added
* @param parentTemplate Mods parent
* @param weaponTemplate Mods root parent (weapon/equipment)
* @param modSlot name of mod slot to choose for
* @param botEquipBlacklist
* @param isRandomisableSlot is flagged as a randomisable slot
* @returns
*/
protected getModPoolForSlot(
itemModPool: Record<string, string[]>,
modSpawnResult: ModSpawn,
parentTemplate: ITemplateItem,
weaponTemplate: ITemplateItem,
modSlot: string,
botEquipBlacklist: EquipmentFilterDetails,
isRandomisableSlot: boolean): string[]
{
// Mod is flagged as being default only, try and find it in globals
if (modSpawnResult === ModSpawn.DEFAULT_MOD)
{
const defaultWeaponPreset = this.presetHelper.getDefaultPreset(weaponTemplate._id)
const matchingMod = defaultWeaponPreset._items.find(item => item?.slotId?.toLowerCase() === modSlot.toLowerCase());
if (matchingMod)
{
return [matchingMod._tpl];
}
this.logger.warning(`No default: ${modSlot} mod found on: ${weaponTemplate._name}`);
// Couuldnt find default in globals, use existing mod pool data
return itemModPool[modSlot];
}
if (isRandomisableSlot)
{
return this.getDynamicModPool(parentTemplate._id, modSlot, botEquipBlacklist);
}
// Required mod is not default or randomisable, use existing pool
return itemModPool[modSlot];
}
/** /**
* Temp fix to prevent certain combinations of weapons with mods that are known to be incompatible * Temp fix to prevent certain combinations of weapons with mods that are known to be incompatible
* @param weapon Weapon * @param weapon Weapon
@ -888,7 +944,7 @@ export class BotEquipmentModGenerator
* @param items items to ensure picked mod is compatible with * @param items items to ensure picked mod is compatible with
* @returns item tpl * @returns item tpl
*/ */
protected getModTplFromItemDb(modTpl: string, parentSlot: Slot, modSlot: string, items: Item[]): string protected getRandomModTplFromItemDb(modTpl: string, parentSlot: Slot, modSlot: string, items: Item[]): string
{ {
// Find compatible mods and make an array of them // Find compatible mods and make an array of them
const allowedItems = parentSlot._props.filters[0].Filter; const allowedItems = parentSlot._props.filters[0].Filter;

View File

@ -175,7 +175,7 @@ export class BotWeaponGenerator
sessionId, sessionId,
weaponWithModsArray, weaponWithModsArray,
modPool, modPool,
weaponWithModsArray[0]._id, weaponWithModsArray[0]._id, // Weapon root id
weaponItemTemplate, weaponItemTemplate,
modChances, modChances,
ammoTpl, ammoTpl,

View File

@ -0,0 +1,5 @@
export enum ModSpawn {
DEFAULT_MOD,
SPAWN,
SKIP
}