Formatting for generator classes.
This commit is contained in:
parent
320c8b7d48
commit
d3e5418fc8
@ -46,12 +46,12 @@ export class BotEquipmentModGenerator
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check mods are compatible and add to array
|
||||
* @param equipment Equipment item to add mods to
|
||||
@ -63,7 +63,15 @@ export class BotEquipmentModGenerator
|
||||
* @param forceSpawn should this mod be forced to spawn
|
||||
* @returns Item + compatible mods as an array
|
||||
*/
|
||||
public generateModsForEquipment(equipment: Item[], modPool: Mods, parentId: string, parentTemplate: ITemplateItem, modSpawnChances: ModsChances, botRole: string, forceSpawn = false): Item[]
|
||||
public generateModsForEquipment(
|
||||
equipment: Item[],
|
||||
modPool: Mods,
|
||||
parentId: string,
|
||||
parentTemplate: ITemplateItem,
|
||||
modSpawnChances: ModsChances,
|
||||
botRole: string,
|
||||
forceSpawn = false,
|
||||
): Item[]
|
||||
{
|
||||
const compatibleModsPool = modPool[parentTemplate._id];
|
||||
|
||||
@ -73,7 +81,13 @@ export class BotEquipmentModGenerator
|
||||
const itemSlot = this.getModItemSlot(modSlot, parentTemplate);
|
||||
if (!itemSlot)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("bot-mod_slot_missing_from_item", {modSlot: modSlot, parentId: parentTemplate._id, parentName: parentTemplate._name}));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-mod_slot_missing_from_item", {
|
||||
modSlot: modSlot,
|
||||
parentId: parentTemplate._id,
|
||||
parentName: parentTemplate._name,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -83,27 +97,33 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
|
||||
// Ensure submods for nvgs all spawn together
|
||||
forceSpawn = (modSlot === "mod_nvg")
|
||||
? true
|
||||
: false;
|
||||
forceSpawn = (modSlot === "mod_nvg") ?
|
||||
true :
|
||||
false;
|
||||
|
||||
let modTpl: string;
|
||||
let found = false;
|
||||
|
||||
|
||||
// Find random mod and check its compatible
|
||||
const exhaustableModPool = new ExhaustableArray(compatibleModsPool[modSlot], this.randomUtil, this.jsonUtil);
|
||||
const exhaustableModPool = new ExhaustableArray(
|
||||
compatibleModsPool[modSlot],
|
||||
this.randomUtil,
|
||||
this.jsonUtil,
|
||||
);
|
||||
while (exhaustableModPool.hasValues())
|
||||
{
|
||||
modTpl = exhaustableModPool.getRandomValue();
|
||||
if (!this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(equipment, modTpl, modSlot).incompatible)
|
||||
if (
|
||||
!this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(equipment, modTpl, modSlot).incompatible
|
||||
)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Combatible item not found but slot REQUIRES item, get random item from db
|
||||
const parentSlot = parentTemplate._props.Slots.find(i => i._name === modSlot);
|
||||
// Compatible item not found but slot REQUIRES item, get random item from db
|
||||
const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot);
|
||||
if (!found && parentSlot !== undefined && parentSlot._required)
|
||||
{
|
||||
modTpl = this.getModTplFromItemDb(modTpl, parentSlot, modSlot, equipment);
|
||||
@ -113,7 +133,7 @@ export class BotEquipmentModGenerator
|
||||
// Compatible item not found + not required
|
||||
if (!found && parentSlot !== undefined && !parentSlot._required)
|
||||
{
|
||||
// Dont add item
|
||||
// Don't add item
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -128,8 +148,16 @@ export class BotEquipmentModGenerator
|
||||
|
||||
if (Object.keys(modPool).includes(modTpl))
|
||||
{
|
||||
// Call self recursivly
|
||||
this.generateModsForEquipment(equipment, modPool, modId, modTemplate[1], modSpawnChances, botRole, forceSpawn);
|
||||
// Call self recursively
|
||||
this.generateModsForEquipment(
|
||||
equipment,
|
||||
modPool,
|
||||
modId,
|
||||
modTemplate[1],
|
||||
modSpawnChances,
|
||||
botRole,
|
||||
forceSpawn,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,8 +174,8 @@ export class BotEquipmentModGenerator
|
||||
* @param modSpawnChances Mod spawn chances
|
||||
* @param ammoTpl Ammo tpl to use when generating magazines/cartridges
|
||||
* @param botRole Role of bot weapon is generated for
|
||||
* @param botLevel lvel of the bot weapon is being generated for
|
||||
* @param modLimits limits placed on certian mod types per gun
|
||||
* @param botLevel Level of the bot weapon is being generated for
|
||||
* @param modLimits limits placed on certain mod types per gun
|
||||
* @param botEquipmentRole role of bot when accessing bot.json equipment config settings
|
||||
* @returns Weapon + mods array
|
||||
*/
|
||||
@ -162,7 +190,8 @@ export class BotEquipmentModGenerator
|
||||
botRole: string,
|
||||
botLevel: number,
|
||||
modLimits: BotModLimits,
|
||||
botEquipmentRole: string): Item[]
|
||||
botEquipmentRole: string,
|
||||
): Item[]
|
||||
{
|
||||
const pmcProfile = this.profileHelper.getPmcProfile(sessionId);
|
||||
|
||||
@ -171,17 +200,27 @@ export class BotEquipmentModGenerator
|
||||
|
||||
// Null guard against bad input weapon
|
||||
// biome-ignore lint/complexity/useSimplifiedLogicExpression: <explanation>
|
||||
if (!parentTemplate._props.Slots.length
|
||||
&& !parentTemplate._props.Cartridges?.length
|
||||
&& !parentTemplate._props.Chambers?.length)
|
||||
if (
|
||||
!parentTemplate._props.Slots.length &&
|
||||
!parentTemplate._props.Cartridges?.length &&
|
||||
!parentTemplate._props.Chambers?.length
|
||||
)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("bot-unable_to_add_mods_to_weapon_missing_ammo_slot", {weaponName: parentTemplate._name, weaponId: parentTemplate._id}));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-unable_to_add_mods_to_weapon_missing_ammo_slot", {
|
||||
weaponName: parentTemplate._name,
|
||||
weaponId: parentTemplate._id,
|
||||
}),
|
||||
);
|
||||
|
||||
return weapon;
|
||||
}
|
||||
|
||||
const botEquipConfig = this.botConfig.equipment[botEquipmentRole];
|
||||
const botEquipBlacklist = this.botEquipmentFilterService.getBotEquipmentBlacklist(botEquipmentRole, pmcProfile.Info.Level);
|
||||
const botEquipBlacklist = this.botEquipmentFilterService.getBotEquipmentBlacklist(
|
||||
botEquipmentRole,
|
||||
pmcProfile.Info.Level,
|
||||
);
|
||||
const botWeaponSightWhitelist = this.botEquipmentFilterService.getBotWeaponSightWhitelist(botEquipmentRole);
|
||||
const randomisationSettings = this.botHelper.getBotRandomizationDetails(botLevel, botEquipConfig);
|
||||
|
||||
@ -193,7 +232,14 @@ export class BotEquipmentModGenerator
|
||||
const modsParentSlot = this.getModItemSlot(modSlot, parentTemplate);
|
||||
if (!modsParentSlot)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("bot-weapon_missing_mod_slot", {modSlot: modSlot, weaponId: parentTemplate._id, weaponName: parentTemplate._name, botRole: botRole}));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-weapon_missing_mod_slot", {
|
||||
modSlot: modSlot,
|
||||
weaponId: parentTemplate._id,
|
||||
weaponName: parentTemplate._name,
|
||||
botRole: botRole,
|
||||
}),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -205,10 +251,19 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
|
||||
const isRandomisableSlot = randomisationSettings?.randomisedWeaponModSlots?.includes(modSlot) ?? false;
|
||||
const modToAdd = this.chooseModToPutIntoSlot(modSlot, isRandomisableSlot, botWeaponSightWhitelist, botEquipBlacklist, compatibleModsPool, weapon, ammoTpl, parentTemplate);
|
||||
const modToAdd = this.chooseModToPutIntoSlot(
|
||||
modSlot,
|
||||
isRandomisableSlot,
|
||||
botWeaponSightWhitelist,
|
||||
botEquipBlacklist,
|
||||
compatibleModsPool,
|
||||
weapon,
|
||||
ammoTpl,
|
||||
parentTemplate,
|
||||
);
|
||||
|
||||
// Compatible mod not found
|
||||
if (!modToAdd || typeof (modToAdd) === "undefined")
|
||||
if (!modToAdd || typeof modToAdd === "undefined")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -220,7 +275,15 @@ export class BotEquipmentModGenerator
|
||||
|
||||
const modToAddTemplate = modToAdd[1];
|
||||
// Skip adding mod to weapon if type limit reached
|
||||
if (this.botWeaponModLimitService.weaponModHasReachedLimit(botEquipmentRole, modToAddTemplate, modLimits, parentTemplate, weapon))
|
||||
if (
|
||||
this.botWeaponModLimitService.weaponModHasReachedLimit(
|
||||
botEquipmentRole,
|
||||
modToAddTemplate,
|
||||
modLimits,
|
||||
parentTemplate,
|
||||
weapon,
|
||||
)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -234,7 +297,7 @@ export class BotEquipmentModGenerator
|
||||
"mod_scope_000",
|
||||
"mod_scope_001",
|
||||
"mod_scope_002",
|
||||
"mod_scope_003"
|
||||
"mod_scope_003",
|
||||
];
|
||||
this.adjustSlotSpawnChances(modSpawnChances, scopeSlots, 100);
|
||||
|
||||
@ -252,7 +315,7 @@ export class BotEquipmentModGenerator
|
||||
const muzzleSlots = [
|
||||
"mod_muzzle",
|
||||
"mod_muzzle_000",
|
||||
"mod_muzzle_001"
|
||||
"mod_muzzle_001",
|
||||
];
|
||||
// Make chance of muzzle devices 95%, nearly certain but not guaranteed
|
||||
this.adjustSlotSpawnChances(modSpawnChances, muzzleSlots, 95);
|
||||
@ -267,7 +330,10 @@ export class BotEquipmentModGenerator
|
||||
|
||||
// Handguard mod can take a sub handguard mod + weapon has no UBGL (takes same slot)
|
||||
// Force spawn chance to be 100% to ensure it gets added
|
||||
if (modSlot === "mod_handguard" && modToAddTemplate._props.Slots.find(x => x._name === "mod_handguard") && !weapon.find(x => x.slotId === "mod_launcher"))
|
||||
if (
|
||||
modSlot === "mod_handguard" && modToAddTemplate._props.Slots.find((x) => x._name === "mod_handguard") &&
|
||||
!weapon.find((x) => x.slotId === "mod_launcher")
|
||||
)
|
||||
{
|
||||
// Needed for handguards with lower
|
||||
modSpawnChances.mod_handguard = 100;
|
||||
@ -275,7 +341,10 @@ export class BotEquipmentModGenerator
|
||||
|
||||
// If stock mod can take a sub stock mod, force spawn chance to be 100% to ensure sub-stock gets added
|
||||
// Or if mod_stock is configured to be forced on
|
||||
if (modSlot === "mod_stock" && (modToAddTemplate._props.Slots.find(x => x._name.includes("mod_stock") || botEquipConfig.forceStock)))
|
||||
if (
|
||||
modSlot === "mod_stock" &&
|
||||
(modToAddTemplate._props.Slots.find((x) => x._name.includes("mod_stock") || botEquipConfig.forceStock))
|
||||
)
|
||||
{
|
||||
// Stock mod can take additional stocks, could be a locking device, force 100% chance
|
||||
const stockSlots = ["mod_stock", "mod_stock_000", "mod_stock_akms"];
|
||||
@ -283,10 +352,12 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
|
||||
const modId = this.hashUtil.generate();
|
||||
weapon.push(this.createModItem(modId, modToAddTemplate._id, weaponParentId, modSlot, modToAddTemplate, botRole));
|
||||
|
||||
weapon.push(
|
||||
this.createModItem(modId, modToAddTemplate._id, weaponParentId, modSlot, modToAddTemplate, botRole),
|
||||
);
|
||||
|
||||
// I first thought we could use the recursive generateModsForItems as previously for cylinder magazines.
|
||||
// However, the recursion doesnt go over the slots of the parent mod but over the modPool which is given by the bot config
|
||||
// However, the recursion doesn't go over the slots of the parent mod but over the modPool which is given by the bot config
|
||||
// where we decided to keep cartridges instead of camoras. And since a CylinderMagazine only has one cartridge entry and
|
||||
// this entry is not to be filled, we need a special handling for the CylinderMagazine
|
||||
const modParentItem = this.databaseServer.getTables().templates.items[modToAddTemplate._parent];
|
||||
@ -312,8 +383,20 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
if (containsModInPool)
|
||||
{
|
||||
// Call self recursivly to add mods to this mod
|
||||
this.generateModsForWeapon(sessionId, weapon, modPool, modId, modToAddTemplate, modSpawnChances, ammoTpl, botRole, botLevel, modLimits, botEquipmentRole);
|
||||
// Call self recursively to add mods to this mod
|
||||
this.generateModsForWeapon(
|
||||
sessionId,
|
||||
weapon,
|
||||
modPool,
|
||||
modId,
|
||||
modToAddTemplate,
|
||||
modSpawnChances,
|
||||
ammoTpl,
|
||||
botRole,
|
||||
botLevel,
|
||||
modLimits,
|
||||
botEquipmentRole,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -328,8 +411,8 @@ export class BotEquipmentModGenerator
|
||||
*/
|
||||
protected modIsFrontOrRearSight(modSlot: string, tpl: string): boolean
|
||||
{
|
||||
if (modSlot === "mod_gas_block" && tpl === "5ae30e795acfc408fb139a0b") // M4A1 front sight with gas block
|
||||
{
|
||||
if (modSlot === "mod_gas_block" && tpl === "5ae30e795acfc408fb139a0b")
|
||||
{ // M4A1 front sight with gas block
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -344,15 +427,27 @@ export class BotEquipmentModGenerator
|
||||
*/
|
||||
protected modSlotCanHoldScope(modSlot: string, modsParentId: string): boolean
|
||||
{
|
||||
return ["mod_scope", "mod_mount", "mod_mount_000", "mod_scope_000", "mod_scope_001", "mod_scope_002", "mod_scope_003"].includes(modSlot.toLowerCase())
|
||||
&& modsParentId === BaseClasses.MOUNT;
|
||||
return [
|
||||
"mod_scope",
|
||||
"mod_mount",
|
||||
"mod_mount_000",
|
||||
"mod_scope_000",
|
||||
"mod_scope_001",
|
||||
"mod_scope_002",
|
||||
"mod_scope_003",
|
||||
].includes(modSlot.toLowerCase()) &&
|
||||
modsParentId === BaseClasses.MOUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mod spawn chances to defined amount
|
||||
* @param modSpawnChances Chance dictionary to update
|
||||
*/
|
||||
protected adjustSlotSpawnChances(modSpawnChances: ModsChances, modSlotsToAdjust: string[], newChancePercent: number): void
|
||||
protected adjustSlotSpawnChances(
|
||||
modSpawnChances: ModsChances,
|
||||
modSlotsToAdjust: string[],
|
||||
newChancePercent: number,
|
||||
): void
|
||||
{
|
||||
if (!modSpawnChances)
|
||||
{
|
||||
@ -418,7 +513,7 @@ export class BotEquipmentModGenerator
|
||||
sortedKeys.push(modRecieverKey);
|
||||
unsortedKeys.splice(unsortedKeys.indexOf(modRecieverKey), 1);
|
||||
}
|
||||
|
||||
|
||||
if (unsortedKeys.includes(modPistolGrip))
|
||||
{
|
||||
sortedKeys.push(modPistolGrip);
|
||||
@ -467,11 +562,11 @@ export class BotEquipmentModGenerator
|
||||
case "patron_in_weapon":
|
||||
case "patron_in_weapon_000":
|
||||
case "patron_in_weapon_001":
|
||||
return parentTemplate._props.Chambers.find(c => c._name.includes(modSlot));
|
||||
return parentTemplate._props.Chambers.find((c) => c._name.includes(modSlot));
|
||||
case "cartridges":
|
||||
return parentTemplate._props.Cartridges.find(c => c._name === modSlot);
|
||||
return parentTemplate._props.Cartridges.find((c) => c._name === modSlot);
|
||||
default:
|
||||
return parentTemplate._props.Slots.find(s => s._name === modSlot);
|
||||
return parentTemplate._props.Slots.find((s) => s._name === modSlot);
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,8 +581,9 @@ export class BotEquipmentModGenerator
|
||||
protected shouldModBeSpawned(itemSlot: Slot, modSlot: string, modSpawnChances: ModsChances): boolean
|
||||
{
|
||||
const modSpawnChance = itemSlot._required || this.getAmmoContainers().includes(modSlot) // Required OR it is ammo
|
||||
? 100
|
||||
: modSpawnChances[modSlot];
|
||||
?
|
||||
100 :
|
||||
modSpawnChances[modSlot];
|
||||
|
||||
if (modSpawnChance === 100)
|
||||
{
|
||||
@ -498,7 +594,6 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param modSlot Slot mod will fit into
|
||||
* @param isRandomisableSlot Will generate a randomised mod pool if true
|
||||
* @param modsParent Parent slot the item will be a part of
|
||||
@ -517,12 +612,13 @@ export class BotEquipmentModGenerator
|
||||
itemModPool: Record<string, string[]>,
|
||||
weapon: Item[],
|
||||
ammoTpl: string,
|
||||
parentTemplate: ITemplateItem): [boolean, ITemplateItem]
|
||||
parentTemplate: ITemplateItem,
|
||||
): [boolean, ITemplateItem]
|
||||
{
|
||||
let modTpl: string;
|
||||
let found = false;
|
||||
const parentSlot = parentTemplate._props.Slots.find(i => i._name === modSlot);
|
||||
|
||||
const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot);
|
||||
|
||||
// It's ammo, use predefined ammo parameter
|
||||
if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine")
|
||||
{
|
||||
@ -539,27 +635,37 @@ export class BotEquipmentModGenerator
|
||||
// Ensure there's a pool of mods to pick from
|
||||
if (!(itemModPool[modSlot] || parentSlot._required))
|
||||
{
|
||||
this.logger.debug(`Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`);
|
||||
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)
|
||||
if (modSlot.includes("mod_scope") && botWeaponSightWhitelist)
|
||||
{
|
||||
// scope pool has more than one scope
|
||||
if (itemModPool[modSlot].length > 1)
|
||||
{
|
||||
itemModPool[modSlot] = this.filterSightsByWeaponType(weapon[0], itemModPool[modSlot], botWeaponSightWhitelist);
|
||||
itemModPool[modSlot] = this.filterSightsByWeaponType(
|
||||
weapon[0],
|
||||
itemModPool[modSlot],
|
||||
botWeaponSightWhitelist,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Pick random mod and check it's compatible
|
||||
const exhaustableModPool = new ExhaustableArray(itemModPool[modSlot], this.randomUtil, this.jsonUtil);
|
||||
let modCompatibilityResult: {incompatible: boolean, reason: string} = {incompatible: false, reason: ""};
|
||||
let modCompatibilityResult: {incompatible: boolean; reason: string;} = {incompatible: false, reason: ""};
|
||||
while (exhaustableModPool.hasValues())
|
||||
{
|
||||
modTpl = exhaustableModPool.getRandomValue();
|
||||
modCompatibilityResult = this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(weapon, modTpl, modSlot);
|
||||
modCompatibilityResult = this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(
|
||||
weapon,
|
||||
modTpl,
|
||||
modSlot,
|
||||
);
|
||||
if (!modCompatibilityResult.incompatible)
|
||||
{
|
||||
found = true;
|
||||
@ -592,7 +698,11 @@ export class BotEquipmentModGenerator
|
||||
{
|
||||
if (parentSlot._required)
|
||||
{
|
||||
this.logger.warning(`Required slot unable to be filled, ${modSlot} on ${parentTemplate._name} ${parentTemplate._id} for weapon: ${weapon[0]._tpl}`);
|
||||
this.logger.warning(
|
||||
`Required slot unable to be filled, ${modSlot} on ${parentTemplate._name} ${parentTemplate._id} for weapon: ${
|
||||
weapon[0]._tpl
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -607,21 +717,27 @@ export class BotEquipmentModGenerator
|
||||
* @param modTpl _tpl
|
||||
* @param parentId parentId
|
||||
* @param modSlot slotId
|
||||
* @param modTemplate Used to add additional properites in the upd object
|
||||
* @param modTemplate Used to add additional properties in the upd object
|
||||
* @returns Item object
|
||||
*/
|
||||
protected createModItem(modId: string, modTpl: string, parentId: string, modSlot: string, modTemplate: ITemplateItem, botRole: string): Item
|
||||
protected createModItem(
|
||||
modId: string,
|
||||
modTpl: string,
|
||||
parentId: string,
|
||||
modSlot: string,
|
||||
modTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): Item
|
||||
{
|
||||
return {
|
||||
_id: modId,
|
||||
_tpl: modTpl,
|
||||
parentId: parentId,
|
||||
slotId: modSlot,
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(modTemplate, botRole)
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(modTemplate, botRole),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of containers that hold ammo
|
||||
* e.g. mod_magazine / patron_in_weapon_000
|
||||
@ -635,14 +751,14 @@ export class BotEquipmentModGenerator
|
||||
/**
|
||||
* Get a random mod from an items compatible mods Filter array
|
||||
* @param modTpl ???? default value to return if nothing found
|
||||
* @param parentSlot item mod will go into, used to get combatible items
|
||||
* @param parentSlot item mod will go into, used to get compatible items
|
||||
* @param modSlot Slot to get mod to fill
|
||||
* @param items items to ensure picked mod is compatible with
|
||||
* @returns item tpl
|
||||
*/
|
||||
protected getModTplFromItemDb(modTpl: string, parentSlot: Slot, modSlot: string, items: Item[]): string
|
||||
{
|
||||
// Find combatible mods and make an array of them
|
||||
// Find compatible mods and make an array of them
|
||||
const allowedItems = parentSlot._props.filters[0].Filter;
|
||||
|
||||
// Find mod item that fits slot from sorted mod array
|
||||
@ -669,12 +785,22 @@ export class BotEquipmentModGenerator
|
||||
* @param parentTemplate template of the mods parent item
|
||||
* @returns true if valid
|
||||
*/
|
||||
protected isModValidForSlot(modToAdd: [boolean, ITemplateItem], itemSlot: Slot, modSlot: string, parentTemplate: ITemplateItem): boolean
|
||||
protected isModValidForSlot(
|
||||
modToAdd: [boolean, ITemplateItem],
|
||||
itemSlot: Slot,
|
||||
modSlot: string,
|
||||
parentTemplate: ITemplateItem,
|
||||
): boolean
|
||||
{
|
||||
// Mod lacks template item
|
||||
if (!modToAdd[1])
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("bot-no_item_template_found_when_adding_mod", {modId: modToAdd[1]._id, modSlot: modSlot}));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-no_item_template_found_when_adding_mod", {
|
||||
modId: modToAdd[1]._id,
|
||||
modSlot: modSlot,
|
||||
}),
|
||||
);
|
||||
this.logger.debug(`Item -> ${parentTemplate._id}; Slot -> ${modSlot}`);
|
||||
|
||||
return false;
|
||||
@ -686,16 +812,31 @@ export class BotEquipmentModGenerator
|
||||
// Slot must be filled, show warning
|
||||
if (itemSlot._required)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("bot-unable_to_add_mod_item_invalid", {itemName: modToAdd[1]._name, modSlot: modSlot, parentItemName: parentTemplate._name}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_add_mod_item_invalid", {
|
||||
itemName: modToAdd[1]._name,
|
||||
modSlot: modSlot,
|
||||
parentItemName: parentTemplate._name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If mod id doesnt exist in slots filter list and mod id doesnt 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)))
|
||||
// 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}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-mod_not_in_slot_filter_list", {
|
||||
modId: modToAdd[1]._id,
|
||||
modSlot: modSlot,
|
||||
parentName: parentTemplate._name,
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -703,26 +844,39 @@ export class BotEquipmentModGenerator
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find mod tpls of a provided type and add to modPool
|
||||
* @param desiredSlotName slot to look up and add we are adding tpls for (e.g mod_scope)
|
||||
* @param modTemplate db object for modItem we get compatible mods from
|
||||
* @param modPool Pool of mods we are adding to
|
||||
*/
|
||||
protected addCompatibleModsForProvidedMod(desiredSlotName: string, modTemplate: ITemplateItem, modPool: Mods, botEquipBlacklist: EquipmentFilterDetails): void
|
||||
protected addCompatibleModsForProvidedMod(
|
||||
desiredSlotName: string,
|
||||
modTemplate: ITemplateItem,
|
||||
modPool: Mods,
|
||||
botEquipBlacklist: EquipmentFilterDetails,
|
||||
): void
|
||||
{
|
||||
const desiredSlotObject = modTemplate._props.Slots.find(x => x._name.includes(desiredSlotName));
|
||||
const desiredSlotObject = modTemplate._props.Slots.find((x) => x._name.includes(desiredSlotName));
|
||||
if (desiredSlotObject)
|
||||
{
|
||||
const supportedSubMods = desiredSlotObject._props.filters[0].Filter;
|
||||
if (supportedSubMods)
|
||||
{
|
||||
// Filter mods
|
||||
let filteredMods = this.filterWeaponModsByBlacklist(supportedSubMods, botEquipBlacklist, desiredSlotName);
|
||||
let filteredMods = this.filterWeaponModsByBlacklist(
|
||||
supportedSubMods,
|
||||
botEquipBlacklist,
|
||||
desiredSlotName,
|
||||
);
|
||||
if (filteredMods.length === 0)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("bot-unable_to_filter_mods_all_blacklisted", {slotName: desiredSlotObject._name, itemName: modTemplate._name}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_filter_mods_all_blacklisted", {
|
||||
slotName: desiredSlotObject._name,
|
||||
itemName: modTemplate._name,
|
||||
}),
|
||||
);
|
||||
filteredMods = supportedSubMods;
|
||||
}
|
||||
|
||||
@ -736,7 +890,6 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the possible items that fit a slot
|
||||
* @param parentItemId item tpl to get compatible items for
|
||||
@ -744,14 +897,22 @@ export class BotEquipmentModGenerator
|
||||
* @param botEquipBlacklist equipment that should not be picked
|
||||
* @returns array of compatible items for that slot
|
||||
*/
|
||||
protected getDynamicModPool(parentItemId: string, modSlot: string, botEquipBlacklist: EquipmentFilterDetails): string[]
|
||||
protected getDynamicModPool(
|
||||
parentItemId: string,
|
||||
modSlot: string,
|
||||
botEquipBlacklist: EquipmentFilterDetails,
|
||||
): string[]
|
||||
{
|
||||
const modsFromDynamicPool = this.jsonUtil.clone(this.botEquipmentModPoolService.getCompatibleModsForWeaponSlot(parentItemId, modSlot));
|
||||
const modsFromDynamicPool = this.jsonUtil.clone(
|
||||
this.botEquipmentModPoolService.getCompatibleModsForWeaponSlot(parentItemId, modSlot),
|
||||
);
|
||||
|
||||
const filteredMods = this.filterWeaponModsByBlacklist(modsFromDynamicPool, botEquipBlacklist, modSlot);
|
||||
if (filteredMods.length === 0)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("bot-unable_to_filter_mod_slot_all_blacklisted", modSlot));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_filter_mod_slot_all_blacklisted", modSlot),
|
||||
);
|
||||
return modsFromDynamicPool;
|
||||
}
|
||||
|
||||
@ -765,18 +926,24 @@ export class BotEquipmentModGenerator
|
||||
* @param modSlot slot mods belong to
|
||||
* @returns Filtered array of mod tpls
|
||||
*/
|
||||
protected filterWeaponModsByBlacklist(allowedMods: string[], botEquipBlacklist: EquipmentFilterDetails, modSlot: string): string[]
|
||||
protected filterWeaponModsByBlacklist(
|
||||
allowedMods: string[],
|
||||
botEquipBlacklist: EquipmentFilterDetails,
|
||||
modSlot: string,
|
||||
): string[]
|
||||
{
|
||||
if (!botEquipBlacklist)
|
||||
{
|
||||
return allowedMods;
|
||||
}
|
||||
|
||||
|
||||
let result: string[] = [];
|
||||
|
||||
// Get item blacklist and mod equipmet blackist as one array
|
||||
const blacklist = this.itemFilterService.getBlacklistedItems().concat(botEquipBlacklist.equipment[modSlot] || []);
|
||||
result = allowedMods.filter(x => !blacklist.includes(x));
|
||||
// Get item blacklist and mod equipment blacklist as one array
|
||||
const blacklist = this.itemFilterService.getBlacklistedItems().concat(
|
||||
botEquipBlacklist.equipment[modSlot] || [],
|
||||
);
|
||||
result = allowedMods.filter((x) => !blacklist.includes(x));
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -796,8 +963,13 @@ export class BotEquipmentModGenerator
|
||||
let itemModPool = modPool[parentTemplate._id];
|
||||
if (!itemModPool)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("bot-unable_to_fill_camora_slot_mod_pool_empty", {weaponId: parentTemplate._id, weaponName: parentTemplate._name}));
|
||||
const camoraSlots = parentTemplate._props.Slots.filter(x => x._name.startsWith("camora"));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_fill_camora_slot_mod_pool_empty", {
|
||||
weaponId: parentTemplate._id,
|
||||
weaponName: parentTemplate._name,
|
||||
}),
|
||||
);
|
||||
const camoraSlots = parentTemplate._props.Slots.filter((x) => x._name.startsWith("camora"));
|
||||
|
||||
// Attempt to generate camora slots for item
|
||||
modPool[parentTemplate._id] = {};
|
||||
@ -818,7 +990,11 @@ export class BotEquipmentModGenerator
|
||||
else if (camoraFirstSlot in itemModPool)
|
||||
{
|
||||
modSlot = camoraFirstSlot;
|
||||
exhaustableModPool = new ExhaustableArray(this.mergeCamoraPoolsTogether(itemModPool), this.randomUtil, this.jsonUtil);
|
||||
exhaustableModPool = new ExhaustableArray(
|
||||
this.mergeCamoraPoolsTogether(itemModPool),
|
||||
this.randomUtil,
|
||||
this.jsonUtil,
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -854,17 +1030,17 @@ export class BotEquipmentModGenerator
|
||||
_id: modId,
|
||||
_tpl: modTpl,
|
||||
parentId: parentId,
|
||||
slotId: modSlotId
|
||||
slotId: modSlotId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a record of camoras and merge the compatable shells into one array
|
||||
* Take a record of camoras and merge the compatible shells into one array
|
||||
* @param camorasWithShells camoras we want to merge into one array
|
||||
* @returns string array of shells fro luitple camora sources
|
||||
* @returns string array of shells for multiple camora sources
|
||||
*/
|
||||
protected mergeCamoraPoolsTogether(camorasWithShells: Record<string, string[]> ): string[]
|
||||
protected mergeCamoraPoolsTogether(camorasWithShells: Record<string, string[]>): string[]
|
||||
{
|
||||
const poolResult: string[] = [];
|
||||
for (const camoraKey in camorasWithShells)
|
||||
@ -889,10 +1065,14 @@ export class BotEquipmentModGenerator
|
||||
* e.g. filter out rifle scopes from SMGs
|
||||
* @param weapon Weapon scopes will be added to
|
||||
* @param scopes Full scope pool
|
||||
* @param botWeaponSightWhitelist Whitelist of scope types by weapon base type
|
||||
* @param botWeaponSightWhitelist Whitelist of scope types by weapon base type
|
||||
* @returns Array of scope tpls that have been filtered to just ones allowed for that weapon type
|
||||
*/
|
||||
protected filterSightsByWeaponType(weapon: Item, scopes: string[], botWeaponSightWhitelist: Record<string, string[]>): string[]
|
||||
protected filterSightsByWeaponType(
|
||||
weapon: Item,
|
||||
scopes: string[],
|
||||
botWeaponSightWhitelist: Record<string, string[]>,
|
||||
): string[]
|
||||
{
|
||||
const weaponDetails = this.itemHelper.getItem(weapon._tpl);
|
||||
|
||||
@ -900,7 +1080,11 @@ export class BotEquipmentModGenerator
|
||||
const whitelistedSightTypes = botWeaponSightWhitelist[weaponDetails[1]._parent];
|
||||
if (!whitelistedSightTypes)
|
||||
{
|
||||
this.logger.debug(`Unable to find whitelist for weapon type: ${weaponDetails[1]._parent} ${weaponDetails[1]._name}, skipping sight filtering`);
|
||||
this.logger.debug(
|
||||
`Unable to find whitelist for weapon type: ${weaponDetails[1]._parent} ${
|
||||
weaponDetails[1]._name
|
||||
}, skipping sight filtering`,
|
||||
);
|
||||
|
||||
return scopes;
|
||||
}
|
||||
@ -920,14 +1104,23 @@ export class BotEquipmentModGenerator
|
||||
// Edge case, what if item is a mount for a scope and not directly a scope?
|
||||
// Check item is mount + has child items
|
||||
const itemDetails = this.itemHelper.getItem(item)[1];
|
||||
if (this.itemHelper.isOfBaseclass(item, BaseClasses.MOUNT) && itemDetails._props.Slots.length > 0 )
|
||||
if (this.itemHelper.isOfBaseclass(item, BaseClasses.MOUNT) && itemDetails._props.Slots.length > 0)
|
||||
{
|
||||
// Check to see if mount has a scope slot (only include primary slot, ignore the rest like the backup sight slots)
|
||||
// Should only find 1 as there's currently no items with a mod_scope AND a mod_scope_000
|
||||
const scopeSlot = itemDetails._props.Slots.filter(x => ["mod_scope", "mod_scope_000"].includes(x._name));
|
||||
const scopeSlot = itemDetails._props.Slots.filter((x) =>
|
||||
["mod_scope", "mod_scope_000"].includes(x._name)
|
||||
);
|
||||
|
||||
// Mods scope slot found must allow ALL whitelisted scope types OR be a mount
|
||||
if (scopeSlot?.every(x => x._props.filters[0].Filter.every(x => this.itemHelper.isOfBaseclasses(x, whitelistedSightTypes) || this.itemHelper.isOfBaseclass(x, BaseClasses.MOUNT))))
|
||||
if (
|
||||
scopeSlot?.every((x) =>
|
||||
x._props.filters[0].Filter.every((x) =>
|
||||
this.itemHelper.isOfBaseclasses(x, whitelistedSightTypes) ||
|
||||
this.itemHelper.isOfBaseclass(x, BaseClasses.MOUNT)
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
// Add mod to allowed list
|
||||
filteredScopesAndMods.push(item);
|
||||
@ -935,14 +1128,16 @@ export class BotEquipmentModGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// No mods added to return list after filtering has occured, send back the original mod list
|
||||
// No mods added to return list after filtering has occurred, send back the original mod list
|
||||
if (!filteredScopesAndMods || filteredScopesAndMods.length === 0)
|
||||
{
|
||||
this.logger.debug(`Scope whitelist too restrictive for: ${weapon._tpl} ${weaponDetails[1]._name}, skipping filter`);
|
||||
this.logger.debug(
|
||||
`Scope whitelist too restrictive for: ${weapon._tpl} ${weaponDetails[1]._name}, skipping filter`,
|
||||
);
|
||||
|
||||
return scopes;
|
||||
}
|
||||
|
||||
return filteredScopesAndMods;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,12 @@ import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
|
||||
import {
|
||||
Common,
|
||||
IBaseJsonSkills, IBaseSkill, IBotBase, Info,
|
||||
Health as PmcHealth,
|
||||
Skills as botSkills
|
||||
IBaseJsonSkills,
|
||||
IBaseSkill,
|
||||
IBotBase,
|
||||
Info,
|
||||
Skills as botSkills,
|
||||
} from "@spt-aki/models/eft/common/tables/IBotBase";
|
||||
import { Appearance, Health, IBotType } from "@spt-aki/models/eft/common/tables/IBotType";
|
||||
import { Item, Upd } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
@ -53,7 +56,7 @@ export class BotGenerator
|
||||
@inject("BotDifficultyHelper") protected botDifficultyHelper: BotDifficultyHelper,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
@ -65,7 +68,7 @@ export class BotGenerator
|
||||
* @param role e.g. assault / pmcbot
|
||||
* @param difficulty easy/normal/hard/impossible
|
||||
* @param botTemplate base bot template to use (e.g. assault/pmcbot)
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
public generatePlayerScav(sessionId: string, role: string, difficulty: string, botTemplate: IBotType): IBotBase
|
||||
{
|
||||
@ -82,7 +85,7 @@ export class BotGenerator
|
||||
botRelativeLevelDeltaMax: 0,
|
||||
botCountToGenerate: 1,
|
||||
botDifficulty: difficulty,
|
||||
isPlayerScav: true
|
||||
isPlayerScav: true,
|
||||
};
|
||||
|
||||
bot = this.generateBot(sessionId, bot, botTemplate, botGenDetails);
|
||||
@ -93,12 +96,13 @@ export class BotGenerator
|
||||
/**
|
||||
* Create x number of bots of the type/side/difficulty defined in botGenerationDetails
|
||||
* @param sessionId Session id
|
||||
* @param botGenerationDetails details on how to generate bots
|
||||
* @param botGenerationDetails details on how to generate bots
|
||||
* @returns array of bots
|
||||
*/
|
||||
public prepareAndGenerateBots(
|
||||
sessionId: string,
|
||||
botGenerationDetails: BotGenerationDetails): IBotBase[]
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
): IBotBase[]
|
||||
{
|
||||
const output: IBotBase[] = [];
|
||||
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++)
|
||||
@ -108,19 +112,24 @@ export class BotGenerator
|
||||
bot.Info.Settings.Role = botGenerationDetails.role;
|
||||
bot.Info.Side = botGenerationDetails.side;
|
||||
bot.Info.Settings.BotDifficulty = botGenerationDetails.botDifficulty;
|
||||
|
||||
|
||||
// Get raw json data for bot (Cloned)
|
||||
const botJsonTemplate = this.jsonUtil.clone(this.botHelper.getBotTemplate(
|
||||
(botGenerationDetails.isPmc)
|
||||
? bot.Info.Side
|
||||
: botGenerationDetails.role));
|
||||
(botGenerationDetails.isPmc) ?
|
||||
bot.Info.Side :
|
||||
botGenerationDetails.role,
|
||||
));
|
||||
|
||||
bot = this.generateBot(sessionId, bot, botJsonTemplate, botGenerationDetails);
|
||||
|
||||
|
||||
output.push(bot);
|
||||
}
|
||||
|
||||
this.logger.debug(`Generated ${botGenerationDetails.botCountToGenerate} ${output[0].Info.Settings.Role} (${botGenerationDetails.eventRole}) bots`);
|
||||
this.logger.debug(
|
||||
`Generated ${botGenerationDetails.botCountToGenerate} ${
|
||||
output[0].Info.Settings.Role
|
||||
} (${botGenerationDetails.eventRole}) bots`,
|
||||
);
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -142,21 +151,43 @@ export class BotGenerator
|
||||
* @param botGenerationDetails details on how to generate the bot
|
||||
* @returns IBotBase object
|
||||
*/
|
||||
protected generateBot(sessionId: string, bot: IBotBase, botJsonTemplate: IBotType, botGenerationDetails: BotGenerationDetails): IBotBase
|
||||
protected generateBot(
|
||||
sessionId: string,
|
||||
bot: IBotBase,
|
||||
botJsonTemplate: IBotType,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
): IBotBase
|
||||
{
|
||||
const botRole = botGenerationDetails.role.toLowerCase();
|
||||
const botLevel = this.botLevelGenerator.generateBotLevel(botJsonTemplate.experience.level, botGenerationDetails, bot);
|
||||
const botLevel = this.botLevelGenerator.generateBotLevel(
|
||||
botJsonTemplate.experience.level,
|
||||
botGenerationDetails,
|
||||
bot,
|
||||
);
|
||||
|
||||
if (!botGenerationDetails.isPlayerScav)
|
||||
{
|
||||
this.botEquipmentFilterService.filterBotEquipment(sessionId, botJsonTemplate, botLevel.level, botGenerationDetails);
|
||||
this.botEquipmentFilterService.filterBotEquipment(
|
||||
sessionId,
|
||||
botJsonTemplate,
|
||||
botLevel.level,
|
||||
botGenerationDetails,
|
||||
);
|
||||
}
|
||||
|
||||
bot.Info.Nickname = this.generateBotNickname(botJsonTemplate, botGenerationDetails.isPlayerScav, botRole, sessionId);
|
||||
bot.Info.Nickname = this.generateBotNickname(
|
||||
botJsonTemplate,
|
||||
botGenerationDetails.isPlayerScav,
|
||||
botRole,
|
||||
sessionId,
|
||||
);
|
||||
|
||||
if (!this.seasonalEventService.christmasEventEnabled())
|
||||
{
|
||||
this.seasonalEventService.removeChristmasItemsFromBotInventory(botJsonTemplate.inventory, botGenerationDetails.role);
|
||||
this.seasonalEventService.removeChristmasItemsFromBotInventory(
|
||||
botJsonTemplate.inventory,
|
||||
botGenerationDetails.role,
|
||||
);
|
||||
}
|
||||
|
||||
// Remove hideout data if bot is not a PMC or pscav
|
||||
@ -167,7 +198,10 @@ export class BotGenerator
|
||||
|
||||
bot.Info.Experience = botLevel.exp;
|
||||
bot.Info.Level = botLevel.level;
|
||||
bot.Info.Settings.Experience = this.randomUtil.getInt(botJsonTemplate.experience.reward.min, botJsonTemplate.experience.reward.max);
|
||||
bot.Info.Settings.Experience = this.randomUtil.getInt(
|
||||
botJsonTemplate.experience.reward.min,
|
||||
botJsonTemplate.experience.reward.max,
|
||||
);
|
||||
bot.Info.Settings.StandingForKill = botJsonTemplate.experience.standingForKill;
|
||||
bot.Info.Voice = this.randomUtil.getArrayValue(botJsonTemplate.appearance.voice);
|
||||
bot.Health = this.generateHealth(botJsonTemplate.health, bot.Info.Side === "Savage");
|
||||
@ -175,7 +209,13 @@ export class BotGenerator
|
||||
|
||||
this.setBotAppearance(bot, botJsonTemplate.appearance, botGenerationDetails);
|
||||
|
||||
bot.Inventory = this.botInventoryGenerator.generateInventory(sessionId, botJsonTemplate, botRole, botGenerationDetails.isPmc, botLevel.level);
|
||||
bot.Inventory = this.botInventoryGenerator.generateInventory(
|
||||
sessionId,
|
||||
botJsonTemplate,
|
||||
botRole,
|
||||
botGenerationDetails.isPmc,
|
||||
botLevel.level,
|
||||
);
|
||||
|
||||
if (this.botHelper.isBotPmc(botRole))
|
||||
{
|
||||
@ -205,7 +245,6 @@ export class BotGenerator
|
||||
* @param appearance Appearance settings to choose from
|
||||
* @param botGenerationDetails Generation details
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected setBotAppearance(bot: IBotBase, appearance: Appearance, botGenerationDetails: BotGenerationDetails): void
|
||||
{
|
||||
bot.Customization.Head = this.randomUtil.getArrayValue(appearance.head);
|
||||
@ -216,17 +255,24 @@ export class BotGenerator
|
||||
|
||||
/**
|
||||
* Create a bot nickname
|
||||
* @param botJsonTemplate x.json from database
|
||||
* @param botJsonTemplate x.json from database
|
||||
* @param isPlayerScav Will bot be player scav
|
||||
* @param botRole role of bot e.g. assault
|
||||
* @returns Nickname for bot
|
||||
*/
|
||||
protected generateBotNickname(botJsonTemplate: IBotType, isPlayerScav: boolean, botRole: string, sessionId: string): string
|
||||
protected generateBotNickname(
|
||||
botJsonTemplate: IBotType,
|
||||
isPlayerScav: boolean,
|
||||
botRole: string,
|
||||
sessionId: string,
|
||||
): string
|
||||
{
|
||||
let name = `${this.randomUtil.getArrayValue(botJsonTemplate.firstName)} ${this.randomUtil.getArrayValue(botJsonTemplate.lastName) || ""}`;
|
||||
let name = `${this.randomUtil.getArrayValue(botJsonTemplate.firstName)} ${
|
||||
this.randomUtil.getArrayValue(botJsonTemplate.lastName) || ""
|
||||
}`;
|
||||
name = name.trim();
|
||||
const playerProfile = this.profileHelper.getPmcProfile(sessionId);
|
||||
|
||||
|
||||
// Simulate bot looking like a Player scav with the pmc name in brackets
|
||||
if (botRole === "assault" && this.randomUtil.getChance100(this.botConfig.chanceAssaultScavHasPlayerScavName))
|
||||
{
|
||||
@ -237,7 +283,7 @@ export class BotGenerator
|
||||
|
||||
const pmcNames = [
|
||||
...this.databaseServer.getTables().bots.types["usec"].firstName,
|
||||
...this.databaseServer.getTables().bots.types["bear"].firstName
|
||||
...this.databaseServer.getTables().bots.types["bear"].firstName,
|
||||
];
|
||||
|
||||
return `${name} (${this.randomUtil.getArrayValue(pmcNames)})`;
|
||||
@ -253,7 +299,6 @@ export class BotGenerator
|
||||
{
|
||||
if (this.randomUtil.getChance100(this.pmcConfig.addPrefixToSameNamePMCAsPlayerChance))
|
||||
{
|
||||
|
||||
const prefix = this.localisationService.getRandomTextThatMatchesPartialKey("pmc-name_prefix_");
|
||||
name = `${prefix} ${name}`;
|
||||
}
|
||||
@ -268,7 +313,10 @@ export class BotGenerator
|
||||
*/
|
||||
protected logPmcGeneratedCount(output: IBotBase[]): void
|
||||
{
|
||||
const pmcCount = output.reduce((acc, cur) => cur.Info.Side === "Bear" || cur.Info.Side === "Usec" ? ++acc : acc, 0);
|
||||
const pmcCount = output.reduce(
|
||||
(acc, cur) => cur.Info.Side === "Bear" || cur.Info.Side === "Usec" ? ++acc : acc,
|
||||
0,
|
||||
);
|
||||
this.logger.debug(`Generated ${output.length} total bots. Replaced ${pmcCount} with PMCs`);
|
||||
}
|
||||
|
||||
@ -280,68 +328,68 @@ export class BotGenerator
|
||||
*/
|
||||
protected generateHealth(healthObj: Health, playerScav = false): PmcHealth
|
||||
{
|
||||
const bodyParts = (playerScav)
|
||||
? healthObj.BodyParts[0]
|
||||
: this.randomUtil.getArrayValue(healthObj.BodyParts);
|
||||
const bodyParts = playerScav ?
|
||||
healthObj.BodyParts[0] :
|
||||
this.randomUtil.getArrayValue(healthObj.BodyParts);
|
||||
|
||||
const newHealth: PmcHealth = {
|
||||
Hydration: {
|
||||
Current: this.randomUtil.getInt(healthObj.Hydration.min, healthObj.Hydration.max),
|
||||
Maximum: healthObj.Hydration.max
|
||||
Maximum: healthObj.Hydration.max,
|
||||
},
|
||||
Energy: {
|
||||
Current: this.randomUtil.getInt(healthObj.Energy.min, healthObj.Energy.max),
|
||||
Maximum: healthObj.Energy.max
|
||||
Maximum: healthObj.Energy.max,
|
||||
},
|
||||
Temperature: {
|
||||
Current: this.randomUtil.getInt(healthObj.Temperature.min, healthObj.Temperature.max),
|
||||
Maximum: healthObj.Temperature.max
|
||||
Maximum: healthObj.Temperature.max,
|
||||
},
|
||||
BodyParts: {
|
||||
Head: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.Head.min, bodyParts.Head.max),
|
||||
Maximum: Math.round(bodyParts.Head.max)
|
||||
}
|
||||
Maximum: Math.round(bodyParts.Head.max),
|
||||
},
|
||||
},
|
||||
Chest: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.Chest.min, bodyParts.Chest.max),
|
||||
Maximum: Math.round(bodyParts.Chest.max)
|
||||
}
|
||||
Maximum: Math.round(bodyParts.Chest.max),
|
||||
},
|
||||
},
|
||||
Stomach: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.Stomach.min, bodyParts.Stomach.max),
|
||||
Maximum: Math.round(bodyParts.Stomach.max)
|
||||
}
|
||||
Maximum: Math.round(bodyParts.Stomach.max),
|
||||
},
|
||||
},
|
||||
LeftArm: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.LeftArm.min, bodyParts.LeftArm.max),
|
||||
Maximum: Math.round(bodyParts.LeftArm.max)
|
||||
}
|
||||
Maximum: Math.round(bodyParts.LeftArm.max),
|
||||
},
|
||||
},
|
||||
RightArm: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.RightArm.min, bodyParts.RightArm.max),
|
||||
Maximum: Math.round(bodyParts.RightArm.max)
|
||||
}
|
||||
Maximum: Math.round(bodyParts.RightArm.max),
|
||||
},
|
||||
},
|
||||
LeftLeg: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.LeftLeg.min, bodyParts.LeftLeg.max),
|
||||
Maximum: Math.round(bodyParts.LeftLeg.max)
|
||||
}
|
||||
Maximum: Math.round(bodyParts.LeftLeg.max),
|
||||
},
|
||||
},
|
||||
RightLeg: {
|
||||
Health: {
|
||||
Current: this.randomUtil.getInt(bodyParts.RightLeg.min, bodyParts.RightLeg.max),
|
||||
Maximum: Math.round(bodyParts.RightLeg.max)
|
||||
}
|
||||
}
|
||||
Maximum: Math.round(bodyParts.RightLeg.max),
|
||||
},
|
||||
},
|
||||
},
|
||||
UpdateTime: this.timeUtil.getTimestamp()
|
||||
UpdateTime: this.timeUtil.getTimestamp(),
|
||||
};
|
||||
|
||||
return newHealth;
|
||||
@ -350,14 +398,14 @@ export class BotGenerator
|
||||
/**
|
||||
* Get a bots skills with randomsied progress value between the min and max values
|
||||
* @param botSkills Skills that should have their progress value randomised
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
protected generateSkills(botSkills: IBaseJsonSkills): botSkills
|
||||
{
|
||||
const skillsToReturn: botSkills = {
|
||||
Common: this.getSkillsWithRandomisedProgressValue(botSkills.Common, true),
|
||||
Mastering: this.getSkillsWithRandomisedProgressValue(botSkills.Mastering, false),
|
||||
Points: 0
|
||||
Points: 0,
|
||||
};
|
||||
|
||||
return skillsToReturn;
|
||||
@ -369,7 +417,10 @@ export class BotGenerator
|
||||
* @param isCommonSkills Are the skills 'common' skills
|
||||
* @returns Skills with randomised progress values as an array
|
||||
*/
|
||||
protected getSkillsWithRandomisedProgressValue(skills: Record<string, IBaseSkill>, isCommonSkills: boolean): IBaseSkill[]
|
||||
protected getSkillsWithRandomisedProgressValue(
|
||||
skills: Record<string, IBaseSkill>,
|
||||
isCommonSkills: boolean,
|
||||
): IBaseSkill[]
|
||||
{
|
||||
if (Object.keys(skills ?? []).length === 0)
|
||||
{
|
||||
@ -384,22 +435,22 @@ export class BotGenerator
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// All skills have id and progress props
|
||||
const skillToAdd: IBaseSkill = {
|
||||
Id: skillKey,
|
||||
Progress: this.randomUtil.getInt(skill.min, skill.max)
|
||||
Progress: this.randomUtil.getInt(skill.min, skill.max),
|
||||
};
|
||||
|
||||
|
||||
// Common skills have additional props
|
||||
if (isCommonSkills)
|
||||
{
|
||||
(skillToAdd as Common).PointsEarnedDuringSession = 0;
|
||||
(skillToAdd as Common).LastAccess = 0;
|
||||
}
|
||||
|
||||
|
||||
return skillToAdd;
|
||||
}).filter(x => x !== null);
|
||||
}).filter((x) => x !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -422,7 +473,7 @@ export class BotGenerator
|
||||
const defaultInventory = "55d7217a4bdc2d86028b456d";
|
||||
const itemsByParentHash: Record<string, Item[]> = {};
|
||||
const inventoryItemHash: Record<string, Item> = {};
|
||||
|
||||
|
||||
// Generate inventoryItem list
|
||||
let inventoryId = "";
|
||||
for (const item of profile.Inventory.items)
|
||||
@ -482,7 +533,9 @@ export class BotGenerator
|
||||
}
|
||||
|
||||
botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.pmcConfig.gameVersionWeight);
|
||||
botInfo.MemberCategory = Number.parseInt(this.weightedRandomHelper.getWeightedValue(this.pmcConfig.accountTypeWeight));
|
||||
botInfo.MemberCategory = Number.parseInt(
|
||||
this.weightedRandomHelper.getWeightedValue(this.pmcConfig.accountTypeWeight),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -505,8 +558,8 @@ export class BotGenerator
|
||||
KillerAccountId: "Unknown",
|
||||
KillerProfileId: "Unknown",
|
||||
KillerName: "Unknown",
|
||||
WeaponName: "Unknown"
|
||||
}
|
||||
WeaponName: "Unknown",
|
||||
},
|
||||
};
|
||||
|
||||
const inventoryItem: Item = {
|
||||
@ -515,11 +568,11 @@ export class BotGenerator
|
||||
parentId: bot.Inventory.equipment,
|
||||
slotId: "Dogtag",
|
||||
location: undefined,
|
||||
upd: upd
|
||||
upd: upd,
|
||||
};
|
||||
|
||||
bot.Inventory.items.push(inventoryItem);
|
||||
|
||||
return bot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ export class BotInventoryGenerator
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService,
|
||||
@inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
@ -54,7 +54,13 @@ export class BotInventoryGenerator
|
||||
* @param botLevel Level of bot being generated
|
||||
* @returns PmcInventory object with equipment/weapons/loot
|
||||
*/
|
||||
public generateInventory(sessionId: string, botJsonTemplate: IBotType, botRole: string, isPmc: boolean, botLevel: number): PmcInventory
|
||||
public generateInventory(
|
||||
sessionId: string,
|
||||
botJsonTemplate: IBotType,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
): PmcInventory
|
||||
{
|
||||
const templateInventory = botJsonTemplate.inventory;
|
||||
const equipmentChances = botJsonTemplate.chances;
|
||||
@ -66,7 +72,16 @@ export class BotInventoryGenerator
|
||||
this.generateAndAddEquipmentToBot(templateInventory, equipmentChances, botRole, botInventory, botLevel);
|
||||
|
||||
// Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed
|
||||
this.generateAndAddWeaponsToBot(templateInventory, equipmentChances, sessionId, botInventory, botRole, isPmc, itemGenerationLimitsMinMax, botLevel);
|
||||
this.generateAndAddWeaponsToBot(
|
||||
templateInventory,
|
||||
equipmentChances,
|
||||
sessionId,
|
||||
botInventory,
|
||||
botRole,
|
||||
isPmc,
|
||||
itemGenerationLimitsMinMax,
|
||||
botLevel,
|
||||
);
|
||||
|
||||
// Pick loot and add to bots containers (rig/backpack/pockets/secure)
|
||||
this.botLootGenerator.generateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel);
|
||||
@ -99,24 +114,24 @@ export class BotInventoryGenerator
|
||||
items: [
|
||||
{
|
||||
_id: equipmentId,
|
||||
_tpl: equipmentTpl
|
||||
_tpl: equipmentTpl,
|
||||
},
|
||||
{
|
||||
_id: stashId,
|
||||
_tpl: stashTpl
|
||||
_tpl: stashTpl,
|
||||
},
|
||||
{
|
||||
_id: questRaidItemsId,
|
||||
_tpl: questRaidItemsTpl
|
||||
_tpl: questRaidItemsTpl,
|
||||
},
|
||||
{
|
||||
_id: questStashItemsId,
|
||||
_tpl: questStashItemsTpl
|
||||
_tpl: questStashItemsTpl,
|
||||
},
|
||||
{
|
||||
_id: sortingTableId,
|
||||
_tpl: sortingTableTpl
|
||||
}
|
||||
_tpl: sortingTableTpl,
|
||||
},
|
||||
],
|
||||
equipment: equipmentId,
|
||||
stash: stashId,
|
||||
@ -124,7 +139,7 @@ export class BotInventoryGenerator
|
||||
questStashItems: questStashItemsId,
|
||||
sortingTable: sortingTableId,
|
||||
hideoutAreaStashes: {},
|
||||
fastPanel: {}
|
||||
fastPanel: {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -136,7 +151,13 @@ export class BotInventoryGenerator
|
||||
* @param botInventory Inventory to add equipment to
|
||||
* @param botLevel Level of bot
|
||||
*/
|
||||
protected generateAndAddEquipmentToBot(templateInventory: Inventory, equipmentChances: Chances, botRole: string, botInventory: PmcInventory, botLevel: number): void
|
||||
protected generateAndAddEquipmentToBot(
|
||||
templateInventory: Inventory,
|
||||
equipmentChances: Chances,
|
||||
botRole: string,
|
||||
botInventory: PmcInventory,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
// These will be handled later
|
||||
const excludedSlots: string[] = [
|
||||
@ -147,7 +168,7 @@ export class BotInventoryGenerator
|
||||
EquipmentSlots.TACTICAL_VEST,
|
||||
EquipmentSlots.FACE_COVER,
|
||||
EquipmentSlots.HEADWEAR,
|
||||
EquipmentSlots.EARPIECE
|
||||
EquipmentSlots.EARPIECE,
|
||||
];
|
||||
|
||||
const botEquipConfig = this.botConfig.equipment[this.botGeneratorHelper.getBotEquipmentRole(botRole)];
|
||||
@ -155,21 +176,69 @@ export class BotInventoryGenerator
|
||||
|
||||
for (const equipmentSlot in templateInventory.equipment)
|
||||
{
|
||||
// Weapons have special generation and will be generated seperately; ArmorVest should be generated after TactivalVest
|
||||
// Weapons have special generation and will be generated separately; ArmorVest should be generated after TactivalVest
|
||||
if (excludedSlots.includes(equipmentSlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.generateEquipment(equipmentSlot, templateInventory.equipment[equipmentSlot], templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails);
|
||||
this.generateEquipment(
|
||||
equipmentSlot,
|
||||
templateInventory.equipment[equipmentSlot],
|
||||
templateInventory.mods,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
randomistionDetails,
|
||||
);
|
||||
}
|
||||
|
||||
// Generate below in specific order
|
||||
this.generateEquipment(EquipmentSlots.FACE_COVER, templateInventory.equipment.FaceCover, templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails);
|
||||
this.generateEquipment(EquipmentSlots.HEADWEAR, templateInventory.equipment.Headwear, templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails);
|
||||
this.generateEquipment(EquipmentSlots.EARPIECE, templateInventory.equipment.Earpiece, templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails);
|
||||
this.generateEquipment(EquipmentSlots.TACTICAL_VEST, templateInventory.equipment.TacticalVest, templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails);
|
||||
this.generateEquipment(EquipmentSlots.ARMOR_VEST, templateInventory.equipment.ArmorVest, templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails);
|
||||
this.generateEquipment(
|
||||
EquipmentSlots.FACE_COVER,
|
||||
templateInventory.equipment.FaceCover,
|
||||
templateInventory.mods,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
randomistionDetails,
|
||||
);
|
||||
this.generateEquipment(
|
||||
EquipmentSlots.HEADWEAR,
|
||||
templateInventory.equipment.Headwear,
|
||||
templateInventory.mods,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
randomistionDetails,
|
||||
);
|
||||
this.generateEquipment(
|
||||
EquipmentSlots.EARPIECE,
|
||||
templateInventory.equipment.Earpiece,
|
||||
templateInventory.mods,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
randomistionDetails,
|
||||
);
|
||||
this.generateEquipment(
|
||||
EquipmentSlots.TACTICAL_VEST,
|
||||
templateInventory.equipment.TacticalVest,
|
||||
templateInventory.mods,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
randomistionDetails,
|
||||
);
|
||||
this.generateEquipment(
|
||||
EquipmentSlots.ARMOR_VEST,
|
||||
templateInventory.equipment.ArmorVest,
|
||||
templateInventory.mods,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
randomistionDetails,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,14 +258,18 @@ export class BotInventoryGenerator
|
||||
spawnChances: Chances,
|
||||
botRole: string,
|
||||
inventory: PmcInventory,
|
||||
randomisationDetails: RandomisationDetails): void
|
||||
randomisationDetails: RandomisationDetails,
|
||||
): void
|
||||
{
|
||||
const spawnChance = ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(equipmentSlot)
|
||||
? 100
|
||||
: spawnChances.equipment[equipmentSlot];
|
||||
const spawnChance =
|
||||
([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(equipmentSlot) ?
|
||||
100 :
|
||||
spawnChances.equipment[equipmentSlot];
|
||||
if (typeof spawnChance === "undefined")
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", equipmentSlot));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", equipmentSlot),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -216,7 +289,13 @@ export class BotInventoryGenerator
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(inventory.items, equipmentItemTpl, equipmentSlot).incompatible)
|
||||
if (
|
||||
this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(
|
||||
inventory.items,
|
||||
equipmentItemTpl,
|
||||
equipmentSlot,
|
||||
).incompatible
|
||||
)
|
||||
{
|
||||
// Bad luck - randomly picked item was not compatible with current gear
|
||||
return;
|
||||
@ -227,19 +306,35 @@ export class BotInventoryGenerator
|
||||
_tpl: equipmentItemTpl,
|
||||
parentId: inventory.equipment,
|
||||
slotId: equipmentSlot,
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], botRole)
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], botRole),
|
||||
};
|
||||
|
||||
// use dynamic mod pool if enabled in config
|
||||
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole);
|
||||
if (this.botConfig.equipment[botEquipmentRole] && randomisationDetails?.randomisedArmorSlots?.includes(equipmentSlot))
|
||||
if (
|
||||
this.botConfig.equipment[botEquipmentRole] &&
|
||||
randomisationDetails?.randomisedArmorSlots?.includes(equipmentSlot)
|
||||
)
|
||||
{
|
||||
modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem(equipmentItemTpl, this.botConfig.equipment[botEquipmentRole].blacklist);
|
||||
modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem(
|
||||
equipmentItemTpl,
|
||||
this.botConfig.equipment[botEquipmentRole].blacklist,
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof(modPool[equipmentItemTpl]) !== "undefined" || Object.keys(modPool[equipmentItemTpl] || {}).length > 0)
|
||||
if (
|
||||
typeof (modPool[equipmentItemTpl]) !== "undefined" ||
|
||||
Object.keys(modPool[equipmentItemTpl] || {}).length > 0
|
||||
)
|
||||
{
|
||||
const items = this.botEquipmentModGenerator.generateModsForEquipment([item], modPool, id, itemTemplate[1], spawnChances.mods, botRole);
|
||||
const items = this.botEquipmentModGenerator.generateModsForEquipment(
|
||||
[item],
|
||||
modPool,
|
||||
id,
|
||||
itemTemplate[1],
|
||||
spawnChances.mods,
|
||||
botRole,
|
||||
);
|
||||
inventory.items.push(...items);
|
||||
}
|
||||
else
|
||||
@ -251,17 +346,20 @@ export class BotInventoryGenerator
|
||||
|
||||
/**
|
||||
* Get all possible mods for item and filter down based on equipment blacklist from bot.json config
|
||||
* @param itemTpl Item mod pool is being retreived and filtered
|
||||
* @param itemTpl Item mod pool is being retrieved and filtered
|
||||
* @param equipmentBlacklist blacklist to filter mod pool with
|
||||
* @returns Filtered pool of mods
|
||||
*/
|
||||
protected getFilteredDynamicModsForItem(itemTpl: string, equipmentBlacklist: EquipmentFilterDetails[]): Record<string, string[]>
|
||||
protected getFilteredDynamicModsForItem(
|
||||
itemTpl: string,
|
||||
equipmentBlacklist: EquipmentFilterDetails[],
|
||||
): Record<string, string[]>
|
||||
{
|
||||
const modPool = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl);
|
||||
for (const modSlot of Object.keys(modPool ?? []))
|
||||
{
|
||||
const blacklistedMods = equipmentBlacklist[0]?.equipment[modSlot] || [];
|
||||
const filteredMods = modPool[modSlot].filter(x => !blacklistedMods.includes(x));
|
||||
const filteredMods = modPool[modSlot].filter((x) => !blacklistedMods.includes(x));
|
||||
|
||||
if (filteredMods.length > 0)
|
||||
{
|
||||
@ -283,7 +381,16 @@ export class BotInventoryGenerator
|
||||
* @param botLevel level of bot having weapon generated
|
||||
* @param itemGenerationLimitsMinMax Limits for items the bot can have
|
||||
*/
|
||||
protected generateAndAddWeaponsToBot(templateInventory: Inventory, equipmentChances: Chances, sessionId: string, botInventory: PmcInventory, botRole: string, isPmc: boolean, itemGenerationLimitsMinMax: Generation, botLevel: number): void
|
||||
protected generateAndAddWeaponsToBot(
|
||||
templateInventory: Inventory,
|
||||
equipmentChances: Chances,
|
||||
sessionId: string,
|
||||
botInventory: PmcInventory,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
itemGenerationLimitsMinMax: Generation,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
const weaponSlotsToFill = this.getDesiredWeaponsForBot(equipmentChances);
|
||||
for (const weaponSlot of weaponSlotsToFill)
|
||||
@ -291,7 +398,17 @@ export class BotInventoryGenerator
|
||||
// Add weapon to bot if true and bot json has something to put into the slot
|
||||
if (weaponSlot.shouldSpawn && Object.keys(templateInventory.equipment[weaponSlot.slot]).length)
|
||||
{
|
||||
this.addWeaponAndMagazinesToInventory(sessionId, weaponSlot, templateInventory, botInventory, equipmentChances, botRole, isPmc, itemGenerationLimitsMinMax, botLevel);
|
||||
this.addWeaponAndMagazinesToInventory(
|
||||
sessionId,
|
||||
weaponSlot,
|
||||
templateInventory,
|
||||
botInventory,
|
||||
equipmentChances,
|
||||
botRole,
|
||||
isPmc,
|
||||
itemGenerationLimitsMinMax,
|
||||
botLevel,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -301,26 +418,27 @@ export class BotInventoryGenerator
|
||||
* @param equipmentChances Chances bot has certain equipment
|
||||
* @returns What slots bot should have weapons generated for
|
||||
*/
|
||||
protected getDesiredWeaponsForBot(equipmentChances: Chances): { slot: EquipmentSlots; shouldSpawn: boolean; }[]
|
||||
protected getDesiredWeaponsForBot(equipmentChances: Chances): {slot: EquipmentSlots; shouldSpawn: boolean;}[]
|
||||
{
|
||||
const shouldSpawnPrimary = this.randomUtil.getChance100(equipmentChances.equipment.FirstPrimaryWeapon);
|
||||
return [
|
||||
{
|
||||
slot: EquipmentSlots.FIRST_PRIMARY_WEAPON,
|
||||
shouldSpawn: shouldSpawnPrimary
|
||||
shouldSpawn: shouldSpawnPrimary,
|
||||
},
|
||||
{
|
||||
slot: EquipmentSlots.SECOND_PRIMARY_WEAPON,
|
||||
shouldSpawn: shouldSpawnPrimary
|
||||
? this.randomUtil.getChance100(equipmentChances.equipment.SecondPrimaryWeapon)
|
||||
: false
|
||||
shouldSpawn: shouldSpawnPrimary ?
|
||||
this.randomUtil.getChance100(equipmentChances.equipment.SecondPrimaryWeapon) :
|
||||
false,
|
||||
},
|
||||
{
|
||||
slot: EquipmentSlots.HOLSTER,
|
||||
shouldSpawn: shouldSpawnPrimary
|
||||
? this.randomUtil.getChance100(equipmentChances.equipment.Holster) // Primary weapon = roll for chance at pistol
|
||||
: true // No primary = force pistol
|
||||
}
|
||||
shouldSpawn: shouldSpawnPrimary ?
|
||||
this.randomUtil.getChance100(equipmentChances.equipment.Holster) // Primary weapon = roll for chance at pistol
|
||||
:
|
||||
true, // No primary = force pistol
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -333,18 +451,19 @@ export class BotInventoryGenerator
|
||||
* @param equipmentChances Chances bot can have equipment equipped
|
||||
* @param botRole assault/pmcBot/bossTagilla etc
|
||||
* @param isPmc Is the bot being generated as a pmc
|
||||
* @param itemGenerationWeights
|
||||
* @param itemGenerationWeights
|
||||
*/
|
||||
protected addWeaponAndMagazinesToInventory(
|
||||
sessionId: string,
|
||||
weaponSlot: { slot: EquipmentSlots; shouldSpawn: boolean; },
|
||||
weaponSlot: {slot: EquipmentSlots; shouldSpawn: boolean;},
|
||||
templateInventory: Inventory,
|
||||
botInventory: PmcInventory,
|
||||
equipmentChances: Chances,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
itemGenerationWeights: Generation,
|
||||
botLevel: number): void
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(
|
||||
sessionId,
|
||||
@ -354,10 +473,16 @@ export class BotInventoryGenerator
|
||||
equipmentChances.mods,
|
||||
botRole,
|
||||
isPmc,
|
||||
botLevel);
|
||||
botLevel,
|
||||
);
|
||||
|
||||
botInventory.items.push(...generatedWeapon.weapon);
|
||||
|
||||
this.botWeaponGenerator.addExtraMagazinesToInventory(generatedWeapon, itemGenerationWeights.items.magazines, botInventory, botRole);
|
||||
this.botWeaponGenerator.addExtraMagazinesToInventory(
|
||||
generatedWeapon,
|
||||
itemGenerationWeights.items.magazines,
|
||||
botInventory,
|
||||
botRole,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ export class BotLevelGenerator
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
)
|
||||
{ }
|
||||
{}
|
||||
|
||||
/**
|
||||
* Return a randomised bot level and exp value
|
||||
@ -26,12 +26,20 @@ export class BotLevelGenerator
|
||||
* @param bot being level is being generated for
|
||||
* @returns IRandomisedBotLevelResult object
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public generateBotLevel(levelDetails: MinMax, botGenerationDetails: BotGenerationDetails, bot: IBotBase): IRandomisedBotLevelResult
|
||||
public generateBotLevel(
|
||||
levelDetails: MinMax,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
bot: IBotBase,
|
||||
): IRandomisedBotLevelResult
|
||||
{
|
||||
const expTable = this.databaseServer.getTables().globals.config.exp.level.exp_table;
|
||||
const highestLevel = this.getHighestRelativeBotLevel(botGenerationDetails.playerLevel, botGenerationDetails.botRelativeLevelDeltaMax, levelDetails, expTable);
|
||||
|
||||
const highestLevel = this.getHighestRelativeBotLevel(
|
||||
botGenerationDetails.playerLevel,
|
||||
botGenerationDetails.botRelativeLevelDeltaMax,
|
||||
levelDetails,
|
||||
expTable,
|
||||
);
|
||||
|
||||
// Get random level based on the exp table.
|
||||
let exp = 0;
|
||||
const level = this.randomUtil.getInt(1, highestLevel);
|
||||
@ -47,16 +55,21 @@ export class BotLevelGenerator
|
||||
exp += this.randomUtil.getInt(0, expTable[level].exp - 1);
|
||||
}
|
||||
|
||||
return { level, exp };
|
||||
return {level, exp};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highest level a bot can be relative to the players level, but no futher than the max size from globals.exp_table
|
||||
* Get the highest level a bot can be relative to the players level, but no further than the max size from globals.exp_table
|
||||
* @param playerLevel Players current level
|
||||
* @param relativeDeltaMax max delta above player level to go
|
||||
* @returns highest level possible for bot
|
||||
*/
|
||||
protected getHighestRelativeBotLevel(playerLevel: number, relativeDeltaMax: number, levelDetails: MinMax, expTable: IExpTable[]): number
|
||||
protected getHighestRelativeBotLevel(
|
||||
playerLevel: number,
|
||||
relativeDeltaMax: number,
|
||||
levelDetails: MinMax,
|
||||
expTable: IExpTable[],
|
||||
): number
|
||||
{
|
||||
const maxPossibleLevel = Math.min(levelDetails.max, expTable.length);
|
||||
|
||||
@ -68,4 +81,4 @@ export class BotLevelGenerator
|
||||
|
||||
return level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export class BotLootGenerator
|
||||
{
|
||||
protected botConfig: IBotConfig;
|
||||
protected pmcConfig: IPmcConfig;
|
||||
|
||||
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@ -44,7 +44,7 @@ export class BotLootGenerator
|
||||
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
|
||||
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
@ -60,15 +60,24 @@ export class BotLootGenerator
|
||||
* @param botInventory Inventory to add loot to
|
||||
* @param botLevel Level of bot
|
||||
*/
|
||||
public generateLoot(sessionId: string, botJsonTemplate: IBotType, isPmc: boolean, botRole: string, botInventory: PmcInventory, botLevel: number): void
|
||||
public generateLoot(
|
||||
sessionId: string,
|
||||
botJsonTemplate: IBotType,
|
||||
isPmc: boolean,
|
||||
botRole: string,
|
||||
botInventory: PmcInventory,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
// Limits on item types to be added as loot
|
||||
const itemCounts = botJsonTemplate.generation.items;
|
||||
|
||||
|
||||
const backpackLootCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.backpackLoot.weights);
|
||||
const pocketLootCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.pocketLoot.weights);
|
||||
const vestLootCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.vestLoot.weights);
|
||||
const specialLootItemCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.specialItems.weights);
|
||||
const specialLootItemCount = this.weightedRandomHelper.getWeightedValue<number>(
|
||||
itemCounts.specialItems.weights,
|
||||
);
|
||||
const healingItemCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.healing.weights);
|
||||
const drugItemCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.drugs.weights);
|
||||
const stimItemCount = this.weightedRandomHelper.getWeightedValue<number>(itemCounts.stims.weights);
|
||||
@ -88,7 +97,8 @@ export class BotLootGenerator
|
||||
containersBotHasAvailable,
|
||||
specialLootItemCount,
|
||||
botInventory,
|
||||
botRole);
|
||||
botRole,
|
||||
);
|
||||
|
||||
// Healing items / Meds
|
||||
this.addLootFromPool(
|
||||
@ -99,7 +109,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
isPmc);
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Drugs
|
||||
this.addLootFromPool(
|
||||
@ -110,7 +121,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
isPmc);
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Stims
|
||||
this.addLootFromPool(
|
||||
@ -121,7 +133,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
true,
|
||||
0,
|
||||
isPmc);
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Grenades
|
||||
this.addLootFromPool(
|
||||
@ -132,8 +145,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
isPmc);
|
||||
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Backpack - generate loot if they have one
|
||||
if (containersBotHasAvailable.includes(EquipmentSlots.BACKPACK))
|
||||
@ -141,7 +154,16 @@ export class BotLootGenerator
|
||||
// Add randomly generated weapon to PMC backpacks
|
||||
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.looseWeaponInBackpackChancePercent))
|
||||
{
|
||||
this.addLooseWeaponsToInventorySlot(sessionId, botInventory, EquipmentSlots.BACKPACK, botJsonTemplate.inventory, botJsonTemplate.chances.mods, botRole, isPmc, botLevel);
|
||||
this.addLooseWeaponsToInventorySlot(
|
||||
sessionId,
|
||||
botInventory,
|
||||
EquipmentSlots.BACKPACK,
|
||||
botJsonTemplate.inventory,
|
||||
botJsonTemplate.chances.mods,
|
||||
botRole,
|
||||
isPmc,
|
||||
botLevel,
|
||||
);
|
||||
}
|
||||
|
||||
this.addLootFromPool(
|
||||
@ -152,9 +174,10 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
true,
|
||||
this.pmcConfig.maxBackpackLootTotalRub,
|
||||
isPmc);
|
||||
isPmc,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// TacticalVest - generate loot if they have one
|
||||
if (containersBotHasAvailable.includes(EquipmentSlots.TACTICAL_VEST))
|
||||
{
|
||||
@ -167,10 +190,10 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
true,
|
||||
this.pmcConfig.maxVestLootTotalRub,
|
||||
isPmc);
|
||||
isPmc,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Pockets
|
||||
this.addLootFromPool(
|
||||
this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.POCKET, botJsonTemplate),
|
||||
@ -180,7 +203,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
true,
|
||||
this.pmcConfig.maxPocketLootTotalRub,
|
||||
isPmc);
|
||||
isPmc,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,13 +216,12 @@ export class BotLootGenerator
|
||||
{
|
||||
const result = [EquipmentSlots.POCKETS];
|
||||
|
||||
if (botInventory.items.find(x => x.slotId === EquipmentSlots.TACTICAL_VEST))
|
||||
if (botInventory.items.find((x) => x.slotId === EquipmentSlots.TACTICAL_VEST))
|
||||
{
|
||||
|
||||
result.push(EquipmentSlots.TACTICAL_VEST);
|
||||
}
|
||||
|
||||
if (botInventory.items.find(x => x.slotId === EquipmentSlots.BACKPACK))
|
||||
if (botInventory.items.find((x) => x.slotId === EquipmentSlots.BACKPACK))
|
||||
{
|
||||
result.push(EquipmentSlots.BACKPACK);
|
||||
}
|
||||
@ -222,7 +245,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
true);
|
||||
true,
|
||||
);
|
||||
|
||||
const surv12 = this.itemHelper.getItem("5d02797c86f774203f38e30a")[1];
|
||||
this.addLootFromPool(
|
||||
@ -233,7 +257,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
true);
|
||||
true,
|
||||
);
|
||||
|
||||
const morphine = this.itemHelper.getItem("544fb3f34bdc2d03748b456a")[1];
|
||||
this.addLootFromPool(
|
||||
@ -244,7 +269,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
true);
|
||||
true,
|
||||
);
|
||||
|
||||
const afak = this.itemHelper.getItem("60098ad7c2240c0fe85c570a")[1];
|
||||
this.addLootFromPool(
|
||||
@ -255,7 +281,8 @@ export class BotLootGenerator
|
||||
botRole,
|
||||
false,
|
||||
0,
|
||||
true);
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -290,14 +317,15 @@ export class BotLootGenerator
|
||||
botRole: string,
|
||||
useLimits = false,
|
||||
totalValueLimitRub = 0,
|
||||
isPmc = false): void
|
||||
isPmc = false,
|
||||
): void
|
||||
{
|
||||
// Loot pool has items
|
||||
if (pool.length)
|
||||
{
|
||||
let currentTotalRub = 0;
|
||||
const itemLimits: Record<string, number> = {};
|
||||
const itemSpawnLimits: Record<string,Record<string, number>> = {};
|
||||
const itemSpawnLimits: Record<string, Record<string, number>> = {};
|
||||
let fitItemIntoContainerAttempts = 0;
|
||||
for (let i = 0; i < totalItemCount; i++)
|
||||
{
|
||||
@ -306,7 +334,7 @@ export class BotLootGenerator
|
||||
const itemsToAdd: Item[] = [{
|
||||
_id: id,
|
||||
_tpl: itemToAddTemplate._id,
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemToAddTemplate, botRole)
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemToAddTemplate, botRole),
|
||||
}];
|
||||
|
||||
if (useLimits)
|
||||
@ -321,11 +349,19 @@ export class BotLootGenerator
|
||||
itemSpawnLimits[botRole] = this.getItemSpawnLimitsForBotType(isPmc, botRole);
|
||||
}
|
||||
|
||||
if (this.itemHasReachedSpawnLimit(itemToAddTemplate, botRole, isPmc, itemLimits, itemSpawnLimits[botRole]))
|
||||
if (
|
||||
this.itemHasReachedSpawnLimit(
|
||||
itemToAddTemplate,
|
||||
botRole,
|
||||
isPmc,
|
||||
itemLimits,
|
||||
itemSpawnLimits[botRole],
|
||||
)
|
||||
)
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill ammo box
|
||||
@ -345,13 +381,21 @@ export class BotLootGenerator
|
||||
}
|
||||
|
||||
// Attempt to add item to container(s)
|
||||
const itemAddedResult = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(equipmentSlots, id, itemToAddTemplate._id, itemsToAdd, inventoryToAddItemsTo);
|
||||
const itemAddedResult = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
||||
equipmentSlots,
|
||||
id,
|
||||
itemToAddTemplate._id,
|
||||
itemsToAdd,
|
||||
inventoryToAddItemsTo,
|
||||
);
|
||||
if (itemAddedResult === ItemAddedResult.NO_SPACE)
|
||||
{
|
||||
fitItemIntoContainerAttempts++;
|
||||
if (fitItemIntoContainerAttempts >= 4)
|
||||
{
|
||||
this.logger.debug(`Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times, skipping`);
|
||||
this.logger.debug(
|
||||
`Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times, skipping`,
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -383,16 +427,48 @@ export class BotLootGenerator
|
||||
* @param botRole bots role .e.g. pmcBot
|
||||
* @param isPmc are we generating for a pmc
|
||||
*/
|
||||
protected addLooseWeaponsToInventorySlot(sessionId: string, botInventory: PmcInventory, equipmentSlot: string, templateInventory: Inventory, modChances: ModsChances, botRole: string, isPmc: boolean, botLevel: number): void
|
||||
protected addLooseWeaponsToInventorySlot(
|
||||
sessionId: string,
|
||||
botInventory: PmcInventory,
|
||||
equipmentSlot: string,
|
||||
templateInventory: Inventory,
|
||||
modChances: ModsChances,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
): void
|
||||
{
|
||||
const chosenWeaponType = this.randomUtil.getArrayValue([EquipmentSlots.FIRST_PRIMARY_WEAPON, EquipmentSlots.FIRST_PRIMARY_WEAPON, EquipmentSlots.FIRST_PRIMARY_WEAPON, EquipmentSlots.HOLSTER]);
|
||||
const randomisedWeaponCount = this.randomUtil.getInt(this.pmcConfig.looseWeaponInBackpackLootMinMax.min, this.pmcConfig.looseWeaponInBackpackLootMinMax.max);
|
||||
const chosenWeaponType = this.randomUtil.getArrayValue([
|
||||
EquipmentSlots.FIRST_PRIMARY_WEAPON,
|
||||
EquipmentSlots.FIRST_PRIMARY_WEAPON,
|
||||
EquipmentSlots.FIRST_PRIMARY_WEAPON,
|
||||
EquipmentSlots.HOLSTER,
|
||||
]);
|
||||
const randomisedWeaponCount = this.randomUtil.getInt(
|
||||
this.pmcConfig.looseWeaponInBackpackLootMinMax.min,
|
||||
this.pmcConfig.looseWeaponInBackpackLootMinMax.max,
|
||||
);
|
||||
if (randomisedWeaponCount > 0)
|
||||
{
|
||||
for (let i = 0; i < randomisedWeaponCount; i++)
|
||||
{
|
||||
const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(sessionId, chosenWeaponType, templateInventory, botInventory.equipment, modChances, botRole, isPmc, botLevel);
|
||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot([equipmentSlot], generatedWeapon.weapon[0]._id, generatedWeapon.weapon[0]._tpl, [...generatedWeapon.weapon], botInventory);
|
||||
const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(
|
||||
sessionId,
|
||||
chosenWeaponType,
|
||||
templateInventory,
|
||||
botInventory.equipment,
|
||||
modChances,
|
||||
botRole,
|
||||
isPmc,
|
||||
botLevel,
|
||||
);
|
||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
||||
[equipmentSlot],
|
||||
generatedWeapon.weapon[0]._id,
|
||||
generatedWeapon.weapon[0]._tpl,
|
||||
[...generatedWeapon.weapon],
|
||||
botInventory,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,7 +481,12 @@ export class BotLootGenerator
|
||||
*/
|
||||
protected getRandomItemFromPoolByRole(pool: ITemplateItem[], botRole: string): ITemplateItem
|
||||
{
|
||||
const itemIndex = this.randomUtil.getBiasedRandomNumber(0, pool.length - 1, pool.length - 1, this.getBotLootNValueByRole(botRole));
|
||||
const itemIndex = this.randomUtil.getBiasedRandomNumber(
|
||||
0,
|
||||
pool.length - 1,
|
||||
pool.length - 1,
|
||||
this.getBotLootNValueByRole(botRole),
|
||||
);
|
||||
return pool[itemIndex];
|
||||
}
|
||||
|
||||
@ -432,7 +513,7 @@ export class BotLootGenerator
|
||||
* All values are set to 0
|
||||
* @param isPmc Is the bot a pmc
|
||||
* @param botRole Role the bot has
|
||||
* @param limitCount
|
||||
* @param limitCount
|
||||
*/
|
||||
protected initItemLimitArray(isPmc: boolean, botRole: string, limitCount: Record<string, number>): void
|
||||
{
|
||||
@ -443,7 +524,7 @@ export class BotLootGenerator
|
||||
limitCount[limit] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if an item has reached its bot-specific spawn limit
|
||||
* @param itemTemplate Item we check to see if its reached spawn limit
|
||||
@ -453,7 +534,13 @@ export class BotLootGenerator
|
||||
* @param itemSpawnLimits The limits this bot is allowed to have
|
||||
* @returns true if item has reached spawn limit
|
||||
*/
|
||||
protected itemHasReachedSpawnLimit(itemTemplate: ITemplateItem, botRole: string, isPmc: boolean, limitCount: Record<string, number>, itemSpawnLimits: Record<string, number>): boolean
|
||||
protected itemHasReachedSpawnLimit(
|
||||
itemTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
limitCount: Record<string, number>,
|
||||
itemSpawnLimits: Record<string, number>,
|
||||
): boolean
|
||||
{
|
||||
// PMCs and scavs have different sections of bot config for spawn limits
|
||||
if (!!itemSpawnLimits && itemSpawnLimits.length === 0)
|
||||
@ -484,7 +571,13 @@ export class BotLootGenerator
|
||||
// Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely
|
||||
if (limitCount[idToCheckFor] > itemSpawnLimits[idToCheckFor] * 10)
|
||||
{
|
||||
this.logger.debug(this.localisationService.getText("bot-item_spawn_limit_reached_skipping_item", {botRole: botRole, itemName: itemTemplate._name, attempts: limitCount[idToCheckFor]}));
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("bot-item_spawn_limit_reached_skipping_item", {
|
||||
botRole: botRole,
|
||||
itemName: itemTemplate._name,
|
||||
attempts: limitCount[idToCheckFor],
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -505,9 +598,9 @@ export class BotLootGenerator
|
||||
{
|
||||
// PMCs have a different stack max size
|
||||
const minStackSize = itemTemplate._props.StackMinRandom;
|
||||
const maxStackSize = (isPmc)
|
||||
? this.pmcConfig.dynamicLoot.moneyStackLimits[itemTemplate._id]
|
||||
: itemTemplate._props.StackMaxRandom;
|
||||
const maxStackSize = isPmc ?
|
||||
this.pmcConfig.dynamicLoot.moneyStackLimits[itemTemplate._id] :
|
||||
itemTemplate._props.StackMaxRandom;
|
||||
const randomSize = this.randomUtil.getInt(minStackSize, maxStackSize);
|
||||
|
||||
if (!moneyItem.upd)
|
||||
@ -515,7 +608,7 @@ export class BotLootGenerator
|
||||
moneyItem.upd = {};
|
||||
}
|
||||
|
||||
moneyItem.upd.StackObjectsCount = randomSize;
|
||||
moneyItem.upd.StackObjectsCount = randomSize;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -526,16 +619,16 @@ export class BotLootGenerator
|
||||
*/
|
||||
protected randomiseAmmoStackSize(isPmc: boolean, itemTemplate: ITemplateItem, ammoItem: Item): void
|
||||
{
|
||||
const randomSize = itemTemplate._props.StackMaxSize === 1
|
||||
? 1
|
||||
: this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom);
|
||||
const randomSize = itemTemplate._props.StackMaxSize === 1 ?
|
||||
1 :
|
||||
this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom);
|
||||
|
||||
if (!ammoItem.upd)
|
||||
{
|
||||
ammoItem.upd = {};
|
||||
}
|
||||
|
||||
ammoItem.upd.StackObjectsCount = randomSize ;
|
||||
ammoItem.upd.StackObjectsCount = randomSize;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -557,7 +650,9 @@ export class BotLootGenerator
|
||||
return this.botConfig.itemSpawnLimits[botRole.toLowerCase()];
|
||||
}
|
||||
|
||||
this.logger.warning(this.localisationService.getText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole),
|
||||
);
|
||||
|
||||
return this.botConfig.itemSpawnLimits["default"];
|
||||
}
|
||||
@ -570,7 +665,6 @@ export class BotLootGenerator
|
||||
*/
|
||||
protected getMatchingIdFromSpawnLimits(itemTemplate: ITemplateItem, spawnLimits: Record<string, number>): string
|
||||
{
|
||||
|
||||
if (itemTemplate._id in spawnLimits)
|
||||
{
|
||||
return itemTemplate._id;
|
||||
@ -585,4 +679,4 @@ export class BotLootGenerator
|
||||
// parentId and tplid not found
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ export class BotWeaponGenerator
|
||||
@inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("RepairService") protected repairService: RepairService,
|
||||
@injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[]
|
||||
@injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[],
|
||||
)
|
||||
{
|
||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
@ -64,16 +64,35 @@ export class BotWeaponGenerator
|
||||
* Pick a random weapon based on weightings and generate a functional weapon
|
||||
* @param equipmentSlot Primary/secondary/holster
|
||||
* @param botTemplateInventory e.g. assault.json
|
||||
* @param weaponParentId
|
||||
* @param modChances
|
||||
* @param weaponParentId
|
||||
* @param modChances
|
||||
* @param botRole role of bot, e.g. assault/followerBully
|
||||
* @param isPmc Is weapon generated for a pmc
|
||||
* @returns GenerateWeaponResult object
|
||||
*/
|
||||
public generateRandomWeapon(sessionId: string, equipmentSlot: string, botTemplateInventory: Inventory, weaponParentId: string, modChances: ModsChances, botRole: string, isPmc: boolean, botLevel: number): GenerateWeaponResult
|
||||
public generateRandomWeapon(
|
||||
sessionId: string,
|
||||
equipmentSlot: string,
|
||||
botTemplateInventory: Inventory,
|
||||
weaponParentId: string,
|
||||
modChances: ModsChances,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
): GenerateWeaponResult
|
||||
{
|
||||
const weaponTpl = this.pickWeightedWeaponTplFromPool(equipmentSlot, botTemplateInventory);
|
||||
return this.generateWeaponByTpl(sessionId, weaponTpl, equipmentSlot, botTemplateInventory, weaponParentId, modChances, botRole, isPmc, botLevel);
|
||||
return this.generateWeaponByTpl(
|
||||
sessionId,
|
||||
weaponTpl,
|
||||
equipmentSlot,
|
||||
botTemplateInventory,
|
||||
weaponParentId,
|
||||
modChances,
|
||||
botRole,
|
||||
isPmc,
|
||||
botLevel,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,8 +118,17 @@ export class BotWeaponGenerator
|
||||
* @param isPmc Is weapon being generated for a pmc
|
||||
* @returns GenerateWeaponResult object
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public generateWeaponByTpl(sessionId: string, weaponTpl: string, equipmentSlot: string, botTemplateInventory: Inventory, weaponParentId: string, modChances: ModsChances, botRole: string, isPmc: boolean, botLevel: number): GenerateWeaponResult
|
||||
public generateWeaponByTpl(
|
||||
sessionId: string,
|
||||
weaponTpl: string,
|
||||
equipmentSlot: string,
|
||||
botTemplateInventory: Inventory,
|
||||
weaponParentId: string,
|
||||
modChances: ModsChances,
|
||||
botRole: string,
|
||||
isPmc: boolean,
|
||||
botLevel: number,
|
||||
): GenerateWeaponResult
|
||||
{
|
||||
const modPool = botTemplateInventory.mods;
|
||||
const weaponItemTemplate = this.itemHelper.getItem(weaponTpl)[1];
|
||||
@ -123,7 +151,13 @@ export class BotWeaponGenerator
|
||||
const ammoTpl = this.getWeightedCompatibleAmmo(botTemplateInventory.Ammo, weaponItemTemplate);
|
||||
|
||||
// Create with just base weapon item
|
||||
let weaponWithModsArray = this.constructWeaponBaseArray(weaponTpl, weaponParentId, equipmentSlot, weaponItemTemplate, botRole);
|
||||
let weaponWithModsArray = this.constructWeaponBaseArray(
|
||||
weaponTpl,
|
||||
weaponParentId,
|
||||
equipmentSlot,
|
||||
weaponItemTemplate,
|
||||
botRole,
|
||||
);
|
||||
|
||||
// Chance to add randomised weapon enhancement
|
||||
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.weaponHasEnhancementChancePercent))
|
||||
@ -137,32 +171,52 @@ export class BotWeaponGenerator
|
||||
{
|
||||
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole);
|
||||
const modLimits = this.botWeaponModLimitService.getWeaponModLimits(botEquipmentRole);
|
||||
weaponWithModsArray = this.botEquipmentModGenerator.generateModsForWeapon(sessionId, weaponWithModsArray, modPool, weaponWithModsArray[0]._id, weaponItemTemplate, modChances, ammoTpl, botRole, botLevel, modLimits, botEquipmentRole);
|
||||
weaponWithModsArray = this.botEquipmentModGenerator.generateModsForWeapon(
|
||||
sessionId,
|
||||
weaponWithModsArray,
|
||||
modPool,
|
||||
weaponWithModsArray[0]._id,
|
||||
weaponItemTemplate,
|
||||
modChances,
|
||||
ammoTpl,
|
||||
botRole,
|
||||
botLevel,
|
||||
modLimits,
|
||||
botEquipmentRole,
|
||||
);
|
||||
}
|
||||
|
||||
// Use weapon preset from globals.json if weapon isnt valid
|
||||
if (!this.isWeaponValid(weaponWithModsArray, botRole))
|
||||
{
|
||||
// Weapon is bad, fall back to weapons preset
|
||||
weaponWithModsArray = this.getPresetWeaponMods(weaponTpl, equipmentSlot, weaponParentId, weaponItemTemplate, botRole);
|
||||
weaponWithModsArray = this.getPresetWeaponMods(
|
||||
weaponTpl,
|
||||
equipmentSlot,
|
||||
weaponParentId,
|
||||
weaponItemTemplate,
|
||||
botRole,
|
||||
);
|
||||
}
|
||||
|
||||
// Fill existing magazines to full and sync ammo type
|
||||
for (const magazine of weaponWithModsArray.filter(x => x.slotId === this.modMagazineSlotId))
|
||||
for (const magazine of weaponWithModsArray.filter((x) => x.slotId === this.modMagazineSlotId))
|
||||
{
|
||||
this.fillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
|
||||
}
|
||||
|
||||
// Add cartridge to gun chamber if weapon has slot for it
|
||||
if (weaponItemTemplate._props.Chambers?.length === 1
|
||||
&& weaponItemTemplate._props.Chambers[0]?._name === "patron_in_weapon"
|
||||
&& weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl))
|
||||
if (
|
||||
weaponItemTemplate._props.Chambers?.length === 1 &&
|
||||
weaponItemTemplate._props.Chambers[0]?._name === "patron_in_weapon" &&
|
||||
weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl)
|
||||
)
|
||||
{
|
||||
this.addCartridgeToChamber(weaponWithModsArray, ammoTpl, "patron_in_weapon");
|
||||
}
|
||||
|
||||
// Fill UBGL if found
|
||||
const ubglMod = weaponWithModsArray.find(x => x.slotId === "mod_launcher");
|
||||
const ubglMod = weaponWithModsArray.find((x) => x.slotId === "mod_launcher");
|
||||
let ubglAmmoTpl: string = undefined;
|
||||
if (ubglMod)
|
||||
{
|
||||
@ -176,7 +230,7 @@ export class BotWeaponGenerator
|
||||
chosenAmmoTpl: ammoTpl,
|
||||
chosenUbglAmmoTpl: ubglAmmoTpl,
|
||||
weaponMods: modPool,
|
||||
weaponTemplate: weaponItemTemplate
|
||||
weaponTemplate: weaponItemTemplate,
|
||||
};
|
||||
}
|
||||
|
||||
@ -189,7 +243,7 @@ export class BotWeaponGenerator
|
||||
protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, desiredSlotId: string): void
|
||||
{
|
||||
// Check for slot first
|
||||
const existingItemWithSlot = weaponWithModsArray.find(x => x.slotId === desiredSlotId);
|
||||
const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === desiredSlotId);
|
||||
if (!existingItemWithSlot)
|
||||
{
|
||||
// Not found, add fresh
|
||||
@ -198,14 +252,14 @@ export class BotWeaponGenerator
|
||||
_tpl: ammoTpl,
|
||||
parentId: weaponWithModsArray[0]._id,
|
||||
slotId: desiredSlotId,
|
||||
upd: {StackObjectsCount: 1}
|
||||
upd: {StackObjectsCount: 1},
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already exists, update values
|
||||
existingItemWithSlot.upd = {
|
||||
StackObjectsCount: 1
|
||||
StackObjectsCount: 1,
|
||||
};
|
||||
existingItemWithSlot._tpl = ammoTpl;
|
||||
}
|
||||
@ -216,19 +270,25 @@ export class BotWeaponGenerator
|
||||
* add additional properties based on weapon type
|
||||
* @param weaponTpl Weapon tpl to create item with
|
||||
* @param weaponParentId Weapons parent id
|
||||
* @param equipmentSlot e.g. primary/secondary/holster
|
||||
* @param equipmentSlot e.g. primary/secondary/holster
|
||||
* @param weaponItemTemplate db template for weapon
|
||||
* @param botRole for durability values
|
||||
* @returns Base weapon item in array
|
||||
*/
|
||||
protected constructWeaponBaseArray(weaponTpl: string, weaponParentId: string, equipmentSlot: string, weaponItemTemplate: ITemplateItem, botRole: string): Item[]
|
||||
protected constructWeaponBaseArray(
|
||||
weaponTpl: string,
|
||||
weaponParentId: string,
|
||||
equipmentSlot: string,
|
||||
weaponItemTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): Item[]
|
||||
{
|
||||
return [{
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: weaponTpl,
|
||||
parentId: weaponParentId,
|
||||
slotId: equipmentSlot,
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(weaponItemTemplate, botRole)
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(weaponItemTemplate, botRole),
|
||||
}];
|
||||
}
|
||||
|
||||
@ -239,10 +299,18 @@ export class BotWeaponGenerator
|
||||
* @param weaponParentId Value used for the parentid
|
||||
* @returns array of weapon mods
|
||||
*/
|
||||
protected getPresetWeaponMods(weaponTpl: string, equipmentSlot: string, weaponParentId: string, itemTemplate: ITemplateItem, botRole: string): Item[]
|
||||
protected getPresetWeaponMods(
|
||||
weaponTpl: string,
|
||||
equipmentSlot: string,
|
||||
weaponParentId: string,
|
||||
itemTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): Item[]
|
||||
{
|
||||
// Invalid weapon generated, fallback to preset
|
||||
this.logger.warning(this.localisationService.getText("bot-weapon_generated_incorrect_using_default", weaponTpl));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapon_generated_incorrect_using_default", weaponTpl),
|
||||
);
|
||||
const weaponMods = [];
|
||||
|
||||
// TODO: Right now, preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
|
||||
@ -260,11 +328,12 @@ export class BotWeaponGenerator
|
||||
{
|
||||
const parentItem = preset._items[0];
|
||||
preset._items[0] = {
|
||||
...parentItem, ...{
|
||||
...parentItem,
|
||||
...{
|
||||
parentId: weaponParentId,
|
||||
slotId: equipmentSlot,
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate, botRole)
|
||||
}
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate, botRole),
|
||||
},
|
||||
};
|
||||
weaponMods.push(...preset._items);
|
||||
}
|
||||
@ -304,17 +373,30 @@ export class BotWeaponGenerator
|
||||
const allowedTpls = modSlot._props.filters[0].Filter;
|
||||
const slotName = modSlot._name;
|
||||
|
||||
const weaponSlotItem = weaponItemArray.find(x => x.parentId === mod._id && x.slotId === slotName);
|
||||
const weaponSlotItem = weaponItemArray.find((x) => x.parentId === mod._id && x.slotId === slotName);
|
||||
if (!weaponSlotItem)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("bot-weapons_required_slot_missing_item", {modSlot: modSlot._name, modName: modDbTemplate._name, slotId: mod.slotId, botRole: botRole}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapons_required_slot_missing_item", {
|
||||
modSlot: modSlot._name,
|
||||
modName: modDbTemplate._name,
|
||||
slotId: mod.slotId,
|
||||
botRole: botRole,
|
||||
}),
|
||||
);
|
||||
|
||||
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}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapon_contains_invalid_item", {
|
||||
modSlot: modSlot._name,
|
||||
modName: modDbTemplate._name,
|
||||
weaponTpl: weaponSlotItem._tpl,
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -332,12 +414,17 @@ export class BotWeaponGenerator
|
||||
* @param inventory Inventory to add magazines to
|
||||
* @param botRole The bot type we're getting generating extra mags for
|
||||
*/
|
||||
public addExtraMagazinesToInventory(generatedWeaponResult: GenerateWeaponResult, magWeights: GenerationData, inventory: PmcInventory, botRole: string): void
|
||||
public addExtraMagazinesToInventory(
|
||||
generatedWeaponResult: GenerateWeaponResult,
|
||||
magWeights: GenerationData,
|
||||
inventory: PmcInventory,
|
||||
botRole: string,
|
||||
): void
|
||||
{
|
||||
const weaponAndMods = generatedWeaponResult.weapon;
|
||||
const weaponTemplate = generatedWeaponResult.weaponTemplate;
|
||||
const magazineTpl = this.getMagazineTplFromWeaponTemplate(weaponAndMods, weaponTemplate, botRole);
|
||||
|
||||
|
||||
const magTemplate = this.itemHelper.getItem(magazineTpl)[1];
|
||||
if (!magTemplate)
|
||||
{
|
||||
@ -349,7 +436,9 @@ export class BotWeaponGenerator
|
||||
const ammoTemplate = this.itemHelper.getItem(generatedWeaponResult.chosenAmmoTpl)[1];
|
||||
if (!ammoTemplate)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("bot-unable_to_find_ammo_item", generatedWeaponResult.chosenAmmoTpl));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-unable_to_find_ammo_item", generatedWeaponResult.chosenAmmoTpl),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -360,11 +449,24 @@ export class BotWeaponGenerator
|
||||
this.addUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
|
||||
}
|
||||
|
||||
const inventoryMagGenModel = new InventoryMagGen(magWeights, magTemplate, weaponTemplate, ammoTemplate, inventory);
|
||||
this.inventoryMagGenComponents.find(v => v.canHandleInventoryMagGen(inventoryMagGenModel)).process(inventoryMagGenModel);
|
||||
const inventoryMagGenModel = new InventoryMagGen(
|
||||
magWeights,
|
||||
magTemplate,
|
||||
weaponTemplate,
|
||||
ammoTemplate,
|
||||
inventory,
|
||||
);
|
||||
this.inventoryMagGenComponents.find((v) => v.canHandleInventoryMagGen(inventoryMagGenModel)).process(
|
||||
inventoryMagGenModel,
|
||||
);
|
||||
|
||||
// Add x stacks of bullets to SecuredContainer (bots use a magic mag packing skill to reload instantly)
|
||||
this.addAmmoToSecureContainer(this.botConfig.secureContainerAmmoStackCount, generatedWeaponResult.chosenAmmoTpl, ammoTemplate._props.StackMaxSize, inventory);
|
||||
this.addAmmoToSecureContainer(
|
||||
this.botConfig.secureContainerAmmoStackCount,
|
||||
generatedWeaponResult.chosenAmmoTpl,
|
||||
ammoTemplate._props.StackMaxSize,
|
||||
inventory,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,25 +475,37 @@ export class BotWeaponGenerator
|
||||
* @param generatedWeaponResult result of weapon generation
|
||||
* @param inventory bot inventory to add grenades to
|
||||
*/
|
||||
protected addUbglGrenadesToBotInventory(weaponMods: Item[], generatedWeaponResult: GenerateWeaponResult, inventory: PmcInventory): void
|
||||
protected addUbglGrenadesToBotInventory(
|
||||
weaponMods: Item[],
|
||||
generatedWeaponResult: GenerateWeaponResult,
|
||||
inventory: PmcInventory,
|
||||
): void
|
||||
{
|
||||
// Find ubgl mod item + get details of it from db
|
||||
const ubglMod = weaponMods.find(x => x.slotId === "mod_launcher");
|
||||
const ubglMod = weaponMods.find((x) => x.slotId === "mod_launcher");
|
||||
const ubglDbTemplate = this.itemHelper.getItem(ubglMod._tpl)[1];
|
||||
|
||||
// Define min/max of how many grenades bot will have
|
||||
const ubglMinMax:GenerationData = {
|
||||
const ubglMinMax: GenerationData = {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
weights: {"1": 1, "2": 1},
|
||||
whitelist: []
|
||||
whitelist: [],
|
||||
};
|
||||
|
||||
// get ammo template from db
|
||||
const ubglAmmoDbTemplate = this.itemHelper.getItem(generatedWeaponResult.chosenUbglAmmoTpl)[1];
|
||||
|
||||
// Add greandes to bot inventory
|
||||
const ubglAmmoGenModel = new InventoryMagGen(ubglMinMax, ubglDbTemplate, ubglDbTemplate, ubglAmmoDbTemplate, inventory);
|
||||
this.inventoryMagGenComponents.find(v => v.canHandleInventoryMagGen(ubglAmmoGenModel)).process(ubglAmmoGenModel);
|
||||
const ubglAmmoGenModel = new InventoryMagGen(
|
||||
ubglMinMax,
|
||||
ubglDbTemplate,
|
||||
ubglDbTemplate,
|
||||
ubglAmmoDbTemplate,
|
||||
inventory,
|
||||
);
|
||||
this.inventoryMagGenComponents.find((v) => v.canHandleInventoryMagGen(ubglAmmoGenModel)).process(
|
||||
ubglAmmoGenModel,
|
||||
);
|
||||
|
||||
// Store extra grenades in secure container
|
||||
this.addAmmoToSecureContainer(5, generatedWeaponResult.chosenUbglAmmoTpl, 20, inventory);
|
||||
@ -404,17 +518,27 @@ export class BotWeaponGenerator
|
||||
* @param stackSize Size of the ammo stack to add
|
||||
* @param inventory Player inventory
|
||||
*/
|
||||
protected addAmmoToSecureContainer(stackCount: number, ammoTpl: string, stackSize: number, inventory: PmcInventory): void
|
||||
protected addAmmoToSecureContainer(
|
||||
stackCount: number,
|
||||
ammoTpl: string,
|
||||
stackSize: number,
|
||||
inventory: PmcInventory,
|
||||
): void
|
||||
{
|
||||
for (let i = 0; i < stackCount; i++)
|
||||
{
|
||||
const id = this.hashUtil.generate();
|
||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot([EquipmentSlots.SECURED_CONTAINER], id, ammoTpl, [{
|
||||
_id: id,
|
||||
_tpl: ammoTpl,
|
||||
upd: { StackObjectsCount: stackSize }
|
||||
}],
|
||||
inventory);
|
||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
||||
[EquipmentSlots.SECURED_CONTAINER],
|
||||
id,
|
||||
ammoTpl,
|
||||
[{
|
||||
_id: id,
|
||||
_tpl: ammoTpl,
|
||||
upd: {StackObjectsCount: stackSize},
|
||||
}],
|
||||
inventory,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,9 +549,13 @@ export class BotWeaponGenerator
|
||||
* @param botRole the bot type we are getting the magazine for
|
||||
* @returns magazine tpl string
|
||||
*/
|
||||
protected getMagazineTplFromWeaponTemplate(weaponMods: Item[], weaponTemplate: ITemplateItem, botRole: string): string
|
||||
protected getMagazineTplFromWeaponTemplate(
|
||||
weaponMods: Item[],
|
||||
weaponTemplate: ITemplateItem,
|
||||
botRole: string,
|
||||
): string
|
||||
{
|
||||
const magazine = weaponMods.find(m => m.slotId === this.modMagazineSlotId);
|
||||
const magazine = weaponMods.find((m) => m.slotId === this.modMagazineSlotId);
|
||||
if (!magazine)
|
||||
{
|
||||
// Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18
|
||||
@ -441,11 +569,15 @@ export class BotWeaponGenerator
|
||||
if (!weaponTemplate._props.isChamberLoad)
|
||||
{
|
||||
// Shouldn't happen
|
||||
this.logger.warning(this.localisationService.getText("bot-weapon_missing_magazine_or_chamber", weaponTemplate._id));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("bot-weapon_missing_magazine_or_chamber", weaponTemplate._id),
|
||||
);
|
||||
}
|
||||
|
||||
const defaultMagTplId = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weaponTemplate);
|
||||
this.logger.debug(`[${botRole}] Unable to find magazine for weapon ${weaponTemplate._id} ${weaponTemplate._name}, using mag template default ${defaultMagTplId}.`);
|
||||
this.logger.debug(
|
||||
`[${botRole}] Unable to find magazine for weapon ${weaponTemplate._id} ${weaponTemplate._name}, using mag template default ${defaultMagTplId}.`,
|
||||
);
|
||||
|
||||
return defaultMagTplId;
|
||||
}
|
||||
@ -459,23 +591,42 @@ export class BotWeaponGenerator
|
||||
* @param weaponTemplate the weapon we want to pick ammo for
|
||||
* @returns an ammo tpl that works with the desired gun
|
||||
*/
|
||||
protected getWeightedCompatibleAmmo(ammo: Record<string, Record<string, number>>, weaponTemplate: ITemplateItem): string
|
||||
protected getWeightedCompatibleAmmo(
|
||||
ammo: Record<string, Record<string, number>>,
|
||||
weaponTemplate: ITemplateItem,
|
||||
): string
|
||||
{
|
||||
const desiredCaliber = this.getWeaponCaliber(weaponTemplate);
|
||||
|
||||
const compatibleCartridges = ammo[desiredCaliber];
|
||||
if (!compatibleCartridges || compatibleCartridges?.length === 0)
|
||||
{
|
||||
this.logger.debug(this.localisationService.getText("bot-no_caliber_data_for_weapon_falling_back_to_default", {weaponId: weaponTemplate._id, weaponName: weaponTemplate._name, defaultAmmo: weaponTemplate._props.defAmmo}));
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("bot-no_caliber_data_for_weapon_falling_back_to_default", {
|
||||
weaponId: weaponTemplate._id,
|
||||
weaponName: weaponTemplate._name,
|
||||
defaultAmmo: weaponTemplate._props.defAmmo,
|
||||
}),
|
||||
);
|
||||
|
||||
// Immediately returns, as default ammo is guaranteed to be compatible
|
||||
return weaponTemplate._props.defAmmo;
|
||||
}
|
||||
|
||||
const chosenAmmoTpl = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
|
||||
if (weaponTemplate._props.Chambers[0] && !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(chosenAmmoTpl))
|
||||
if (
|
||||
weaponTemplate._props.Chambers[0] &&
|
||||
!weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(chosenAmmoTpl)
|
||||
)
|
||||
{
|
||||
this.logger.debug(this.localisationService.getText("bot-incompatible_ammo_for_weapon_falling_back_to_default", {chosenAmmo: chosenAmmoTpl, weaponId: weaponTemplate._id, weaponName: weaponTemplate._name, defaultAmmo: weaponTemplate._props.defAmmo}));
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("bot-incompatible_ammo_for_weapon_falling_back_to_default", {
|
||||
chosenAmmo: chosenAmmoTpl,
|
||||
weaponId: weaponTemplate._id,
|
||||
weaponName: weaponTemplate._name,
|
||||
defaultAmmo: weaponTemplate._props.defAmmo,
|
||||
}),
|
||||
);
|
||||
|
||||
// Incompatible ammo found, return default (can happen with .366 and 7.62x39 weapons)
|
||||
return weaponTemplate._props.defAmmo;
|
||||
@ -503,7 +654,9 @@ export class BotWeaponGenerator
|
||||
|
||||
if (weaponTemplate._props.LinkedWeapon)
|
||||
{
|
||||
const ammoInChamber = this.itemHelper.getItem(weaponTemplate._props.Chambers[0]._props.filters[0].Filter[0]);
|
||||
const ammoInChamber = this.itemHelper.getItem(
|
||||
weaponTemplate._props.Chambers[0]._props.filters[0].Filter[0],
|
||||
);
|
||||
if (!ammoInChamber[0])
|
||||
{
|
||||
return;
|
||||
@ -559,9 +712,9 @@ export class BotWeaponGenerator
|
||||
parentId: ubglMod._id,
|
||||
slotId: "patron_in_weapon",
|
||||
upd: {
|
||||
StackObjectsCount: 1
|
||||
}
|
||||
}
|
||||
StackObjectsCount: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -573,9 +726,16 @@ export class BotWeaponGenerator
|
||||
* @param newStackSize how many cartridges should go into the magazine
|
||||
* @param magazineTemplate magazines db template
|
||||
*/
|
||||
protected addOrUpdateMagazinesChildWithAmmo(weaponWithMods: Item[], magazine: Item, chosenAmmoTpl: string, magazineTemplate: ITemplateItem): void
|
||||
protected addOrUpdateMagazinesChildWithAmmo(
|
||||
weaponWithMods: Item[],
|
||||
magazine: Item,
|
||||
chosenAmmoTpl: string,
|
||||
magazineTemplate: ITemplateItem,
|
||||
): void
|
||||
{
|
||||
const magazineCartridgeChildItem = weaponWithMods.find(m => m.parentId === magazine._id && m.slotId === "cartridges");
|
||||
const magazineCartridgeChildItem = weaponWithMods.find((m) =>
|
||||
m.parentId === magazine._id && m.slotId === "cartridges"
|
||||
);
|
||||
if (magazineCartridgeChildItem)
|
||||
{
|
||||
// Delete the existing cartridge object and create fresh below
|
||||
@ -603,7 +763,7 @@ export class BotWeaponGenerator
|
||||
// for CylinderMagazine we exchange the ammo in the "camoras".
|
||||
// This might not be necessary since we already filled the camoras with a random whitelisted and compatible ammo type,
|
||||
// but I'm not sure whether this is also used elsewhere
|
||||
const camoras = weaponMods.filter(x => x.parentId === magazineId && x.slotId.startsWith("camora"));
|
||||
const camoras = weaponMods.filter((x) => x.parentId === magazineId && x.slotId.startsWith("camora"));
|
||||
for (const camora of camoras)
|
||||
{
|
||||
camora._tpl = ammoTpl;
|
||||
@ -613,8 +773,8 @@ export class BotWeaponGenerator
|
||||
}
|
||||
else
|
||||
{
|
||||
camora.upd = { StackObjectsCount: 1 };
|
||||
camora.upd = {StackObjectsCount: 1};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ export class FenceBaseAssortGenerator
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||
@ -43,7 +43,7 @@ export class FenceBaseAssortGenerator
|
||||
const baseFenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort;
|
||||
|
||||
const dbItems = Object.values(this.databaseServer.getTables().templates.items);
|
||||
for (const item of dbItems.filter(x => this.isValidFenceItem(x)))
|
||||
for (const item of dbItems.filter((x) => this.isValidFenceItem(x)))
|
||||
{
|
||||
// Skip blacklisted items
|
||||
if (this.itemFilterService.isItemBlacklisted(item._id))
|
||||
@ -65,8 +65,10 @@ export class FenceBaseAssortGenerator
|
||||
// Skip items on fence ignore list
|
||||
if (this.traderConfig.fence.blacklist.length > 0)
|
||||
{
|
||||
if (this.traderConfig.fence.blacklist.includes(item._id)
|
||||
|| this.itemHelper.isOfBaseclasses(item._id, this.traderConfig.fence.blacklist))
|
||||
if (
|
||||
this.traderConfig.fence.blacklist.includes(item._id) ||
|
||||
this.itemHelper.isOfBaseclasses(item._id, this.traderConfig.fence.blacklist)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -80,8 +82,10 @@ export class FenceBaseAssortGenerator
|
||||
|
||||
// Create barter scheme object
|
||||
const barterSchemeToAdd: IBarterScheme = {
|
||||
count: Math.round(this.handbookHelper.getTemplatePrice(item._id) * this.traderConfig.fence.itemPriceMult),
|
||||
_tpl: Money.ROUBLES
|
||||
count: Math.round(
|
||||
this.handbookHelper.getTemplatePrice(item._id) * this.traderConfig.fence.itemPriceMult,
|
||||
),
|
||||
_tpl: Money.ROUBLES,
|
||||
};
|
||||
|
||||
// Add barter data to base
|
||||
@ -95,8 +99,8 @@ export class FenceBaseAssortGenerator
|
||||
slotId: "hideout",
|
||||
upd: {
|
||||
StackObjectsCount: 9999999,
|
||||
UnlimitedCount: true
|
||||
}
|
||||
UnlimitedCount: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Add item to base
|
||||
@ -121,4 +125,4 @@ export class FenceBaseAssortGenerator
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,14 @@ import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
||||
import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper";
|
||||
import { IContainerMinMax, IStaticContainer } from "@spt-aki/models/eft/common/ILocation";
|
||||
import { ILocationBase } from "@spt-aki/models/eft/common/ILocationBase";
|
||||
import { ILooseLoot, Spawnpoint, SpawnpointTemplate, SpawnpointsForced } from "@spt-aki/models/eft/common/ILooseLoot";
|
||||
import { ILooseLoot, Spawnpoint, SpawnpointsForced, SpawnpointTemplate } from "@spt-aki/models/eft/common/ILooseLoot";
|
||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { IStaticAmmoDetails, IStaticContainerData, IStaticForcedProps, IStaticLootDetails } from "@spt-aki/models/eft/common/tables/ILootBase";
|
||||
import {
|
||||
IStaticAmmoDetails,
|
||||
IStaticContainerData,
|
||||
IStaticForcedProps,
|
||||
IStaticLootDetails,
|
||||
} from "@spt-aki/models/eft/common/tables/ILootBase";
|
||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { Money } from "@spt-aki/models/enums/Money";
|
||||
@ -25,17 +30,17 @@ import { ProbabilityObject, ProbabilityObjectArray, RandomUtil } from "@spt-aki/
|
||||
|
||||
export interface IContainerItem
|
||||
{
|
||||
items: Item[]
|
||||
width: number
|
||||
height: number
|
||||
items: Item[];
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface IContainerGroupCount
|
||||
{
|
||||
/** Containers this group has + probabilty to spawn */
|
||||
containerIdsWithProbability: Record<string, number>
|
||||
containerIdsWithProbability: Record<string, number>;
|
||||
/** How many containers the map should spawn with this group id */
|
||||
chosenCount: number
|
||||
chosenCount: number;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
@ -56,7 +61,7 @@ export class LocationGenerator
|
||||
@inject("ContainerHelper") protected containerHelper: ContainerHelper,
|
||||
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||
@ -68,7 +73,10 @@ export class LocationGenerator
|
||||
* @param staticAmmoDist Static ammo distribution - database.loot.staticAmmo
|
||||
* @returns Array of container objects
|
||||
*/
|
||||
public generateStaticContainers(locationBase: ILocationBase, staticAmmoDist: Record<string, IStaticAmmoDetails[]>): SpawnpointTemplate[]
|
||||
public generateStaticContainers(
|
||||
locationBase: ILocationBase,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
): SpawnpointTemplate[]
|
||||
{
|
||||
const result: SpawnpointTemplate[] = [];
|
||||
const locationId = locationBase.Id.toLowerCase();
|
||||
@ -84,7 +92,9 @@ export class LocationGenerator
|
||||
// Add mounted weapons to output loot
|
||||
result.push(...staticWeaponsOnMap ?? []);
|
||||
|
||||
const allStaticContainersOnMap = this.jsonUtil.clone(db.loot.staticContainers[locationBase.Name]?.staticContainers);
|
||||
const allStaticContainersOnMap = this.jsonUtil.clone(
|
||||
db.loot.staticContainers[locationBase.Name]?.staticContainers,
|
||||
);
|
||||
if (!allStaticContainersOnMap)
|
||||
{
|
||||
this.logger.error(`Unable to find static container data for map: ${locationBase.Name}`);
|
||||
@ -105,23 +115,40 @@ export class LocationGenerator
|
||||
const staticLootDist = db.loot.staticLoot;
|
||||
const guaranteedContainers = this.getGuaranteedContainers(allStaticContainersOnMap);
|
||||
staticContainerCount += guaranteedContainers.length;
|
||||
|
||||
|
||||
// Add loot to guaranteed containers and add to result
|
||||
for (const container of guaranteedContainers)
|
||||
{
|
||||
const containerWithLoot = this.addLootToContainer(container, staticForcedOnMap, staticLootDist, staticAmmoDist, locationId);
|
||||
const containerWithLoot = this.addLootToContainer(
|
||||
container,
|
||||
staticForcedOnMap,
|
||||
staticLootDist,
|
||||
staticAmmoDist,
|
||||
locationId,
|
||||
);
|
||||
result.push(containerWithLoot.template);
|
||||
}
|
||||
|
||||
this.logger.success(`Added ${guaranteedContainers.length} guaranteed containers`);
|
||||
|
||||
// randomisation is turned off globally or just turned off for this map
|
||||
if (!this.locationConfig.containerRandomisationSettings.enabled || !this.locationConfig.containerRandomisationSettings.maps[locationId])
|
||||
if (
|
||||
!this.locationConfig.containerRandomisationSettings.enabled ||
|
||||
!this.locationConfig.containerRandomisationSettings.maps[locationId]
|
||||
)
|
||||
{
|
||||
this.logger.debug(`Container randomisation disabled, Adding ${staticRandomisableContainersOnMap.length} containers to ${locationBase.Name}`);
|
||||
this.logger.debug(
|
||||
`Container randomisation disabled, Adding ${staticRandomisableContainersOnMap.length} containers to ${locationBase.Name}`,
|
||||
);
|
||||
for (const container of staticRandomisableContainersOnMap)
|
||||
{
|
||||
const containerWithLoot = this.addLootToContainer(container, staticForcedOnMap, staticLootDist, staticAmmoDist, locationId);
|
||||
const containerWithLoot = this.addLootToContainer(
|
||||
container,
|
||||
staticForcedOnMap,
|
||||
staticLootDist,
|
||||
staticAmmoDist,
|
||||
locationId,
|
||||
);
|
||||
result.push(containerWithLoot.template);
|
||||
}
|
||||
|
||||
@ -129,7 +156,7 @@ export class LocationGenerator
|
||||
}
|
||||
|
||||
// Group containers by their groupId
|
||||
const staticContainerGroupData: IStaticContainer = db.locations[locationId].statics;
|
||||
const staticContainerGroupData: IStaticContainer = db.locations[locationId].statics;
|
||||
const mapping = this.getGroupIdToContainerMappings(staticContainerGroupData, staticRandomisableContainersOnMap);
|
||||
|
||||
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
|
||||
@ -145,7 +172,9 @@ export class LocationGenerator
|
||||
|
||||
if (Object.keys(data.containerIdsWithProbability).length === 0)
|
||||
{
|
||||
this.logger.debug(`Group: ${groupId} has no containers with < 100% spawn chance to choose from, skipping`);
|
||||
this.logger.debug(
|
||||
`Group: ${groupId} has no containers with < 100% spawn chance to choose from, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -178,48 +207,72 @@ export class LocationGenerator
|
||||
for (const chosenContainerId of chosenContainerIds)
|
||||
{
|
||||
// Look up container object from full list of containers on map
|
||||
const containerObject = staticRandomisableContainersOnMap.find(x => x.template.Id === chosenContainerId);
|
||||
const containerObject = staticRandomisableContainersOnMap.find((x) =>
|
||||
x.template.Id === chosenContainerId
|
||||
);
|
||||
if (!containerObject)
|
||||
{
|
||||
this.logger.debug(`Container: ${chosenContainerIds[chosenContainerId]} not found in staticRandomisableContainersOnMap, this is bad`);
|
||||
this.logger.debug(
|
||||
`Container: ${
|
||||
chosenContainerIds[chosenContainerId]
|
||||
} not found in staticRandomisableContainersOnMap, this is bad`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add loot to container and push into result object
|
||||
const containerWithLoot = this.addLootToContainer(containerObject, staticForcedOnMap, staticLootDist, staticAmmoDist, locationId);
|
||||
const containerWithLoot = this.addLootToContainer(
|
||||
containerObject,
|
||||
staticForcedOnMap,
|
||||
staticLootDist,
|
||||
staticAmmoDist,
|
||||
locationId,
|
||||
);
|
||||
result.push(containerWithLoot.template);
|
||||
staticContainerCount++;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.success(this.localisationService.getText("location-containers_generated_success", staticContainerCount));
|
||||
this.logger.success(
|
||||
this.localisationService.getText("location-containers_generated_success", staticContainerCount),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get containers with a non-100% chance to spawn OR are NOT on the container type randomistion blacklist
|
||||
* @param staticContainers
|
||||
* @param staticContainers
|
||||
* @returns IStaticContainerData array
|
||||
*/
|
||||
protected getRandomisableContainersOnMap(staticContainers: IStaticContainerData[]): IStaticContainerData[]
|
||||
{
|
||||
return staticContainers
|
||||
.filter(x => x.probability !== 1 && !x.template.IsAlwaysSpawn && !this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(x.template.Items[0]._tpl));
|
||||
.filter((x) =>
|
||||
x.probability !== 1 && !x.template.IsAlwaysSpawn &&
|
||||
!this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(
|
||||
x.template.Items[0]._tpl,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get containers with 100% spawn rate or have a type on the randomistion ignore list
|
||||
* @param staticContainersOnMap
|
||||
* @param staticContainersOnMap
|
||||
* @returns IStaticContainerData array
|
||||
*/
|
||||
protected getGuaranteedContainers(staticContainersOnMap: IStaticContainerData[]): IStaticContainerData[]
|
||||
{
|
||||
return staticContainersOnMap.filter(x => x.probability === 1 || x.template.IsAlwaysSpawn || this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(x.template.Items[0]._tpl));
|
||||
return staticContainersOnMap.filter((x) =>
|
||||
x.probability === 1 || x.template.IsAlwaysSpawn ||
|
||||
this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(
|
||||
x.template.Items[0]._tpl,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose a number of containers based on their probabilty value to fulfil the desired count in containerData.chosenCount
|
||||
* Choose a number of containers based on their probability value to fulfil the desired count in containerData.chosenCount
|
||||
* @param groupId Name of the group the containers are being collected for
|
||||
* @param containerData Containers and probability values for a groupId
|
||||
* @returns List of chosen container Ids
|
||||
@ -231,15 +284,19 @@ export class LocationGenerator
|
||||
const containerIds = Object.keys(containerData.containerIdsWithProbability);
|
||||
if (containerData.chosenCount > containerIds.length)
|
||||
{
|
||||
this.logger.debug(`Group: ${groupId} wants ${containerData.chosenCount} containers but pool only has ${containerIds.length}, add what's available`);
|
||||
this.logger.debug(
|
||||
`Group: ${groupId} wants ${containerData.chosenCount} containers but pool only has ${containerIds.length}, add what's available`,
|
||||
);
|
||||
return containerIds;
|
||||
}
|
||||
|
||||
// Create probability array with all possible container ids in this group and their relataive probability of spawning
|
||||
// Create probability array with all possible container ids in this group and their relative probability of spawning
|
||||
const containerDistribution = new ProbabilityObjectArray<string>(this.mathUtil, this.jsonUtil);
|
||||
for (const containerId of containerIds)
|
||||
{
|
||||
containerDistribution.push(new ProbabilityObject(containerId, containerData.containerIdsWithProbability[containerId]));
|
||||
containerDistribution.push(
|
||||
new ProbabilityObject(containerId, containerData.containerIdsWithProbability[containerId]),
|
||||
);
|
||||
}
|
||||
|
||||
chosenContainerIds.push(...containerDistribution.draw(containerData.chosenCount));
|
||||
@ -254,7 +311,8 @@ export class LocationGenerator
|
||||
*/
|
||||
protected getGroupIdToContainerMappings(
|
||||
staticContainerGroupData: IStaticContainer | Record<string, IContainerMinMax>,
|
||||
staticContainersOnMap: IStaticContainerData[]): Record<string, IContainerGroupCount>
|
||||
staticContainersOnMap: IStaticContainerData[],
|
||||
): Record<string, IContainerGroupCount>
|
||||
{
|
||||
// Create dictionary of all group ids and choose a count of containers the map will spawn of that group
|
||||
const mapping: Record<string, IContainerGroupCount> = {};
|
||||
@ -266,9 +324,15 @@ export class LocationGenerator
|
||||
mapping[groupId] = {
|
||||
containerIdsWithProbability: {},
|
||||
chosenCount: this.randomUtil.getInt(
|
||||
Math.round(groupData.minContainers * this.locationConfig.containerRandomisationSettings.containerGroupMinSizeMultiplier),
|
||||
Math.round(groupData.maxContainers * this.locationConfig.containerRandomisationSettings.containerGroupMaxSizeMultiplier)
|
||||
)
|
||||
Math.round(
|
||||
groupData.minContainers *
|
||||
this.locationConfig.containerRandomisationSettings.containerGroupMinSizeMultiplier,
|
||||
),
|
||||
Math.round(
|
||||
groupData.maxContainers *
|
||||
this.locationConfig.containerRandomisationSettings.containerGroupMaxSizeMultiplier,
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -290,7 +354,9 @@ export class LocationGenerator
|
||||
|
||||
if (container.probability === 1)
|
||||
{
|
||||
this.logger.debug(`Container ${container.template.Id} with group ${groupData.groupId} had 100% chance to spawn was picked as random container, skipping`);
|
||||
this.logger.debug(
|
||||
`Container ${container.template.Id} with group ${groupData.groupId} had 100% chance to spawn was picked as random container, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
mapping[groupData.groupId].containerIdsWithProbability[container.template.Id] = container.probability;
|
||||
@ -314,7 +380,8 @@ export class LocationGenerator
|
||||
staticForced: IStaticForcedProps[],
|
||||
staticLootDist: Record<string, IStaticLootDetails>,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
locationName: string): IStaticContainerData
|
||||
locationName: string,
|
||||
): IStaticContainerData
|
||||
{
|
||||
const container = this.jsonUtil.clone(staticContainer);
|
||||
const containerTpl = container.template.Items[0]._tpl;
|
||||
@ -333,7 +400,7 @@ export class LocationGenerator
|
||||
const containerLootPool = this.getPossibleLootItemsForContainer(containerTpl, staticLootDist);
|
||||
|
||||
// Some containers need to have items forced into it (quest keys etc)
|
||||
const tplsForced = staticForced.filter(x => x.containerId === container.template.Id).map(x => x.itemTpl);
|
||||
const tplsForced = staticForced.filter((x) => x.containerId === container.template.Id).map((x) => x.itemTpl);
|
||||
|
||||
// Draw random loot
|
||||
// Money spawn more than once in container
|
||||
@ -341,7 +408,11 @@ export class LocationGenerator
|
||||
const locklist = [Money.ROUBLES, Money.DOLLARS, Money.EUROS];
|
||||
|
||||
// Choose items to add to container, factor in weighting + lock money down
|
||||
const chosenTpls = containerLootPool.draw(itemCountToAdd, this.locationConfig.allowDuplicateItemsInStaticContainers, locklist);
|
||||
const chosenTpls = containerLootPool.draw(
|
||||
itemCountToAdd,
|
||||
this.locationConfig.allowDuplicateItemsInStaticContainers,
|
||||
locklist,
|
||||
);
|
||||
|
||||
// Add forced loot to chosen item pool
|
||||
const tplsToAddToContainer = tplsForced.concat(chosenTpls);
|
||||
@ -368,11 +439,18 @@ export class LocationGenerator
|
||||
continue;
|
||||
}
|
||||
|
||||
containerMap = this.containerHelper.fillContainerMapWithItem(containerMap, result.x, result.y, width, height, result.rotation);
|
||||
containerMap = this.containerHelper.fillContainerMapWithItem(
|
||||
containerMap,
|
||||
result.x,
|
||||
result.y,
|
||||
width,
|
||||
height,
|
||||
result.rotation,
|
||||
);
|
||||
const rotation = result.rotation ? 1 : 0;
|
||||
|
||||
items[0].slotId = "main";
|
||||
items[0].location = { x: result.x, y: result.y, r: rotation };
|
||||
items[0].location = {x: result.x, y: result.y, r: rotation};
|
||||
|
||||
// Add loot to container before returning
|
||||
for (const item of items)
|
||||
@ -409,15 +487,19 @@ export class LocationGenerator
|
||||
* @param locationName Map name (to get per-map multiplier for from config)
|
||||
* @returns item count
|
||||
*/
|
||||
protected getWeightedCountOfContainerItems(containerTypeId: string, staticLootDist: Record<string, IStaticLootDetails>, locationName: string): number
|
||||
protected getWeightedCountOfContainerItems(
|
||||
containerTypeId: string,
|
||||
staticLootDist: Record<string, IStaticLootDetails>,
|
||||
locationName: string,
|
||||
): number
|
||||
{
|
||||
// Create probability array to calcualte the total count of lootable items inside container
|
||||
// Create probability array to calculate the total count of lootable items inside container
|
||||
const itemCountArray = new ProbabilityObjectArray<number>(this.mathUtil, this.jsonUtil);
|
||||
for (const itemCountDistribution of staticLootDist[containerTypeId].itemcountDistribution)
|
||||
{
|
||||
// Add each count of items into array
|
||||
itemCountArray.push(
|
||||
new ProbabilityObject(itemCountDistribution.count, itemCountDistribution.relativeProbability)
|
||||
new ProbabilityObject(itemCountDistribution.count, itemCountDistribution.relativeProbability),
|
||||
);
|
||||
}
|
||||
|
||||
@ -427,11 +509,14 @@ export class LocationGenerator
|
||||
/**
|
||||
* Get all possible loot items that can be placed into a container
|
||||
* Do not add seasonal items if found + current date is inside seasonal event
|
||||
* @param containerTypeId Contianer to get possible loot for
|
||||
* @param containerTypeId Container to get possible loot for
|
||||
* @param staticLootDist staticLoot.json
|
||||
* @returns ProbabilityObjectArray of item tpls + probabilty
|
||||
* @returns ProbabilityObjectArray of item tpls + probability
|
||||
*/
|
||||
protected getPossibleLootItemsForContainer(containerTypeId: string, staticLootDist: Record<string, IStaticLootDetails>): ProbabilityObjectArray<string, number>
|
||||
protected getPossibleLootItemsForContainer(
|
||||
containerTypeId: string,
|
||||
staticLootDist: Record<string, IStaticLootDetails>,
|
||||
): ProbabilityObjectArray<string, number>
|
||||
{
|
||||
const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled();
|
||||
const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems();
|
||||
@ -446,7 +531,7 @@ export class LocationGenerator
|
||||
}
|
||||
|
||||
itemDistribution.push(
|
||||
new ProbabilityObject(icd.tpl, icd.relativeProbability)
|
||||
new ProbabilityObject(icd.tpl, icd.relativeProbability),
|
||||
);
|
||||
}
|
||||
|
||||
@ -465,12 +550,16 @@ export class LocationGenerator
|
||||
|
||||
/**
|
||||
* Create array of loose + forced loot using probability system
|
||||
* @param dynamicLootDist
|
||||
* @param staticAmmoDist
|
||||
* @param dynamicLootDist
|
||||
* @param staticAmmoDist
|
||||
* @param locationName Location to generate loot for
|
||||
* @returns Array of spawn points with loot in them
|
||||
*/
|
||||
public generateDynamicLoot(dynamicLootDist: ILooseLoot, staticAmmoDist: Record<string, IStaticAmmoDetails[]>, locationName: string): SpawnpointTemplate[]
|
||||
public generateDynamicLoot(
|
||||
dynamicLootDist: ILooseLoot,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
locationName: string,
|
||||
): SpawnpointTemplate[]
|
||||
{
|
||||
const loot: SpawnpointTemplate[] = [];
|
||||
|
||||
@ -478,14 +567,14 @@ export class LocationGenerator
|
||||
this.addForcedLoot(loot, dynamicLootDist.spawnpointsForced, locationName);
|
||||
|
||||
const allDynamicSpawnpoints = dynamicLootDist.spawnpoints;
|
||||
|
||||
//Draw from random distribution
|
||||
|
||||
// Draw from random distribution
|
||||
const desiredSpawnpointCount = Math.round(
|
||||
this.getLooseLootMultiplerForLocation(locationName) *
|
||||
this.randomUtil.randn(
|
||||
dynamicLootDist.spawnpointCount.mean,
|
||||
dynamicLootDist.spawnpointCount.std
|
||||
)
|
||||
this.randomUtil.randn(
|
||||
dynamicLootDist.spawnpointCount.mean,
|
||||
dynamicLootDist.spawnpointCount.std,
|
||||
),
|
||||
);
|
||||
|
||||
// Positions not in forced but have 100% chance to spawn
|
||||
@ -496,7 +585,7 @@ export class LocationGenerator
|
||||
|
||||
for (const spawnpoint of allDynamicSpawnpoints)
|
||||
{
|
||||
// Point is blacklsited, skip
|
||||
// Point is blacklisted, skip
|
||||
if (blacklistedSpawnpoints?.includes(spawnpoint.template.Id))
|
||||
{
|
||||
this.logger.debug(`Ignoring loose loot location: ${spawnpoint.template.Id}`);
|
||||
@ -510,7 +599,7 @@ export class LocationGenerator
|
||||
}
|
||||
|
||||
spawnpointArray.push(
|
||||
new ProbabilityObject(spawnpoint.template.Id, spawnpoint.probability, spawnpoint)
|
||||
new ProbabilityObject(spawnpoint.template.Id, spawnpoint.probability, spawnpoint),
|
||||
);
|
||||
}
|
||||
|
||||
@ -525,13 +614,19 @@ export class LocationGenerator
|
||||
}
|
||||
|
||||
// Filter out duplicate locationIds
|
||||
chosenSpawnpoints = [...new Map(chosenSpawnpoints.map(x => [x.locationId, x])).values()];
|
||||
chosenSpawnpoints = [...new Map(chosenSpawnpoints.map((x) => [x.locationId, x])).values()];
|
||||
|
||||
// Do we have enough items in pool to fulfill requirement
|
||||
const tooManySpawnPointsRequested = (desiredSpawnpointCount - chosenSpawnpoints.length) > 0;
|
||||
if (tooManySpawnPointsRequested)
|
||||
{
|
||||
this.logger.debug(this.localisationService.getText("location-spawn_point_count_requested_vs_found", {requested: desiredSpawnpointCount+guaranteedLoosePoints.length, found: chosenSpawnpoints.length, mapName: locationName}));
|
||||
this.logger.debug(
|
||||
this.localisationService.getText("location-spawn_point_count_requested_vs_found", {
|
||||
requested: desiredSpawnpointCount + guaranteedLoosePoints.length,
|
||||
found: chosenSpawnpoints.length,
|
||||
mapName: locationName,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Iterate over spawnpoints
|
||||
@ -541,29 +636,37 @@ export class LocationGenerator
|
||||
{
|
||||
if (!spawnPoint.template)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("location-missing_dynamic_template", spawnPoint.locationId));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-missing_dynamic_template", spawnPoint.locationId),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!spawnPoint.template.Items || spawnPoint.template.Items.length === 0)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-spawnpoint_missing_items", spawnPoint.template.Id));
|
||||
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-spawnpoint_missing_items", spawnPoint.template.Id),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const itemArray = new ProbabilityObjectArray<string>(this.mathUtil, this.jsonUtil);
|
||||
for (const itemDist of spawnPoint.itemDistribution)
|
||||
{
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.includes(spawnPoint.template.Items.find(x => x._id === itemDist.composedKey.key)._tpl))
|
||||
if (
|
||||
!seasonalEventActive && seasonalItemTplBlacklist.includes(
|
||||
spawnPoint.template.Items.find((x) => x._id === itemDist.composedKey.key)._tpl,
|
||||
)
|
||||
)
|
||||
{
|
||||
// Skip seasonal event items if they're not enabled
|
||||
continue;
|
||||
}
|
||||
|
||||
itemArray.push(
|
||||
new ProbabilityObject(itemDist.composedKey.key, itemDist.relativeProbability)
|
||||
new ProbabilityObject(itemDist.composedKey.key, itemDist.relativeProbability),
|
||||
);
|
||||
}
|
||||
|
||||
@ -587,7 +690,11 @@ export class LocationGenerator
|
||||
* @param forcedSpawnPoints forced loot to add
|
||||
* @param name of map currently generating forced loot for
|
||||
*/
|
||||
protected addForcedLoot(loot: SpawnpointTemplate[], forcedSpawnPoints: SpawnpointsForced[], locationName: string): void
|
||||
protected addForcedLoot(
|
||||
loot: SpawnpointTemplate[],
|
||||
forcedSpawnPoints: SpawnpointsForced[],
|
||||
locationName: string,
|
||||
): void
|
||||
{
|
||||
const lootToForceSingleAmountOnMap = this.locationConfig.forcedLootSingleSpawnById[locationName];
|
||||
if (lootToForceSingleAmountOnMap)
|
||||
@ -596,27 +703,32 @@ export class LocationGenerator
|
||||
for (const itemTpl of lootToForceSingleAmountOnMap)
|
||||
{
|
||||
// Get all spawn positions for item tpl in forced loot array
|
||||
const items = forcedSpawnPoints.filter(x => x.template.Items[0]._tpl === itemTpl);
|
||||
const items = forcedSpawnPoints.filter((x) => x.template.Items[0]._tpl === itemTpl);
|
||||
if (!items || items.length === 0)
|
||||
{
|
||||
this.logger.debug(`Unable to adjust loot item ${itemTpl} as it does not exist inside ${locationName} forced loot.`);
|
||||
this.logger.debug(
|
||||
`Unable to adjust loot item ${itemTpl} as it does not exist inside ${locationName} forced loot.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create probability array of all spawn positions for this spawn id
|
||||
const spawnpointArray = new ProbabilityObjectArray<string, SpawnpointsForced>(this.mathUtil, this.jsonUtil);
|
||||
const spawnpointArray = new ProbabilityObjectArray<string, SpawnpointsForced>(
|
||||
this.mathUtil,
|
||||
this.jsonUtil,
|
||||
);
|
||||
for (const si of items)
|
||||
{
|
||||
// use locationId as template.Id is the same across all items
|
||||
spawnpointArray.push(
|
||||
new ProbabilityObject(si.locationId, si.probability, si)
|
||||
new ProbabilityObject(si.locationId, si.probability, si),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Choose 1 out of all found spawn positions for spawn id and add to loot array
|
||||
for (const spawnPointLocationId of spawnpointArray.draw(1, false))
|
||||
{
|
||||
const itemToAdd = items.find(x => x.locationId === spawnPointLocationId);
|
||||
const itemToAdd = items.find((x) => x.locationId === spawnPointLocationId);
|
||||
const lootItem = itemToAdd.template;
|
||||
lootItem.Root = this.objectId.generate();
|
||||
lootItem.Items[0]._id = lootItem.Root;
|
||||
@ -656,29 +768,36 @@ export class LocationGenerator
|
||||
* @param staticAmmoDist ammo distributions
|
||||
* @returns IContainerItem
|
||||
*/
|
||||
protected createDynamicLootItem(chosenComposedKey: string, spawnPoint: Spawnpoint, staticAmmoDist: Record<string, IStaticAmmoDetails[]>): IContainerItem
|
||||
protected createDynamicLootItem(
|
||||
chosenComposedKey: string,
|
||||
spawnPoint: Spawnpoint,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
): IContainerItem
|
||||
{
|
||||
const chosenItem = spawnPoint.template.Items.find(x => x._id === chosenComposedKey);
|
||||
const chosenItem = spawnPoint.template.Items.find((x) => x._id === chosenComposedKey);
|
||||
const chosenTpl = chosenItem._tpl;
|
||||
|
||||
// Item array to return
|
||||
const itemWithMods: Item[] = [];
|
||||
|
||||
// Money/Ammo - don't rely on items in spawnPoint.template.Items so we can randomise it ourselves
|
||||
if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY) || this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO))
|
||||
if (
|
||||
this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY) ||
|
||||
this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO)
|
||||
)
|
||||
{
|
||||
const itemTemplate = this.itemHelper.getItem(chosenTpl)[1];
|
||||
|
||||
const stackCount = itemTemplate._props.StackMaxSize === 1
|
||||
? 1
|
||||
: this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom);
|
||||
const stackCount = itemTemplate._props.StackMaxSize === 1 ?
|
||||
1 :
|
||||
this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom);
|
||||
|
||||
itemWithMods.push(
|
||||
{
|
||||
_id: this.objectId.generate(),
|
||||
_tpl: chosenTpl,
|
||||
upd: { StackObjectsCount: stackCount }
|
||||
}
|
||||
upd: {StackObjectsCount: stackCount},
|
||||
},
|
||||
);
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
|
||||
@ -687,7 +806,7 @@ export class LocationGenerator
|
||||
const ammoBoxTemplate = this.itemHelper.getItem(chosenTpl)[1];
|
||||
const ammoBoxItem: Item[] = [{
|
||||
_id: this.objectId.generate(),
|
||||
_tpl: chosenTpl
|
||||
_tpl: chosenTpl,
|
||||
}];
|
||||
this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, ammoBoxTemplate);
|
||||
itemWithMods.push(...ammoBoxItem);
|
||||
@ -698,15 +817,24 @@ export class LocationGenerator
|
||||
const magazineTemplate = this.itemHelper.getItem(chosenTpl)[1];
|
||||
const magazineItem: Item[] = [{
|
||||
_id: this.objectId.generate(),
|
||||
_tpl: chosenTpl
|
||||
_tpl: chosenTpl,
|
||||
}];
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(magazineItem, magazineTemplate, staticAmmoDist, null, this.locationConfig.minFillLooseMagazinePercent / 100);
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(
|
||||
magazineItem,
|
||||
magazineTemplate,
|
||||
staticAmmoDist,
|
||||
null,
|
||||
this.locationConfig.minFillLooseMagazinePercent / 100,
|
||||
);
|
||||
itemWithMods.push(...magazineItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get item + children and add into array we return
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(spawnPoint.template.Items, chosenItem._id);
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
spawnPoint.template.Items,
|
||||
chosenItem._id,
|
||||
);
|
||||
|
||||
// We need to reparent to ensure ids are unique
|
||||
this.reparentItemAndChildren(itemWithChildren);
|
||||
@ -720,7 +848,7 @@ export class LocationGenerator
|
||||
return {
|
||||
items: itemWithMods,
|
||||
width: size.width,
|
||||
height: size.height
|
||||
height: size.height,
|
||||
};
|
||||
}
|
||||
|
||||
@ -757,14 +885,18 @@ export class LocationGenerator
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON))
|
||||
{
|
||||
return items.find(v => v._tpl === chosenTpl && v.parentId === undefined);
|
||||
return items.find((v) => v._tpl === chosenTpl && v.parentId === undefined);
|
||||
}
|
||||
|
||||
return items.find(x => x._tpl === chosenTpl);
|
||||
return items.find((x) => x._tpl === chosenTpl);
|
||||
}
|
||||
|
||||
// TODO: rewrite, BIG yikes
|
||||
protected createStaticLootItem(tpl: string, staticAmmoDist: Record<string, IStaticAmmoDetails[]>, parentId: string = undefined): IContainerItem
|
||||
protected createStaticLootItem(
|
||||
tpl: string,
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
parentId: string = undefined,
|
||||
): IContainerItem
|
||||
{
|
||||
const itemTemplate = this.itemHelper.getItem(tpl)[1];
|
||||
let width = itemTemplate._props.Width;
|
||||
@ -772,8 +904,8 @@ export class LocationGenerator
|
||||
let items: Item[] = [
|
||||
{
|
||||
_id: this.objectId.generate(),
|
||||
_tpl: tpl
|
||||
}
|
||||
_tpl: tpl,
|
||||
},
|
||||
];
|
||||
|
||||
// Use passed in parentId as override for new item
|
||||
@ -782,13 +914,16 @@ export class LocationGenerator
|
||||
items[0].parentId = parentId;
|
||||
}
|
||||
|
||||
if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.MONEY) || this.itemHelper.isOfBaseclass(tpl, BaseClasses.AMMO))
|
||||
if (
|
||||
this.itemHelper.isOfBaseclass(tpl, BaseClasses.MONEY) ||
|
||||
this.itemHelper.isOfBaseclass(tpl, BaseClasses.AMMO)
|
||||
)
|
||||
{
|
||||
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
|
||||
const stackCount = itemTemplate._props.StackMaxSize === 1
|
||||
? 1
|
||||
: this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom);
|
||||
items[0].upd = { StackObjectsCount: stackCount };
|
||||
const stackCount = itemTemplate._props.StackMaxSize === 1 ?
|
||||
1 :
|
||||
this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom);
|
||||
items[0].upd = {StackObjectsCount: stackCount};
|
||||
}
|
||||
// No spawn point, use default template
|
||||
else if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.WEAPON))
|
||||
@ -806,21 +941,30 @@ export class LocationGenerator
|
||||
// this item already broke it once without being reproducible tpl = "5839a40f24597726f856b511"; AKS-74UB Default
|
||||
// 5ea03f7400685063ec28bfa8 // ppsh default
|
||||
// 5ba26383d4351e00334c93d9 //mp7_devgru
|
||||
this.logger.warning(this.localisationService.getText("location-preset_not_found", {tpl: tpl, defaultId: defaultPreset._id, defaultName: defaultPreset._name, parentId: parentId}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("location-preset_not_found", {
|
||||
tpl: tpl,
|
||||
defaultId: defaultPreset._id,
|
||||
defaultName: defaultPreset._name,
|
||||
parentId: parentId,
|
||||
}),
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesnt have any default presets and kills this code below as it has no chidren to reparent
|
||||
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesn't have any default presets and kills this code below as it has no children to reparent
|
||||
this.logger.debug(`createItem() No preset found for weapon: ${tpl}`);
|
||||
}
|
||||
|
||||
const rootItem = items[0];
|
||||
if (!rootItem)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-missing_root_item", {tpl: tpl, parentId: parentId}));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-missing_root_item", {tpl: tpl, parentId: parentId}),
|
||||
);
|
||||
|
||||
throw new Error(this.localisationService.getText("location-critical_error_see_log"));
|
||||
}
|
||||
@ -830,21 +974,25 @@ export class LocationGenerator
|
||||
if (children?.length > 0)
|
||||
{
|
||||
items = this.ragfairServerHelper.reparentPresets(rootItem, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("location-unable_to_reparent_item", {tpl: tpl, parentId: parentId}));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("location-unable_to_reparent_item", {
|
||||
tpl: tpl,
|
||||
parentId: parentId,
|
||||
}),
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
// Here we should use generalized BotGenerators functions e.g. fillExistingMagazines in the future since
|
||||
// it can handle revolver ammo (it's not restructured to be used here yet.)
|
||||
// General: Make a WeaponController for Ragfair preset stuff and the generating weapons and ammo stuff from
|
||||
// BotGenerator
|
||||
const magazine = items.filter(x => x.slotId === "mod_magazine")[0];
|
||||
const magazine = items.filter((x) => x.slotId === "mod_magazine")[0];
|
||||
// some weapon presets come without magazine; only fill the mag if it exists
|
||||
if (magazine)
|
||||
{
|
||||
@ -853,7 +1001,12 @@ export class LocationGenerator
|
||||
|
||||
// Create array with just magazine
|
||||
const magazineWithCartridges = [magazine];
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(magazineWithCartridges, magTemplate, staticAmmoDist, weaponTemplate._props.ammoCaliber);
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(
|
||||
magazineWithCartridges,
|
||||
magTemplate,
|
||||
staticAmmoDist,
|
||||
weaponTemplate._props.ammoCaliber,
|
||||
);
|
||||
|
||||
// Replace existing magazine with above array
|
||||
items.splice(items.indexOf(magazine), 1, ...magazineWithCartridges);
|
||||
@ -872,7 +1025,13 @@ export class LocationGenerator
|
||||
{
|
||||
// Create array with just magazine
|
||||
const magazineWithCartridges = [items[0]];
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(magazineWithCartridges, itemTemplate, staticAmmoDist, null, this.locationConfig.minFillStaticMagazinePercent / 100);
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(
|
||||
magazineWithCartridges,
|
||||
itemTemplate,
|
||||
staticAmmoDist,
|
||||
null,
|
||||
this.locationConfig.minFillStaticMagazinePercent / 100,
|
||||
);
|
||||
|
||||
// Replace existing magazine with above array
|
||||
items.splice(items.indexOf(items[0]), 1, ...magazineWithCartridges);
|
||||
@ -881,7 +1040,7 @@ export class LocationGenerator
|
||||
return {
|
||||
items: items,
|
||||
width: width,
|
||||
height: height
|
||||
height: height,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
|
||||
type ItemLimit = {
|
||||
current: number,
|
||||
max: number
|
||||
current: number;
|
||||
max: number;
|
||||
};
|
||||
|
||||
@injectable()
|
||||
@ -38,7 +38,7 @@ export class LootGenerator
|
||||
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("RagfairLinkedItemService") protected ragfairLinkedItemService: RagfairLinkedItemService,
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
)
|
||||
{}
|
||||
|
||||
@ -54,7 +54,10 @@ export class LootGenerator
|
||||
const itemTypeCounts = this.initItemLimitCounter(options.itemLimits);
|
||||
|
||||
const tables = this.databaseServer.getTables();
|
||||
const itemBlacklist = new Set<string>([...this.itemFilterService.getBlacklistedItems(), ...options.itemBlacklist]);
|
||||
const itemBlacklist = new Set<string>([
|
||||
...this.itemFilterService.getBlacklistedItems(),
|
||||
...options.itemBlacklist,
|
||||
]);
|
||||
if (!options.allowBossItems)
|
||||
{
|
||||
for (const bossItem of this.itemFilterService.getBossItems())
|
||||
@ -64,12 +67,17 @@ export class LootGenerator
|
||||
}
|
||||
|
||||
// Handle sealed weapon containers
|
||||
const desiredWeaponCrateCount = this.randomUtil.getInt(options.weaponCrateCount.min, options.weaponCrateCount.max);
|
||||
const desiredWeaponCrateCount = this.randomUtil.getInt(
|
||||
options.weaponCrateCount.min,
|
||||
options.weaponCrateCount.max,
|
||||
);
|
||||
if (desiredWeaponCrateCount > 0)
|
||||
{
|
||||
// Get list of all sealed containers from db
|
||||
const sealedWeaponContainerPool = Object.values(tables.templates.items).filter(x => x._name.includes("event_container_airdrop"));
|
||||
|
||||
const sealedWeaponContainerPool = Object.values(tables.templates.items).filter((x) =>
|
||||
x._name.includes("event_container_airdrop")
|
||||
);
|
||||
|
||||
for (let index = 0; index < desiredWeaponCrateCount; index++)
|
||||
{
|
||||
// Choose one at random + add to results array
|
||||
@ -78,16 +86,18 @@ export class LootGenerator
|
||||
id: this.hashUtil.generate(),
|
||||
tpl: chosenSealedContainer._id,
|
||||
isPreset: false,
|
||||
stackCount: 1
|
||||
stackCount: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get items from items.json that have a type of item + not in global blacklist + basetype is in whitelist
|
||||
const items = Object.entries(tables.templates.items).filter(x => !itemBlacklist.has(x[1]._id)
|
||||
&& x[1]._type.toLowerCase() === "item"
|
||||
&& !x[1]._props.QuestItem
|
||||
&& options.itemTypeWhitelist.includes(x[1]._parent));
|
||||
const items = Object.entries(tables.templates.items).filter((x) =>
|
||||
!itemBlacklist.has(x[1]._id) &&
|
||||
x[1]._type.toLowerCase() === "item" &&
|
||||
!x[1]._props.QuestItem &&
|
||||
options.itemTypeWhitelist.includes(x[1]._parent)
|
||||
);
|
||||
|
||||
const randomisedItemCount = this.randomUtil.getInt(options.itemCount.min, options.itemCount.max);
|
||||
for (let index = 0; index < randomisedItemCount; index++)
|
||||
@ -95,10 +105,12 @@ export class LootGenerator
|
||||
if (!this.findAndAddRandomItemToLoot(items, itemTypeCounts, options, result))
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const globalDefaultPresets = Object.entries(tables.globals.ItemPresets).filter(x => x[1]._encyclopedia !== undefined);
|
||||
const globalDefaultPresets = Object.entries(tables.globals.ItemPresets).filter((x) =>
|
||||
x[1]._encyclopedia !== undefined
|
||||
);
|
||||
const randomisedPresetCount = this.randomUtil.getInt(options.presetCount.min, options.presetCount.max);
|
||||
const itemBlacklistArray = Array.from(itemBlacklist);
|
||||
for (let index = 0; index < randomisedPresetCount; index++)
|
||||
@ -124,7 +136,7 @@ export class LootGenerator
|
||||
{
|
||||
itemTypeCounts[itemTypeId] = {
|
||||
current: 0,
|
||||
max: limits[itemTypeId]
|
||||
max: limits[itemTypeId],
|
||||
};
|
||||
}
|
||||
|
||||
@ -141,9 +153,10 @@ export class LootGenerator
|
||||
*/
|
||||
protected findAndAddRandomItemToLoot(
|
||||
items: [string, ITemplateItem][],
|
||||
itemTypeCounts: Record<string, { current: number; max: number; }>,
|
||||
itemTypeCounts: Record<string, {current: number; max: number;}>,
|
||||
options: LootRequest,
|
||||
result: LootItem[]): boolean
|
||||
result: LootItem[],
|
||||
): boolean
|
||||
{
|
||||
const randomItem = this.randomUtil.getArrayValue(items)[1];
|
||||
|
||||
@ -157,16 +170,18 @@ export class LootGenerator
|
||||
id: this.hashUtil.generate(),
|
||||
tpl: randomItem._id,
|
||||
isPreset: false,
|
||||
stackCount: 1
|
||||
stackCount: 1,
|
||||
};
|
||||
|
||||
// Check if armor has level in allowed whitelist
|
||||
if (randomItem._parent === BaseClasses.ARMOR
|
||||
|| randomItem._parent === BaseClasses.VEST)
|
||||
if (
|
||||
randomItem._parent === BaseClasses.ARMOR ||
|
||||
randomItem._parent === BaseClasses.VEST
|
||||
)
|
||||
{
|
||||
if (!options.armorLevelWhitelist.includes(Number(randomItem._props.armorClass)))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +190,7 @@ export class LootGenerator
|
||||
{
|
||||
newLootItem.stackCount = this.getRandomisedStackCount(randomItem, options);
|
||||
}
|
||||
|
||||
|
||||
newLootItem.tpl = randomItem._id;
|
||||
result.push(newLootItem);
|
||||
|
||||
@ -219,9 +234,10 @@ export class LootGenerator
|
||||
*/
|
||||
protected findAndAddRandomPresetToLoot(
|
||||
globalDefaultPresets: [string, IPreset][],
|
||||
itemTypeCounts: Record<string, { current: number; max: number; }>,
|
||||
itemTypeCounts: Record<string, {current: number; max: number;}>,
|
||||
itemBlacklist: string[],
|
||||
result: LootItem[]): boolean
|
||||
result: LootItem[],
|
||||
): boolean
|
||||
{
|
||||
// Choose random preset and get details from item.json using encyclopedia value (encyclopedia === tplId)
|
||||
const randomPreset = this.randomUtil.getArrayValue(globalDefaultPresets)[1];
|
||||
@ -264,9 +280,9 @@ export class LootGenerator
|
||||
const newLootItem: LootItem = {
|
||||
tpl: randomPreset._items[0]._tpl,
|
||||
isPreset: true,
|
||||
stackCount: 1
|
||||
stackCount: 1,
|
||||
};
|
||||
|
||||
|
||||
result.push(newLootItem);
|
||||
|
||||
if (itemLimitCount)
|
||||
@ -274,7 +290,7 @@ export class LootGenerator
|
||||
// increment item count as its in limit array
|
||||
itemLimitCount.current++;
|
||||
}
|
||||
|
||||
|
||||
// item added okay
|
||||
return true;
|
||||
}
|
||||
@ -289,19 +305,23 @@ export class LootGenerator
|
||||
const itemsToReturn: AddItem[] = [];
|
||||
|
||||
// choose a weapon to give to the player (weighted)
|
||||
const chosenWeaponTpl = this.weightedRandomHelper.getWeightedValue<string>(containerSettings.weaponRewardWeight);
|
||||
const chosenWeaponTpl = this.weightedRandomHelper.getWeightedValue<string>(
|
||||
containerSettings.weaponRewardWeight,
|
||||
);
|
||||
const weaponDetailsDb = this.itemHelper.getItem(chosenWeaponTpl);
|
||||
if (!weaponDetailsDb[0])
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl),
|
||||
);
|
||||
|
||||
return itemsToReturn;
|
||||
}
|
||||
|
||||
|
||||
// Get weapon preset - default or choose a random one from all possible
|
||||
let chosenWeaponPreset = containerSettings.defaultPresetsOnly
|
||||
? this.presetHelper.getDefaultPreset(chosenWeaponTpl)
|
||||
: this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl));
|
||||
let chosenWeaponPreset = containerSettings.defaultPresetsOnly ?
|
||||
this.presetHelper.getDefaultPreset(chosenWeaponTpl) :
|
||||
this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl));
|
||||
|
||||
if (!chosenWeaponPreset)
|
||||
{
|
||||
@ -314,12 +334,14 @@ export class LootGenerator
|
||||
count: 1,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
item_id: chosenWeaponPreset._id,
|
||||
isPreset: true
|
||||
isPreset: true,
|
||||
});
|
||||
|
||||
// Get items related to chosen weapon
|
||||
const linkedItemsToWeapon = this.ragfairLinkedItemService.getLinkedDbItems(chosenWeaponTpl);
|
||||
itemsToReturn.push(...this.getSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset));
|
||||
itemsToReturn.push(
|
||||
...this.getSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset),
|
||||
);
|
||||
|
||||
// Handle non-weapon mod reward types
|
||||
itemsToReturn.push(...this.getSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb[1]));
|
||||
@ -333,7 +355,10 @@ export class LootGenerator
|
||||
* @param weaponDetailsDb Details for the weapon to reward player
|
||||
* @returns AddItem array
|
||||
*/
|
||||
protected getSealedContainerNonWeaponModRewards(containerSettings: ISealedAirdropContainerSettings, weaponDetailsDb: ITemplateItem): AddItem[]
|
||||
protected getSealedContainerNonWeaponModRewards(
|
||||
containerSettings: ISealedAirdropContainerSettings,
|
||||
weaponDetailsDb: ITemplateItem,
|
||||
): AddItem[]
|
||||
{
|
||||
const rewards: AddItem[] = [];
|
||||
|
||||
@ -351,15 +376,15 @@ export class LootGenerator
|
||||
if (rewardTypeId === BaseClasses.AMMO_BOX)
|
||||
{
|
||||
// Get ammoboxes from db
|
||||
const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map(x =>
|
||||
const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map((x) =>
|
||||
{
|
||||
const itemDetails = this.itemHelper.getItem(x);
|
||||
return itemDetails[1];
|
||||
});
|
||||
|
||||
|
||||
// Need to find boxes that matches weapons caliber
|
||||
const weaponCaliber = weaponDetailsDb._props.ammoCaliber;
|
||||
const ammoBoxesMatchingCaliber = ammoBoxesDetails.filter(x => x._props.ammoCaliber === weaponCaliber);
|
||||
const ammoBoxesMatchingCaliber = ammoBoxesDetails.filter((x) => x._props.ammoCaliber === weaponCaliber);
|
||||
if (ammoBoxesMatchingCaliber.length === 0)
|
||||
{
|
||||
this.logger.debug(`No ammo box with caliber ${weaponCaliber} found, skipping`);
|
||||
@ -373,7 +398,7 @@ export class LootGenerator
|
||||
count: rewardCount,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
item_id: chosenAmmoBox._id,
|
||||
isPreset: false
|
||||
isPreset: false,
|
||||
});
|
||||
|
||||
continue;
|
||||
@ -381,11 +406,13 @@ export class LootGenerator
|
||||
|
||||
// Get all items of the desired type + not quest items + not globally blacklisted
|
||||
const rewardItemPool = Object.values(this.databaseServer.getTables().templates.items)
|
||||
.filter(x => x._parent === rewardTypeId
|
||||
&& x._type.toLowerCase() === "item"
|
||||
&& !this.itemFilterService.isItemBlacklisted(x._id)
|
||||
&& (!containerSettings.allowBossItems && !this.itemFilterService.isBossItem(x._id))
|
||||
&& !x._props.QuestItem);
|
||||
.filter((x) =>
|
||||
x._parent === rewardTypeId &&
|
||||
x._type.toLowerCase() === "item" &&
|
||||
!this.itemFilterService.isItemBlacklisted(x._id) &&
|
||||
(!containerSettings.allowBossItems && !this.itemFilterService.isBossItem(x._id)) &&
|
||||
!x._props.QuestItem
|
||||
);
|
||||
|
||||
if (rewardItemPool.length === 0)
|
||||
{
|
||||
@ -398,7 +425,7 @@ export class LootGenerator
|
||||
{
|
||||
// choose a random item from pool
|
||||
const chosenRewardItem = this.randomUtil.getArrayValue(rewardItemPool);
|
||||
this.addOrIncrementItemToArray(chosenRewardItem._id, rewards);
|
||||
this.addOrIncrementItemToArray(chosenRewardItem._id, rewards);
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,7 +439,11 @@ export class LootGenerator
|
||||
* @param chosenWeaponPreset The weapon preset given to player as reward
|
||||
* @returns AddItem array
|
||||
*/
|
||||
protected getSealedContainerWeaponModRewards(containerSettings: ISealedAirdropContainerSettings, linkedItemsToWeapon: ITemplateItem[], chosenWeaponPreset: IPreset): AddItem[]
|
||||
protected getSealedContainerWeaponModRewards(
|
||||
containerSettings: ISealedAirdropContainerSettings,
|
||||
linkedItemsToWeapon: ITemplateItem[],
|
||||
chosenWeaponPreset: IPreset,
|
||||
): AddItem[]
|
||||
{
|
||||
const modRewards: AddItem[] = [];
|
||||
for (const rewardTypeId in containerSettings.weaponModRewardLimits)
|
||||
@ -426,16 +457,20 @@ export class LootGenerator
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get items that fulfil reward type critera from items that fit on gun
|
||||
const relatedItems = linkedItemsToWeapon.filter(x => x._parent === rewardTypeId && !this.itemFilterService.isItemBlacklisted(x._id));
|
||||
// Get items that fulfil reward type criteria from items that fit on gun
|
||||
const relatedItems = linkedItemsToWeapon.filter((x) =>
|
||||
x._parent === rewardTypeId && !this.itemFilterService.isItemBlacklisted(x._id)
|
||||
);
|
||||
if (!relatedItems || relatedItems.length === 0)
|
||||
{
|
||||
this.logger.debug(`No items found to fulfil reward type ${rewardTypeId} for weapon: ${chosenWeaponPreset._name}, skipping type`);
|
||||
this.logger.debug(
|
||||
`No items found to fulfil reward type ${rewardTypeId} for weapon: ${chosenWeaponPreset._name}, skipping type`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a random item of the desired type and add as reward
|
||||
for (let index = 0; index < rewardCount; index++)
|
||||
for (let index = 0; index < rewardCount; index++)
|
||||
{
|
||||
const chosenItem = this.randomUtil.drawRandomFromList(relatedItems);
|
||||
this.addOrIncrementItemToArray(chosenItem[0]._id, modRewards);
|
||||
@ -447,7 +482,7 @@ export class LootGenerator
|
||||
|
||||
/**
|
||||
* Handle event-related loot containers - currently just the halloween jack-o-lanterns that give food rewards
|
||||
* @param rewardContainerDetails
|
||||
* @param rewardContainerDetails
|
||||
* @returns AddItem array
|
||||
*/
|
||||
public getRandomLootContainerLoot(rewardContainerDetails: RewardDetails): AddItem[]
|
||||
@ -458,7 +493,9 @@ export class LootGenerator
|
||||
for (let index = 0; index < rewardContainerDetails.rewardCount; index++)
|
||||
{
|
||||
// Pick random reward from pool, add to request object
|
||||
const chosenRewardItemTpl = this.weightedRandomHelper.getWeightedValue<string>(rewardContainerDetails.rewardTplPool);
|
||||
const chosenRewardItemTpl = this.weightedRandomHelper.getWeightedValue<string>(
|
||||
rewardContainerDetails.rewardTplPool,
|
||||
);
|
||||
this.addOrIncrementItemToArray(chosenRewardItemTpl, itemsToReturn);
|
||||
}
|
||||
|
||||
@ -473,7 +510,7 @@ export class LootGenerator
|
||||
*/
|
||||
protected addOrIncrementItemToArray(itemTplToAdd: string, resultsArray: AddItem[]): void
|
||||
{
|
||||
const existingItemIndex = resultsArray.findIndex(x => x.item_id === itemTplToAdd);
|
||||
const existingItemIndex = resultsArray.findIndex((x) => x.item_id === itemTplToAdd);
|
||||
if (existingItemIndex > -1)
|
||||
{
|
||||
// Exists in array already, increment count
|
||||
@ -485,4 +522,4 @@ export class LootGenerator
|
||||
resultsArray.push({item_id: itemTplToAdd, count: 1, isPreset: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,10 @@ import { ItemFilterService } from "@spt-aki/services/ItemFilterService";
|
||||
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
|
||||
|
||||
/**
|
||||
* Handle the generation of dynamic PMC loot in pockets and backpacks
|
||||
* Handle the generation of dynamic PMC loot in pockets and backpacks
|
||||
* and the removal of blacklisted items
|
||||
*/
|
||||
@injectable()
|
||||
|
||||
export class PMCLootGenerator
|
||||
{
|
||||
protected pocketLootPool: string[] = [];
|
||||
@ -27,7 +26,7 @@ export class PMCLootGenerator
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
)
|
||||
{
|
||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
@ -47,7 +46,7 @@ export class PMCLootGenerator
|
||||
const allowedItemTypes = this.pmcConfig.pocketLoot.whitelist;
|
||||
const pmcItemBlacklist = this.pmcConfig.pocketLoot.blacklist;
|
||||
const itemBlacklist = this.itemFilterService.getBlacklistedItems();
|
||||
|
||||
|
||||
// Blacklist seasonal items if not inside seasonal event
|
||||
// Blacklist seasonal items if not inside seasonal event
|
||||
if (!this.seasonalEventService.seasonalEventEnabled())
|
||||
@ -56,14 +55,16 @@ export class PMCLootGenerator
|
||||
itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems());
|
||||
}
|
||||
|
||||
const itemsToAdd = Object.values(items).filter(item => allowedItemTypes.includes(item._parent)
|
||||
&& this.itemHelper.isValidItem(item._id)
|
||||
&& !pmcItemBlacklist.includes(item._id)
|
||||
&& !itemBlacklist.includes(item._id)
|
||||
&& item._props.Width === 1
|
||||
&& item._props.Height === 1);
|
||||
const itemsToAdd = Object.values(items).filter((item) =>
|
||||
allowedItemTypes.includes(item._parent) &&
|
||||
this.itemHelper.isValidItem(item._id) &&
|
||||
!pmcItemBlacklist.includes(item._id) &&
|
||||
!itemBlacklist.includes(item._id) &&
|
||||
item._props.Width === 1 &&
|
||||
item._props.Height === 1
|
||||
);
|
||||
|
||||
this.pocketLootPool = itemsToAdd.map(x => x._id);
|
||||
this.pocketLootPool = itemsToAdd.map((x) => x._id);
|
||||
}
|
||||
|
||||
return this.pocketLootPool;
|
||||
@ -83,7 +84,7 @@ export class PMCLootGenerator
|
||||
const allowedItemTypes = this.pmcConfig.vestLoot.whitelist;
|
||||
const pmcItemBlacklist = this.pmcConfig.vestLoot.blacklist;
|
||||
const itemBlacklist = this.itemFilterService.getBlacklistedItems();
|
||||
|
||||
|
||||
// Blacklist seasonal items if not inside seasonal event
|
||||
// Blacklist seasonal items if not inside seasonal event
|
||||
if (!this.seasonalEventService.seasonalEventEnabled())
|
||||
@ -92,13 +93,15 @@ export class PMCLootGenerator
|
||||
itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems());
|
||||
}
|
||||
|
||||
const itemsToAdd = Object.values(items).filter(item => allowedItemTypes.includes(item._parent)
|
||||
&& this.itemHelper.isValidItem(item._id)
|
||||
&& !pmcItemBlacklist.includes(item._id)
|
||||
&& !itemBlacklist.includes(item._id)
|
||||
&& this.itemFitsInto2By2Slot(item));
|
||||
const itemsToAdd = Object.values(items).filter((item) =>
|
||||
allowedItemTypes.includes(item._parent) &&
|
||||
this.itemHelper.isValidItem(item._id) &&
|
||||
!pmcItemBlacklist.includes(item._id) &&
|
||||
!itemBlacklist.includes(item._id) &&
|
||||
this.itemFitsInto2By2Slot(item)
|
||||
);
|
||||
|
||||
this.vestLootPool = itemsToAdd.map(x => x._id);
|
||||
this.vestLootPool = itemsToAdd.map((x) => x._id);
|
||||
}
|
||||
|
||||
return this.vestLootPool;
|
||||
@ -129,7 +132,7 @@ export class PMCLootGenerator
|
||||
const allowedItemTypes = this.pmcConfig.backpackLoot.whitelist;
|
||||
const pmcItemBlacklist = this.pmcConfig.backpackLoot.blacklist;
|
||||
const itemBlacklist = this.itemFilterService.getBlacklistedItems();
|
||||
|
||||
|
||||
// blacklist event items if not inside seasonal event
|
||||
if (!this.seasonalEventService.seasonalEventEnabled())
|
||||
{
|
||||
@ -137,14 +140,16 @@ export class PMCLootGenerator
|
||||
itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems());
|
||||
}
|
||||
|
||||
const itemsToAdd = Object.values(items).filter(item => allowedItemTypes.includes(item._parent)
|
||||
&& this.itemHelper.isValidItem(item._id)
|
||||
&& !pmcItemBlacklist.includes(item._id)
|
||||
&& !itemBlacklist.includes(item._id));
|
||||
const itemsToAdd = Object.values(items).filter((item) =>
|
||||
allowedItemTypes.includes(item._parent) &&
|
||||
this.itemHelper.isValidItem(item._id) &&
|
||||
!pmcItemBlacklist.includes(item._id) &&
|
||||
!itemBlacklist.includes(item._id)
|
||||
);
|
||||
|
||||
this.backpackLootPool = itemsToAdd.map(x => x._id);
|
||||
this.backpackLootPool = itemsToAdd.map((x) => x._id);
|
||||
}
|
||||
|
||||
return this.backpackLootPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ export class PlayerScavGenerator
|
||||
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("BotGenerator") protected botGenerator: BotGenerator,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.playerScavConfig = this.configServer.getConfig(ConfigTypes.PLAYERSCAV);
|
||||
@ -66,9 +66,9 @@ export class PlayerScavGenerator
|
||||
const existingScavData = this.jsonUtil.clone(profile.characters.scav);
|
||||
|
||||
// scav profile can be empty on first profile creation
|
||||
const scavKarmaLevel = ((Object.keys(existingScavData).length === 0))
|
||||
? 0
|
||||
: this.getScavKarmaLevel(pmcData);
|
||||
const scavKarmaLevel = (Object.keys(existingScavData).length === 0) ?
|
||||
0 :
|
||||
this.getScavKarmaLevel(pmcData);
|
||||
|
||||
// use karma level to get correct karmaSettings
|
||||
const playerScavKarmaSettings = this.playerScavConfig.karmaLevel[scavKarmaLevel];
|
||||
@ -83,7 +83,12 @@ export class PlayerScavGenerator
|
||||
const baseBotNode: IBotType = this.constructBotBaseTemplate(playerScavKarmaSettings.botTypeForLoot);
|
||||
this.adjustBotTemplateWithKarmaSpecificSettings(playerScavKarmaSettings, baseBotNode);
|
||||
|
||||
let scavData = this.botGenerator.generatePlayerScav(sessionID, playerScavKarmaSettings.botTypeForLoot.toLowerCase(), "easy", baseBotNode);
|
||||
let scavData = this.botGenerator.generatePlayerScav(
|
||||
sessionID,
|
||||
playerScavKarmaSettings.botTypeForLoot.toLowerCase(),
|
||||
"easy",
|
||||
baseBotNode,
|
||||
);
|
||||
|
||||
// Remove cached bot data after scav was generated
|
||||
this.botLootCacheService.clearCache();
|
||||
@ -113,7 +118,6 @@ export class PlayerScavGenerator
|
||||
scavData.Notes = existingScavData.Notes ?? {Notes: []};
|
||||
scavData.WishList = existingScavData.WishList ?? [];
|
||||
|
||||
|
||||
// Add an extra labs card to pscav backpack based on config chance
|
||||
if (this.randomUtil.getChance100(playerScavKarmaSettings.labsAccessCardChancePercent))
|
||||
{
|
||||
@ -121,9 +125,15 @@ export class PlayerScavGenerator
|
||||
const itemsToAdd: Item[] = [{
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: labsCard._id,
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard)
|
||||
...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard),
|
||||
}];
|
||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(["TacticalVest", "Pockets", "Backpack"], itemsToAdd[0]._id, labsCard._id, itemsToAdd, scavData.Inventory);
|
||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
||||
["TacticalVest", "Pockets", "Backpack"],
|
||||
itemsToAdd[0]._id,
|
||||
labsCard._id,
|
||||
itemsToAdd,
|
||||
scavData.Inventory,
|
||||
);
|
||||
}
|
||||
|
||||
// Remove secure container
|
||||
@ -251,7 +261,7 @@ export class PlayerScavGenerator
|
||||
return {
|
||||
Common: [],
|
||||
Mastering: [],
|
||||
Points: 0
|
||||
Points: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -292,7 +302,7 @@ export class PlayerScavGenerator
|
||||
* take into account scav cooldown bonus
|
||||
* @param scavData scav profile
|
||||
* @param pmcData pmc profile
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
protected setScavCooldownTimer(scavData: IPmcData, pmcData: IPmcData): IPmcData
|
||||
{
|
||||
@ -314,7 +324,7 @@ export class PlayerScavGenerator
|
||||
const fenceInfo = this.fenceService.getFenceInfo(pmcData);
|
||||
modifier *= fenceInfo.SavageCooldownModifier;
|
||||
scavLockDuration *= modifier;
|
||||
|
||||
|
||||
const fullProfile = this.profileHelper.getFullProfile(pmcData?.sessionId);
|
||||
if (fullProfile?.info?.edition?.toLowerCase?.().startsWith?.(AccountTypes.SPT_DEVELOPER))
|
||||
{
|
||||
@ -323,7 +333,7 @@ export class PlayerScavGenerator
|
||||
}
|
||||
|
||||
scavData.Info.SavageLockTime = (Date.now() / 1000) + scavLockDuration;
|
||||
|
||||
|
||||
return scavData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export class RagfairAssortGenerator
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
@ -62,9 +62,9 @@ export class RagfairAssortGenerator
|
||||
const results: Item[] = [];
|
||||
const items = this.itemHelper.getItems();
|
||||
|
||||
const weaponPresets = (this.ragfairConfig.dynamic.showDefaultPresetsOnly)
|
||||
? this.getDefaultPresets()
|
||||
: this.getPresets();
|
||||
const weaponPresets = (this.ragfairConfig.dynamic.showDefaultPresetsOnly) ?
|
||||
this.getDefaultPresets() :
|
||||
this.getPresets();
|
||||
|
||||
const ragfairItemInvalidBaseTypes: string[] = [
|
||||
BaseClasses.LOOT_CONTAINER, // safe, barrel cache etc
|
||||
@ -72,7 +72,7 @@ export class RagfairAssortGenerator
|
||||
BaseClasses.SORTING_TABLE,
|
||||
BaseClasses.INVENTORY,
|
||||
BaseClasses.STATIONARY_CONTAINER,
|
||||
BaseClasses.POCKETS
|
||||
BaseClasses.POCKETS,
|
||||
];
|
||||
|
||||
const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled();
|
||||
@ -84,7 +84,10 @@ export class RagfairAssortGenerator
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.ragfairConfig.dynamic.removeSeasonalItemsWhenNotInEvent && !seasonalEventActive && seasonalItemTplBlacklist.includes(item._id))
|
||||
if (
|
||||
this.ragfairConfig.dynamic.removeSeasonalItemsWhenNotInEvent && !seasonalEventActive &&
|
||||
seasonalItemTplBlacklist.includes(item._id)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -99,7 +102,7 @@ export class RagfairAssortGenerator
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get presets from globals.json
|
||||
* @returns Preset object array
|
||||
@ -116,9 +119,9 @@ export class RagfairAssortGenerator
|
||||
*/
|
||||
protected getDefaultPresets(): IPreset[]
|
||||
{
|
||||
return this.getPresets().filter(x => x._encyclopedia);
|
||||
return this.getPresets().filter((x) => x._encyclopedia);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a base assort item and return it with populated values + 999999 stack count + unlimited count = true
|
||||
* @param tplId tplid to add to item
|
||||
@ -134,8 +137,8 @@ export class RagfairAssortGenerator
|
||||
slotId: "hideout",
|
||||
upd: {
|
||||
StackObjectsCount: 99999999,
|
||||
UnlimitedCount: true
|
||||
}
|
||||
UnlimitedCount: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ import { TimeUtil } from "@spt-aki/utils/TimeUtil";
|
||||
export class RagfairOfferGenerator
|
||||
{
|
||||
protected ragfairConfig: IRagfairConfig;
|
||||
protected allowedFleaPriceItemsForBarter: { tpl: string; price: number; }[];
|
||||
protected allowedFleaPriceItemsForBarter: {tpl: string; price: number;}[];
|
||||
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@ -54,7 +54,7 @@ export class RagfairOfferGenerator
|
||||
@inject("RagfairCategoriesService") protected ragfairCategoriesService: RagfairCategoriesService,
|
||||
@inject("FenceService") protected fenceService: FenceService,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
@ -70,7 +70,14 @@ export class RagfairOfferGenerator
|
||||
* @param sellInOnePiece Flags sellInOnePiece to be true
|
||||
* @returns IRagfairOffer
|
||||
*/
|
||||
public createFleaOffer(userID: string, time: number, items: Item[], barterScheme: IBarterScheme[], loyalLevel: number, sellInOnePiece = false): IRagfairOffer
|
||||
public createFleaOffer(
|
||||
userID: string,
|
||||
time: number,
|
||||
items: Item[],
|
||||
barterScheme: IBarterScheme[],
|
||||
loyalLevel: number,
|
||||
sellInOnePiece = false,
|
||||
): IRagfairOffer
|
||||
{
|
||||
const offer = this.createOffer(userID, time, items, barterScheme, loyalLevel, sellInOnePiece);
|
||||
this.ragfairOfferService.addOffer(offer);
|
||||
@ -88,7 +95,14 @@ export class RagfairOfferGenerator
|
||||
* @param sellInOnePiece Set StackObjectsCount to 1
|
||||
* @returns IRagfairOffer
|
||||
*/
|
||||
protected createOffer(userID: string, time: number, items: Item[], barterScheme: IBarterScheme[], loyalLevel: number, sellInOnePiece = false): IRagfairOffer
|
||||
protected createOffer(
|
||||
userID: string,
|
||||
time: number,
|
||||
items: Item[],
|
||||
barterScheme: IBarterScheme[],
|
||||
loyalLevel: number,
|
||||
sellInOnePiece = false,
|
||||
): IRagfairOffer
|
||||
{
|
||||
const isTrader = this.ragfairServerHelper.isTrader(userID);
|
||||
|
||||
@ -98,13 +112,13 @@ export class RagfairOfferGenerator
|
||||
const requirement: OfferRequirement = {
|
||||
_tpl: barter._tpl,
|
||||
count: +barter.count.toFixed(2),
|
||||
onlyFunctional: barter.onlyFunctional ?? false
|
||||
onlyFunctional: barter.onlyFunctional ?? false,
|
||||
};
|
||||
|
||||
offerRequirements.push(requirement);
|
||||
}
|
||||
|
||||
const itemCount = items.filter(x => x.slotId === "hideout").length;
|
||||
const itemCount = items.filter((x) => x.slotId === "hideout").length;
|
||||
const roublePrice = Math.round(this.convertOfferRequirementsIntoRoubles(offerRequirements));
|
||||
|
||||
const offer: IRagfairOffer = {
|
||||
@ -112,13 +126,13 @@ export class RagfairOfferGenerator
|
||||
intId: 0,
|
||||
user: {
|
||||
id: this.getTraderId(userID),
|
||||
memberType: (userID === "ragfair")
|
||||
? MemberCategory.DEFAULT
|
||||
: this.ragfairServerHelper.getMemberType(userID),
|
||||
memberType: (userID === "ragfair") ?
|
||||
MemberCategory.DEFAULT :
|
||||
this.ragfairServerHelper.getMemberType(userID),
|
||||
nickname: this.ragfairServerHelper.getNickname(userID),
|
||||
rating: this.getRating(userID),
|
||||
isRatingGrowing: this.getRatingGrowing(userID),
|
||||
avatar: this.getAvatarUrl(isTrader, userID)
|
||||
avatar: this.getAvatarUrl(isTrader, userID),
|
||||
},
|
||||
root: items[0]._id,
|
||||
items: this.jsonUtil.clone(items),
|
||||
@ -134,7 +148,7 @@ export class RagfairOfferGenerator
|
||||
locked: false,
|
||||
unlimitedCount: false,
|
||||
notAvailable: false,
|
||||
CurrentItemCount: itemCount
|
||||
CurrentItemCount: itemCount,
|
||||
};
|
||||
|
||||
return offer;
|
||||
@ -150,9 +164,9 @@ export class RagfairOfferGenerator
|
||||
let roublePrice = 0;
|
||||
for (const requirement of offerRequirements)
|
||||
{
|
||||
roublePrice += this.paymentHelper.isMoneyTpl(requirement._tpl)
|
||||
? Math.round(this.calculateRoublePrice(requirement.count, requirement._tpl))
|
||||
: this.ragfairPriceService.getFleaPriceForItem(requirement._tpl) * requirement.count; // get flea price for barter offer items
|
||||
roublePrice += this.paymentHelper.isMoneyTpl(requirement._tpl) ?
|
||||
Math.round(this.calculateRoublePrice(requirement.count, requirement._tpl)) :
|
||||
this.ragfairPriceService.getFleaPriceForItem(requirement._tpl) * requirement.count; // get flea price for barter offer items
|
||||
}
|
||||
|
||||
return roublePrice;
|
||||
@ -249,7 +263,7 @@ export class RagfairOfferGenerator
|
||||
return true;
|
||||
}
|
||||
|
||||
// generated offer
|
||||
// generated offer
|
||||
// 50/50 growing/falling
|
||||
return this.randomUtil.getBool();
|
||||
}
|
||||
@ -275,7 +289,13 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
// Generated fake-player offer
|
||||
return Math.round(time + this.randomUtil.getInt(this.ragfairConfig.dynamic.endTimeSeconds.min, this.ragfairConfig.dynamic.endTimeSeconds.max));
|
||||
return Math.round(
|
||||
time +
|
||||
this.randomUtil.getInt(
|
||||
this.ragfairConfig.dynamic.endTimeSeconds.min,
|
||||
this.ragfairConfig.dynamic.endTimeSeconds.max,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,28 +307,34 @@ export class RagfairOfferGenerator
|
||||
const config = this.ragfairConfig.dynamic;
|
||||
|
||||
// get assort items from param if they exist, otherwise grab freshly generated assorts
|
||||
const assortItemsToProcess: Item[] = (expiredOffers)
|
||||
? expiredOffers
|
||||
: this.ragfairAssortGenerator.getAssortItems();
|
||||
const assortItemsToProcess: Item[] = expiredOffers ?
|
||||
expiredOffers :
|
||||
this.ragfairAssortGenerator.getAssortItems();
|
||||
|
||||
// Store all functions to create an offer for every item and pass into Promise.all to run async
|
||||
const assorOffersForItemsProcesses = [];
|
||||
for (const assortItemIndex in assortItemsToProcess)
|
||||
{
|
||||
assorOffersForItemsProcesses.push(this.createOffersForItems(assortItemIndex, assortItemsToProcess, expiredOffers, config));
|
||||
assorOffersForItemsProcesses.push(
|
||||
this.createOffersForItems(assortItemIndex, assortItemsToProcess, expiredOffers, config),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(assorOffersForItemsProcesses);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param assortItemIndex Index of assort item
|
||||
* @param assortItemsToProcess Item array containing index
|
||||
* @param expiredOffers Currently expired offers on flea
|
||||
* @param config Ragfair dynamic config
|
||||
*/
|
||||
protected async createOffersForItems(assortItemIndex: string, assortItemsToProcess: Item[], expiredOffers: Item[], config: Dynamic): Promise<void>
|
||||
protected async createOffersForItems(
|
||||
assortItemIndex: string,
|
||||
assortItemsToProcess: Item[],
|
||||
expiredOffers: Item[],
|
||||
config: Dynamic,
|
||||
): Promise<void>
|
||||
{
|
||||
const assortItem = assortItemsToProcess[assortItemIndex];
|
||||
const itemDetails = this.itemHelper.getItem(assortItem._tpl);
|
||||
@ -322,15 +348,21 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
// Get item + sub-items if preset, otherwise just get item
|
||||
const items: Item[] = (isPreset)
|
||||
? this.ragfairServerHelper.getPresetItems(assortItem)
|
||||
: [...[assortItem], ...this.itemHelper.findAndReturnChildrenByAssort(assortItem._id, this.ragfairAssortGenerator.getAssortItems())];
|
||||
const items: Item[] = isPreset ?
|
||||
this.ragfairServerHelper.getPresetItems(assortItem) :
|
||||
[
|
||||
...[assortItem],
|
||||
...this.itemHelper.findAndReturnChildrenByAssort(
|
||||
assortItem._id,
|
||||
this.ragfairAssortGenerator.getAssortItems(),
|
||||
),
|
||||
];
|
||||
|
||||
// Get number of offers to create
|
||||
// Limit to 1 offer when processing expired
|
||||
const offerCount = (expiredOffers)
|
||||
? 1
|
||||
: Math.round(this.randomUtil.getInt(config.offerItemCount.min, config.offerItemCount.max));
|
||||
const offerCount = expiredOffers ?
|
||||
1 :
|
||||
Math.round(this.randomUtil.getInt(config.offerItemCount.min, config.offerItemCount.max));
|
||||
|
||||
// Store all functions to create offers for this item and pass into Promise.all to run async
|
||||
const assortSingleOfferProcesses = [];
|
||||
@ -342,7 +374,6 @@ export class RagfairOfferGenerator
|
||||
await Promise.all(assortSingleOfferProcesses);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create one flea offer for a specific item
|
||||
* @param items Item to create offer for
|
||||
@ -350,23 +381,30 @@ export class RagfairOfferGenerator
|
||||
* @param itemDetails raw db item details
|
||||
* @returns Item array
|
||||
*/
|
||||
protected async createSingleOfferForItem(items: Item[], isPreset: boolean, itemDetails: [boolean, ITemplateItem]): Promise<void>
|
||||
protected async createSingleOfferForItem(
|
||||
items: Item[],
|
||||
isPreset: boolean,
|
||||
itemDetails: [boolean, ITemplateItem],
|
||||
): Promise<void>
|
||||
{
|
||||
// Set stack size to random value
|
||||
items[0].upd.StackObjectsCount = this.ragfairServerHelper.calculateDynamicStackCount(items[0]._tpl, isPreset);
|
||||
|
||||
|
||||
const isBarterOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.barter.chancePercent);
|
||||
const isPackOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent)
|
||||
&& !isBarterOffer
|
||||
&& items.length === 1
|
||||
&& this.itemHelper.isOfBaseclasses(items[0]._tpl, this.ragfairConfig.dynamic.pack.itemTypeWhitelist);
|
||||
const isPackOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent) &&
|
||||
!isBarterOffer &&
|
||||
items.length === 1 &&
|
||||
this.itemHelper.isOfBaseclasses(items[0]._tpl, this.ragfairConfig.dynamic.pack.itemTypeWhitelist);
|
||||
const randomUserId = this.hashUtil.generate();
|
||||
|
||||
let barterScheme: IBarterScheme[];
|
||||
if (isPackOffer)
|
||||
{
|
||||
// Set pack size
|
||||
const stackSize = this.randomUtil.getInt(this.ragfairConfig.dynamic.pack.itemCountMin, this.ragfairConfig.dynamic.pack.itemCountMax);
|
||||
const stackSize = this.randomUtil.getInt(
|
||||
this.ragfairConfig.dynamic.pack.itemCountMin,
|
||||
this.ragfairConfig.dynamic.pack.itemCountMax,
|
||||
);
|
||||
items[0].upd.StackObjectsCount = stackSize;
|
||||
|
||||
// Don't randomise pack items
|
||||
@ -391,7 +429,8 @@ export class RagfairOfferGenerator
|
||||
items,
|
||||
barterScheme,
|
||||
1,
|
||||
isPreset || isPackOffer); // sellAsOnePiece
|
||||
isPreset || isPackOffer,
|
||||
); // sellAsOnePiece
|
||||
|
||||
this.ragfairCategoriesService.incrementCategory(offer);
|
||||
}
|
||||
@ -413,7 +452,12 @@ export class RagfairOfferGenerator
|
||||
// Trader assorts / assort items are missing
|
||||
if (!assorts?.items?.length)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("ragfair-no_trader_assorts_cant_generate_flea_offers", trader.base.nickname));
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"ragfair-no_trader_assorts_cant_generate_flea_offers",
|
||||
trader.base.nickname,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -444,14 +488,20 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
const isPreset = this.presetHelper.isPreset(item._id);
|
||||
const items: Item[] = (isPreset)
|
||||
? this.ragfairServerHelper.getPresetItems(item)
|
||||
: [...[item], ...this.itemHelper.findAndReturnChildrenByAssort(item._id, assorts.items)];
|
||||
const items: Item[] = isPreset ?
|
||||
this.ragfairServerHelper.getPresetItems(item) :
|
||||
[...[item], ...this.itemHelper.findAndReturnChildrenByAssort(item._id, assorts.items)];
|
||||
|
||||
const barterScheme = assorts.barter_scheme[item._id];
|
||||
if (!barterScheme)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("ragfair-missing_barter_scheme", {itemId: item._id, tpl: item._tpl, name: trader.base.nickname}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("ragfair-missing_barter_scheme", {
|
||||
itemId: item._id,
|
||||
tpl: item._tpl,
|
||||
name: trader.base.nickname,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -473,11 +523,11 @@ export class RagfairOfferGenerator
|
||||
* @param userID id of owner of item
|
||||
* @param itemWithMods Item and mods, get condition of first item (only first array item is used)
|
||||
* @param itemDetails db details of first item
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
protected randomiseItemUpdProperties(userID: string, itemWithMods: Item[], itemDetails: ITemplateItem): Item[]
|
||||
{
|
||||
// Add any missing properties to first item in array
|
||||
// Add any missing properties to first item in array
|
||||
itemWithMods[0] = this.addMissingConditions(itemWithMods[0]);
|
||||
|
||||
if (!(this.ragfairServerHelper.isPlayer(userID) || this.ragfairServerHelper.isTrader(userID)))
|
||||
@ -508,9 +558,9 @@ export class RagfairOfferGenerator
|
||||
{
|
||||
// Get keys from condition config dictionary
|
||||
const configConditions = Object.keys(this.ragfairConfig.dynamic.condition);
|
||||
for (const baseClass of configConditions)
|
||||
for (const baseClass of configConditions)
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(tpl, baseClass))
|
||||
if (this.itemHelper.isOfBaseclass(tpl, baseClass))
|
||||
{
|
||||
return baseClass;
|
||||
}
|
||||
@ -527,7 +577,10 @@ export class RagfairOfferGenerator
|
||||
*/
|
||||
protected randomiseItemCondition(conditionSettingsId: string, item: Item, itemDetails: ITemplateItem): void
|
||||
{
|
||||
const multiplier = this.randomUtil.getFloat(this.ragfairConfig.dynamic.condition[conditionSettingsId].min, this.ragfairConfig.dynamic.condition[conditionSettingsId].max);
|
||||
const multiplier = this.randomUtil.getFloat(
|
||||
this.ragfairConfig.dynamic.condition[conditionSettingsId].min,
|
||||
this.ragfairConfig.dynamic.condition[conditionSettingsId].max,
|
||||
);
|
||||
|
||||
// Armor or weapons
|
||||
if (item.upd.Repairable)
|
||||
@ -571,7 +624,7 @@ export class RagfairOfferGenerator
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.upd.RepairKit)
|
||||
if (item.upd.RepairKit)
|
||||
{
|
||||
// randomize repair kit (armor/weapon) uses
|
||||
item.upd.RepairKit.Resource = Math.round(itemDetails._props.MaxRepairResource * multiplier) || 1;
|
||||
@ -585,7 +638,7 @@ export class RagfairOfferGenerator
|
||||
const remainingFuel = Math.round(totalCapacity * multiplier);
|
||||
item.upd.Resource = {
|
||||
UnitsConsumed: totalCapacity - remainingFuel,
|
||||
Value: remainingFuel
|
||||
Value: remainingFuel,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -600,7 +653,9 @@ export class RagfairOfferGenerator
|
||||
item.upd.Repairable.Durability = Math.round(item.upd.Repairable.Durability * multiplier) || 1;
|
||||
|
||||
// randomize max durability, store to a temporary value so we can still compare the max durability
|
||||
let tempMaxDurability = Math.round(this.randomUtil.getFloat(item.upd.Repairable.Durability - 5, item.upd.Repairable.MaxDurability + 5)) || item.upd.Repairable.Durability;
|
||||
let tempMaxDurability = Math.round(
|
||||
this.randomUtil.getFloat(item.upd.Repairable.Durability - 5, item.upd.Repairable.MaxDurability + 5),
|
||||
) || item.upd.Repairable.Durability;
|
||||
|
||||
// clamp values to max/current
|
||||
if (tempMaxDurability >= item.upd.Repairable.MaxDurability)
|
||||
@ -626,45 +681,45 @@ export class RagfairOfferGenerator
|
||||
protected addMissingConditions(item: Item): Item
|
||||
{
|
||||
const props = this.itemHelper.getItem(item._tpl)[1]._props;
|
||||
const isRepairable = ("Durability" in props);
|
||||
const isMedkit = ("MaxHpResource" in props);
|
||||
const isKey = ("MaximumNumberOfUsage" in props);
|
||||
const isConsumable = (props.MaxResource > 1 && "foodUseTime" in props);
|
||||
const isRepairKit = ("MaxRepairResource" in props);
|
||||
const isRepairable = "Durability" in props;
|
||||
const isMedkit = "MaxHpResource" in props;
|
||||
const isKey = "MaximumNumberOfUsage" in props;
|
||||
const isConsumable = props.MaxResource > 1 && "foodUseTime" in props;
|
||||
const isRepairKit = "MaxRepairResource" in props;
|
||||
|
||||
if (isRepairable && props.Durability > 0)
|
||||
{
|
||||
item.upd.Repairable = {
|
||||
Durability: props.Durability,
|
||||
MaxDurability: props.Durability
|
||||
MaxDurability: props.Durability,
|
||||
};
|
||||
}
|
||||
|
||||
if (isMedkit && props.MaxHpResource > 0)
|
||||
{
|
||||
item.upd.MedKit = {
|
||||
HpResource: props.MaxHpResource
|
||||
HpResource: props.MaxHpResource,
|
||||
};
|
||||
}
|
||||
|
||||
if (isKey)
|
||||
if (isKey)
|
||||
{
|
||||
item.upd.Key = {
|
||||
NumberOfUsages: 0
|
||||
NumberOfUsages: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (isConsumable)
|
||||
if (isConsumable)
|
||||
{
|
||||
item.upd.FoodDrink = {
|
||||
HpPercent: props.MaxResource
|
||||
HpPercent: props.MaxResource,
|
||||
};
|
||||
}
|
||||
|
||||
if (isRepairKit)
|
||||
if (isRepairKit)
|
||||
{
|
||||
item.upd.RepairKit = {
|
||||
Resource: props.MaxRepairResource
|
||||
Resource: props.MaxRepairResource,
|
||||
};
|
||||
}
|
||||
|
||||
@ -679,7 +734,11 @@ export class RagfairOfferGenerator
|
||||
protected createBarterBarterScheme(offerItems: Item[]): IBarterScheme[]
|
||||
{
|
||||
// get flea price of item being sold
|
||||
const priceOfItemOffer = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, Money.ROUBLES, false);
|
||||
const priceOfItemOffer = this.ragfairPriceService.getDynamicOfferPriceForOffer(
|
||||
offerItems,
|
||||
Money.ROUBLES,
|
||||
false,
|
||||
);
|
||||
|
||||
// Dont make items under a designated rouble value into barter offers
|
||||
if (priceOfItemOffer < this.ragfairConfig.dynamic.barter.minRoubleCostToBecomeBarter)
|
||||
@ -688,7 +747,10 @@ export class RagfairOfferGenerator
|
||||
}
|
||||
|
||||
// Get a randomised number of barter items to list offer for
|
||||
const barterItemCount = this.randomUtil.getInt(this.ragfairConfig.dynamic.barter.itemCountMin, this.ragfairConfig.dynamic.barter.itemCountMax);
|
||||
const barterItemCount = this.randomUtil.getInt(
|
||||
this.ragfairConfig.dynamic.barter.itemCountMin,
|
||||
this.ragfairConfig.dynamic.barter.itemCountMax,
|
||||
);
|
||||
|
||||
// Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k
|
||||
const desiredItemCost = Math.round(priceOfItemOffer / barterItemCount);
|
||||
@ -699,7 +761,10 @@ export class RagfairOfferGenerator
|
||||
const fleaPrices = this.getFleaPricesAsArray();
|
||||
|
||||
// Filter possible barters to items that match the price range + not itself
|
||||
const filtered = fleaPrices.filter(x => x.price >= desiredItemCost - offerCostVariance && x.price <= desiredItemCost + offerCostVariance && x.tpl !== offerItems[0]._tpl);
|
||||
const filtered = fleaPrices.filter((x) =>
|
||||
x.price >= desiredItemCost - offerCostVariance && x.price <= desiredItemCost + offerCostVariance &&
|
||||
x.tpl !== offerItems[0]._tpl
|
||||
);
|
||||
|
||||
// No items on flea have a matching price, fall back to currency
|
||||
if (filtered.length === 0)
|
||||
@ -713,8 +778,8 @@ export class RagfairOfferGenerator
|
||||
return [
|
||||
{
|
||||
count: barterItemCount,
|
||||
_tpl: randomItem.tpl
|
||||
}
|
||||
_tpl: randomItem.tpl,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -722,18 +787,20 @@ export class RagfairOfferGenerator
|
||||
* Get an array of flea prices + item tpl, cached in generator class inside `allowedFleaPriceItemsForBarter`
|
||||
* @returns array with tpl/price values
|
||||
*/
|
||||
protected getFleaPricesAsArray(): { tpl: string; price: number; }[]
|
||||
protected getFleaPricesAsArray(): {tpl: string; price: number;}[]
|
||||
{
|
||||
// Generate if needed
|
||||
if (!this.allowedFleaPriceItemsForBarter)
|
||||
{
|
||||
const fleaPrices = this.databaseServer.getTables().templates.prices;
|
||||
const fleaArray = Object.entries(fleaPrices).map(([tpl, price]) => ({ tpl: tpl, price: price }));
|
||||
const fleaArray = Object.entries(fleaPrices).map(([tpl, price]) => ({tpl: tpl, price: price}));
|
||||
|
||||
// Only get item prices for items that also exist in items.json
|
||||
const filteredItems = fleaArray.filter(x => this.itemHelper.getItem(x.tpl)[0]);
|
||||
const filteredItems = fleaArray.filter((x) => this.itemHelper.getItem(x.tpl)[0]);
|
||||
|
||||
this.allowedFleaPriceItemsForBarter = filteredItems.filter(x => !this.itemHelper.isOfBaseclasses(x.tpl, this.ragfairConfig.dynamic.barter.itemTypeBlacklist));
|
||||
this.allowedFleaPriceItemsForBarter = filteredItems.filter((x) =>
|
||||
!this.itemHelper.isOfBaseclasses(x.tpl, this.ragfairConfig.dynamic.barter.itemTypeBlacklist)
|
||||
);
|
||||
}
|
||||
|
||||
return this.allowedFleaPriceItemsForBarter;
|
||||
@ -749,13 +816,14 @@ export class RagfairOfferGenerator
|
||||
protected createCurrencyBarterScheme(offerItems: Item[], isPackOffer: boolean, multipler = 1): IBarterScheme[]
|
||||
{
|
||||
const currency = this.ragfairServerHelper.getDynamicOfferCurrency();
|
||||
const price = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, currency, isPackOffer) * multipler;
|
||||
const price = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, currency, isPackOffer) *
|
||||
multipler;
|
||||
|
||||
return [
|
||||
{
|
||||
count: price,
|
||||
_tpl: currency
|
||||
}
|
||||
_tpl: currency,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +15,25 @@ import {
|
||||
IEliminationCondition,
|
||||
IEquipmentConditionProps,
|
||||
IExploration,
|
||||
IExplorationCondition, IKillConditionProps,
|
||||
IExplorationCondition,
|
||||
IKillConditionProps,
|
||||
IPickup,
|
||||
IRepeatableQuest, IReward, IRewards
|
||||
IRepeatableQuest,
|
||||
IReward,
|
||||
IRewards,
|
||||
} from "@spt-aki/models/eft/common/tables/IRepeatableQuests";
|
||||
import { ITemplateItem } 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 { Money } from "@spt-aki/models/enums/Money";
|
||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||
import { IBaseQuestConfig, IBossInfo, IEliminationConfig, IQuestConfig, IRepeatableQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig";
|
||||
import {
|
||||
IBaseQuestConfig,
|
||||
IBossInfo,
|
||||
IEliminationConfig,
|
||||
IQuestConfig,
|
||||
IRepeatableQuestConfig,
|
||||
} from "@spt-aki/models/spt/config/IQuestConfig";
|
||||
import { IQuestTypePool } from "@spt-aki/models/spt/repeatable/IQuestTypePool";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder";
|
||||
@ -66,7 +75,7 @@ export class RepeatableQuestGenerator
|
||||
@inject("ObjectId") protected objectId: ObjectId,
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("RepeatableQuestHelper") protected repeatableQuestHelper: RepeatableQuestHelper,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST);
|
||||
@ -85,15 +94,17 @@ export class RepeatableQuestGenerator
|
||||
pmcLevel: number,
|
||||
pmcTraderInfo: Record<string, TraderInfo>,
|
||||
questTypePool: IQuestTypePool,
|
||||
repeatableConfig: IRepeatableQuestConfig
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
): IRepeatableQuest
|
||||
{
|
||||
const questType = this.randomUtil.drawRandomFromList<string>(questTypePool.types)[0];
|
||||
|
||||
// get traders from whitelist and filter by quest type availability
|
||||
let traders = repeatableConfig.traderWhitelist.filter(x => x.questTypes.includes(questType)).map(x => x.traderId);
|
||||
let traders = repeatableConfig.traderWhitelist.filter((x) => x.questTypes.includes(questType)).map((x) =>
|
||||
x.traderId
|
||||
);
|
||||
// filter out locked traders
|
||||
traders = traders.filter(x => pmcTraderInfo[x].unlocked);
|
||||
traders = traders.filter((x) => pmcTraderInfo[x].unlocked);
|
||||
const traderId = this.randomUtil.drawRandomFromList(traders)[0];
|
||||
|
||||
switch (questType)
|
||||
@ -123,15 +134,19 @@ export class RepeatableQuestGenerator
|
||||
pmcLevel: number,
|
||||
traderId: string,
|
||||
questTypePool: IQuestTypePool,
|
||||
repeatableConfig: IRepeatableQuestConfig
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
): IElimination
|
||||
{
|
||||
const eliminationConfig = this.repeatableQuestHelper.getEliminationConfigByPmcLevel(pmcLevel, repeatableConfig);
|
||||
const locationsConfig = repeatableConfig.locations;
|
||||
let targetsConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.targets);
|
||||
const bodypartsConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.bodyParts);
|
||||
const weaponCategoryRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.weaponCategoryRequirements);
|
||||
const weaponRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.weaponRequirements);
|
||||
const weaponCategoryRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray(
|
||||
eliminationConfig.weaponCategoryRequirements,
|
||||
);
|
||||
const weaponRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray(
|
||||
eliminationConfig.weaponRequirements,
|
||||
);
|
||||
|
||||
// the difficulty of the quest varies in difficulty depending on the condition
|
||||
// possible conditions are
|
||||
@ -146,7 +161,7 @@ export class RepeatableQuestGenerator
|
||||
// Savage: 7,
|
||||
// AnyPmc: 2,
|
||||
// bossBully: 0.5
|
||||
//}
|
||||
// }
|
||||
// higher is more likely. We define the difficulty to be the inverse of the relative probability.
|
||||
|
||||
// We want to generate a reward which is scaled by the difficulty of this mission. To get a upper bound with which we scale
|
||||
@ -165,18 +180,26 @@ export class RepeatableQuestGenerator
|
||||
|
||||
const maxKillDifficulty = eliminationConfig.maxKills;
|
||||
|
||||
function difficultyWeighing(target: number, bodyPart: number, dist: number, kill: number, weaponRequirement: number): number
|
||||
function difficultyWeighing(
|
||||
target: number,
|
||||
bodyPart: number,
|
||||
dist: number,
|
||||
kill: number,
|
||||
weaponRequirement: number,
|
||||
): number
|
||||
{
|
||||
return Math.sqrt(Math.sqrt(target) + bodyPart + dist + weaponRequirement) * kill;
|
||||
}
|
||||
|
||||
targetsConfig = targetsConfig.filter(x => Object.keys(questTypePool.pool.Elimination.targets).includes(x.key));
|
||||
if (targetsConfig.length === 0 || targetsConfig.every(x => x.data.isBoss))
|
||||
targetsConfig = targetsConfig.filter((x) =>
|
||||
Object.keys(questTypePool.pool.Elimination.targets).includes(x.key)
|
||||
);
|
||||
if (targetsConfig.length === 0 || targetsConfig.every((x) => x.data.isBoss))
|
||||
{
|
||||
// There are no more targets left for elimination; delete it as a possible quest type
|
||||
// also if only bosses are left we need to leave otherwise it's a guaranteed boss elimination
|
||||
// -> then it would not be a quest with low probability anymore
|
||||
questTypePool.types = questTypePool.types.filter(t => t !== "Elimination");
|
||||
questTypePool.types = questTypePool.types.filter((t) => t !== "Elimination");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -188,18 +211,23 @@ export class RepeatableQuestGenerator
|
||||
// we use any as location if "any" is in the pool and we do not hit the specific location random
|
||||
// we use any also if the random condition is not met in case only "any" was in the pool
|
||||
let locationKey = "any";
|
||||
if (locations.includes("any") && (eliminationConfig.specificLocationProb < Math.random() || locations.length <= 1))
|
||||
if (
|
||||
locations.includes("any") &&
|
||||
(eliminationConfig.specificLocationProb < Math.random() || locations.length <= 1)
|
||||
)
|
||||
{
|
||||
locationKey = "any";
|
||||
delete questTypePool.pool.Elimination.targets[targetKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
locations = locations.filter(l => l !== "any");
|
||||
locations = locations.filter((l) => l !== "any");
|
||||
if (locations.length > 0)
|
||||
{
|
||||
locationKey = this.randomUtil.drawRandomFromList<string>(locations)[0];
|
||||
questTypePool.pool.Elimination.targets[targetKey].locations = locations.filter(l => l !== locationKey);
|
||||
questTypePool.pool.Elimination.targets[targetKey].locations = locations.filter((l) =>
|
||||
l !== locationKey
|
||||
);
|
||||
if (questTypePool.pool.Elimination.targets[targetKey].locations.length === 0)
|
||||
{
|
||||
delete questTypePool.pool.Elimination.targets[targetKey];
|
||||
@ -243,15 +271,17 @@ export class RepeatableQuestGenerator
|
||||
if (targetsConfig.data(targetKey).isBoss)
|
||||
{
|
||||
// get all boss spawn information
|
||||
const bossSpawns = Object.values(this.databaseServer.getTables().locations).filter(x => "base" in x && "Id" in x.base).map(
|
||||
(x) => ({ Id: x.base.Id, BossSpawn: x.base.BossLocationSpawn })
|
||||
const bossSpawns = Object.values(this.databaseServer.getTables().locations).filter((x) =>
|
||||
"base" in x && "Id" in x.base
|
||||
).map(
|
||||
(x) => ({Id: x.base.Id, BossSpawn: x.base.BossLocationSpawn}),
|
||||
);
|
||||
// filter for the current boss to spawn on map
|
||||
const thisBossSpawns = bossSpawns.map(
|
||||
(x) => ({ Id: x.Id, BossSpawn: x.BossSpawn.filter(e => e.BossName === targetKey) })
|
||||
).filter(x => x.BossSpawn.length > 0);
|
||||
(x) => ({Id: x.Id, BossSpawn: x.BossSpawn.filter((e) => e.BossName === targetKey)}),
|
||||
).filter((x) => x.BossSpawn.length > 0);
|
||||
// remove blacklisted locations
|
||||
const allowedSpawns = thisBossSpawns.filter(x => !eliminationConfig.distLocationBlacklist.includes(x.Id));
|
||||
const allowedSpawns = thisBossSpawns.filter((x) => !eliminationConfig.distLocationBlacklist.includes(x.Id));
|
||||
// if the boss spawns on nom-blacklisted locations and the current location is allowed we can generate a distance kill requirement
|
||||
isDistanceRequirementAllowed = isDistanceRequirementAllowed && (allowedSpawns.length > 0);
|
||||
}
|
||||
@ -259,7 +289,10 @@ export class RepeatableQuestGenerator
|
||||
if (eliminationConfig.distProb > Math.random() && isDistanceRequirementAllowed)
|
||||
{
|
||||
// random distance with lower values more likely; simple distribution for starters...
|
||||
distance = Math.floor(Math.abs(Math.random() - Math.random()) * (1 + eliminationConfig.maxDist - eliminationConfig.minDist) + eliminationConfig.minDist);
|
||||
distance = Math.floor(
|
||||
Math.abs(Math.random() - Math.random()) * (1 + eliminationConfig.maxDist - eliminationConfig.minDist) +
|
||||
eliminationConfig.minDist,
|
||||
);
|
||||
distance = Math.ceil(distance / 5) * 5;
|
||||
distanceDifficulty = maxDistDifficulty * distance / eliminationConfig.maxDist;
|
||||
}
|
||||
@ -296,7 +329,7 @@ export class RepeatableQuestGenerator
|
||||
bodyPartDifficulty / maxBodyPartsDifficulty,
|
||||
distanceDifficulty / maxDistDifficulty,
|
||||
killDifficulty / maxKillDifficulty,
|
||||
(allowedWeaponsCategory || allowedWeapon) ? 1 : 0
|
||||
(allowedWeaponsCategory || allowedWeapon) ? 1 : 0,
|
||||
);
|
||||
|
||||
// Aforementioned issue makes it a bit crazy since now all easier quests give significantly lower rewards than Completion / Exploration
|
||||
@ -305,7 +338,7 @@ export class RepeatableQuestGenerator
|
||||
const difficulty = this.mathUtil.mapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2);
|
||||
|
||||
const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side) as IElimination;
|
||||
|
||||
|
||||
// ASSUMPTION: All fence quests are for scavs
|
||||
if (traderId === Traders.FENCE)
|
||||
{
|
||||
@ -319,14 +352,30 @@ export class RepeatableQuestGenerator
|
||||
// Only add specific location condition if specific map selected
|
||||
if (locationKey !== "any")
|
||||
{
|
||||
availableForFinishCondition._props.counter.conditions.push(this.generateEliminationLocation(locationsConfig[locationKey]));
|
||||
availableForFinishCondition._props.counter.conditions.push(
|
||||
this.generateEliminationLocation(locationsConfig[locationKey]),
|
||||
);
|
||||
}
|
||||
availableForFinishCondition._props.counter.conditions.push(this.generateEliminationCondition(targetKey, bodyPartsToClient, distance, allowedWeapon, allowedWeaponsCategory));
|
||||
availableForFinishCondition._props.counter.conditions.push(
|
||||
this.generateEliminationCondition(
|
||||
targetKey,
|
||||
bodyPartsToClient,
|
||||
distance,
|
||||
allowedWeapon,
|
||||
allowedWeaponsCategory,
|
||||
),
|
||||
);
|
||||
availableForFinishCondition._props.value = desiredKillCount;
|
||||
availableForFinishCondition._props.id = this.objectId.generate();
|
||||
quest.location = this.getQuestLocationByMapId(locationKey);
|
||||
|
||||
quest.rewards = this.generateReward(pmcLevel, Math.min(difficulty, 1), traderId, repeatableConfig, eliminationConfig);
|
||||
quest.rewards = this.generateReward(
|
||||
pmcLevel,
|
||||
Math.min(difficulty, 1),
|
||||
traderId,
|
||||
repeatableConfig,
|
||||
eliminationConfig,
|
||||
);
|
||||
|
||||
return quest;
|
||||
}
|
||||
@ -338,7 +387,11 @@ export class RepeatableQuestGenerator
|
||||
* @param eliminationConfig Config
|
||||
* @returns Number of AI to kill
|
||||
*/
|
||||
protected getEliminationKillCount(targetKey: string, targetsConfig: ProbabilityObjectArray<string, IBossInfo>, eliminationConfig: IEliminationConfig): number
|
||||
protected getEliminationKillCount(
|
||||
targetKey: string,
|
||||
targetsConfig: ProbabilityObjectArray<string, IBossInfo>,
|
||||
eliminationConfig: IEliminationConfig,
|
||||
): number
|
||||
{
|
||||
if (targetsConfig.data(targetKey).isBoss)
|
||||
{
|
||||
@ -366,11 +419,11 @@ export class RepeatableQuestGenerator
|
||||
_props: {
|
||||
target: location,
|
||||
id: this.objectId.generate(),
|
||||
dynamicLocale: true
|
||||
dynamicLocale: true,
|
||||
},
|
||||
_parent: "Location"
|
||||
_parent: "Location",
|
||||
};
|
||||
|
||||
|
||||
return propsObject;
|
||||
}
|
||||
|
||||
@ -383,13 +436,19 @@ export class RepeatableQuestGenerator
|
||||
* @param allowedWeaponCategory What category of weapon must be used - undefined = any
|
||||
* @returns IEliminationCondition object
|
||||
*/
|
||||
protected generateEliminationCondition(target: string, targetedBodyParts: string[], distance: number, allowedWeapon: string, allowedWeaponCategory: string): IEliminationCondition
|
||||
protected generateEliminationCondition(
|
||||
target: string,
|
||||
targetedBodyParts: string[],
|
||||
distance: number,
|
||||
allowedWeapon: string,
|
||||
allowedWeaponCategory: string,
|
||||
): IEliminationCondition
|
||||
{
|
||||
const killConditionProps: IKillConditionProps = {
|
||||
target: target,
|
||||
value: 1,
|
||||
id: this.objectId.generate(),
|
||||
dynamicLocale: true
|
||||
dynamicLocale: true,
|
||||
};
|
||||
|
||||
if (target.startsWith("boss"))
|
||||
@ -409,7 +468,7 @@ export class RepeatableQuestGenerator
|
||||
{
|
||||
killConditionProps.distance = {
|
||||
compareMethod: ">=",
|
||||
value: distance
|
||||
value: distance,
|
||||
};
|
||||
}
|
||||
|
||||
@ -427,7 +486,7 @@ export class RepeatableQuestGenerator
|
||||
|
||||
return {
|
||||
_props: killConditionProps,
|
||||
_parent: "Kills"
|
||||
_parent: "Kills",
|
||||
};
|
||||
}
|
||||
|
||||
@ -442,7 +501,7 @@ export class RepeatableQuestGenerator
|
||||
protected generateCompletionQuest(
|
||||
pmcLevel: number,
|
||||
traderId: string,
|
||||
repeatableConfig: IRepeatableQuestConfig
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
): ICompletion
|
||||
{
|
||||
const completionConfig = repeatableConfig.questConfig.Completion;
|
||||
@ -456,48 +515,64 @@ export class RepeatableQuestGenerator
|
||||
numberDistinctItems = 2;
|
||||
}
|
||||
|
||||
const quest = this.generateRepeatableTemplate("Completion", traderId,repeatableConfig.side) as ICompletion;
|
||||
const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion;
|
||||
|
||||
// Filter the items.json items to items the player must retrieve to complete queist: shouldn't be a quest item or "non-existant"
|
||||
let itemSelection = this.getRewardableItems(repeatableConfig);
|
||||
|
||||
// Be fair, don't let the items be more expensive than the reward
|
||||
let roublesBudget = Math.floor(this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(0.5, 1));
|
||||
let roublesBudget = Math.floor(
|
||||
this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(0.5, 1),
|
||||
);
|
||||
roublesBudget = Math.max(roublesBudget, 5000);
|
||||
itemSelection = itemSelection.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget);
|
||||
itemSelection = itemSelection.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget);
|
||||
|
||||
// We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as
|
||||
// [{minPlayerLevel: 1, itemIds: ["id1",...]}, {minPlayerLevel: 15, itemIds: ["id3",...]}]
|
||||
if (repeatableConfig.questConfig.Completion.useWhitelist)
|
||||
{
|
||||
const itemWhitelist = this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsWhitelist;
|
||||
const itemWhitelist =
|
||||
this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsWhitelist;
|
||||
|
||||
// Filter and concatenate the arrays according to current player level
|
||||
const itemIdsWhitelisted = itemWhitelist.filter(p => p.minPlayerLevel <= pmcLevel).reduce((a, p) => a.concat(p.itemIds), []);
|
||||
itemSelection = itemSelection.filter(x =>
|
||||
const itemIdsWhitelisted = itemWhitelist.filter((p) => p.minPlayerLevel <= pmcLevel).reduce(
|
||||
(a, p) => a.concat(p.itemIds),
|
||||
[],
|
||||
);
|
||||
itemSelection = itemSelection.filter((x) =>
|
||||
{
|
||||
// Whitelist can contain item tpls and item base type ids
|
||||
return (itemIdsWhitelisted.some(v => this.itemHelper.isOfBaseclass(x[0], v)) || itemIdsWhitelisted.includes(x[0]));
|
||||
return (itemIdsWhitelisted.some((v) => this.itemHelper.isOfBaseclass(x[0], v)) ||
|
||||
itemIdsWhitelisted.includes(x[0]));
|
||||
});
|
||||
// check if items are missing
|
||||
//const flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []);
|
||||
//const missing = itemIdsWhitelisted.filter(l => !flatList.includes(l));
|
||||
// const flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []);
|
||||
// const missing = itemIdsWhitelisted.filter(l => !flatList.includes(l));
|
||||
}
|
||||
|
||||
if (repeatableConfig.questConfig.Completion.useBlacklist)
|
||||
{
|
||||
const itemBlacklist = this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsBlacklist;
|
||||
const itemBlacklist =
|
||||
this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsBlacklist;
|
||||
// we filter and concatenate the arrays according to current player level
|
||||
const itemIdsBlacklisted = itemBlacklist.filter(p => p.minPlayerLevel <= pmcLevel).reduce((a, p) => a.concat(p.itemIds), []);
|
||||
itemSelection = itemSelection.filter(x =>
|
||||
const itemIdsBlacklisted = itemBlacklist.filter((p) => p.minPlayerLevel <= pmcLevel).reduce(
|
||||
(a, p) => a.concat(p.itemIds),
|
||||
[],
|
||||
);
|
||||
itemSelection = itemSelection.filter((x) =>
|
||||
{
|
||||
return itemIdsBlacklisted.every(v => !this.itemHelper.isOfBaseclass(x[0], v)) || !itemIdsBlacklisted.includes(x[0]);
|
||||
return itemIdsBlacklisted.every((v) => !this.itemHelper.isOfBaseclass(x[0], v)) ||
|
||||
!itemIdsBlacklisted.includes(x[0]);
|
||||
});
|
||||
}
|
||||
|
||||
if (itemSelection.length === 0)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("repeatable-completion_quest_whitelist_too_small_or_blacklist_too_restrictive"));
|
||||
this.logger.error(
|
||||
this.localisationService.getText(
|
||||
"repeatable-completion_quest_whitelist_too_small_or_blacklist_too_restrictive",
|
||||
),
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -532,7 +607,7 @@ export class RepeatableQuestGenerator
|
||||
if (roublesBudget > 0)
|
||||
{
|
||||
// reduce the list possible items to fulfill the new budget constraint
|
||||
itemSelection = itemSelection.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget);
|
||||
itemSelection = itemSelection.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget);
|
||||
if (itemSelection.length === 0)
|
||||
{
|
||||
break;
|
||||
@ -561,12 +636,18 @@ export class RepeatableQuestGenerator
|
||||
{
|
||||
let minDurability = 0;
|
||||
let onlyFoundInRaid = true;
|
||||
if (this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.WEAPON) || this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.ARMOR))
|
||||
if (
|
||||
this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.WEAPON) ||
|
||||
this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.ARMOR)
|
||||
)
|
||||
{
|
||||
minDurability = 80;
|
||||
}
|
||||
|
||||
if (this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_USEC) || this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_BEAR))
|
||||
if (
|
||||
this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_USEC) ||
|
||||
this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_BEAR)
|
||||
)
|
||||
{
|
||||
onlyFoundInRaid = false;
|
||||
}
|
||||
@ -583,10 +664,10 @@ export class RepeatableQuestGenerator
|
||||
minDurability: minDurability,
|
||||
maxDurability: 100,
|
||||
dogtagLevel: 0,
|
||||
onlyFoundInRaid: onlyFoundInRaid
|
||||
onlyFoundInRaid: onlyFoundInRaid,
|
||||
},
|
||||
_parent: "HandoverItem",
|
||||
dynamicLocale: true
|
||||
dynamicLocale: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -603,7 +684,7 @@ export class RepeatableQuestGenerator
|
||||
pmcLevel: number,
|
||||
traderId: string,
|
||||
questTypePool: IQuestTypePool,
|
||||
repeatableConfig: IRepeatableQuestConfig
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
): IExploration
|
||||
{
|
||||
const explorationConfig = repeatableConfig.questConfig.Exploration;
|
||||
@ -611,7 +692,7 @@ export class RepeatableQuestGenerator
|
||||
if (Object.keys(questTypePool.pool.Exploration.locations).length === 0)
|
||||
{
|
||||
// there are no more locations left for exploration; delete it as a possible quest type
|
||||
questTypePool.types = questTypePool.types.filter(t => t !== "Exploration");
|
||||
questTypePool.types = questTypePool.types.filter((t) => t !== "Exploration");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -625,7 +706,7 @@ export class RepeatableQuestGenerator
|
||||
|
||||
const numExtracts = this.randomUtil.randInt(1, explorationConfig.maxExtracts + 1);
|
||||
|
||||
const quest = this.generateRepeatableTemplate("Exploration", traderId,repeatableConfig.side) as IExploration;
|
||||
const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side) as IExploration;
|
||||
|
||||
const exitStatusCondition: IExplorationCondition = {
|
||||
_parent: "ExitStatus",
|
||||
@ -633,23 +714,23 @@ export class RepeatableQuestGenerator
|
||||
id: this.objectId.generate(),
|
||||
dynamicLocale: true,
|
||||
status: [
|
||||
"Survived"
|
||||
]
|
||||
}
|
||||
"Survived",
|
||||
],
|
||||
},
|
||||
};
|
||||
const locationCondition: IExplorationCondition = {
|
||||
_parent: "Location",
|
||||
_props: {
|
||||
id: this.objectId.generate(),
|
||||
dynamicLocale: true,
|
||||
target: locationTarget
|
||||
}
|
||||
target: locationTarget,
|
||||
},
|
||||
};
|
||||
|
||||
quest.conditions.AvailableForFinish[0]._props.counter.id = this.objectId.generate();
|
||||
quest.conditions.AvailableForFinish[0]._props.counter.conditions = [
|
||||
exitStatusCondition,
|
||||
locationCondition
|
||||
locationCondition,
|
||||
];
|
||||
quest.conditions.AvailableForFinish[0]._props.value = numExtracts;
|
||||
quest.conditions.AvailableForFinish[0]._props.id = this.objectId.generate();
|
||||
@ -659,11 +740,15 @@ export class RepeatableQuestGenerator
|
||||
{
|
||||
// Filter by whitelist, it's also possible that the field "PassageRequirement" does not exist (e.g. Shoreline)
|
||||
// Scav exits are not listed at all in locations.base currently. If that changes at some point, additional filtering will be required
|
||||
const mapExits = (this.databaseServer.getTables().locations[locationKey.toLowerCase()].base as ILocationBase).exits;
|
||||
const mapExits =
|
||||
(this.databaseServer.getTables().locations[locationKey.toLowerCase()].base as ILocationBase).exits;
|
||||
const possibleExists = mapExits.filter(
|
||||
x => (!("PassageRequirement" in x)
|
||||
|| repeatableConfig.questConfig.Exploration.specificExits.passageRequirementWhitelist.includes(x.PassageRequirement))
|
||||
&& x.Chance > 0
|
||||
(x) =>
|
||||
(!("PassageRequirement" in x) ||
|
||||
repeatableConfig.questConfig.Exploration.specificExits.passageRequirementWhitelist.includes(
|
||||
x.PassageRequirement,
|
||||
)) &&
|
||||
x.Chance > 0,
|
||||
);
|
||||
const exit = this.randomUtil.drawRandomFromList(possibleExists, 1)[0];
|
||||
const exitCondition = this.generateExplorationExitCondition(exit);
|
||||
@ -682,7 +767,7 @@ export class RepeatableQuestGenerator
|
||||
pmcLevel: number,
|
||||
traderId: string,
|
||||
questTypePool: IQuestTypePool,
|
||||
repeatableConfig: IRepeatableQuestConfig
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
): IPickup
|
||||
{
|
||||
const pickupConfig = repeatableConfig.questConfig.Pickup;
|
||||
@ -690,21 +775,28 @@ export class RepeatableQuestGenerator
|
||||
const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side) as IPickup;
|
||||
|
||||
const itemTypeToFetchWithCount = this.randomUtil.getArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount);
|
||||
const itemCountToFetch = this.randomUtil.randInt(itemTypeToFetchWithCount.minPickupCount, itemTypeToFetchWithCount.maxPickupCount + 1);
|
||||
const itemCountToFetch = this.randomUtil.randInt(
|
||||
itemTypeToFetchWithCount.minPickupCount,
|
||||
itemTypeToFetchWithCount.maxPickupCount + 1,
|
||||
);
|
||||
// Choose location - doesnt seem to work for anything other than 'any'
|
||||
//const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0];
|
||||
//const locationTarget = questTypePool.pool.Pickup.locations[locationKey];
|
||||
// const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0];
|
||||
// const locationTarget = questTypePool.pool.Pickup.locations[locationKey];
|
||||
|
||||
const findCondition = quest.conditions.AvailableForFinish.find(x => x._parent === "FindItem");
|
||||
const findCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "FindItem");
|
||||
findCondition._props.target = [itemTypeToFetchWithCount.itemType];
|
||||
findCondition._props.value = itemCountToFetch;
|
||||
|
||||
const counterCreatorCondition = quest.conditions.AvailableForFinish.find(x => x._parent === "CounterCreator");
|
||||
//const locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location");
|
||||
//(locationCondition._props as ILocationConditionProps).target = [...locationTarget];
|
||||
const counterCreatorCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "CounterCreator");
|
||||
// const locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location");
|
||||
// (locationCondition._props as ILocationConditionProps).target = [...locationTarget];
|
||||
|
||||
const equipmentCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Equipment");
|
||||
(equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[itemTypeToFetchWithCount.itemType]];
|
||||
const equipmentCondition = counterCreatorCondition._props.counter.conditions.find((x) =>
|
||||
x._parent === "Equipment"
|
||||
);
|
||||
(equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[
|
||||
itemTypeToFetchWithCount.itemType,
|
||||
]];
|
||||
|
||||
// Add rewards
|
||||
quest.rewards = this.generateReward(pmcLevel, 1, traderId, repeatableConfig, pickupConfig);
|
||||
@ -736,8 +828,8 @@ export class RepeatableQuestGenerator
|
||||
_props: {
|
||||
exitName: exit.Name,
|
||||
id: this.objectId.generate(),
|
||||
dynamicLocale: true
|
||||
}
|
||||
dynamicLocale: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -766,7 +858,7 @@ export class RepeatableQuestGenerator
|
||||
difficulty: number,
|
||||
traderId: string,
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
questConfig: IBaseQuestConfig
|
||||
questConfig: IBaseQuestConfig,
|
||||
): IRewards
|
||||
{
|
||||
// difficulty could go from 0.2 ... -> for lowest diffuculty receive 0.2*nominal reward
|
||||
@ -786,11 +878,22 @@ export class RepeatableQuestGenerator
|
||||
}
|
||||
|
||||
// rewards are generated based on pmcLevel, difficulty and a random spread
|
||||
const rewardXP = Math.floor(difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, xpConfig) * this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig));
|
||||
const rewardRoubles = Math.floor(difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig));
|
||||
const rewardNumItems = this.randomUtil.randInt(1, Math.round(this.mathUtil.interp1(pmcLevel, levelsConfig, itemsConfig)) + 1);
|
||||
const rewardReputation = Math.round(100 * difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, reputationConfig)
|
||||
* this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig)) / 100;
|
||||
const rewardXP = Math.floor(
|
||||
difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, xpConfig) *
|
||||
this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig),
|
||||
);
|
||||
const rewardRoubles = Math.floor(
|
||||
difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) *
|
||||
this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig),
|
||||
);
|
||||
const rewardNumItems = this.randomUtil.randInt(
|
||||
1,
|
||||
Math.round(this.mathUtil.interp1(pmcLevel, levelsConfig, itemsConfig)) + 1,
|
||||
);
|
||||
const rewardReputation = Math.round(
|
||||
100 * difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, reputationConfig) *
|
||||
this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig),
|
||||
) / 100;
|
||||
const skillRewardChance = this.mathUtil.interp1(pmcLevel, levelsConfig, skillRewardChanceConfig);
|
||||
const skillPointReward = this.mathUtil.interp1(pmcLevel, levelsConfig, skillPointRewardConfig);
|
||||
|
||||
@ -804,16 +907,18 @@ export class RepeatableQuestGenerator
|
||||
{
|
||||
value: rewardXP,
|
||||
type: "Experience",
|
||||
index: 0
|
||||
}
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
Fail: []
|
||||
Fail: [],
|
||||
};
|
||||
|
||||
if (traderId === Traders.PEACEKEEPER)
|
||||
{
|
||||
// convert to equivalent dollars
|
||||
rewards.Success.push(this.generateRewardItem(Money.EUROS, this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS), 1));
|
||||
rewards.Success.push(
|
||||
this.generateRewardItem(Money.EUROS, this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS), 1),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -837,14 +942,20 @@ export class RepeatableQuestGenerator
|
||||
}
|
||||
|
||||
// If we provide ammo we don't want to provide just one bullet
|
||||
value = this.randomUtil.randInt(repeatableConfig.rewardAmmoStackMinSize, itemSelected._props.StackMaxSize);
|
||||
value = this.randomUtil.randInt(
|
||||
repeatableConfig.rewardAmmoStackMinSize,
|
||||
itemSelected._props.StackMaxSize,
|
||||
);
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON))
|
||||
{
|
||||
const defaultPreset = this.presetHelper.getDefaultPreset(itemSelected._id);
|
||||
if (defaultPreset)
|
||||
{
|
||||
children = this.ragfairServerHelper.reparentPresets(defaultPreset._items[0], defaultPreset._items);
|
||||
children = this.ragfairServerHelper.reparentPresets(
|
||||
defaultPreset._items[0],
|
||||
defaultPreset._items,
|
||||
);
|
||||
}
|
||||
}
|
||||
rewards.Success.push(this.generateRewardItem(itemSelected._id, value, index, children));
|
||||
@ -859,7 +970,9 @@ export class RepeatableQuestGenerator
|
||||
if (roublesBudget > 0)
|
||||
{
|
||||
// Filter possible reward items to only items with a price below the remaining budget
|
||||
chosenRewardItems = chosenRewardItems.filter(x => this.itemHelper.getStaticItemPrice(x._id) < roublesBudget);
|
||||
chosenRewardItems = chosenRewardItems.filter((x) =>
|
||||
this.itemHelper.getStaticItemPrice(x._id) < roublesBudget
|
||||
);
|
||||
if (chosenRewardItems.length === 0)
|
||||
{
|
||||
break; // No reward items left, exit
|
||||
@ -879,7 +992,7 @@ export class RepeatableQuestGenerator
|
||||
target: traderId,
|
||||
value: rewardReputation,
|
||||
type: "TraderStanding",
|
||||
index: index
|
||||
index: index,
|
||||
};
|
||||
rewards.Success.push(reward);
|
||||
}
|
||||
@ -891,7 +1004,7 @@ export class RepeatableQuestGenerator
|
||||
target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards),
|
||||
value: skillPointReward,
|
||||
type: "Skill",
|
||||
index: index
|
||||
index: index,
|
||||
};
|
||||
rewards.Success.push(reward);
|
||||
}
|
||||
@ -905,17 +1018,29 @@ export class RepeatableQuestGenerator
|
||||
* @param roublesBudget Total value of items to return
|
||||
* @returns Array of reward items that fit budget
|
||||
*/
|
||||
protected chooseRewardItemsWithinBudget(repeatableConfig: IRepeatableQuestConfig, roublesBudget: number): ITemplateItem[]
|
||||
protected chooseRewardItemsWithinBudget(
|
||||
repeatableConfig: IRepeatableQuestConfig,
|
||||
roublesBudget: number,
|
||||
): ITemplateItem[]
|
||||
{
|
||||
// First filter for type and baseclass to avoid lookup in handbook for non-available items
|
||||
const rewardableItems = this.getRewardableItems(repeatableConfig);
|
||||
const minPrice = Math.min(25000, 0.5 * roublesBudget);
|
||||
let itemSelection = rewardableItems.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget && this.itemHelper.getItemPrice(x[0]) > minPrice).map(x => x[1]);
|
||||
let itemSelection = rewardableItems.filter((x) =>
|
||||
this.itemHelper.getItemPrice(x[0]) < roublesBudget && this.itemHelper.getItemPrice(x[0]) > minPrice
|
||||
).map((x) => x[1]);
|
||||
if (itemSelection.length === 0)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", {minPrice: minPrice, roublesBudget: roublesBudget}));
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", {
|
||||
minPrice: minPrice,
|
||||
roublesBudget: roublesBudget,
|
||||
}),
|
||||
);
|
||||
// In case we don't find any items in the price range
|
||||
itemSelection = rewardableItems.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget).map(x => x[1]);
|
||||
itemSelection = rewardableItems.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget).map((x) =>
|
||||
x[1]
|
||||
);
|
||||
}
|
||||
|
||||
return itemSelection;
|
||||
@ -936,7 +1061,7 @@ export class RepeatableQuestGenerator
|
||||
target: id,
|
||||
value: value,
|
||||
type: "Item",
|
||||
index: index
|
||||
index: index,
|
||||
};
|
||||
|
||||
const rootItem = {
|
||||
@ -944,8 +1069,8 @@ export class RepeatableQuestGenerator
|
||||
_tpl: tpl,
|
||||
upd: {
|
||||
StackObjectsCount: value,
|
||||
SpawnedInSession: true
|
||||
}
|
||||
SpawnedInSession: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (preset)
|
||||
@ -960,7 +1085,7 @@ export class RepeatableQuestGenerator
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks rewardable items from items.json. This means they need to fit into the inventory and they shouldn't be keys (debatable)
|
||||
* Picks rewardable items from items.json. This means they need to fit into the inventory and they shouldn't be keys (debatable)
|
||||
* @param repeatableQuestConfig Config file
|
||||
* @returns List of rewardable items [[_tpl, itemTemplate],...]
|
||||
*/
|
||||
@ -970,7 +1095,6 @@ export class RepeatableQuestGenerator
|
||||
// also check if the price is greater than 0; there are some items whose price can not be found
|
||||
// those are not in the game yet (e.g. AGS grenade launcher)
|
||||
return Object.entries(this.databaseServer.getTables().templates.items).filter(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
([tpl, itemTemplate]) =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
@ -980,7 +1104,7 @@ export class RepeatableQuestGenerator
|
||||
}
|
||||
|
||||
return this.isValidRewardItem(tpl, repeatableQuestConfig);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -999,8 +1123,10 @@ export class RepeatableQuestGenerator
|
||||
}
|
||||
|
||||
// Item is on repeatable or global blacklist
|
||||
if (repeatableQuestConfig.rewardBlacklist.includes(tpl)
|
||||
|| this.itemFilterService.isItemBlacklisted(tpl))
|
||||
if (
|
||||
repeatableQuestConfig.rewardBlacklist.includes(tpl) ||
|
||||
this.itemFilterService.isItemBlacklisted(tpl)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -1011,15 +1137,23 @@ export class RepeatableQuestGenerator
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.itemHelper.isOfBaseclasses(tpl, [BaseClasses.DOG_TAG_USEC, BaseClasses.DOG_TAG_BEAR, BaseClasses.MOUNT, BaseClasses.KEY, BaseClasses.ARMBAND]))
|
||||
if (
|
||||
this.itemHelper.isOfBaseclasses(tpl, [
|
||||
BaseClasses.DOG_TAG_USEC,
|
||||
BaseClasses.DOG_TAG_BEAR,
|
||||
BaseClasses.MOUNT,
|
||||
BaseClasses.KEY,
|
||||
BaseClasses.ARMBAND,
|
||||
])
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip globally blacklisted items + boss items
|
||||
// biome-ignore lint/complexity/useSimplifiedLogicExpression: <explanation>
|
||||
valid = !this.itemFilterService.isItemBlacklisted(tpl)
|
||||
&& !this.itemFilterService.isBossItem(tpl);
|
||||
valid = !this.itemFilterService.isItemBlacklisted(tpl) &&
|
||||
!this.itemFilterService.isBossItem(tpl);
|
||||
|
||||
return valid;
|
||||
}
|
||||
@ -1030,36 +1164,59 @@ export class RepeatableQuestGenerator
|
||||
*
|
||||
* @param {string} type Quest type: "Elimination", "Completion" or "Extraction"
|
||||
* @param {string} traderId Trader from which the quest will be provided
|
||||
* @param {string} side Scav daily or pmc daily/weekly quest
|
||||
* @param {string} side Scav daily or pmc daily/weekly quest
|
||||
* @returns {object} Object which contains the base elements for repeatable quests of the requests type
|
||||
* (needs to be filled with reward and conditions by called to make a valid quest)
|
||||
*/
|
||||
// @Incomplete: define Type for "type".
|
||||
protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest
|
||||
{
|
||||
const quest = this.jsonUtil.clone<IRepeatableQuest>(this.databaseServer.getTables().templates.repeatableQuests.templates[type]);
|
||||
const quest = this.jsonUtil.clone<IRepeatableQuest>(
|
||||
this.databaseServer.getTables().templates.repeatableQuests.templates[type],
|
||||
);
|
||||
quest._id = this.objectId.generate();
|
||||
quest.traderId = traderId;
|
||||
|
||||
/* in locale, these id correspond to the text of quests
|
||||
template ids -pmc : Elimination = 616052ea3054fc0e2c24ce6e / Completion = 61604635c725987e815b1a46 / Exploration = 616041eb031af660100c9967
|
||||
template ids -scav : Elimination = 62825ef60e88d037dc1eb428 / Completion = 628f588ebb558574b2260fe5 / Exploration = 62825ef60e88d037dc1eb42c
|
||||
template ids -scav : Elimination = 62825ef60e88d037dc1eb428 / Completion = 628f588ebb558574b2260fe5 / Exploration = 62825ef60e88d037dc1eb42c
|
||||
*/
|
||||
|
||||
// Get template id from config based on side and type of quest
|
||||
quest.templateId = this.questConfig.questTemplateIds[side.toLowerCase()][type.toLowerCase()];
|
||||
|
||||
quest.name = quest.name.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.note = quest.note.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.description = quest.description.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.successMessageText = quest.successMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.failMessageText = quest.failMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.startedMessageText = quest.startedMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.changeQuestMessageText = quest.changeQuestMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.acceptPlayerMessage = quest.acceptPlayerMessage.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.declinePlayerMessage = quest.declinePlayerMessage.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.completePlayerMessage = quest.completePlayerMessage.replace("{traderId}", traderId).replace("{templateId}",quest.templateId);
|
||||
quest.name = quest.name.replace("{traderId}", traderId).replace("{templateId}", quest.templateId);
|
||||
quest.note = quest.note.replace("{traderId}", traderId).replace("{templateId}", quest.templateId);
|
||||
quest.description = quest.description.replace("{traderId}", traderId).replace("{templateId}", quest.templateId);
|
||||
quest.successMessageText = quest.successMessageText.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
quest.failMessageText = quest.failMessageText.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
quest.startedMessageText = quest.startedMessageText.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
quest.changeQuestMessageText = quest.changeQuestMessageText.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
quest.acceptPlayerMessage = quest.acceptPlayerMessage.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
quest.declinePlayerMessage = quest.declinePlayerMessage.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
quest.completePlayerMessage = quest.completePlayerMessage.replace("{traderId}", traderId).replace(
|
||||
"{templateId}",
|
||||
quest.templateId,
|
||||
);
|
||||
|
||||
return quest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { Money } from "@spt-aki/models/enums/Money";
|
||||
import { IScavCaseConfig } from "@spt-aki/models/spt/config/IScavCaseConfig";
|
||||
import {
|
||||
RewardCountAndPriceDetails, ScavCaseRewardCountsAndPrices
|
||||
RewardCountAndPriceDetails,
|
||||
ScavCaseRewardCountsAndPrices,
|
||||
} from "@spt-aki/models/spt/hideout/ScavCaseRewardCountsAndPrices";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
@ -20,7 +21,7 @@ import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
|
||||
/**
|
||||
/**
|
||||
* Handle the creation of randomised scav case rewards
|
||||
*/
|
||||
@injectable()
|
||||
@ -38,12 +39,12 @@ export class ScavCaseRewardGenerator
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
@inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService,
|
||||
@inject("ItemFilterService") protected itemFilterService: ItemFilterService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.scavCaseConfig = this.configServer.getConfig(ConfigTypes.SCAVCASE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an array of rewards that will be given to the player upon completing their scav case build
|
||||
* @param recipeId recipe of the scav case craft
|
||||
@ -54,7 +55,7 @@ export class ScavCaseRewardGenerator
|
||||
this.cacheDbItems();
|
||||
|
||||
// Get scavcase details from hideout/scavcase.json
|
||||
const scavCaseDetails = this.databaseServer.getTables().hideout.scavcase.find(r => r._id === recipeId);
|
||||
const scavCaseDetails = this.databaseServer.getTables().hideout.scavcase.find((r) => r._id === recipeId);
|
||||
const rewardItemCounts = this.getScavCaseRewardCountsAndPrices(scavCaseDetails);
|
||||
|
||||
// Get items that fit the price criteria as set by the scavCase config
|
||||
@ -63,9 +64,17 @@ export class ScavCaseRewardGenerator
|
||||
const superRarePricedItems = this.getFilteredItemsByPrice(this.dbItemsCache, rewardItemCounts.Superrare);
|
||||
|
||||
// Get randomly picked items from each item collction, the count range of which is defined in hideout/scavcase.json
|
||||
const randomlyPickedCommonRewards = this.pickRandomRewards(commonPricedItems, rewardItemCounts.Common, "common");
|
||||
const randomlyPickedCommonRewards = this.pickRandomRewards(
|
||||
commonPricedItems,
|
||||
rewardItemCounts.Common,
|
||||
"common",
|
||||
);
|
||||
const randomlyPickedRareRewards = this.pickRandomRewards(rarePricedItems, rewardItemCounts.Rare, "rare");
|
||||
const randomlyPickedSuperRareRewards = this.pickRandomRewards(superRarePricedItems, rewardItemCounts.Superrare, "superrare");
|
||||
const randomlyPickedSuperRareRewards = this.pickRandomRewards(
|
||||
superRarePricedItems,
|
||||
rewardItemCounts.Superrare,
|
||||
"superrare",
|
||||
);
|
||||
|
||||
// Add randomised stack sizes to ammo and money rewards
|
||||
const commonRewards = this.randomiseContainerItemRewards(randomlyPickedCommonRewards, "common");
|
||||
@ -95,11 +104,13 @@ export class ScavCaseRewardGenerator
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Skip item if item id is on blacklist
|
||||
if ((item._type !== "Item")
|
||||
|| this.scavCaseConfig.rewardItemBlacklist.includes(item._id)
|
||||
|| this.itemFilterService.isItemBlacklisted(item._id))
|
||||
if (
|
||||
(item._type !== "Item") ||
|
||||
this.scavCaseConfig.rewardItemBlacklist.includes(item._id) ||
|
||||
this.itemFilterService.isItemBlacklisted(item._id)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -108,13 +119,13 @@ export class ScavCaseRewardGenerator
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Skip item if parent id is blacklisted
|
||||
if (this.itemHelper.isOfBaseclasses(item._id, this.scavCaseConfig.rewardItemParentBlacklist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@ -139,13 +150,13 @@ export class ScavCaseRewardGenerator
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Skip ammo that doesn't stack as high as value in config
|
||||
if (item._props.StackMaxSize < this.scavCaseConfig.ammoRewards.minStackSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@ -155,27 +166,31 @@ export class ScavCaseRewardGenerator
|
||||
* Pick a number of items to be rewards, the count is defined by the values in `itemFilters` param
|
||||
* @param items item pool to pick rewards from
|
||||
* @param itemFilters how the rewards should be filtered down (by item count)
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
protected pickRandomRewards(items: ITemplateItem[], itemFilters: RewardCountAndPriceDetails, rarity: string): ITemplateItem[]
|
||||
protected pickRandomRewards(
|
||||
items: ITemplateItem[],
|
||||
itemFilters: RewardCountAndPriceDetails,
|
||||
rarity: string,
|
||||
): ITemplateItem[]
|
||||
{
|
||||
const result: ITemplateItem[] = [];
|
||||
|
||||
|
||||
let rewardWasMoney = false;
|
||||
let rewardWasAmmo = false;
|
||||
const randomCount = this.randomUtil.getInt(itemFilters.minCount, itemFilters.maxCount);
|
||||
for (let i = 0; i < randomCount; i++)
|
||||
{
|
||||
if (this.rewardShouldBeMoney() && !rewardWasMoney) // Only allow one reward to be money
|
||||
{
|
||||
if (this.rewardShouldBeMoney() && !rewardWasMoney)
|
||||
{ // Only allow one reward to be money
|
||||
result.push(this.getRandomMoney());
|
||||
if (!this.scavCaseConfig.allowMultipleMoneyRewardsPerRarity)
|
||||
{
|
||||
rewardWasMoney = true;
|
||||
}
|
||||
}
|
||||
else if (this.rewardShouldBeAmmo() && !rewardWasAmmo) // Only allow one reward to be ammo
|
||||
{
|
||||
else if (this.rewardShouldBeAmmo() && !rewardWasAmmo)
|
||||
{ // Only allow one reward to be ammo
|
||||
result.push(this.getRandomAmmo(rarity));
|
||||
if (!this.scavCaseConfig.allowMultipleAmmoRewardsPerRarity)
|
||||
{
|
||||
@ -214,10 +229,9 @@ export class ScavCaseRewardGenerator
|
||||
*/
|
||||
protected getRandomMoney(): ITemplateItem
|
||||
{
|
||||
|
||||
const money: ITemplateItem[] = [];
|
||||
money.push(this.databaseServer.getTables().templates.items["5449016a4bdc2d6f028b456f"]); //rub
|
||||
money.push(this.databaseServer.getTables().templates.items["569668774bdc2da2298b4568"]); //euro
|
||||
money.push(this.databaseServer.getTables().templates.items["5449016a4bdc2d6f028b456f"]); // rub
|
||||
money.push(this.databaseServer.getTables().templates.items["569668774bdc2da2298b4568"]); // euro
|
||||
money.push(this.databaseServer.getTables().templates.items["5696686a4bdc2da3298b456a"]); // dollar
|
||||
|
||||
return this.randomUtil.getArrayValue(money);
|
||||
@ -234,8 +248,10 @@ export class ScavCaseRewardGenerator
|
||||
{
|
||||
// Is ammo handbook price between desired range
|
||||
const handbookPrice = this.ragfairPriceService.getStaticPriceForItem(ammo._id);
|
||||
if (handbookPrice >= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].min
|
||||
&& handbookPrice <= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].max)
|
||||
if (
|
||||
handbookPrice >= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].min &&
|
||||
handbookPrice <= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].max
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -266,7 +282,7 @@ export class ScavCaseRewardGenerator
|
||||
const resultItem = {
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: item._id,
|
||||
upd: undefined
|
||||
upd: undefined,
|
||||
};
|
||||
|
||||
this.addStackCountToAmmoAndMoney(item, resultItem, rarity);
|
||||
@ -288,29 +304,37 @@ export class ScavCaseRewardGenerator
|
||||
* @param item money or ammo item
|
||||
* @param resultItem money or ammo item with a randomise stack size
|
||||
*/
|
||||
protected addStackCountToAmmoAndMoney(item: ITemplateItem, resultItem: { _id: string; _tpl: string; upd: Upd; }, rarity: string): void
|
||||
protected addStackCountToAmmoAndMoney(
|
||||
item: ITemplateItem,
|
||||
resultItem: {_id: string; _tpl: string; upd: Upd;},
|
||||
rarity: string,
|
||||
): void
|
||||
{
|
||||
if (item._parent === BaseClasses.AMMO || item._parent === BaseClasses.MONEY)
|
||||
{
|
||||
resultItem.upd = {
|
||||
StackObjectsCount: this.getRandomAmountRewardForScavCase(item, rarity)
|
||||
StackObjectsCount: this.getRandomAmountRewardForScavCase(item, rarity),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dbItems all items from the items.json
|
||||
* @param itemFilters controls how the dbItems will be filtered and returned (handbook price)
|
||||
* @returns filtered dbItems array
|
||||
*/
|
||||
protected getFilteredItemsByPrice(dbItems: ITemplateItem[], itemFilters: RewardCountAndPriceDetails): ITemplateItem[]
|
||||
protected getFilteredItemsByPrice(
|
||||
dbItems: ITemplateItem[],
|
||||
itemFilters: RewardCountAndPriceDetails,
|
||||
): ITemplateItem[]
|
||||
{
|
||||
return dbItems.filter((item) =>
|
||||
{
|
||||
const handbookPrice = this.ragfairPriceService.getStaticPriceForItem(item._id);
|
||||
if (handbookPrice >= itemFilters.minPriceRub
|
||||
&& handbookPrice <= itemFilters.maxPriceRub)
|
||||
if (
|
||||
handbookPrice >= itemFilters.minPriceRub &&
|
||||
handbookPrice <= itemFilters.maxPriceRub
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -330,12 +354,11 @@ export class ScavCaseRewardGenerator
|
||||
// Create reward min/max counts for each type
|
||||
for (const rewardType of rewardTypes)
|
||||
{
|
||||
result[rewardType] =
|
||||
{
|
||||
result[rewardType] = {
|
||||
minCount: scavCaseDetails.EndProducts[rewardType].min,
|
||||
maxCount: scavCaseDetails.EndProducts[rewardType].max,
|
||||
minPriceRub: this.scavCaseConfig.rewardItemValueRangeRub[rewardType.toLowerCase()].min,
|
||||
maxPriceRub: this.scavCaseConfig.rewardItemValueRangeRub[rewardType.toLowerCase()].max
|
||||
maxPriceRub: this.scavCaseConfig.rewardItemValueRangeRub[rewardType.toLowerCase()].max,
|
||||
};
|
||||
}
|
||||
|
||||
@ -353,23 +376,35 @@ export class ScavCaseRewardGenerator
|
||||
let amountToGive = 1;
|
||||
if (itemToCalculate._parent === BaseClasses.AMMO)
|
||||
{
|
||||
amountToGive = this.randomUtil.getInt(this.scavCaseConfig.ammoRewards.minStackSize, itemToCalculate._props.StackMaxSize);
|
||||
amountToGive = this.randomUtil.getInt(
|
||||
this.scavCaseConfig.ammoRewards.minStackSize,
|
||||
itemToCalculate._props.StackMaxSize,
|
||||
);
|
||||
}
|
||||
else if (itemToCalculate._parent === BaseClasses.MONEY)
|
||||
{
|
||||
switch (itemToCalculate._id)
|
||||
{
|
||||
case Money.ROUBLES:
|
||||
amountToGive = this.randomUtil.getInt(this.scavCaseConfig.moneyRewards.rubCount[rarity].min, this.scavCaseConfig.moneyRewards.rubCount[rarity].max);
|
||||
amountToGive = this.randomUtil.getInt(
|
||||
this.scavCaseConfig.moneyRewards.rubCount[rarity].min,
|
||||
this.scavCaseConfig.moneyRewards.rubCount[rarity].max,
|
||||
);
|
||||
break;
|
||||
case Money.EUROS:
|
||||
amountToGive = this.randomUtil.getInt(this.scavCaseConfig.moneyRewards.eurCount[rarity].min, this.scavCaseConfig.moneyRewards.eurCount[rarity].max);
|
||||
amountToGive = this.randomUtil.getInt(
|
||||
this.scavCaseConfig.moneyRewards.eurCount[rarity].min,
|
||||
this.scavCaseConfig.moneyRewards.eurCount[rarity].max,
|
||||
);
|
||||
break;
|
||||
case Money.DOLLARS:
|
||||
amountToGive = this.randomUtil.getInt(this.scavCaseConfig.moneyRewards.usdCount[rarity].min, this.scavCaseConfig.moneyRewards.usdCount[rarity].max);
|
||||
amountToGive = this.randomUtil.getInt(
|
||||
this.scavCaseConfig.moneyRewards.usdCount[rarity].min,
|
||||
this.scavCaseConfig.moneyRewards.usdCount[rarity].max,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return amountToGive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export class WeatherGenerator
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
{
|
||||
this.weatherConfig = this.configServer.getConfig(ConfigTypes.WEATHER);
|
||||
@ -62,16 +62,17 @@ export class WeatherGenerator
|
||||
/**
|
||||
* Get the current in-raid time
|
||||
* @param currentDate (new Date())
|
||||
* @returns Date object of current in-raid time
|
||||
* @returns Date object of current in-raid time
|
||||
*/
|
||||
public getInRaidTime(currentDate: Date): Date
|
||||
{
|
||||
// Get timestamp of when client conneted to server
|
||||
const gameStartTimeStampMS = this.applicationContext.getLatestValue(ContextVariableType.CLIENT_START_TIMESTAMP).getValue<number>();
|
||||
const gameStartTimeStampMS = this.applicationContext.getLatestValue(ContextVariableType.CLIENT_START_TIMESTAMP)
|
||||
.getValue<number>();
|
||||
|
||||
// Get delta between now and when client connected to server in milliseconds
|
||||
const deltaMSFromNow = (Date.now() - gameStartTimeStampMS);
|
||||
const acceleratedMS = (deltaMSFromNow * (this.weatherConfig.acceleration - 1)); // For some reason nodejs moves faster than client time, reducing acceleration by 1 when client is 7 helps
|
||||
const deltaMSFromNow = Date.now() - gameStartTimeStampMS;
|
||||
const acceleratedMS = deltaMSFromNow * (this.weatherConfig.acceleration - 1); // For some reason nodejs moves faster than client time, reducing acceleration by 1 when client is 7 helps
|
||||
const clientAcceleratedDate = new Date(currentDate.valueOf() + acceleratedMS);
|
||||
|
||||
return clientAcceleratedDate;
|
||||
@ -105,15 +106,15 @@ export class WeatherGenerator
|
||||
wind_gustiness: this.getRandomFloat("windGustiness"),
|
||||
rain: rain,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
rain_intensity: (rain > 1)
|
||||
? this.getRandomFloat("rainIntensity")
|
||||
: 0,
|
||||
rain_intensity: (rain > 1) ?
|
||||
this.getRandomFloat("rainIntensity") :
|
||||
0,
|
||||
fog: this.getWeightedFog(),
|
||||
temp: this.getRandomFloat("temp"),
|
||||
pressure: this.getRandomFloat("pressure"),
|
||||
time: "",
|
||||
date: "",
|
||||
timestamp: 0
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
this.setCurrentDateTime(result);
|
||||
@ -139,32 +140,49 @@ export class WeatherGenerator
|
||||
|
||||
protected getWeightedWindDirection(): WindDirection
|
||||
{
|
||||
return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.windDirection.values, this.weatherConfig.weather.windDirection.weights).item;
|
||||
return this.weightedRandomHelper.weightedRandom(
|
||||
this.weatherConfig.weather.windDirection.values,
|
||||
this.weatherConfig.weather.windDirection.weights,
|
||||
).item;
|
||||
}
|
||||
|
||||
protected getWeightedClouds(): number
|
||||
{
|
||||
return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.clouds.values, this.weatherConfig.weather.clouds.weights).item;
|
||||
return this.weightedRandomHelper.weightedRandom(
|
||||
this.weatherConfig.weather.clouds.values,
|
||||
this.weatherConfig.weather.clouds.weights,
|
||||
).item;
|
||||
}
|
||||
|
||||
protected getWeightedWindSpeed(): number
|
||||
{
|
||||
return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.windSpeed.values, this.weatherConfig.weather.windSpeed.weights).item;
|
||||
return this.weightedRandomHelper.weightedRandom(
|
||||
this.weatherConfig.weather.windSpeed.values,
|
||||
this.weatherConfig.weather.windSpeed.weights,
|
||||
).item;
|
||||
}
|
||||
|
||||
protected getWeightedFog(): number
|
||||
{
|
||||
return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.fog.values, this.weatherConfig.weather.fog.weights).item;
|
||||
return this.weightedRandomHelper.weightedRandom(
|
||||
this.weatherConfig.weather.fog.values,
|
||||
this.weatherConfig.weather.fog.weights,
|
||||
).item;
|
||||
}
|
||||
|
||||
protected getWeightedRain(): number
|
||||
{
|
||||
return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.rain.values, this.weatherConfig.weather.rain.weights).item;
|
||||
return this.weightedRandomHelper.weightedRandom(
|
||||
this.weatherConfig.weather.rain.values,
|
||||
this.weatherConfig.weather.rain.weights,
|
||||
).item;
|
||||
}
|
||||
|
||||
protected getRandomFloat(node: string): number
|
||||
{
|
||||
return parseFloat(this.randomUtil.getFloat(this.weatherConfig.weather[node].min,
|
||||
this.weatherConfig.weather[node].max).toPrecision(3));
|
||||
return parseFloat(
|
||||
this.randomUtil.getFloat(this.weatherConfig.weather[node].min, this.weatherConfig.weather[node].max)
|
||||
.toPrecision(3),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,4 @@ export interface IInventoryMagGen
|
||||
getPriority(): number;
|
||||
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean;
|
||||
process(inventoryMagGen: InventoryMagGen): void;
|
||||
}
|
||||
}
|
||||
|
@ -2,40 +2,39 @@ import { Inventory } from "@spt-aki/models/eft/common/tables/IBotBase";
|
||||
import { GenerationData } from "@spt-aki/models/eft/common/tables/IBotType";
|
||||
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||
|
||||
export class InventoryMagGen
|
||||
export class InventoryMagGen
|
||||
{
|
||||
constructor(
|
||||
private magCounts: GenerationData,
|
||||
private magazineTemplate: ITemplateItem,
|
||||
private weaponTemplate: ITemplateItem,
|
||||
private ammoTemplate: ITemplateItem,
|
||||
private pmcInventory: Inventory
|
||||
)
|
||||
{
|
||||
}
|
||||
private pmcInventory: Inventory,
|
||||
)
|
||||
{}
|
||||
|
||||
public getMagCount(): GenerationData
|
||||
public getMagCount(): GenerationData
|
||||
{
|
||||
return this.magCounts;
|
||||
}
|
||||
|
||||
public getMagazineTemplate(): ITemplateItem
|
||||
public getMagazineTemplate(): ITemplateItem
|
||||
{
|
||||
return this.magazineTemplate;
|
||||
}
|
||||
|
||||
public getWeaponTemplate(): ITemplateItem
|
||||
public getWeaponTemplate(): ITemplateItem
|
||||
{
|
||||
return this.weaponTemplate;
|
||||
}
|
||||
|
||||
public getAmmoTemplate(): ITemplateItem
|
||||
public getAmmoTemplate(): ITemplateItem
|
||||
{
|
||||
return this.ammoTemplate;
|
||||
}
|
||||
|
||||
public getPmcInventory(): Inventory
|
||||
public getPmcInventory(): Inventory
|
||||
{
|
||||
return this.pmcInventory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,36 +8,43 @@ import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
@injectable()
|
||||
export class BarrelInventoryMagGen implements IInventoryMagGen
|
||||
{
|
||||
|
||||
constructor(
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
|
||||
)
|
||||
{ }
|
||||
{}
|
||||
|
||||
getPriority(): number
|
||||
getPriority(): number
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
{
|
||||
return inventoryMagGen.getWeaponTemplate()._props.ReloadMode === "OnlyBarrel";
|
||||
}
|
||||
|
||||
process(inventoryMagGen: InventoryMagGen): void
|
||||
|
||||
process(inventoryMagGen: InventoryMagGen): void
|
||||
{
|
||||
// Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot"
|
||||
let randomisedAmmoStackSize: number;
|
||||
if (inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom === 1) // doesnt stack
|
||||
if (inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom === 1)
|
||||
{
|
||||
// doesnt stack
|
||||
randomisedAmmoStackSize = this.randomUtil.getInt(3, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
randomisedAmmoStackSize = this.randomUtil.getInt(inventoryMagGen.getAmmoTemplate()._props.StackMinRandom, inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom);
|
||||
randomisedAmmoStackSize = this.randomUtil.getInt(
|
||||
inventoryMagGen.getAmmoTemplate()._props.StackMinRandom,
|
||||
inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom,
|
||||
);
|
||||
}
|
||||
|
||||
this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(inventoryMagGen.getAmmoTemplate()._id, randomisedAmmoStackSize, inventoryMagGen.getPmcInventory());
|
||||
this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.getAmmoTemplate()._id,
|
||||
randomisedAmmoStackSize,
|
||||
inventoryMagGen.getPmcInventory(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,60 +12,71 @@ import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
@injectable()
|
||||
export class ExternalInventoryMagGen implements IInventoryMagGen
|
||||
{
|
||||
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
|
||||
)
|
||||
{ }
|
||||
{}
|
||||
|
||||
getPriority(): number
|
||||
getPriority(): number
|
||||
{
|
||||
return 99;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
|
||||
canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
{
|
||||
return true; // Fallback, if code reaches here it means no other implementation can handle this type of magazine
|
||||
}
|
||||
|
||||
process(inventoryMagGen: InventoryMagGen): void
|
||||
process(inventoryMagGen: InventoryMagGen): void
|
||||
{
|
||||
let magTemplate = inventoryMagGen.getMagazineTemplate();
|
||||
let magazineTpl = magTemplate._id;
|
||||
const randomizedMagazineCount = Number(this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()));
|
||||
const randomizedMagazineCount = Number(
|
||||
this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()),
|
||||
);
|
||||
for (let i = 0; i < randomizedMagazineCount; i++)
|
||||
{
|
||||
const magazineWithAmmo = this.botWeaponGeneratorHelper.createMagazineWithAmmo(magazineTpl, inventoryMagGen.getAmmoTemplate()._id, magTemplate);
|
||||
const magazineWithAmmo = this.botWeaponGeneratorHelper.createMagazineWithAmmo(
|
||||
magazineTpl,
|
||||
inventoryMagGen.getAmmoTemplate()._id,
|
||||
magTemplate,
|
||||
);
|
||||
|
||||
const ableToFitMagazinesIntoBotInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
||||
[EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS],
|
||||
magazineWithAmmo[0]._id,
|
||||
magazineTpl,
|
||||
magazineWithAmmo,
|
||||
inventoryMagGen.getPmcInventory());
|
||||
inventoryMagGen.getPmcInventory(),
|
||||
);
|
||||
|
||||
if (ableToFitMagazinesIntoBotInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount)
|
||||
{
|
||||
/* We were unable to fit at least the minimum amount of magazines,
|
||||
* so we fallback to default magazine and try again.
|
||||
* Temporary workaround to Killa spawning with no extras if he spawns with a drum mag */
|
||||
|
||||
if (magazineTpl === this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(inventoryMagGen.getWeaponTemplate()))
|
||||
// We were unable to fit at least the minimum amount of magazines, so we fallback to default magazine
|
||||
// and try again. Temporary workaround to Killa spawning with no extras if he spawns with a drum mag.
|
||||
// TODO: Fix this properly
|
||||
if (
|
||||
magazineTpl ===
|
||||
this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(inventoryMagGen.getWeaponTemplate())
|
||||
)
|
||||
{
|
||||
// We were already on default - stop here to prevent infinite looping
|
||||
break;
|
||||
}
|
||||
|
||||
// Get default magazine tpl, reset loop counter by 1 and try again
|
||||
magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(inventoryMagGen.getWeaponTemplate());
|
||||
magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(
|
||||
inventoryMagGen.getWeaponTemplate(),
|
||||
);
|
||||
magTemplate = this.itemHelper.getItem(magazineTpl)[1];
|
||||
if (!magTemplate)
|
||||
{
|
||||
this.logger.error(this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl));
|
||||
this.logger.error(
|
||||
this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -78,5 +89,4 @@ export class ExternalInventoryMagGen implements IInventoryMagGen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,25 +7,31 @@ import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHel
|
||||
@injectable()
|
||||
export class InternalMagazineInventoryMagGen implements IInventoryMagGen
|
||||
{
|
||||
|
||||
constructor(
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
|
||||
)
|
||||
{ }
|
||||
{}
|
||||
|
||||
public getPriority(): number
|
||||
public getPriority(): number
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
{
|
||||
return inventoryMagGen.getMagazineTemplate()._props.ReloadMagType === "InternalMagazine";
|
||||
}
|
||||
|
||||
public process(inventoryMagGen: InventoryMagGen): void
|
||||
public process(inventoryMagGen: InventoryMagGen): void
|
||||
{
|
||||
const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount(inventoryMagGen.getMagCount(), inventoryMagGen.getMagazineTemplate());
|
||||
this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(inventoryMagGen.getAmmoTemplate()._id, bulletCount, inventoryMagGen.getPmcInventory());
|
||||
const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount(
|
||||
inventoryMagGen.getMagCount(),
|
||||
inventoryMagGen.getMagazineTemplate(),
|
||||
);
|
||||
this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.getAmmoTemplate()._id,
|
||||
bulletCount,
|
||||
inventoryMagGen.getPmcInventory(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,25 +9,32 @@ import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots";
|
||||
@injectable()
|
||||
export class UbglExternalMagGen implements IInventoryMagGen
|
||||
{
|
||||
|
||||
constructor(
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper
|
||||
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
|
||||
)
|
||||
{ }
|
||||
{}
|
||||
|
||||
public getPriority(): number
|
||||
public getPriority(): number
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean
|
||||
{
|
||||
return inventoryMagGen.getWeaponTemplate()._parent === BaseClasses.UBGL;
|
||||
}
|
||||
|
||||
public process(inventoryMagGen: InventoryMagGen): void
|
||||
public process(inventoryMagGen: InventoryMagGen): void
|
||||
{
|
||||
const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount(inventoryMagGen.getMagCount(), inventoryMagGen.getMagazineTemplate());
|
||||
this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(inventoryMagGen.getAmmoTemplate()._id, bulletCount, inventoryMagGen.getPmcInventory(), [EquipmentSlots.TACTICAL_VEST]);
|
||||
const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount(
|
||||
inventoryMagGen.getMagCount(),
|
||||
inventoryMagGen.getMagazineTemplate(),
|
||||
);
|
||||
this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.getAmmoTemplate()._id,
|
||||
bulletCount,
|
||||
inventoryMagGen.getPmcInventory(),
|
||||
[EquipmentSlots.TACTICAL_VEST],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user