Improve selection of items to add mods for:

quest rewards
bot loot gen
map loot gen
fence assort gen

Updated `removeRandomModsOfItem()` to also skip slots with a _required prop
This commit is contained in:
Dev 2024-01-11 17:42:58 +00:00
parent 6a8e261af1
commit cb18d251ba
6 changed files with 73 additions and 30 deletions

View File

@ -344,7 +344,7 @@ export class BotLootGenerator
this.randomiseAmmoStackSize(isPmc, itemToAddTemplate, itemsToAdd[0]); this.randomiseAmmoStackSize(isPmc, itemToAddTemplate, itemsToAdd[0]);
} }
// Must add soft inserts/plates // Must add soft inserts/plates
else if (this.itemHelper.itemCanRequireArmorInserts(itemToAddTemplate._id)) else if (this.itemHelper.itemRequiresSoftInserts(itemToAddTemplate._id))
{ {
itemsToAdd = this.itemHelper.addChildSlotItems(itemsToAdd, itemToAddTemplate, null, true); itemsToAdd = this.itemHelper.addChildSlotItems(itemsToAdd, itemToAddTemplate, null, true);
} }

View File

@ -861,7 +861,7 @@ export class LocationGenerator
); );
itemWithMods.push(...magazineItem); itemWithMods.push(...magazineItem);
} }
else if (this.itemHelper.itemCanRequireArmorInserts(chosenTpl)) else if (this.itemHelper.armorItemCanHoldMods(chosenTpl))
{ {
itemWithMods.push({ itemWithMods.push({
_id: this.objectId.generate(), _id: this.objectId.generate(),
@ -1071,8 +1071,9 @@ export class LocationGenerator
// Replace existing magazine with above array // Replace existing magazine with above array
items.splice(items.indexOf(items[0]), 1, ...magazineWithCartridges); items.splice(items.indexOf(items[0]), 1, ...magazineWithCartridges);
} }
else if (this.itemHelper.itemCanRequireArmorInserts(chosenTpl)) else if (this.itemHelper.armorItemCanHoldMods(chosenTpl))
{ {
// We make base item above, at start of function, no need to do it here
if (itemTemplate._props.Slots?.length > 0) if (itemTemplate._props.Slots?.length > 0)
{ {
items = this.itemHelper.addChildSlotItems(items, itemTemplate, this.locationConfig.equipmentLootSettings.modSpawnChancePercent); items = this.itemHelper.addChildSlotItems(items, itemTemplate, this.locationConfig.equipmentLootSettings.modSpawnChancePercent);

View File

@ -105,11 +105,47 @@ export class ItemHelper
* @param itemTpl item to check * @param itemTpl item to check
* @returns Does item have the possibility ot need soft inserts * @returns Does item have the possibility ot need soft inserts
*/ */
public itemCanRequireArmorInserts(itemTpl: string): boolean public armorItemCanHoldMods(itemTpl: string): boolean
{ {
return this.isOfBaseclasses(itemTpl, [BaseClasses.HEADWEAR, BaseClasses.VEST, BaseClasses.ARMOR]); return this.isOfBaseclasses(itemTpl, [BaseClasses.HEADWEAR, BaseClasses.VEST, BaseClasses.ARMOR]);
} }
/**
* Does the provided item tpl require soft inserts to become a valid armor item
* @param itemTpl Item tpl to check
* @returns True if it needs armor inserts
*/
public itemRequiresSoftInserts(itemTpl: string): boolean
{
// not a slot that takes soft-inserts
if (!this.armorItemCanHoldMods(itemTpl))
{
return false;
}
// Check is an item
const itemDbDetails = this.getItem(itemTpl);
if (!itemDbDetails[0])
{
return false;
}
// Has no slots
if (!(itemDbDetails[1]._props.Slots ?? []).length)
{
return false;
}
// Check if item has slots that match soft insert name ids
const softInsertSlotIds = ["groin", "soft_armor_back", "soft_armor_front", "soft_armor_left", "soft_armor_right", "shoulder_l", "shoulder_r", "collar"];
if (itemDbDetails[1]._props.Slots.find(slot => softInsertSlotIds.includes(slot._name.toLowerCase())))
{
return true;
}
return false;
}
/** /**
* Returns the item price based on the handbook or as a fallback from the prices.json if the item is not * Returns the item price based on the handbook or as a fallback from the prices.json if the item is not
* found in the handbook. If the price can't be found at all return 0 * found in the handbook. If the price can't be found at all return 0

View File

@ -267,8 +267,8 @@ export class QuestHelper
const mods: Item[] = []; const mods: Item[] = [];
const rootItem = questReward.items[0]; const rootItem = questReward.items[0];
// Is armor item that needs inserts / plates // Is armor item that may need inserts / plates
if (questReward.items.length === 1 && this.itemHelper.itemCanRequireArmorInserts(rootItem._tpl)) if (questReward.items.length === 1 && this.itemHelper.armorItemCanHoldMods(rootItem._tpl))
{ {
// Attempt to pull default preset from globals and add child items to reward // Attempt to pull default preset from globals and add child items to reward
this.generateArmorRewardChildSlots(rootItem, questReward); this.generateArmorRewardChildSlots(rootItem, questReward);

View File

@ -403,7 +403,7 @@ export class FenceService
protected createAssorts(assortCount: number, assorts: ITraderAssort, loyaltyLevel: number): void protected createAssorts(assortCount: number, assorts: ITraderAssort, loyaltyLevel: number): void
{ {
const fenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort; const fenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort;
const defaultWeaponPresets = this.presetHelper.getDefaultPresets(); const defaultPresets = this.presetHelper.getDefaultPresets();
const fenceAssortIds = Object.keys(fenceAssort.loyal_level_items); const fenceAssortIds = Object.keys(fenceAssort.loyal_level_items);
const itemTypeCounts = this.initItemLimitCounter(this.traderConfig.fence.itemTypeLimits); const itemTypeCounts = this.initItemLimitCounter(this.traderConfig.fence.itemTypeLimits);
@ -412,7 +412,7 @@ export class FenceService
// Add presets // Add presets
const maxPresetCount = Math.round(assortCount * (this.traderConfig.fence.maxPresetsPercent / 100)); const maxPresetCount = Math.round(assortCount * (this.traderConfig.fence.maxPresetsPercent / 100));
const randomisedPresetCount = this.randomUtil.getInt(0, maxPresetCount); const randomisedPresetCount = this.randomUtil.getInt(0, maxPresetCount);
this.addPresets(randomisedPresetCount, defaultWeaponPresets, assorts, loyaltyLevel); this.addPresets(randomisedPresetCount, defaultPresets, assorts, loyaltyLevel);
} }
protected addItemAssorts( protected addItemAssorts(
@ -481,7 +481,7 @@ export class FenceService
rootItemToPush.upd.UnlimitedCount = false; rootItemToPush.upd.UnlimitedCount = false;
// Need to add mods to armors so they dont show as red in the trade screen // Need to add mods to armors so they dont show as red in the trade screen
if (this.itemHelper.itemCanRequireArmorInserts(rootItemToPush._tpl)) if (this.itemHelper.itemRequiresSoftInserts(rootItemToPush._tpl))
{ {
this.addModsToArmorModSlots(itemsToPush, itemDbDetails); this.addModsToArmorModSlots(itemsToPush, itemDbDetails);
} }
@ -594,25 +594,25 @@ export class FenceService
} }
/** /**
* Add preset weapons to fence presets * Add weapon/armor presets to fence
* @param assortCount how many assorts to add to assorts * @param assortCount how many assorts to add to assorts
* @param defaultWeaponPresets a dictionary of default weapon presets * @param defaultPresets a dictionary of default weapon presets
* @param assorts object to add presets to * @param assorts object to add presets to
* @param loyaltyLevel loyalty level to requre item at * @param loyaltyLevel loyalty level to requre item at
*/ */
protected addPresets( protected addPresets(
desiredPresetCount: number, desiredPresetCount: number,
defaultWeaponPresets: Record<string, IPreset>, defaultPresets: Record<string, IPreset>,
assorts: ITraderAssort, assorts: ITraderAssort,
loyaltyLevel: number, loyaltyLevel: number,
): void ): void
{ {
let presetCount = 0; let presetCount = 0;
const presetKeys = Object.keys(defaultWeaponPresets); const presetKeys = Object.keys(defaultPresets);
for (let index = 0; index < desiredPresetCount; index++) for (let index = 0; index < desiredPresetCount; index++)
{ {
const presetId = presetKeys[this.randomUtil.getInt(0, presetKeys.length - 1)]; const presetId = presetKeys[this.randomUtil.getInt(0, presetKeys.length - 1)];
const preset = defaultWeaponPresets[presetId]; const preset = defaultPresets[presetId];
// Check we're under preset limit // Check we're under preset limit
if (presetCount > desiredPresetCount) if (presetCount > desiredPresetCount)
@ -626,12 +626,12 @@ export class FenceService
continue; continue;
} }
// Construct weapon + mods // Construct preset + mods
const weaponAndMods: Item[] = this.itemHelper.replaceIDs( const weaponAndMods: Item[] = this.itemHelper.replaceIDs(
null, null,
this.jsonUtil.clone(defaultWeaponPresets[preset._id]._items), this.jsonUtil.clone(defaultPresets[preset._id]._items),
); );
this.removeRandomPartsOfWeapon(weaponAndMods); this.removeRandomModsOfItem(weaponAndMods);
for (let i = 0; i < weaponAndMods.length; i++) for (let i = 0; i < weaponAndMods.length; i++)
{ {
const mod = weaponAndMods[i]; const mod = weaponAndMods[i];
@ -676,42 +676,48 @@ export class FenceService
/** /**
* Remove parts of a weapon prior to being listed on flea * Remove parts of a weapon prior to being listed on flea
* @param weaponAndMods Weapon to remove parts from * @param itemAndMods Weapon to remove parts from
*/ */
protected removeRandomPartsOfWeapon(weaponAndMods: Item[]): void protected removeRandomModsOfItem(itemAndMods: Item[]): void
{ {
// Items to be removed from inventory // Items to be removed from inventory
const toDelete: string[] = []; const toDelete: string[] = [];
// Loop over insurance items, find items to delete from player inventory // Find mods to remove from item that could've been scavenged by other players in-raid
for (const weaponMod of weaponAndMods) for (const itemMod of itemAndMods)
{ {
if (this.presetModItemWillBeRemoved(weaponMod, toDelete)) if (this.presetModItemWillBeRemoved(itemMod, toDelete))
{ {
// Skip if not an item // Skip if not an item
const itemDbDetails = this.itemHelper.getItem(weaponMod._tpl); const itemDbDetails = this.itemHelper.getItem(itemMod._tpl);
if (!itemDbDetails[0]) if (!itemDbDetails[0])
{ {
continue; continue;
} }
// Is a mod and can't be edited in-raid // Is a mod and can't be edited in-raid
if (weaponMod.slotId !== "hideout" && !itemDbDetails[1]._props.RaidModdable) if (itemMod.slotId !== "hideout" && !itemDbDetails[1]._props.RaidModdable)
{
continue;
}
const slotDetails = itemDbDetails[1]._props.Slots.find(slot => slot._name === itemMod.slotId);
if (slotDetails?._required)
{ {
continue; continue;
} }
// Remove item and its sub-items to prevent orphans // Remove item and its sub-items to prevent orphans
toDelete.push(...this.itemHelper.findAndReturnChildrenByItems(weaponAndMods, weaponMod._id)); toDelete.push(...this.itemHelper.findAndReturnChildrenByItems(itemAndMods, itemMod._id));
} }
} }
// Reverse loop and remove items // Reverse loop and remove items
for (let index = weaponAndMods.length - 1; index >= 0; --index) for (let index = itemAndMods.length - 1; index >= 0; --index)
{ {
if (toDelete.includes(weaponAndMods[index]._id)) if (toDelete.includes(itemAndMods[index]._id))
{ {
weaponAndMods.splice(index, 1); itemAndMods.splice(index, 1);
} }
} }
} }

View File

@ -302,7 +302,7 @@ export class InsuranceService
if (itemClientInsuranceData || itemIsSoftInsert) if (itemClientInsuranceData || itemIsSoftInsert)
{ {
// Get baseline item to return, clone pre raid item // Get baseline item to return, clone pre-raid item
const itemToReturn: Item = this.jsonUtil.clone(preRaidItem); const itemToReturn: Item = this.jsonUtil.clone(preRaidItem);
// Add upd if it doesnt exist // Add upd if it doesnt exist
@ -311,7 +311,7 @@ export class InsuranceService
itemToReturn.upd = {}; itemToReturn.upd = {};
} }
// Check for slotid values that need to be updated and adjust // Check for slotId values that need to be updated and adjust
this.updateSlotIdValue(pmcData.Inventory.equipment, itemToReturn); this.updateSlotIdValue(pmcData.Inventory.equipment, itemToReturn);
// Remove location property // Remove location property