"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const config_json_1 = __importDefault(require("../config/config.json")); const CommonUtils_1 = require("./CommonUtils"); const QuestManager_1 = require("./QuestManager"); const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes"); const modName = "SPTQuestingBots"; class QuestingBots { commonUtils; questManager; logger; configServer; databaseServer; databaseTables; localeService; questHelper; profileHelper; vfs; httpResponseUtil; randomUtil; botController; botGenerationCacheService; iBotConfig; iPmcConfig; iLocationConfig; iAirdropConfig; convertIntoPmcChanceOrig = {}; basePScavConversionChance; preAkiLoad(container) { this.logger = container.resolve("WinstonLogger"); const staticRouterModService = container.resolve("StaticRouterModService"); const dynamicRouterModService = container.resolve("DynamicRouterModService"); // Get config.json settings for the bepinex plugin staticRouterModService.registerStaticRouter(`StaticGetConfig${modName}`, [{ url: "/QuestingBots/GetConfig", action: () => { return JSON.stringify(config_json_1.default); } }], "GetConfig"); // Report error messages to the SPT-AKI server console in case the user hasn't enabled the bepinex console dynamicRouterModService.registerDynamicRouter(`DynamicReportError${modName}`, [{ url: "/QuestingBots/ReportError/", action: (url) => { const urlParts = url.split("/"); const errorMessage = urlParts[urlParts.length - 1]; const regex = /%20/g; this.commonUtils.logError(errorMessage.replace(regex, " ")); return JSON.stringify({ resp: "OK" }); } }], "ReportError"); // Get the logging directory for saving quest information after raids staticRouterModService.registerStaticRouter(`StaticGetLoggingPath${modName}`, [{ url: "/QuestingBots/GetLoggingPath", action: () => { const loggingPath = `${__dirname}\\..\\log\\`; this.commonUtils.logInfo(`Logging path: ${loggingPath}`); return JSON.stringify({ path: loggingPath }); } }], "GetLoggingPath"); if (!config_json_1.default.enabled) { return; } // Game start // Needed to update Scav timer staticRouterModService.registerStaticRouter(`StaticAkiGameStart${modName}`, [{ url: "/client/game/start", // biome-ignore lint/suspicious/noExplicitAny: action: (url, info, sessionId, output) => { if (config_json_1.default.debug.enabled) { this.updateScavTimer(sessionId); } return output; } }], "aki"); // Apply a scalar factor to the SPT-AKI PMC conversion chances dynamicRouterModService.registerDynamicRouter(`DynamicAdjustPMCConversionChances${modName}`, [{ url: "/QuestingBots/AdjustPMCConversionChances/", action: (url) => { const urlParts = url.split("/"); const factor = Number(urlParts[urlParts.length - 2]); const verify = JSON.parse(urlParts[urlParts.length - 1].toLowerCase()); this.adjustPmcConversionChance(factor, verify); return JSON.stringify({ resp: "OK" }); } }], "AdjustPMCConversionChances"); // Apply a scalar factor to the SPT-AKI PScav conversion chance dynamicRouterModService.registerDynamicRouter(`DynamicAdjustPScavChance${modName}`, [{ url: "/QuestingBots/AdjustPScavChance/", action: (url) => { const urlParts = url.split("/"); const factor = Number(urlParts[urlParts.length - 1]); this.iBotConfig.chanceAssaultScavHasPlayerScavName = Math.round(this.basePScavConversionChance * factor); this.commonUtils.logInfo(`Adjusted PScav spawn chance to ${this.iBotConfig.chanceAssaultScavHasPlayerScavName}%`); return JSON.stringify({ resp: "OK" }); } }], "AdjustPScavChance"); // Get all EFT quest templates // NOTE: This includes custom quests added by mods staticRouterModService.registerStaticRouter(`GetAllQuestTemplates${modName}`, [{ url: "/QuestingBots/GetAllQuestTemplates", action: () => { return JSON.stringify({ templates: this.questHelper.getQuestsFromDb() }); } }], "GetAllQuestTemplates"); // Override bot generation to include PScav conversion chance dynamicRouterModService.registerDynamicRouter(`DynamicGenerateBot${modName}`, [{ url: "/QuestingBots/GenerateBot", action: (url, info, sessionID) => { const urlParts = url.split("/"); const pScavChance = Number(urlParts[urlParts.length - 1]); const bots = this.generateBots(info, sessionID, this.randomUtil.getChance100(pScavChance)); return this.httpResponseUtil.getBody(bots); } }], "GenerateBot"); // Get Scav-raid settings to determine PScav conversion chances staticRouterModService.registerStaticRouter(`GetScavRaidSettings${modName}`, [{ url: "/QuestingBots/GetScavRaidSettings", action: () => { return JSON.stringify({ maps: this.iLocationConfig.scavRaidTimeSettings.maps }); } }], "GetScavRaidSettings"); } postDBLoad(container) { this.configServer = container.resolve("ConfigServer"); this.databaseServer = container.resolve("DatabaseServer"); this.localeService = container.resolve("LocaleService"); this.questHelper = container.resolve("QuestHelper"); this.profileHelper = container.resolve("ProfileHelper"); this.vfs = container.resolve("VFS"); this.httpResponseUtil = container.resolve("HttpResponseUtil"); this.randomUtil = container.resolve("RandomUtil"); this.botController = container.resolve("BotController"); this.botGenerationCacheService = container.resolve("BotGenerationCacheService"); this.iBotConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.BOT); this.iPmcConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.PMC); this.iLocationConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.LOCATION); this.iAirdropConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.AIRDROP); this.databaseTables = this.databaseServer.getTables(); this.basePScavConversionChance = this.iBotConfig.chanceAssaultScavHasPlayerScavName; this.commonUtils = new CommonUtils_1.CommonUtils(this.logger, this.databaseTables, this.localeService); this.questManager = new QuestManager_1.QuestManager(this.commonUtils, this.vfs); if (!config_json_1.default.enabled) { return; } // Ensure all of the custom quests are valid JSON files this.questManager.validateCustomQuests(); if (config_json_1.default.debug.always_have_airdrops) { this.commonUtils.logInfo("Forcing airdrops to occur at the beginning of every raid..."); this.iAirdropConfig.airdropChancePercent.bigmap = 100; this.iAirdropConfig.airdropChancePercent.woods = 100; this.iAirdropConfig.airdropChancePercent.lighthouse = 100; this.iAirdropConfig.airdropChancePercent.shoreline = 100; this.iAirdropConfig.airdropChancePercent.interchange = 100; this.iAirdropConfig.airdropChancePercent.reserve = 100; this.iAirdropConfig.airdropChancePercent.tarkovStreets = 100; this.iAirdropConfig.airdropChancePercent.sandbox = 100; this.iAirdropConfig.airdropMinStartTimeSeconds = 5; this.iAirdropConfig.airdropMaxStartTimeSeconds = 10; } // Adjust parameters to make debugging easier if (config_json_1.default.debug.enabled) { this.commonUtils.logInfo("Applying debug options..."); if (config_json_1.default.debug.scav_cooldown_time < this.databaseTables.globals.config.SavagePlayCooldown) { this.databaseTables.globals.config.SavagePlayCooldown = config_json_1.default.debug.scav_cooldown_time; } if (config_json_1.default.debug.free_labs_access) { this.databaseTables.locations.laboratory.base.AccessKeys = []; this.databaseTables.locations.laboratory.base.DisabledForScav = false; } if (config_json_1.default.debug.full_length_scav_raids) { this.forceFullLengthScavRaids(); } } } postAkiLoad(container) { if (!config_json_1.default.enabled) { this.commonUtils.logInfo("Mod disabled in config.json", true); return; } this.removeBlacklistedBrainTypes(); // If we find SWAG or MOAR, disable initial spawns const preAkiModLoader = container.resolve("PreAkiModLoader"); if (config_json_1.default.bot_spawns.enabled && preAkiModLoader.getImportedModsNames().includes("SWAG")) { this.commonUtils.logWarning("SWAG Detected. Disabling bot spawning."); config_json_1.default.bot_spawns.enabled = false; } if (config_json_1.default.bot_spawns.enabled && preAkiModLoader.getImportedModsNames().includes("DewardianDev-MOAR")) { this.commonUtils.logWarning("MOAR Detected. Disabling bot spawning."); config_json_1.default.bot_spawns.enabled = false; } if (preAkiModLoader.getImportedModsNames().includes("Andrudis-QuestManiac")) { this.commonUtils.logWarning("QuestManiac Detected. This mod is known to cause performance issues when used with QuestingBots. No support will be provided."); } // Make Questing Bots control PScav spawning if (config_json_1.default.adjust_pscav_chance.enabled || (config_json_1.default.bot_spawns.enabled && config_json_1.default.bot_spawns.player_scavs.enabled)) { this.iBotConfig.chanceAssaultScavHasPlayerScavName = 0; } if (!config_json_1.default.bot_spawns.enabled) { return; } this.commonUtils.logInfo("Configuring game for bot spawning..."); // Store the current PMC-conversion chances in case they need to be restored later this.setOriginalPMCConversionChances(); // Currently these are all PMC waves, which are unnecessary with PMC spawns in this mod this.disableCustomBossWaves(); // Disable all of the extra Scavs that spawn into Factory this.disableCustomScavWaves(); // If Rogues don't spawn immediately, PMC spawns will be significantly delayed if (config_json_1.default.bot_spawns.limit_initial_boss_spawns.disable_rogue_delay) { this.commonUtils.logInfo("Removing SPT Rogue spawn delay..."); this.iLocationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds = -1; } if (config_json_1.default.bot_spawns.advanced_eft_bot_count_management) { this.commonUtils.logWarning("Enabling advanced_eft_bot_count_management will instruct EFT to ignore this mod's PMC's and PScavs when spawning more bots."); } if (config_json_1.default.bot_spawns.bot_cap_adjustments.enabled) { this.increaseBotCaps(); } this.commonUtils.logInfo("Configuring game for bot spawning...done."); } updateScavTimer(sessionId) { const pmcData = this.profileHelper.getPmcProfile(sessionId); const scavData = this.profileHelper.getScavProfile(sessionId); if ((scavData.Info === null) || (scavData.Info === undefined)) { this.commonUtils.logInfo("Scav profile hasn't been created yet."); return; } // In case somebody disables scav runs and later wants to enable them, we need to reset their Scav timer unless it's plausible const worstCooldownFactor = this.getWorstSavageCooldownModifier(); if (scavData.Info.SavageLockTime - pmcData.Info.LastTimePlayedAsSavage > this.databaseTables.globals.config.SavagePlayCooldown * worstCooldownFactor * 1.1) { this.commonUtils.logInfo(`Resetting scav timer for sessionId=${sessionId}...`); scavData.Info.SavageLockTime = 0; } } // Return the highest Scav cooldown factor from Fence's rep levels getWorstSavageCooldownModifier() { // Initialize the return value at something very low let worstCooldownFactor = 0.01; for (const level in this.databaseTables.globals.config.FenceSettings.Levels) { if (this.databaseTables.globals.config.FenceSettings.Levels[level].SavageCooldownModifier > worstCooldownFactor) worstCooldownFactor = this.databaseTables.globals.config.FenceSettings.Levels[level].SavageCooldownModifier; } return worstCooldownFactor; } setOriginalPMCConversionChances() { // Store the default PMC-conversion chances for each bot type defined in SPT's configuration file let logMessage = ""; for (const pmcType in this.iPmcConfig.convertIntoPmcChance) { if (this.convertIntoPmcChanceOrig[pmcType] !== undefined) { logMessage += `${pmcType}: already buffered, `; continue; } const chances = { min: this.iPmcConfig.convertIntoPmcChance[pmcType].min, max: this.iPmcConfig.convertIntoPmcChance[pmcType].max }; this.convertIntoPmcChanceOrig[pmcType] = chances; logMessage += `${pmcType}: ${chances.min}-${chances.max}%, `; } this.commonUtils.logInfo(`Reading default PMC spawn chances: ${logMessage}`); } adjustPmcConversionChance(scalingFactor, verify) { // Adjust the chances for each applicable bot type let logMessage = ""; let verified = true; for (const pmcType in this.iPmcConfig.convertIntoPmcChance) { // Do not allow the chances to exceed 100%. Who knows what might happen... const min = Math.round(Math.min(100, this.convertIntoPmcChanceOrig[pmcType].min * scalingFactor)); const max = Math.round(Math.min(100, this.convertIntoPmcChanceOrig[pmcType].max * scalingFactor)); if (verify) { if (this.iPmcConfig.convertIntoPmcChance[pmcType].min !== min) { verified = false; break; } if (this.iPmcConfig.convertIntoPmcChance[pmcType].max !== max) { verified = false; break; } } else { this.iPmcConfig.convertIntoPmcChance[pmcType].min = min; this.iPmcConfig.convertIntoPmcChance[pmcType].max = max; logMessage += `${pmcType}: ${min}-${max}%, `; } } if (!verify) { this.commonUtils.logInfo(`Adjusting PMC spawn chances (${scalingFactor}): ${logMessage}`); } if (!verified) { this.commonUtils.logError("Another mod has changed the PMC conversion chances. This mod may not work properly!"); } } disableCustomBossWaves() { this.commonUtils.logInfo("Disabling custom boss waves..."); this.iLocationConfig.customWaves.boss = {}; } disableCustomScavWaves() { this.commonUtils.logInfo("Disabling custom Scav waves..."); this.iLocationConfig.customWaves.normal = {}; } increaseBotCaps() { if (!config_json_1.default.bot_spawns.bot_cap_adjustments.add_max_players_to_bot_cap) { return; } const maxAddtlBots = config_json_1.default.bot_spawns.bot_cap_adjustments.max_additional_bots; const maxTotalBots = config_json_1.default.bot_spawns.bot_cap_adjustments.max_total_bots; this.iBotConfig.maxBotCap.factory4_day = Math.min(this.iBotConfig.maxBotCap.factory4_day + Math.min(this.databaseTables.locations.factory4_day.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.factory4_night = Math.min(this.iBotConfig.maxBotCap.factory4_night + Math.min(this.databaseTables.locations.factory4_night.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.bigmap = Math.min(this.iBotConfig.maxBotCap.bigmap + Math.min(this.databaseTables.locations.bigmap.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.woods = Math.min(this.iBotConfig.maxBotCap.woods + Math.min(this.databaseTables.locations.woods.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.shoreline = Math.min(this.iBotConfig.maxBotCap.shoreline + Math.min(this.databaseTables.locations.shoreline.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.lighthouse = Math.min(this.iBotConfig.maxBotCap.lighthouse + Math.min(this.databaseTables.locations.lighthouse.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.rezervbase = Math.min(this.iBotConfig.maxBotCap.rezervbase + Math.min(this.databaseTables.locations.rezervbase.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.interchange = Math.min(this.iBotConfig.maxBotCap.interchange + Math.min(this.databaseTables.locations.interchange.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.laboratory = Math.min(this.iBotConfig.maxBotCap.laboratory + Math.min(this.databaseTables.locations.laboratory.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.tarkovstreets = Math.min(this.iBotConfig.maxBotCap.tarkovstreets + Math.min(this.databaseTables.locations.tarkovstreets.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.sandbox = Math.min(this.iBotConfig.maxBotCap.sandbox + Math.min(this.databaseTables.locations.sandbox.base.MaxPlayers, maxAddtlBots), maxTotalBots); this.iBotConfig.maxBotCap.default = Math.min(this.iBotConfig.maxBotCap.default + maxAddtlBots, maxTotalBots); for (const location in this.iBotConfig.maxBotCap) { this.commonUtils.logInfo(`Changed bot cap for ${location} to: ${this.iBotConfig.maxBotCap[location]}`); } } removeBlacklistedBrainTypes() { const badBrains = config_json_1.default.bot_spawns.blacklisted_pmc_bot_brains; this.commonUtils.logInfo("Removing blacklisted brain types from being used for PMC's..."); let removedBrains = 0; for (const pmcType in this.iPmcConfig.pmcType) { for (const map in this.iPmcConfig.pmcType[pmcType]) { const mapBrains = this.iPmcConfig.pmcType[pmcType][map]; for (const i in badBrains) { if (mapBrains[badBrains[i]] === undefined) { continue; } //this.commonUtils.logInfo(`Removing ${badBrains[i]} from ${pmcType} in ${map}...`); delete mapBrains[badBrains[i]]; removedBrains++; } } } this.commonUtils.logInfo(`Removing blacklisted brain types from being used for PMC's...done. Removed entries: ${removedBrains}`); } forceFullLengthScavRaids() { this.commonUtils.logInfo("Forcing full-length Scav raids..."); for (const map in this.iLocationConfig.scavRaidTimeSettings.maps) { this.iLocationConfig.scavRaidTimeSettings.maps[map].reducedChancePercent = 0; } } generateBots(info, sessionID, shouldBePScavGroup) { const bots = this.botController.generate(sessionID, info); if (!shouldBePScavGroup) { return bots; } const pmcNames = [ ...this.databaseTables.bots.types.usec.firstName, ...this.databaseTables.bots.types.bear.firstName ]; for (const bot in bots) { if (info.conditions[0].Role !== "assault") { continue; } bots[bot].Info.Nickname = `${bots[bot].Info.Nickname} (${this.randomUtil.getArrayValue(pmcNames)})`; } return bots; } } module.exports = { mod: new QuestingBots() }; //# sourceMappingURL=mod.js.map