f3964639bd
regenerate bot jsons to include this new data (includes correct inclusion of equipment slot "TacticalVest" which was previously missing) Fix issue with PMM ammo causes generation issues
340 lines
13 KiB
TypeScript
340 lines
13 KiB
TypeScript
import { inject, injectable } from "tsyringe";
|
|
|
|
import { BotGenerator } from "@spt-aki/generators/BotGenerator";
|
|
import { BotGeneratorHelper } from "@spt-aki/helpers/BotGeneratorHelper";
|
|
import { BotHelper } from "@spt-aki/helpers/BotHelper";
|
|
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
|
|
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
|
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
|
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
|
import { Settings, Skills, Stats } from "@spt-aki/models/eft/common/tables/IBotBase";
|
|
import { IBotType } from "@spt-aki/models/eft/common/tables/IBotType";
|
|
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
|
import { AccountTypes } from "@spt-aki/models/enums/AccountTypes";
|
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
|
import { ItemAddedResult } from "@spt-aki/models/enums/ItemAddedResult";
|
|
import { MemberCategory } from "@spt-aki/models/enums/MemberCategory";
|
|
import { Traders } from "@spt-aki/models/enums/Traders";
|
|
import { IPlayerScavConfig, KarmaLevel } from "@spt-aki/models/spt/config/IPlayerScavConfig";
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
|
import { SaveServer } from "@spt-aki/servers/SaveServer";
|
|
import { BotLootCacheService } from "@spt-aki/services/BotLootCacheService";
|
|
import { FenceService } from "@spt-aki/services/FenceService";
|
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
|
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
|
|
|
@injectable()
|
|
export class PlayerScavGenerator
|
|
{
|
|
protected playerScavConfig: IPlayerScavConfig;
|
|
|
|
constructor(
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
|
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
|
|
@inject("BotGeneratorHelper") protected botGeneratorHelper: BotGeneratorHelper,
|
|
@inject("SaveServer") protected saveServer: SaveServer,
|
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
|
@inject("BotHelper") protected botHelper: BotHelper,
|
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
|
@inject("FenceService") protected fenceService: FenceService,
|
|
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
@inject("BotGenerator") protected botGenerator: BotGenerator,
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
)
|
|
{
|
|
this.playerScavConfig = this.configServer.getConfig(ConfigTypes.PLAYERSCAV);
|
|
}
|
|
|
|
/**
|
|
* Update a player profile to include a new player scav profile
|
|
* @param sessionID session id to specify what profile is updated
|
|
* @returns profile object
|
|
*/
|
|
public generate(sessionID: string): IPmcData
|
|
{
|
|
// get karma level from profile
|
|
const profile = this.saveServer.getProfile(sessionID);
|
|
const pmcData = this.jsonUtil.clone(profile.characters.pmc);
|
|
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);
|
|
|
|
// use karma level to get correct karmaSettings
|
|
const playerScavKarmaSettings = this.playerScavConfig.karmaLevel[scavKarmaLevel];
|
|
if (!playerScavKarmaSettings)
|
|
{
|
|
this.logger.error(this.localisationService.getText("scav-missing_karma_settings", scavKarmaLevel));
|
|
}
|
|
|
|
this.logger.debug(`generated player scav loadout with karma level ${scavKarmaLevel}`);
|
|
|
|
// Edit baseBotNode values
|
|
const baseBotNode: IBotType = this.constructBotBaseTemplate(playerScavKarmaSettings.botTypeForLoot);
|
|
this.adjustBotTemplateWithKarmaSpecificSettings(playerScavKarmaSettings, baseBotNode);
|
|
|
|
let scavData = this.botGenerator.generatePlayerScav(
|
|
sessionID,
|
|
playerScavKarmaSettings.botTypeForLoot.toLowerCase(),
|
|
"easy",
|
|
baseBotNode,
|
|
);
|
|
|
|
// Remove cached bot data after scav was generated
|
|
this.botLootCacheService.clearCache();
|
|
|
|
// Add scav metadata
|
|
scavData.savage = null;
|
|
scavData.aid = pmcData.aid;
|
|
scavData.TradersInfo = pmcData.TradersInfo;
|
|
scavData.Info.Settings = {} as Settings;
|
|
scavData.Info.Bans = [];
|
|
scavData.Info.RegistrationDate = pmcData.Info.RegistrationDate;
|
|
scavData.Info.GameVersion = pmcData.Info.GameVersion;
|
|
scavData.Info.MemberCategory = MemberCategory.UNIQUE_ID;
|
|
scavData.Info.lockedMoveCommands = true;
|
|
scavData.RagfairInfo = pmcData.RagfairInfo;
|
|
scavData.UnlockedInfo = pmcData.UnlockedInfo;
|
|
|
|
// Persist previous scav data into new scav
|
|
scavData._id = existingScavData._id ?? pmcData.savage;
|
|
scavData.sessionId = existingScavData.sessionId ?? pmcData.sessionId;
|
|
scavData.Skills = this.getScavSkills(existingScavData);
|
|
scavData.Stats = this.getScavStats(existingScavData);
|
|
scavData.Info.Level = this.getScavLevel(existingScavData);
|
|
scavData.Info.Experience = this.getScavExperience(existingScavData);
|
|
scavData.Quests = existingScavData.Quests ?? [];
|
|
scavData.TaskConditionCounters = existingScavData.TaskConditionCounters ?? { };
|
|
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))
|
|
{
|
|
const labsCard = this.itemHelper.getItem("5c94bbff86f7747ee735c08f")[1];
|
|
const itemsToAdd: Item[] = [{
|
|
_id: this.hashUtil.generate(),
|
|
_tpl: labsCard._id,
|
|
...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard),
|
|
}];
|
|
const result = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
|
|
["TacticalVest", "Pockets", "Backpack"],
|
|
itemsToAdd[0]._id,
|
|
labsCard._id,
|
|
itemsToAdd,
|
|
scavData.Inventory,
|
|
);
|
|
|
|
if (result !== ItemAddedResult.SUCCESS)
|
|
{
|
|
this.logger.debug(`Unable to add keycard to bot. Reason: ${ItemAddedResult[result]}`);
|
|
}
|
|
}
|
|
|
|
// Remove secure container
|
|
scavData = this.profileHelper.removeSecureContainer(scavData);
|
|
|
|
// Set cooldown timer
|
|
scavData = this.setScavCooldownTimer(scavData, pmcData);
|
|
|
|
// Add scav to the profile
|
|
this.saveServer.getProfile(sessionID).characters.scav = scavData;
|
|
|
|
return scavData;
|
|
}
|
|
|
|
/**
|
|
* Get the scav karama level for a profile
|
|
* Is also the fence trader rep level
|
|
* @param pmcData pmc profile
|
|
* @returns karma level
|
|
*/
|
|
protected getScavKarmaLevel(pmcData: IPmcData): number
|
|
{
|
|
const fenceInfo = pmcData.TradersInfo[Traders.FENCE];
|
|
|
|
// Can be empty during profile creation
|
|
if (!fenceInfo)
|
|
{
|
|
this.logger.warning(this.localisationService.getText("scav-missing_karma_level_getting_default"));
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (fenceInfo.standing > 6)
|
|
{
|
|
return 6;
|
|
}
|
|
|
|
// e.g. 2.09 becomes 2
|
|
return Math.floor(fenceInfo.standing);
|
|
}
|
|
|
|
/**
|
|
* Get a baseBot template
|
|
* If the parameter doesnt match "assault", take parts from the loot type and apply to the return bot template
|
|
* @param botTypeForLoot bot type to use for inventory/chances
|
|
* @returns IBotType object
|
|
*/
|
|
protected constructBotBaseTemplate(botTypeForLoot: string): IBotType
|
|
{
|
|
const baseScavType = "assault";
|
|
const assaultBase = this.jsonUtil.clone(this.botHelper.getBotTemplate(baseScavType));
|
|
|
|
// Loot bot is same as base bot, return base with no modification
|
|
if (botTypeForLoot === baseScavType)
|
|
{
|
|
return assaultBase;
|
|
}
|
|
|
|
const lootBase = this.jsonUtil.clone(this.botHelper.getBotTemplate(botTypeForLoot));
|
|
assaultBase.inventory = lootBase.inventory;
|
|
assaultBase.chances = lootBase.chances;
|
|
assaultBase.generation = lootBase.generation;
|
|
|
|
return assaultBase;
|
|
}
|
|
|
|
/**
|
|
* Adjust equipment/mod/item generation values based on scav karma levels
|
|
* @param karmaSettings Values to modify the bot template with
|
|
* @param baseBotNode bot template to modify according to karama level settings
|
|
*/
|
|
protected adjustBotTemplateWithKarmaSpecificSettings(karmaSettings: KarmaLevel, baseBotNode: IBotType): void
|
|
{
|
|
// Adjust equipment chance values
|
|
for (const equipmentKey in karmaSettings.modifiers.equipment)
|
|
{
|
|
if (karmaSettings.modifiers.equipment[equipmentKey] === 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
baseBotNode.chances.equipment[equipmentKey] += karmaSettings.modifiers.equipment[equipmentKey];
|
|
}
|
|
|
|
// Adjust mod chance values
|
|
for (const modKey in karmaSettings.modifiers.mod)
|
|
{
|
|
if (karmaSettings.modifiers.mod[modKey] === 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
baseBotNode.chances.weaponMods[modKey] += karmaSettings.modifiers.mod[modKey];
|
|
}
|
|
|
|
// Adjust item spawn quantity values
|
|
for (const itemLimitkey in karmaSettings.itemLimits)
|
|
{
|
|
baseBotNode.generation.items[itemLimitkey] = karmaSettings.itemLimits[itemLimitkey];
|
|
}
|
|
|
|
// Blacklist equipment
|
|
for (const equipmentKey in karmaSettings.equipmentBlacklist)
|
|
{
|
|
const blacklistedItemTpls = karmaSettings.equipmentBlacklist[equipmentKey];
|
|
for (const itemToRemove of blacklistedItemTpls)
|
|
{
|
|
delete baseBotNode.inventory.equipment[equipmentKey][itemToRemove];
|
|
}
|
|
}
|
|
}
|
|
|
|
protected getScavSkills(scavProfile: IPmcData): Skills
|
|
{
|
|
if (scavProfile.Skills)
|
|
{
|
|
return scavProfile.Skills;
|
|
}
|
|
|
|
return this.getDefaultScavSkills();
|
|
}
|
|
|
|
protected getDefaultScavSkills(): Skills
|
|
{
|
|
return { Common: [], Mastering: [], Points: 0 };
|
|
}
|
|
|
|
protected getScavStats(scavProfile: IPmcData): Stats
|
|
{
|
|
if (scavProfile.Stats)
|
|
{
|
|
return scavProfile.Stats;
|
|
}
|
|
|
|
return this.profileHelper.getDefaultCounters();
|
|
}
|
|
|
|
protected getScavLevel(scavProfile: IPmcData): number
|
|
{
|
|
// Info can be null on initial account creation
|
|
if (!(scavProfile.Info?.Level))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return scavProfile.Info.Level;
|
|
}
|
|
|
|
protected getScavExperience(scavProfile: IPmcData): number
|
|
{
|
|
// Info can be null on initial account creation
|
|
if (!(scavProfile.Info?.Experience))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return scavProfile.Info.Experience;
|
|
}
|
|
|
|
/**
|
|
* Set cooldown till pscav is playable
|
|
* take into account scav cooldown bonus
|
|
* @param scavData scav profile
|
|
* @param pmcData pmc profile
|
|
* @returns
|
|
*/
|
|
protected setScavCooldownTimer(scavData: IPmcData, pmcData: IPmcData): IPmcData
|
|
{
|
|
// Set cooldown time.
|
|
// Make sure to apply ScavCooldownTimer bonus from Hideout if the player has it.
|
|
let scavLockDuration = this.databaseServer.getTables().globals.config.SavagePlayCooldown;
|
|
let modifier = 1;
|
|
|
|
for (const bonus of pmcData.Bonuses)
|
|
{
|
|
if (bonus.type === "ScavCooldownTimer")
|
|
{
|
|
// Value is negative, so add.
|
|
// Also note that for scav cooldown, multiple bonuses stack additively.
|
|
modifier += bonus.value / 100;
|
|
}
|
|
}
|
|
|
|
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))
|
|
{
|
|
// Set scav cooldown timer to 10 seconds for spt developer account
|
|
scavLockDuration = 10;
|
|
}
|
|
|
|
scavData.Info.SavageLockTime = (Date.now() / 1000) + scavLockDuration;
|
|
|
|
return scavData;
|
|
}
|
|
}
|