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

View File

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

View File

@ -5612,7 +5612,8 @@
"5926dad986f7741f82604363"
],
"mod_muzzle": [
"5926e16e86f7742f5a0f7ecb"
"5926e16e86f7742f5a0f7ecb",
"5c0000c00db834001a6697fc"
],
"mod_sight_rear": [
"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 { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
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 { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
@ -97,8 +98,8 @@ export class BotEquipmentModGenerator
);
continue;
}
if (!(this.shouldModBeSpawned(itemSlotTemplate, modSlotName.toLowerCase(), settings.spawnChances.equipmentMods) || forceSpawn))
const modSpawnResult = this.shouldModBeSpawned(itemSlotTemplate, modSlotName.toLowerCase(), settings.spawnChances.equipmentMods);
if (modSpawnResult === ModSpawn.SKIP && !forceSpawn)
{
continue;
}
@ -151,7 +152,7 @@ export class BotEquipmentModGenerator
// Compatible item not found but slot REQUIRES item, get random item from db
if (!found && itemSlotTemplate._required)
{
modTpl = this.getModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment);
modTpl = this.getRandomModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment);
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`);
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
@ -275,7 +277,7 @@ export class BotEquipmentModGenerator
}
result.result = Result.SUCCESS;
result.plateModTpls = [relatedItemDbModSlot._props.filters[0].Plate];
result.plateModTpls = [defaultPlate];
return result;
}
@ -292,7 +294,7 @@ export class BotEquipmentModGenerator
* @param sessionId session id
* @param weapon Weapon to add mods to
* @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 modSpawnChances Mod spawn chances
* @param ammoTpl Ammo tpl to use when generating magazines/cartridges
@ -306,7 +308,7 @@ export class BotEquipmentModGenerator
sessionId: string,
weapon: Item[],
modPool: Mods,
weaponParentId: string,
weaponId: string,
parentTemplate: ITemplateItem,
modSpawnChances: ModsChances,
ammoTpl: string,
@ -366,7 +368,8 @@ export class BotEquipmentModGenerator
}
// Check spawn chance of mod
if (!this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances))
const modSpawnResult = this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances);
if (modSpawnResult === ModSpawn.SKIP)
{
continue;
}
@ -381,6 +384,7 @@ export class BotEquipmentModGenerator
weapon,
ammoTpl,
parentTemplate,
modSpawnResult
);
// Compatible mod not found
@ -465,7 +469,7 @@ export class BotEquipmentModGenerator
const modId = this.hashUtil.generate();
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.
@ -684,24 +688,28 @@ export class BotEquipmentModGenerator
/**
* 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 modSlot slot the mod sits in
* @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
? 100
: modSpawnChances[modSlot];
if (modSpawnChance === 100)
const slotRequired = itemSlot._required;
if (this.getAmmoContainers().includes(modSlot))
{
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 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
* @returns ITemplateItem
* @returns itemHelper.getItem() result
*/
protected chooseModToPutIntoSlot(
modSlot: string,
@ -724,11 +732,15 @@ export class BotEquipmentModGenerator
weapon: Item[],
ammoTpl: string,
parentTemplate: ITemplateItem,
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];
// It's ammo, use predefined ammo parameter
if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine")
@ -737,15 +749,11 @@ export class BotEquipmentModGenerator
}
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
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(
`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)
{
// 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],
itemModPool[modSlot],
modPool,
botWeaponSightWhitelist,
);
}
}
// 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; } = {
incompatible: false,
reason: "",
@ -799,7 +807,7 @@ export class BotEquipmentModGenerator
// Get random mod to attach from items db for required slots if none found above
if (!found && parentSlot !== undefined && parentSlot._required)
{
modTpl = this.getModTplFromItemDb(modTpl, parentSlot, modSlot, weapon);
modTpl = this.getRandomModTplFromItemDb(modTpl, parentSlot, modSlot, weapon);
found = !!modTpl;
}
@ -826,6 +834,54 @@ export class BotEquipmentModGenerator
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
* @param weapon Weapon
@ -888,7 +944,7 @@ export class BotEquipmentModGenerator
* @param items items to ensure picked mod is compatible with
* @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
const allowedItems = parentSlot._props.filters[0].Filter;

View File

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

View File

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