From 6eecae59f62498d9f1ae2a8231b0f7b84d3f7732 Mon Sep 17 00:00:00 2001 From: Rage Date: Mon, 13 Jan 2025 18:17:36 -0500 Subject: [PATCH] Updated MOAR & Tweaked Config --- .../BepInEx/config/MOAR.settings.cfg | 57 ++- .../src/db/rotationData.json | 6 +- .../BepInEx/plugins/MOAR.dll | Bin 31232 -> 34816 bytes mods/MOAR - Ultra Lite Spawn Mod/meta.ini | 4 +- .../DewardianDev-MOAR/config/Presets.json | 5 +- .../mods/DewardianDev-MOAR/config/config.json | 14 +- .../DewardianDev-MOAR/config/mapConfig.json | 59 +-- .../user/mods/DewardianDev-MOAR/package.json | 2 +- .../DewardianDev-MOAR/src/Routes/routes.ts | 8 +- .../src/Spawning/Spawning.ts | 20 +- .../src/Spawning/buildBossWaves.ts | 35 +- .../src/Spawning/buildPmcs.ts | 29 +- .../src/Spawning/buildScavMarksmanWaves.ts | 46 +- .../src/Spawning/updateSpawnLocations.ts | 133 +++++- .../DewardianDev-MOAR/src/Spawning/utils.ts | 70 ++- .../BepInEx/plugins/MOAR.dll | Bin 0 -> 31232 bytes .../meta.ini | 28 ++ .../.github/workflows/action.yml | 0 .../user/mods/DewardianDev-MOAR/.releaserc | 0 .../user/mods/DewardianDev-MOAR/LICENSE.md | 21 + .../config/PresetWeightings.json | 12 + .../DewardianDev-MOAR/config/Presets.json | 65 +++ .../DewardianDev-MOAR/config/bossConfig.json | 63 +++ .../mods/DewardianDev-MOAR/config/config.json | 46 ++ .../DewardianDev-MOAR/config/mapConfig.json | 149 ++++++ .../user/mods/DewardianDev-MOAR/package.json | 25 + .../DewardianDev-MOAR/src/GlobalValues.ts | 11 + .../DewardianDev-MOAR/src/Routes/routes.ts | 168 +++++++ .../src/Spawning/Spawning.ts | 154 +++++++ .../src/Spawning/buildBossWaves.ts | 278 +++++++++++ .../src/Spawning/buildPmcs.ts | 87 ++++ .../src/Spawning/buildScavMarksmanWaves.ts | 226 +++++++++ .../src/Spawning/buildZombieWaves.ts | 80 ++++ .../src/Spawning/constants.ts | 204 +++++++++ .../src/Spawning/updateSpawnLocations.ts | 38 ++ .../DewardianDev-MOAR/src/Spawning/utils.ts | 430 ++++++++++++++++++ .../src/Tests/checkPresets.ts | 28 ++ .../DewardianDev-MOAR/src/Zombies/Zombies.ts | 160 +++++++ .../user/mods/DewardianDev-MOAR/src/mod.ts | 29 ++ .../user/mods/DewardianDev-MOAR/src/utils.ts | 57 +++ profiles/Server/modlist.txt | 1 - 41 files changed, 2649 insertions(+), 199 deletions(-) create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/BepInEx/plugins/MOAR.dll create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/meta.ini rename mods/{MOAR - Ultra Lite Spawn Mod => MOAR - Ultra Lite Spawn Mod_backup}/user/mods/DewardianDev-MOAR/.github/workflows/action.yml (100%) rename mods/{MOAR - Ultra Lite Spawn Mod => MOAR - Ultra Lite Spawn Mod_backup}/user/mods/DewardianDev-MOAR/.releaserc (100%) create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/LICENSE.md create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/config/PresetWeightings.json create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/config/Presets.json create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/config/bossConfig.json create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/config/config.json create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/config/mapConfig.json create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/package.json create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/GlobalValues.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Routes/routes.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildZombieWaves.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/constants.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Tests/checkPresets.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Zombies/Zombies.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/mod.ts create mode 100644 mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/utils.ts diff --git a/mods/Config Files/BepInEx/config/MOAR.settings.cfg b/mods/Config Files/BepInEx/config/MOAR.settings.cfg index 25e46ff..3a638f8 100644 --- a/mods/Config Files/BepInEx/config/MOAR.settings.cfg +++ b/mods/Config Files/BepInEx/config/MOAR.settings.cfg @@ -1,8 +1,23 @@ -## Settings file was created by plugin MOAR v2.5.6 +## Settings file was created by plugin MOAR v2.6.7 ## Plugin GUID: MOAR.settings [1. Main Settings] +## All Bots/Players (excluding bosses) can spawn anywhere (overrides PMC/Player openzone options) +# Setting type: Boolean +# Default value: false +PMC/Scav/Player OpenZones On/Off = false + +## Adds a large number of zones (including all scav zones) to pmc bots spawn pool +# Setting type: Boolean +# Default value: true +PMC OpenZones On/Off = true + +## Adds a large number of zones to the Player's (you) spawn pool +# Setting type: Boolean +# Default value: false +Player OpenZones On/Off = false + ## Causes all PMCs to spawn in the first few minutes of the game (performance intensive) # Setting type: Boolean # Default value: false @@ -16,9 +31,9 @@ Pmc difficulty = 0.6 ## Works with SAIN or SPT to decide the bot's 'difficulty' preset (EASY: 0, easy-MEDIUM: 0.4, easy-MEDIUM-hard: 0.6, medium-hard: 0.85, HARD-impossible: 1, etc..) # Setting type: Double -# Default value: 0.3 +# Default value: 0.4 # Acceptable value range: From 0 to 1.5 -Scav difficulty = 0.3 +Scav difficulty = 0.4 ## Preset to be used, random pulls a random weighted preset from the PresetWeights.json every time a raid ends # Setting type: String @@ -36,12 +51,6 @@ Preset Announce On/Off = true # Default value: FIKA DETECTED: ALWAYS PRESS THIS FIRST BEFORE MAKING CHANGES!! = -PMC/Scav/Player OpenZones On/Off = false - -PMC OpenZones On/Off = true - -Player OpenZones On/Off = true - [2. Custom game Settings] ## Pushes settings to server @@ -63,7 +72,7 @@ gradualBossInvasion On/Off = true # Setting type: Int32 # Default value: 5 # Acceptable value range: From 0 to 100 -bossInvasionSpawnChance = 5 +bossInvasionSpawnChance = 0 ## Allows the main bosses (not knight,rogues,raiders) to invade other maps with a reduced retinue, by default they will spawn in native boss locations # Setting type: Boolean @@ -123,23 +132,23 @@ scavMaxGroupSize = 4 # Setting type: Int32 # Default value: 4 # Acceptable value range: From 0 to 10 -pmcMaxGroupSize = 4 +pmcMaxGroupSize = 5 ## Increases chances of pmc groups spawning, doesn't dramatically increase quantity. # Setting type: Boolean # Default value: false -morePmcGroups On/Off = false +morePmcGroups On/Off = true ## Increases chances of scav groups spawning, doesn't dramatically increase quantity. # Setting type: Boolean # Default value: false -moreScavGroups On/Off = false +moreScavGroups On/Off = true ## Max bots permitted in any particular spawn zone, recommend not to touch this. # Setting type: Int32 -# Default value: 7 +# Default value: 5 # Acceptable value range: From 0 to 15 -MaxBotPerZone = 7 +MaxBotPerZone = 5 ## Max bots alive at one time # Setting type: Int32 @@ -157,34 +166,34 @@ ZombieHealth = 1 # Setting type: Double # Default value: 1 # Acceptable value range: From 0 to 10 -ZombieWaveQuantity = 1 +ZombieWaveQuantity = 1.5 ## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid # Setting type: Double # Default value: 0.5 # Acceptable value range: From 0 to 1 -ZombieWaveDistribution = 0.5 +ZombieWaveDistribution = 0.2 ## Enables zombies to spawn # Setting type: Boolean # Default value: false -zombiesEnabled On/Off = false +zombiesEnabled On/Off = true ## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid # Setting type: Double -# Default value: 0.3 +# Default value: 0.5 # Acceptable value range: From 0 to 1 -ScavWaveDistribution = 0.3 +ScavWaveDistribution = 0.5 ## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid # Setting type: Double -# Default value: 0.8 +# Default value: 0.7 # Acceptable value range: From 0 to 1 -PmcWaveDistribution = 0.8 +PmcWaveDistribution = 0.7 ## Multiplies wave counts seen in the server's mapConfig.json by this number # Setting type: Double -# Default value: 0.5 +# Default value: 1 # Acceptable value range: From 0 to 10 ScavWaveQuantity = 1 @@ -192,7 +201,7 @@ ScavWaveQuantity = 1 # Setting type: Double # Default value: 1 # Acceptable value range: From 0 to 10 -PmcWaveQuantity = 1.2 +PmcWaveQuantity = 1.5 [3.Debug] diff --git a/mods/Dynamic Goons/user/mods/inory-dynamicgoons/src/db/rotationData.json b/mods/Dynamic Goons/user/mods/inory-dynamicgoons/src/db/rotationData.json index a0576ef..ea6eb17 100644 --- a/mods/Dynamic Goons/user/mods/inory-dynamicgoons/src/db/rotationData.json +++ b/mods/Dynamic Goons/user/mods/inory-dynamicgoons/src/db/rotationData.json @@ -1,6 +1,6 @@ { - "nextUpdateTime": 1736141944920, - "selectedMap": "woods", + "nextUpdateTime": 1736820722143, + "selectedMap": "lighthouse", "lastRotationInterval": 180, - "lastUpdateTime": 1736131144920 + "lastUpdateTime": 1736809922143 } \ No newline at end of file diff --git a/mods/MOAR - Ultra Lite Spawn Mod/BepInEx/plugins/MOAR.dll b/mods/MOAR - Ultra Lite Spawn Mod/BepInEx/plugins/MOAR.dll index 1da0b445ca1f9a6d5908ae3e5b7287eccb61c1fa..a26e85e9dd1de39683e8fd8ea6eed4e5cccaac4e 100644 GIT binary patch literal 34816 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 literal 31232 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 { "StaticRouterModService" ); - const dynamicRouterModService = container.resolve( - "DynamicRouterModService" - ); + // const dynamicRouterModService = container.resolve( + // "DynamicRouterModService" + // ); // Make buildwaves run on game end staticRouterModService.registerStaticRouter( diff --git a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts index 1aaad28..1ac5549 100644 --- a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts +++ b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts @@ -7,7 +7,11 @@ import { ConfigServer } from "@spt/servers/ConfigServer"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { DependencyContainer } from "tsyringe"; import { globalValues } from "../GlobalValues"; -import { cloneDeep, getRandomPresetOrCurrentlySelectedPreset } from "../utils"; +import { + cloneDeep, + getRandomPresetOrCurrentlySelectedPreset, + saveToFile, +} from "../utils"; import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig.d"; import { originalMapList } from "./constants"; import { buildBossWaves } from "./buildBossWaves"; @@ -63,12 +67,12 @@ export const buildWaves = (container: DependencyContainer) => { } }); - config.debug && - console.log( - globalValues.forcedPreset === "custom" - ? "custom" - : globalValues.currentPreset - ); + // config.debug && + console.log( + globalValues.forcedPreset === "custom" + ? "custom" + : globalValues.currentPreset + ); const { bigmap: customs, @@ -127,7 +131,7 @@ export const buildWaves = (container: DependencyContainer) => { rezervbase: { pmcbot: { min: 0, max: 0 } }, }; - updateSpawnLocations(locationList); + updateSpawnLocations(locationList, config); setEscapeTimeOverrides(locationList, _mapConfig, Logger, config); diff --git a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts index 02853e7..85d7d6d 100644 --- a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts +++ b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts @@ -139,36 +139,20 @@ export function buildBossWaves( for (let key = 0; key < locationList.length; key++) { //Gather bosses to avoid duplicating. - let bossLocations = ""; const duplicateBosses = [ ...locationList[key].base.BossLocationSpawn.filter( - ({ BossName, BossZone }) => { - bossLocations += BossZone + ","; - return bossList.includes(BossName); - } + ({ BossName, BossZone }) => bossList.includes(BossName) ).map(({ BossName }) => BossName), "bossKnight", // So knight doesn't invade ]; - const uniqueBossZones = bossOpenZones - ? "" - : [ - ...new Set( - bossLocations - .split(",") - .filter( - (zone) => !!zone && !zone.toLowerCase().includes("snipe") - ) - ), - ].join(","); - //Build bosses to add const bossesToAdd = shuffle(Object.values(bosses)) .filter(({ BossName }) => !duplicateBosses.includes(BossName)) .map((boss, j) => ({ ...boss, - BossZone: uniqueBossZones, + BossZone: "", BossEscortAmount: boss.BossEscortAmount === "0" ? boss.BossEscortAmount : "1", ...(gradualBossInvasion ? { Time: j * 20 + 1 } : {}), @@ -268,7 +252,22 @@ export function buildBossWaves( configLocations[index] }: ${bossesToAdd.map(({ BossName }) => BossName)}` ); + // console.log(locationList[index].base.BossLocationSpawn.length); + + // Apply the percentages on all bosses, cull those that won't spawn, make all bosses 100 chance that remain. + locationList[index].base.BossLocationSpawn = locationList[ + index + ].base.BossLocationSpawn.filter(({ BossChance, BossName }, bossIndex) => { + if (BossChance < 100 && BossChance / 100 < Math.random()) { + return false; + } + return true; + }).map((boss) => ({ ...boss, ...{ BossChance: 100 } })); + + // if (mapName === "customs") + // console.log(mapName, locationList[index].base.BossLocationSpawn); }); + if (hasChangedBossSpawns) { console.log( `[MOAR]: --- Adjusting default boss spawn rates complete --- \n` diff --git a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts index 8b19992..52c60d4 100644 --- a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts +++ b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts @@ -30,25 +30,21 @@ export default function buildPmcs( .filter( ({ Categories, BotZoneName }) => !!BotZoneName && - (Categories.includes("Player") || - (map === "laboratory" && - !BotZoneName.includes("BotZoneGate"))) && - !BotZoneName.includes("snipe") + !BotZoneName.includes("snipe") && + (Categories.includes("Player") || Categories.includes("All")) && + !BotZoneName.includes("BotZoneGate") ) .map(({ BotZoneName, ...rest }) => { return BotZoneName; }) ), - ...pmcHotZones, ]); + // Make labs have only named zones if (map === "laboratory") { pmcZones = new Array(10).fill(pmcZones).flat(1); - // console.log(pmcZones); } - const timeLimit = locationList[index].base.EscapeTimeLimit * 60; - const { pmcWaveCount } = mapConfig[map]; const escapeTimeLimitRatio = Math.round( @@ -58,13 +54,12 @@ export default function buildPmcs( const totalWaves = Math.round( pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio ); - // console.log(pmcZones.length, totalWaves); + const numberOfZoneless = totalWaves - pmcZones.length; if (numberOfZoneless > 0) { const addEmpty = new Array(numberOfZoneless).fill(""); pmcZones = shuffle([...pmcZones, ...addEmpty]); } - // if (map === "laboratory") console.log(numberOfZoneless, pmcZones); if (config.debug) { console.log(`${map} PMC count ${totalWaves} \n`); @@ -75,10 +70,16 @@ export default function buildPmcs( ); } - const waves = buildPmcWaves(pmcWaveCount, timeLimit, config, pmcZones); - // if (map === "laboratory") - // console.log(waves.map(({ BossZone }) => BossZone)); - // apply our new waves + const timeLimit = locationList[index].base.EscapeTimeLimit * 60; + + const waves = buildPmcWaves( + totalWaves, + timeLimit, + config, + pmcZones, + pmcHotZones + ); + locationList[index].base.BossLocationSpawn = [ ...waves, ...locationList[index].base.BossLocationSpawn, diff --git a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts index 93b9baf..9d58dae 100644 --- a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts +++ b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts @@ -89,19 +89,16 @@ export default function buildScavMarksmanWaves( const sniperLocations = new Set( [...locationList[index].base.SpawnPointParams] .filter( - ({ Categories, Sides, BotZoneName }) => - !!BotZoneName && - Sides.includes("Savage") && - !Categories.includes("Boss") + ({ Categories, DelayToCanSpawnSec, BotZoneName, Sides }) => + !Categories.includes("Boss") && + Sides[0] === "Savage" && + (BotZoneName?.toLowerCase().includes("snipe") || + DelayToCanSpawnSec > 40) ) - .filter( - ({ BotZoneName, DelayToCanSpawnSec }) => - BotZoneName?.toLowerCase().includes("snipe") || - DelayToCanSpawnSec > 300 - ) - .map(({ BotZoneName }) => BotZoneName) + .map(({ BotZoneName }) => BotZoneName || "") ); + if (sniperLocations.size) { locationList[index].base.MinMaxBots = [ { @@ -112,32 +109,21 @@ export default function buildScavMarksmanWaves( ]; } - const scavZones = shuffle([ + let scavZones = shuffle([ ...new Set( [...locationList[index].base.SpawnPointParams] .filter( ({ Categories, Sides, BotZoneName }) => !!BotZoneName && - Sides.includes("Savage") && - !Categories.includes("Boss") + Categories.includes("Bot") && + (Sides.includes("Savage") || Sides.includes("All")) ) .map(({ BotZoneName }) => BotZoneName) .filter((name) => !sniperLocations.has(name)) ), ]); - // Reduced Zone Delay - locationList[index].base.SpawnPointParams = locationList[ - index - ].base.SpawnPointParams.map((spawn) => ({ - ...spawn, - DelayToCanSpawnSec: - spawn.DelayToCanSpawnSec > 20 - ? Math.round(spawn.DelayToCanSpawnSec / 10) - : spawn.DelayToCanSpawnSec, - })); - const timeLimit = locationList[index].base.EscapeTimeLimit * 60; const { scavWaveCount } = mapConfig[map]; const escapeTimeLimitRatio = Math.round( @@ -149,12 +135,19 @@ export default function buildScavMarksmanWaves( scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio ); + const numberOfZoneless = scavTotalWaveCount - scavZones.length; + // console.log(numberOfZoneless); + if (numberOfZoneless > 0) { + const addEmpty = new Array(numberOfZoneless).fill(""); + scavZones = shuffle([...scavZones, ...addEmpty]); + } + // console.log(scavZones); config.debug && escapeTimeLimitRatio !== 1 && console.log( `${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment` ); - + const timeLimit = locationList[index].base.EscapeTimeLimit * 60; let snipers = waveBuilder( sniperLocations.size, Math.round(timeLimit / 4), @@ -166,14 +159,13 @@ export default function buildScavMarksmanWaves( [], shuffle([...sniperLocations]), 80, - false, + true, true ); if (snipersHaveFriends) snipers = snipers.map((wave) => ({ ...wave, - slots_min: 0, ...(snipersHaveFriends && wave.slots_max < 2 ? { slots_min: 1, slots_max: 2 } : {}), diff --git a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts index f4464b8..605f951 100644 --- a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts +++ b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts @@ -1,36 +1,137 @@ import { ILocation } from "@spt/models/eft/common/ILocation"; import { configLocations } from "./constants"; import mapConfig from "../../config/mapConfig.json"; +import _config from "../../config/config.json"; -export default function updateSpawnLocations(locationList: ILocation[]) { +export default function updateSpawnLocations( + locationList: ILocation[], + config: typeof _config +) { for (let index = 0; index < locationList.length; index++) { const map = configLocations[index]; - + // console.log(map); const limit = mapConfig[map].spawnMinDistance; + const InfiltrationList = [ + ...new Set( + locationList[index].base.SpawnPointParams.filter( + ({ Infiltration }) => Infiltration + ).map(({ Infiltration }) => Infiltration) + ), + ]; + + // console.log(map, InfiltrationList); + const getRandomInfil = (): string => + InfiltrationList[Math.floor(Math.random() * InfiltrationList.length)]; + // console.log(InfiltrationList); // console.log("\n" + map); locationList[index].base.SpawnPointParams.forEach( ( - { ColliderParams, BotZoneName, DelayToCanSpawnSec, Categories, Sides }, + { + ColliderParams, + BotZoneName, + DelayToCanSpawnSec, + Categories, + Sides, + Infiltration, + }, innerIndex ) => { if ( - ColliderParams?._props?.Radius !== undefined && - ColliderParams?._props?.Radius < limit && + !Categories.includes("Boss") && !BotZoneName?.toLowerCase().includes("snipe") && - DelayToCanSpawnSec < 300 + DelayToCanSpawnSec < 41 ) { - // console.log( - // "----", - // ColliderParams._props.Radius, - // "=>", - // limit, - // BotZoneName - // ); + // Make it so players/pmcs can spawn anywhere. + if ( + config.playerOpenZones && + !!Infiltration && + (Sides.includes("Pmc") || Sides.includes("All")) + ) { + locationList[index].base.SpawnPointParams[innerIndex].Categories = [ + "Player", + "Coop", + innerIndex % 2 === 0 ? "Group" : "Opposite", + ]; - locationList[index].base.SpawnPointParams[ - innerIndex - ].ColliderParams._props.Radius = limit; + locationList[index].base.SpawnPointParams[innerIndex].Sides = [ + "Pmc", + "All", + ]; + // console.log( + // BotZoneName || "none", + // locationList[index].base.SpawnPointParams[innerIndex].Categories, + // locationList[index].base.SpawnPointParams[innerIndex].Sides + // ); + } + if (!Infiltration) { + if ( + !config.allOpenZones && + config.pmcOpenZones && + Categories.includes("Bot") && + Sides[0] === "Savage" + ) { + // if (BotZoneName === "Zone_LongRoad") console.log("yes"); + locationList[index].base.SpawnPointParams[innerIndex].Categories = + ["Player", "Bot"]; + + locationList[index].base.SpawnPointParams[ + innerIndex + ].Infiltration = getRandomInfil(); + } + + if (config.allOpenZones) { + locationList[index].base.SpawnPointParams[innerIndex].Categories = + [ + "Bot", + "Player", + "Coop", + innerIndex % 2 === 0 ? "Group" : "Opposite", + ]; + + locationList[index].base.SpawnPointParams[ + innerIndex + ].Infiltration = getRandomInfil(); + // console.log( + // locationList[index].base.SpawnPointParams[innerIndex].Infiltration + // ); + locationList[index].base.SpawnPointParams[innerIndex].Sides = [ + "Pmc", + "Savage", + "All", + ]; + } + + if (config.bossOpenZones && Categories.includes("Bot")) { + locationList[index].base.SpawnPointParams[ + innerIndex + ].Categories.push("Boss"); + } + } + + if ( + ColliderParams?._props?.Radius !== undefined && + ColliderParams?._props?.Radius < limit + ) { + locationList[index].base.SpawnPointParams[ + innerIndex + ].ColliderParams._props.Radius = limit; + } + } else { + if (!Categories.includes("Boss") && DelayToCanSpawnSec > 40) { + locationList[index].base.SpawnPointParams[ + innerIndex + ].DelayToCanSpawnSec = Math.round( + DelayToCanSpawnSec * Math.random() * Math.random() * 0.5 + ); + + // console.log( + // BotZoneName, + // DelayToCanSpawnSec, + // locationList[index].base.SpawnPointParams[innerIndex] + // .DelayToCanSpawnSec + // ); + } } } ); diff --git a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts index 77f60d6..804bb17 100644 --- a/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts +++ b/mods/MOAR - Ultra Lite Spawn Mod/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts @@ -44,7 +44,7 @@ export const waveBuilder = ( ); const min = !offset && waves.length < 1 ? 0 : timeStart; - const max = !offset && waves.length < 1 ? 0 : timeStart + 10; + const max = !offset && waves.length < 1 ? 0 : timeStart + 60; if (waves.length >= 1 || offset) timeStart = timeStart + stage; const BotPreset = getDifficulty(difficulty); @@ -55,8 +55,9 @@ export const waveBuilder = ( ); if (slotMax < 1) slotMax = 1; - const slotMin = (Math.round(Math.random() * slotMax) || 1) - 1; + let slotMin = (Math.round(Math.random() * slotMax) || 1) - 1; + if (wildSpawnType === "marksman" && slotMin < 1) slotMin = 1; waves.push({ BotPreset, BotSide: getBotSide(wildSpawnType), @@ -189,11 +190,26 @@ export const getRandomZombieType = () => zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())]; export const buildPmcWaves = ( - totalWaves: number, + pmcTotal: number, escapeTimeLimit: number, config: typeof _config, - bossZones: string[] + bossZones: string[], + hotZones: string[] ): IBossLocationSpawn[] => { + // console.log(pmcTotal) + if (!pmcTotal) return []; + const halfIndex = Math.round(bossZones.length * 0.75); //Put hotzones in the 2 - 4 spawns + // console.log(bossZones.length); + bossZones = [ + ...bossZones.slice(0, halfIndex), + ...hotZones, + ...bossZones.slice(halfIndex), + ]; + + // console.log(bossZones.length, hotZones.length); + // console.log(bossZones); + pmcTotal = pmcTotal + hotZones.length; + let { pmcMaxGroupSize, pmcDifficulty, @@ -202,14 +218,12 @@ export const buildPmcWaves = ( pmcWaveDistribution, } = config; - const averageTime = escapeTimeLimit / totalWaves; - const firstHalf = Math.round(averageTime * (1 - pmcWaveDistribution)); - const secondHalf = Math.round(averageTime * (1 + pmcWaveDistribution)); - let timeStart = -1; - const waves: IBossLocationSpawn[] = []; - let maxSlotsReached = totalWaves; + const averageTime = (escapeTimeLimit * 0.8) / pmcTotal; - while (totalWaves > 0) { + const waves: IBossLocationSpawn[] = []; + let maxSlotsReached = pmcTotal; + + while (pmcTotal > 0) { let bossEscortAmount = Math.round( (morePmcGroups ? 1 : Math.random()) * Math.random() * @@ -217,20 +231,24 @@ export const buildPmcWaves = ( ); if (bossEscortAmount < 0) bossEscortAmount = 0; - const accelerate = totalWaves > 5 && waves.length < totalWaves / 3; - const stage = startingPmcs - ? 10 - : Math.round( - waves.length < Math.round(totalWaves * 0.5) - ? accelerate - ? firstHalf / 3 - : firstHalf - : secondHalf - ); - if (waves.length >= 1) timeStart = timeStart + stage; + // const totalCountThisWave = bossEscortAmount + 1; + const totalCountThusFar = pmcTotal - maxSlotsReached; + + const timeToUse = + totalCountThusFar < pmcTotal * pmcWaveDistribution + ? Math.round( + averageTime * (1 - pmcWaveDistribution) * totalCountThusFar + ) + : Math.round( + escapeTimeLimit * (1 - pmcWaveDistribution) + + (1 - pmcWaveDistribution) * totalCountThusFar * averageTime + ); + + let timeStart = + (startingPmcs ? totalCountThusFar * totalCountThusFar * 3 : timeToUse) || + -1; - // console.log(timeStart, BossEscortAmount); const side = Math.random() > 0.5 ? "pmcBEAR" : "pmcUSEC"; const BossDifficult = getDifficulty(pmcDifficulty); @@ -260,7 +278,10 @@ export const buildPmcWaves = ( maxSlotsReached -= 1 + bossEscortAmount; if (maxSlotsReached <= 0) break; } - + // console.log( + // escapeTimeLimit, + // waves.map(({ Time }) => Time) + // ); return waves; }; @@ -270,6 +291,7 @@ export const buildZombie = ( waveDistribution: number, BossChance: number = 100 ): IBossLocationSpawn[] => { + if (!totalWaves) return []; const averageTime = (escapeTimeLimit * 60) / totalWaves; const firstHalf = Math.round(averageTime * (1 - waveDistribution)); const secondHalf = Math.round(averageTime * (1 + waveDistribution)); diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/BepInEx/plugins/MOAR.dll b/mods/MOAR - Ultra Lite Spawn Mod_backup/BepInEx/plugins/MOAR.dll new file mode 100644 index 0000000000000000000000000000000000000000..1da0b445ca1f9a6d5908ae3e5b7287eccb61c1fa GIT binary patch literal 31232 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 = undefined; + public static locationsBase: ILocationBase[] = undefined; + public static currentPreset: string = ""; + public static forcedPreset: string = "custom"; + public static addedMapZones: Record = {}; +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Routes/routes.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Routes/routes.ts new file mode 100644 index 0000000..bbbe886 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Routes/routes.ts @@ -0,0 +1,168 @@ +import { DependencyContainer } from "tsyringe"; +import { buildWaves } from "../Spawning/Spawning"; +import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService"; +import { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService"; +import { globalValues } from "../GlobalValues"; +import { kebabToTitle } from "../utils"; +import PresetWeightingsConfig from "../../config/PresetWeightings.json"; + +export const setupRoutes = (container: DependencyContainer) => { + const staticRouterModService = container.resolve( + "StaticRouterModService" + ); + + const dynamicRouterModService = container.resolve( + "DynamicRouterModService" + ); + + // Make buildwaves run on game end + staticRouterModService.registerStaticRouter( + `moarUpdater`, + [ + { + url: "/client/match/local/end", + action: async (_url, info, sessionId, output) => { + buildWaves(container); + return output; + }, + }, + ], + "moarUpdater" + ); + + staticRouterModService.registerStaticRouter( + `moarGetCurrentPreset`, + [ + { + url: "/moar/currentPreset", + action: async () => { + return globalValues.forcedPreset || "random"; + }, + }, + ], + "moarGetCurrentPreset" + ); + + staticRouterModService.registerStaticRouter( + `moarGetAnnouncePreset`, + [ + { + url: "/moar/announcePreset", + action: async () => { + if (globalValues.forcedPreset?.toLowerCase() === "random") { + return globalValues.currentPreset; + } + return globalValues.forcedPreset || globalValues.currentPreset; + }, + }, + ], + "moarGetAnnouncePreset" + ); + + staticRouterModService.registerStaticRouter( + `getDefaultConfig`, + [ + { + url: "/moar/getDefaultConfig", + action: async () => { + return JSON.stringify(globalValues.baseConfig); + }, + }, + ], + "getDefaultConfig" + ); + + staticRouterModService.registerStaticRouter( + `getServerConfigWithOverrides`, + [ + { + url: "/moar/getServerConfigWithOverrides", + action: async () => { + return JSON.stringify({ + ...(globalValues.baseConfig || {}), + ...(globalValues.overrideConfig || {}), + }); + }, + }, + ], + "getServerConfigWithOverrides" + ); + + staticRouterModService.registerStaticRouter( + `getServerConfigWithOverrides`, + [ + { + url: "/moar/getServerConfigWithOverrides", + action: async () => { + return JSON.stringify({ + ...globalValues.baseConfig, + ...globalValues.overrideConfig, + }); + }, + }, + ], + "getServerConfigWithOverrides" + ); + + staticRouterModService.registerStaticRouter( + `moarGetPresetsList`, + [ + { + url: "/moar/getPresets", + action: async () => { + let result = [ + ...Object.keys(PresetWeightingsConfig).map((preset) => ({ + Name: kebabToTitle(preset), + Label: preset, + })), + { Name: "Random", Label: "random" }, + { Name: "Custom", Label: "custom" }, + ]; + + return JSON.stringify({ data: result }); + }, + }, + ], + "moarGetPresetsList" + ); + + staticRouterModService.registerStaticRouter( + "setOverrideConfig", + [ + { + url: "/moar/setOverrideConfig", + action: async ( + url: string, + overrideConfig: typeof globalValues.overrideConfig = {}, + sessionID, + output + ) => { + globalValues.overrideConfig = overrideConfig; + + buildWaves(container); + + return "Success"; + }, + }, + ], + "setOverrideConfig" + ); + + staticRouterModService.registerStaticRouter( + "moarSetPreset", + [ + { + url: "/moar/setPreset", + action: async (url: string, { Preset }, sessionID, output) => { + globalValues.forcedPreset = Preset; + buildWaves(container); + + return `Current Preset: ${kebabToTitle( + globalValues.forcedPreset || "Random" + )}`; + }, + }, + ], + "moarSetPreset" + ); +}; diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts new file mode 100644 index 0000000..1aaad28 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts @@ -0,0 +1,154 @@ +import { IBotConfig } from "@spt/models/spt/config/IBotConfig.d"; +import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig.d"; +import { DatabaseServer } from "@spt/servers/DatabaseServer"; +import _config from "../../config/config.json"; +import _mapConfig from "../../config/mapConfig.json"; +import { ConfigServer } from "@spt/servers/ConfigServer"; +import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; +import { DependencyContainer } from "tsyringe"; +import { globalValues } from "../GlobalValues"; +import { cloneDeep, getRandomPresetOrCurrentlySelectedPreset } from "../utils"; +import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig.d"; +import { originalMapList } from "./constants"; +import { buildBossWaves } from "./buildBossWaves"; +import buildZombieWaves from "./buildZombieWaves"; +import buildScavMarksmanWaves from "./buildScavMarksmanWaves"; +import buildPmcs from "./buildPmcs"; +import { setEscapeTimeOverrides } from "./utils"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import updateSpawnLocations from "./updateSpawnLocations"; + +export const buildWaves = (container: DependencyContainer) => { + const configServer = container.resolve("ConfigServer"); + const Logger = container.resolve("WinstonLogger"); + const pmcConfig = configServer.getConfig(ConfigTypes.PMC); + const botConfig = configServer.getConfig(ConfigTypes.BOT); + + const locationConfig = configServer.getConfig( + ConfigTypes.LOCATION + ); + + locationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds = 60; + locationConfig.enableBotTypeLimits = false; + locationConfig.fitLootIntoContainerAttempts = 1; // Move to ALP + locationConfig.addCustomBotWavesToMaps = false; + locationConfig.customWaves = { boss: {}, normal: {} }; + + const databaseServer = container.resolve("DatabaseServer"); + + const { locations, bots, globals } = databaseServer.getTables(); + + let config = cloneDeep(globalValues.baseConfig) as typeof _config; + + const preset = getRandomPresetOrCurrentlySelectedPreset(); + + Object.keys(globalValues.overrideConfig).forEach((key) => { + if (config[key] !== globalValues.overrideConfig[key]) { + config.debug && + console.log( + `[MOAR] overrideConfig ${key} changed from ${config[key]} to ${globalValues.overrideConfig[key]}` + ); + config[key] = globalValues.overrideConfig[key]; + } + }); + + // Set from preset if preset above is not empty + Object.keys(preset).forEach((key) => { + if (config[key] !== preset[key]) { + config.debug && + console.log( + `[MOAR] preset ${globalValues.currentPreset}: ${key} changed from ${config[key]} to ${preset[key]}` + ); + config[key] = preset[key]; + } + }); + + config.debug && + console.log( + globalValues.forcedPreset === "custom" + ? "custom" + : globalValues.currentPreset + ); + + const { + bigmap: customs, + factory4_day: factoryDay, + factory4_night: factoryNight, + interchange, + laboratory, + lighthouse, + rezervbase, + shoreline, + tarkovstreets, + woods, + sandbox: gzLow, + sandbox_high: gzHigh, + } = locations; + + let locationList = [ + customs, + factoryDay, + factoryNight, + interchange, + laboratory, + lighthouse, + rezervbase, + shoreline, + tarkovstreets, + woods, + gzLow, + gzHigh, + ]; + + // This resets all locations to original state + if (!globalValues.locationsBase) { + globalValues.locationsBase = locationList.map(({ base }) => + cloneDeep(base) + ); + } else { + locationList = locationList.map((item, key) => ({ + ...item, + base: cloneDeep(globalValues.locationsBase[key]), + })); + } + + pmcConfig.convertIntoPmcChance = { + default: { + assault: { min: 0, max: 0 }, + cursedassault: { min: 0, max: 0 }, + pmcbot: { min: 0, max: 0 }, + exusec: { min: 0, max: 0 }, + arenafighter: { min: 0, max: 0 }, + arenafighterevent: { min: 0, max: 0 }, + crazyassaultevent: { min: 0, max: 0 }, + }, + factory4_day: { assault: { min: 0, max: 0 } }, + laboratory: { pmcbot: { min: 0, max: 0 } }, + rezervbase: { pmcbot: { min: 0, max: 0 } }, + }; + + updateSpawnLocations(locationList); + + setEscapeTimeOverrides(locationList, _mapConfig, Logger, config); + + // Make main waves + buildScavMarksmanWaves(config, locationList, botConfig); + + // BOSS RELATED STUFF! + buildBossWaves(config, locationList); + + //Zombies + if (config.zombiesEnabled) { + buildZombieWaves(config, locationList, bots); + } + + buildPmcs(config, locationList); + + originalMapList.forEach((name, index) => { + if (!locations[name]) { + console.log("[MOAR] OH CRAP we have a problem!", name); + } else { + locations[name] = locationList[index]; + } + }); +}; diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts new file mode 100644 index 0000000..02853e7 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts @@ -0,0 +1,278 @@ +import { ILocation } from "@spt/models/eft/common/ILocation"; +import _config from "../../config/config.json"; +import bossConfig from "../../config/bossConfig.json"; +import mapConfig from "../../config/mapConfig.json"; +import { + bossesToRemoveFromPool, + configLocations, + mainBossNameList, + originalMapList, +} from "./constants"; +import { buildBossBasedWave, shuffle } from "./utils"; +import { IBossLocationSpawn } from "@spt/models/eft/common/ILocationBase"; +import { cloneDeep } from "../utils"; + +export function buildBossWaves( + config: typeof _config, + locationList: ILocation[] +) { + let { + randomRaiderGroup, + randomRaiderGroupChance, + randomRogueGroup, + randomRogueGroupChance, + mainBossChanceBuff, + bossInvasion, + bossInvasionSpawnChance, + disableBosses, + bossOpenZones, + gradualBossInvasion, + } = config; + + const bossList = mainBossNameList.filter( + (bossName) => !["bossKnight"].includes(bossName) + ); + + const allBosses: Record = {}; + for (const key in locationList) { + locationList[key].base.BossLocationSpawn.forEach((boss) => { + if (!allBosses[boss.BossName]) { + allBosses[boss.BossName] = boss; + } + }); + } + + // CreateBossList + const bosses: Record = {}; + for (let indx = 0; indx < locationList.length; indx++) { + // Disable Bosses + if (disableBosses && !!locationList[indx].base?.BossLocationSpawn) { + locationList[indx].base.BossLocationSpawn = []; + } else { + //Remove all other spawns from pool now that we have the spawns zone list + locationList[indx].base.BossLocationSpawn = locationList[ + indx + ].base.BossLocationSpawn.filter( + (boss) => !bossesToRemoveFromPool.has(boss.BossName) + ); + + const location = locationList[indx]; + + const defaultBossSettings = + mapConfig?.[configLocations[indx]]?.defaultBossSettings; + + // Sets bosses spawn chance from settings + if ( + location?.base?.BossLocationSpawn && + defaultBossSettings && + Object.keys(defaultBossSettings)?.length + ) { + const filteredBossList = Object.keys(defaultBossSettings).filter( + (name) => defaultBossSettings[name]?.BossChance !== undefined + ); + if (filteredBossList?.length) { + filteredBossList.forEach((bossName) => { + location.base.BossLocationSpawn = + location.base.BossLocationSpawn.map((boss) => ({ + ...boss, + ...(boss.BossName === bossName + ? { BossChance: defaultBossSettings[bossName].BossChance } + : {}), + })); + }); + } + } + + if (randomRaiderGroup) { + const raiderWave = buildBossBasedWave( + randomRaiderGroupChance, + "1,2,2,2,3", + "pmcBot", + "pmcBot", + "", + locationList[indx].base.EscapeTimeLimit + ); + location.base.BossLocationSpawn.push(raiderWave); + } + + if (randomRogueGroup) { + const rogueWave = buildBossBasedWave( + randomRogueGroupChance, + "1,2,2,2,3", + "exUsec", + "exUsec", + "", + locationList[indx].base.EscapeTimeLimit + ); + location.base.BossLocationSpawn.push(rogueWave); + } + + //Add each boss from each map to bosses object + const filteredBosses = location.base.BossLocationSpawn?.filter( + ({ BossName }) => mainBossNameList.includes(BossName) + ); + + if (filteredBosses.length) { + for (let index = 0; index < filteredBosses.length; index++) { + const boss = filteredBosses[index]; + if ( + !bosses[boss.BossName] || + (bosses[boss.BossName] && + bosses[boss.BossName].BossChance < boss.BossChance) + ) { + bosses[boss.BossName] = { ...boss }; + } + } + } + } + } + + if (!disableBosses) { + // Make boss Invasion + if (bossInvasion) { + if (bossInvasionSpawnChance) { + bossList.forEach((bossName) => { + if (bosses[bossName]) + bosses[bossName].BossChance = bossInvasionSpawnChance; + }); + } + + for (let key = 0; key < locationList.length; key++) { + //Gather bosses to avoid duplicating. + let bossLocations = ""; + + const duplicateBosses = [ + ...locationList[key].base.BossLocationSpawn.filter( + ({ BossName, BossZone }) => { + bossLocations += BossZone + ","; + return bossList.includes(BossName); + } + ).map(({ BossName }) => BossName), + "bossKnight", // So knight doesn't invade + ]; + + const uniqueBossZones = bossOpenZones + ? "" + : [ + ...new Set( + bossLocations + .split(",") + .filter( + (zone) => !!zone && !zone.toLowerCase().includes("snipe") + ) + ), + ].join(","); + + //Build bosses to add + const bossesToAdd = shuffle(Object.values(bosses)) + .filter(({ BossName }) => !duplicateBosses.includes(BossName)) + .map((boss, j) => ({ + ...boss, + BossZone: uniqueBossZones, + BossEscortAmount: + boss.BossEscortAmount === "0" ? boss.BossEscortAmount : "1", + ...(gradualBossInvasion ? { Time: j * 20 + 1 } : {}), + })); + + // UpdateBosses + locationList[key].base.BossLocationSpawn = [ + ...locationList[key].base.BossLocationSpawn, + ...bossesToAdd, + ]; + } + } + let hasChangedBossSpawns = false; + // console.log(Object.keys(allBosses)); + configLocations.forEach((mapName, index) => { + const bossLocationSpawn = locationList[index].base.BossLocationSpawn; + const mapBossConfig: Record = cloneDeep( + bossConfig[mapName] || {} + ); + // if (Object.keys(mapBossConfig).length === 0) console.log(name, "empty"); + const adjusted = new Set([]); + + bossLocationSpawn.forEach(({ BossName, BossChance }, bossIndex) => { + if (typeof mapBossConfig[BossName] === "number") { + if (BossChance !== mapBossConfig[BossName]) { + if (!hasChangedBossSpawns) { + console.log( + `\n[MOAR]: --- Adjusting default boss spawn rates --- ` + ); + hasChangedBossSpawns = true; + } + console.log( + `[MOAR]: ${mapName} ${BossName}: ${locationList[index].base.BossLocationSpawn[bossIndex].BossChance} => ${mapBossConfig[BossName]}` + ); + locationList[index].base.BossLocationSpawn[bossIndex].BossChance = + mapBossConfig[BossName]; + } + adjusted.add(BossName); + } + }); + + const bossesToAdd = Object.keys(mapBossConfig) + .filter( + (adjustName) => !adjusted.has(adjustName) && !!allBosses[adjustName] + ) + .map((bossName) => { + `[MOAR]: Adding non-default boss ${bossName} to ${originalMapList[index]}`; + + const newBoss: IBossLocationSpawn = cloneDeep( + allBosses[bossName] || {} + ); + newBoss.BossChance = mapBossConfig[bossName]; + // console.log( + // "Adding boss", + // bossName, + // "to ", + // originalMapList[index], + // "spawn chance =>", + // mapBossConfig[bossName] + // ); + return newBoss; + }); + + // console.log(bossesToAdd); + + if (bossOpenZones || mainBossChanceBuff) { + locationList[index].base?.BossLocationSpawn?.forEach((boss, key) => { + if (bossList.includes(boss.BossName)) { + if (bossOpenZones) { + locationList[index].base.BossLocationSpawn[key] = { + ...locationList[index].base.BossLocationSpawn[key], + BossZone: "", + }; + } + + if (!!boss.BossChance && mainBossChanceBuff > 0) { + locationList[index].base.BossLocationSpawn[key] = { + ...locationList[index].base.BossLocationSpawn[key], + BossChance: + boss.BossChance + mainBossChanceBuff > 100 + ? 100 + : Math.round(boss.BossChance + mainBossChanceBuff), + }; + } + } + }); + } + + locationList[index].base.BossLocationSpawn = [ + ...locationList[index].base.BossLocationSpawn, + ...bossesToAdd, + ]; + + bossesToAdd.length && + console.log( + `[MOAR] Adding the following bosses to map ${ + configLocations[index] + }: ${bossesToAdd.map(({ BossName }) => BossName)}` + ); + }); + if (hasChangedBossSpawns) { + console.log( + `[MOAR]: --- Adjusting default boss spawn rates complete --- \n` + ); + } + } +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts new file mode 100644 index 0000000..8b19992 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts @@ -0,0 +1,87 @@ +import { ILocation } from "@spt/models/eft/common/ILocation"; +import _config from "../../config/config.json"; +import mapConfig from "../../config/mapConfig.json"; +import { + bossesToRemoveFromPool, + defaultEscapeTimes, + defaultHostility, +} from "./constants"; +import { buildPmcWaves, MapSettings, shuffle } from "./utils"; +import { saveToFile } from "../utils"; + +export default function buildPmcs( + config: typeof _config, + locationList: ILocation[] +) { + for (let index = 0; index < locationList.length; index++) { + const mapSettingsList = Object.keys(mapConfig) as Array< + keyof typeof mapConfig + >; + const map = mapSettingsList[index]; + + locationList[index].base.BotLocationModifier.AdditionalHostilitySettings = + defaultHostility; + + const { pmcHotZones = [] } = (mapConfig?.[map] as MapSettings) || {}; + + let pmcZones = shuffle([ + ...new Set( + [...locationList[index].base.SpawnPointParams] + .filter( + ({ Categories, BotZoneName }) => + !!BotZoneName && + (Categories.includes("Player") || + (map === "laboratory" && + !BotZoneName.includes("BotZoneGate"))) && + !BotZoneName.includes("snipe") + ) + .map(({ BotZoneName, ...rest }) => { + return BotZoneName; + }) + ), + ...pmcHotZones, + ]); + // Make labs have only named zones + if (map === "laboratory") { + pmcZones = new Array(10).fill(pmcZones).flat(1); + // console.log(pmcZones); + } + + const timeLimit = locationList[index].base.EscapeTimeLimit * 60; + + const { pmcWaveCount } = mapConfig[map]; + + const escapeTimeLimitRatio = Math.round( + locationList[index].base.EscapeTimeLimit / defaultEscapeTimes[map] + ); + + const totalWaves = Math.round( + pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio + ); + // console.log(pmcZones.length, totalWaves); + const numberOfZoneless = totalWaves - pmcZones.length; + if (numberOfZoneless > 0) { + const addEmpty = new Array(numberOfZoneless).fill(""); + pmcZones = shuffle([...pmcZones, ...addEmpty]); + } + // if (map === "laboratory") console.log(numberOfZoneless, pmcZones); + + if (config.debug) { + console.log(`${map} PMC count ${totalWaves} \n`); + + escapeTimeLimitRatio !== 1 && + console.log( + `${map} PMC wave count changed from ${pmcWaveCount} to ${totalWaves} due to escapeTimeLimit adjustment` + ); + } + + const waves = buildPmcWaves(pmcWaveCount, timeLimit, config, pmcZones); + // if (map === "laboratory") + // console.log(waves.map(({ BossZone }) => BossZone)); + // apply our new waves + locationList[index].base.BossLocationSpawn = [ + ...waves, + ...locationList[index].base.BossLocationSpawn, + ]; + } +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts new file mode 100644 index 0000000..93b9baf --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildScavMarksmanWaves.ts @@ -0,0 +1,226 @@ +import { ILocation } from "@spt/models/eft/common/ILocation"; +import _config from "../../config/config.json"; +import mapConfig from "../../config/mapConfig.json"; +import { + configLocations, + defaultEscapeTimes, + defaultHostility, + originalMapList, +} from "./constants"; +import { MapSettings, shuffle, waveBuilder } from "./utils"; +import { IWave, WildSpawnType } from "@spt/models/eft/common/ILocationBase"; +import { IBotConfig } from "@spt/models/spt/config/IBotConfig"; +import { saveToFile } from "../utils"; + +export default function buildScavMarksmanWaves( + config: typeof _config, + locationList: ILocation[], + botConfig: IBotConfig +) { + let { + debug, + maxBotCap, + scavWaveQuantity, + scavWaveDistribution, + snipersHaveFriends, + maxBotPerZone, + scavMaxGroupSize, + scavDifficulty, + moreScavGroups, + } = config; + + for (let index = 0; index < locationList.length; index++) { + const mapSettingsList = Object.keys(mapConfig) as Array< + keyof typeof mapConfig + >; + const map = mapSettingsList[index]; + + locationList[index].base = { + ...locationList[index].base, + ...{ + NewSpawn: false, + OcculsionCullingEnabled: true, + OfflineNewSpawn: false, + OfflineOldSpawn: true, + OldSpawn: true, + BotSpawnCountStep: 0, + }, + }; + + locationList[index].base.NonWaveGroupScenario.Enabled = false; + locationList[index].base["BotStartPlayer"] = 0; + if ( + locationList[index].base.BotStop < + locationList[index].base.EscapeTimeLimit * 60 + ) { + locationList[index].base.BotStop = + locationList[index].base.EscapeTimeLimit * 60; + } + + const { + maxBotPerZoneOverride, + maxBotCapOverride, + EscapeTimeLimit, + scavHotZones, + } = (mapConfig?.[map] as MapSettings) || {}; + + // Set per map EscapeTimeLimit + if (EscapeTimeLimit) { + locationList[index].base.EscapeTimeLimit = EscapeTimeLimit; + locationList[index].base.exit_access_time = EscapeTimeLimit + 1; + } + + // Set default or per map maxBotCap + if (maxBotCapOverride || maxBotCap) { + const capToSet = maxBotCapOverride || maxBotCap; + // console.log(map, capToSet, maxBotCapOverride, maxBotCap); + locationList[index].base.BotMax = capToSet; + locationList[index].base.BotMaxPvE = capToSet; + botConfig.maxBotCap[originalMapList[index]] = capToSet; + } + + // Adjust botZone quantity + if (maxBotPerZoneOverride || maxBotPerZone) { + const BotPerZone = maxBotPerZoneOverride || maxBotPerZone; + // console.log(map, BotPerZone, maxBotPerZoneOverride, maxBotPerZone); + locationList[index].base.MaxBotPerZone = BotPerZone; + } + + const sniperLocations = new Set( + [...locationList[index].base.SpawnPointParams] + .filter( + ({ Categories, Sides, BotZoneName }) => + !!BotZoneName && + Sides.includes("Savage") && + !Categories.includes("Boss") + ) + .filter( + ({ BotZoneName, DelayToCanSpawnSec }) => + BotZoneName?.toLowerCase().includes("snipe") || + DelayToCanSpawnSec > 300 + ) + .map(({ BotZoneName }) => BotZoneName) + ); + + if (sniperLocations.size) { + locationList[index].base.MinMaxBots = [ + { + WildSpawnType: "marksman", + max: sniperLocations.size * 5, + min: sniperLocations.size, + }, + ]; + } + + const scavZones = shuffle([ + ...new Set( + [...locationList[index].base.SpawnPointParams] + .filter( + ({ Categories, Sides, BotZoneName }) => + !!BotZoneName && + Sides.includes("Savage") && + !Categories.includes("Boss") + ) + .map(({ BotZoneName }) => BotZoneName) + .filter((name) => !sniperLocations.has(name)) + ), + ]); + + // Reduced Zone Delay + locationList[index].base.SpawnPointParams = locationList[ + index + ].base.SpawnPointParams.map((spawn) => ({ + ...spawn, + DelayToCanSpawnSec: + spawn.DelayToCanSpawnSec > 20 + ? Math.round(spawn.DelayToCanSpawnSec / 10) + : spawn.DelayToCanSpawnSec, + })); + + const timeLimit = locationList[index].base.EscapeTimeLimit * 60; + const { scavWaveCount } = mapConfig[map]; + + const escapeTimeLimitRatio = Math.round( + locationList[index].base.EscapeTimeLimit / defaultEscapeTimes[map] + ); + + // Scavs + const scavTotalWaveCount = Math.round( + scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio + ); + + config.debug && + escapeTimeLimitRatio !== 1 && + console.log( + `${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment` + ); + + let snipers = waveBuilder( + sniperLocations.size, + Math.round(timeLimit / 4), + 0.5, + WildSpawnType.MARKSMAN, + 0.7, + false, + 2, + [], + shuffle([...sniperLocations]), + 80, + false, + true + ); + + if (snipersHaveFriends) + snipers = snipers.map((wave) => ({ + ...wave, + slots_min: 0, + ...(snipersHaveFriends && wave.slots_max < 2 + ? { slots_min: 1, slots_max: 2 } + : {}), + })); + + const scavWaves = waveBuilder( + scavTotalWaveCount, + timeLimit, + scavWaveDistribution, + WildSpawnType.ASSAULT, + scavDifficulty, + false, + scavMaxGroupSize, + map === "gzHigh" ? [] : scavZones, + scavHotZones, + 0, + false, + !!moreScavGroups + ); + + if (debug) { + let totalscav = 0; + scavWaves.forEach(({ slots_max }) => (totalscav += slots_max)); + + console.log(configLocations[index]); + console.log( + "Scavs:", + totalscav, + "configVal", + Math.round((totalscav / scavWaveCount) * 100) / 100, + "configWaveCount", + scavWaveCount, + "waveCount", + scavWaves.length, + "\n" + ); + } + + // const finalSniperWaves = snipers?.map(({ ...rest }, snipKey) => ({ + // ...rest, + // number: snipKey, + // time_min: snipKey * 120, + // time_max: snipKey * 120 + 120, + // })); + // if (map === "customs") saveToFile({ scavWaves }, "scavWaves.json"); + locationList[index].base.waves = [...snipers, ...scavWaves] + .sort(({ time_min: a }, { time_min: b }) => a - b) + .map((wave, i) => ({ ...wave, number: i + 1 })); + } +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildZombieWaves.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildZombieWaves.ts new file mode 100644 index 0000000..ab1ecd2 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/buildZombieWaves.ts @@ -0,0 +1,80 @@ +import { ILocation } from "@spt/models/eft/common/ILocation"; +import _config from "../../config/config.json"; +import mapConfig from "../../config/mapConfig.json"; +import { configLocations, defaultEscapeTimes } from "./constants"; +import { + buildZombie, + getHealthBodyPartsByPercentage, + zombieTypes, +} from "./utils"; +import { IBots } from "@spt/models/spt/bots/IBots"; + +export default function buildZombieWaves( + config: typeof _config, + locationList: ILocation[], + bots: IBots +) { + let { debug, zombieWaveDistribution, zombieWaveQuantity, zombieHealth } = + config; + + const zombieBodyParts = getHealthBodyPartsByPercentage(zombieHealth); + zombieTypes.forEach((type) => { + bots.types?.[type]?.health?.BodyParts?.forEach((_, index) => { + bots.types[type].health.BodyParts[index] = zombieBodyParts; + }); + }); + + for (let indx = 0; indx < locationList.length; indx++) { + const location = locationList[indx].base; + const mapSettingsList = Object.keys(mapConfig) as Array< + keyof typeof mapConfig + >; + const map = mapSettingsList[indx]; + + const { zombieWaveCount } = mapConfig?.[configLocations[indx]]; + + // if (location.Events?.Halloween2024?.MaxCrowdAttackSpawnLimit) + // location.Events.Halloween2024.MaxCrowdAttackSpawnLimit = 100; + // if (location.Events?.Halloween2024?.CrowdCooldownPerPlayerSec) + // location.Events.Halloween2024.CrowdCooldownPerPlayerSec = 60; + // if (location.Events?.Halloween2024?.CrowdCooldownPerPlayerSec) + // location.Events.Halloween2024.CrowdsLimit = 10; + // if (location.Events?.Halloween2024?.CrowdAttackSpawnParams) + // location.Events.Halloween2024.CrowdAttackSpawnParams = []; + + if (!zombieWaveCount) return; + + const escapeTimeLimitRatio = Math.round( + locationList[indx].base.EscapeTimeLimit / defaultEscapeTimes[map] + ); + + const zombieTotalWaveCount = Math.round( + zombieWaveCount * zombieWaveQuantity * escapeTimeLimitRatio + ); + + config.debug && + escapeTimeLimitRatio !== 1 && + console.log( + `${map} Zombie wave count changed from ${zombieWaveCount} to ${zombieTotalWaveCount} due to escapeTimeLimit adjustment` + ); + + const zombieWaves = buildZombie( + zombieTotalWaveCount, + location.EscapeTimeLimit, + zombieWaveDistribution, + 9999 + ); + + debug && + console.log( + configLocations[indx], + " generated ", + zombieWaves.length, + "Zombies" + ); + + location.BossLocationSpawn.push(...zombieWaves); + + // console.log(zombieWaves[0], zombieWaves[7]); + } +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/constants.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/constants.ts new file mode 100644 index 0000000..6698732 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/constants.ts @@ -0,0 +1,204 @@ +export const defaultHostility = [ + { + AlwaysEnemies: [ + "bossTest", + "followerTest", + "bossKilla", + "bossKojaniy", + "followerKojaniy", + "cursedAssault", + "bossGluhar", + "followerGluharAssault", + "followerGluharSecurity", + "followerGluharScout", + "followerGluharSnipe", + "followerSanitar", + "bossSanitar", + "test", + "assaultGroup", + "sectantWarrior", + "sectantPriest", + "bossTagilla", + "followerTagilla", + "bossKnight", + "followerBigPipe", + "followerBirdEye", + "bossBoar", + "followerBoar", + "arenaFighter", + "arenaFighterEvent", + "bossBoarSniper", + "crazyAssaultEvent", + "sectactPriestEvent", + "followerBoarClose1", + "followerBoarClose2", + "bossKolontay", + "followerKolontayAssault", + "followerKolontaySecurity", + "shooterBTR", + "bossPartisan", + "spiritWinter", + "spiritSpring", + "peacemaker", + "skier", + "assault", + "marksman", + "pmcUSEC", + "pmcBEAR", + "exUsec", + "pmcBot", + "bossBully", + ], + AlwaysFriends: [ + "bossZryachiy", + "followerZryachiy", + "peacefullZryachiyEvent", + "ravangeZryachiyEvent", + "gifter", + ], + BearEnemyChance: 100, + BearPlayerBehaviour: "AlwaysEnemies", + BotRole: "pmcBEAR", + ChancedEnemies: [], + Neutral: [], + SavagePlayerBehaviour: "AlwaysEnemies", + UsecEnemyChance: 100, + UsecPlayerBehaviour: "AlwaysEnemies", + Warn: ["sectactPriestEvent"], + }, + { + AlwaysEnemies: [ + "bossTest", + "followerTest", + "bossKilla", + "bossKojaniy", + "followerKojaniy", + "cursedAssault", + "bossGluhar", + "followerGluharAssault", + "followerGluharSecurity", + "followerGluharScout", + "followerGluharSnipe", + "followerSanitar", + "bossSanitar", + "test", + "assaultGroup", + "sectantWarrior", + "sectantPriest", + "bossTagilla", + "followerTagilla", + "bossKnight", + "followerBigPipe", + "followerBirdEye", + "bossBoar", + "followerBoar", + "arenaFighter", + "arenaFighterEvent", + "bossBoarSniper", + "crazyAssaultEvent", + "sectactPriestEvent", + "followerBoarClose1", + "followerBoarClose2", + "bossKolontay", + "followerKolontayAssault", + "followerKolontaySecurity", + "shooterBTR", + "bossPartisan", + "spiritWinter", + "spiritSpring", + "peacemaker", + "skier", + "assault", + "marksman", + "pmcUSEC", + "pmcBEAR", + "exUsec", + "pmcBot", + "bossBully", + ], + AlwaysFriends: [ + "bossZryachiy", + "followerZryachiy", + "peacefullZryachiyEvent", + "ravangeZryachiyEvent", + "gifter", + ], + BearEnemyChance: 100, + BearPlayerBehaviour: "AlwaysEnemies", + BotRole: "pmcUSEC", + ChancedEnemies: [], + Neutral: [], + SavagePlayerBehaviour: "AlwaysEnemies", + UsecEnemyChance: 100, + UsecPlayerBehaviour: "AlwaysEnemies", + Warn: ["sectactPriestEvent"], + }, +]; + +export const configLocations = [ + "customs", + "factoryDay", + "factoryNight", + "interchange", + "laboratory", + "lighthouse", + "rezervbase", + "shoreline", + "tarkovstreets", + "woods", + "gzLow", + "gzHigh", +]; + +export const originalMapList = [ + "bigmap", + "factory4_day", + "factory4_night", + "interchange", + "laboratory", + "lighthouse", + "rezervbase", + "shoreline", + "tarkovstreets", + "woods", + "sandbox", + "sandbox_high", +]; + +export const bossesToRemoveFromPool = new Set([ + "assault", + "pmcBEAR", + "pmcUSEC", + "infectedAssault", + "infectedTagilla", + "infectedLaborant", + "infectedCivil", +]); + +export const mainBossNameList = [ + "bossKojaniy", + "bossGluhar", + "bossSanitar", + "bossKilla", + "bossTagilla", + "bossKnight", + "bossBoar", + "bossKolontay", + "bossPartisan", + "bossBully", +]; + +export const defaultEscapeTimes = { + customs: 40, + factoryDay: 20, + factoryNight: 25, + interchange: 40, + laboratory: 35, + lighthouse: 40, + rezervbase: 40, + shoreline: 45, + tarkovstreets: 50, + woods: 40, + gzLow: 35, + gzHigh: 35, +}; diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts new file mode 100644 index 0000000..f4464b8 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/updateSpawnLocations.ts @@ -0,0 +1,38 @@ +import { ILocation } from "@spt/models/eft/common/ILocation"; +import { configLocations } from "./constants"; +import mapConfig from "../../config/mapConfig.json"; + +export default function updateSpawnLocations(locationList: ILocation[]) { + for (let index = 0; index < locationList.length; index++) { + const map = configLocations[index]; + + const limit = mapConfig[map].spawnMinDistance; + + // console.log("\n" + map); + locationList[index].base.SpawnPointParams.forEach( + ( + { ColliderParams, BotZoneName, DelayToCanSpawnSec, Categories, Sides }, + innerIndex + ) => { + if ( + ColliderParams?._props?.Radius !== undefined && + ColliderParams?._props?.Radius < limit && + !BotZoneName?.toLowerCase().includes("snipe") && + DelayToCanSpawnSec < 300 + ) { + // console.log( + // "----", + // ColliderParams._props.Radius, + // "=>", + // limit, + // BotZoneName + // ); + + locationList[index].base.SpawnPointParams[ + innerIndex + ].ColliderParams._props.Radius = limit; + } + } + ); + } +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts new file mode 100644 index 0000000..77f60d6 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Spawning/utils.ts @@ -0,0 +1,430 @@ +import { + IBossLocationSpawn, + IWave, + WildSpawnType, +} from "@spt/models/eft/common/ILocationBase"; +import _config from "../../config/config.json"; +import { ILocation } from "@spt/models/eft/common/ILocation"; +import { defaultEscapeTimes } from "./constants"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; + +export const waveBuilder = ( + totalWaves: number, + timeLimit: number, + waveDistribution: number, + wildSpawnType: "marksman" | "assault", + difficulty: number, + isPlayer: boolean, + maxSlots: number, + combinedZones: string[] = [], + specialZones: string[] = [], + offset?: number, + starting?: boolean, + moreGroups?: boolean +): IWave[] => { + if (totalWaves === 0) return []; + + const averageTime = timeLimit / totalWaves; + const firstHalf = Math.round(averageTime * (1 - waveDistribution)); + const secondHalf = Math.round(averageTime * (1 + waveDistribution)); + let timeStart = offset || 0; + const waves: IWave[] = []; + let maxSlotsReached = Math.round(1.3 * totalWaves); + while ( + totalWaves > 0 && + (waves.length < totalWaves || specialZones.length > 0) + ) { + const accelerate = totalWaves > 5 && waves.length < totalWaves / 3; + const stage = Math.round( + waves.length < Math.round(totalWaves * 0.5) + ? accelerate + ? firstHalf / 3 + : firstHalf + : secondHalf + ); + + const min = !offset && waves.length < 1 ? 0 : timeStart; + const max = !offset && waves.length < 1 ? 0 : timeStart + 10; + + if (waves.length >= 1 || offset) timeStart = timeStart + stage; + const BotPreset = getDifficulty(difficulty); + // console.log(wildSpawnType, BotPreset); + // Math.round((1 - waves.length / totalWaves) * maxSlots) || 1; + let slotMax = Math.round( + (moreGroups ? Math.random() : Math.random() * Math.random()) * maxSlots + ); + + if (slotMax < 1) slotMax = 1; + const slotMin = (Math.round(Math.random() * slotMax) || 1) - 1; + + waves.push({ + BotPreset, + BotSide: getBotSide(wildSpawnType), + SpawnPoints: getZone( + specialZones, + combinedZones, + waves.length >= totalWaves + ), + isPlayers: isPlayer, + slots_max: slotMax, + slots_min: slotMin, + time_min: starting || !max ? -1 : min, + time_max: starting || !max ? -1 : max, + WildSpawnType: wildSpawnType as WildSpawnType, + number: waves.length, + sptId: wildSpawnType + waves.length, + SpawnMode: ["regular", "pve"], + }); + maxSlotsReached -= slotMax; + // if (wildSpawnType === "assault") console.log(slotMax, maxSlotsReached); + if (maxSlotsReached <= 0) break; + } + // console.log(waves.map(({ slots_min }) => slots_min)); + return waves; +}; + +const getZone = (specialZones, combinedZones, specialOnly) => { + if (!specialOnly && combinedZones.length) + return combinedZones[ + Math.round((combinedZones.length - 1) * Math.random()) + ]; + if (specialZones.length) return specialZones.pop(); + return ""; +}; + +export const getDifficulty = (diff: number) => { + const randomNumb = Math.random() + diff; + switch (true) { + case randomNumb < 0.55: + return "easy"; + case randomNumb < 1.4: + return "normal"; + case randomNumb < 1.85: + return "hard"; + default: + return "impossible"; + } +}; + +export const shuffle = (array: any): n => { + let currentIndex = array.length, + randomIndex; + + // While there remain elements to shuffle. + while (currentIndex != 0) { + // Pick a remaining element. + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + // And swap it with the current element. + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], + array[currentIndex], + ]; + } + + return array; +}; + +const getBotSide = ( + spawnType: "marksman" | "assault" | "pmcBEAR" | "pmcUSEC" +) => { + switch (spawnType) { + case "pmcBEAR": + return "Bear"; + case "pmcUSEC": + return "Usec"; + default: + return "Savage"; + } +}; + +export const buildBossBasedWave = ( + BossChance: number, + BossEscortAmount: string, + BossEscortType: string, + BossName: string, + BossZone: string, + raidTime?: number +): IBossLocationSpawn => { + return { + BossChance, + BossDifficult: "normal", + BossEscortAmount, + BossEscortDifficult: "normal", + BossEscortType, + BossName, + BossPlayer: false, + BossZone, + Delay: 0, + ForceSpawn: false, + IgnoreMaxBots: true, + RandomTimeSpawn: false, + Time: raidTime ? Math.round(Math.random() * (raidTime * 5)) : -1, + Supports: null, + TriggerId: "", + TriggerName: "", + spawnMode: ["regular", "pve"], + }; +}; + +export const zombieTypes = [ + "infectedassault", + "infectedpmc", + "infectedlaborant", + "infectedcivil", +]; + +export const zombieTypesCaps = [ + "infectedAssault", + "infectedPmc", + "infectedLaborant", + "infectedCivil", +]; + +export const getRandomDifficulty = (num: number = 1.5) => + getDifficulty(Math.round(Math.random() * num * 10) / 10); + +export const getRandomZombieType = () => + zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())]; + +export const buildPmcWaves = ( + totalWaves: number, + escapeTimeLimit: number, + config: typeof _config, + bossZones: string[] +): IBossLocationSpawn[] => { + let { + pmcMaxGroupSize, + pmcDifficulty, + startingPmcs, + morePmcGroups, + pmcWaveDistribution, + } = config; + + const averageTime = escapeTimeLimit / totalWaves; + const firstHalf = Math.round(averageTime * (1 - pmcWaveDistribution)); + const secondHalf = Math.round(averageTime * (1 + pmcWaveDistribution)); + let timeStart = -1; + const waves: IBossLocationSpawn[] = []; + let maxSlotsReached = totalWaves; + + while (totalWaves > 0) { + let bossEscortAmount = Math.round( + (morePmcGroups ? 1 : Math.random()) * + Math.random() * + (pmcMaxGroupSize - 1) + ); + + if (bossEscortAmount < 0) bossEscortAmount = 0; + const accelerate = totalWaves > 5 && waves.length < totalWaves / 3; + const stage = startingPmcs + ? 10 + : Math.round( + waves.length < Math.round(totalWaves * 0.5) + ? accelerate + ? firstHalf / 3 + : firstHalf + : secondHalf + ); + + if (waves.length >= 1) timeStart = timeStart + stage; + + // console.log(timeStart, BossEscortAmount); + const side = Math.random() > 0.5 ? "pmcBEAR" : "pmcUSEC"; + + const BossDifficult = getDifficulty(pmcDifficulty); + + waves.push({ + BossChance: 9999, + BossDifficult, + BossEscortAmount: bossEscortAmount.toString(), + BossEscortDifficult: "normal", + BossEscortType: side, + BossName: side, + BossPlayer: false, + BossZone: bossZones.pop() || "", + Delay: 0, + DependKarma: false, + DependKarmaPVE: false, + ForceSpawn: true, + IgnoreMaxBots: true, + RandomTimeSpawn: false, + Time: timeStart, + Supports: null, + TriggerId: "", + TriggerName: "", + spawnMode: ["regular", "pve"], + }); + + maxSlotsReached -= 1 + bossEscortAmount; + if (maxSlotsReached <= 0) break; + } + + return waves; +}; + +export const buildZombie = ( + totalWaves: number, + escapeTimeLimit: number, + waveDistribution: number, + BossChance: number = 100 +): IBossLocationSpawn[] => { + const averageTime = (escapeTimeLimit * 60) / totalWaves; + const firstHalf = Math.round(averageTime * (1 - waveDistribution)); + const secondHalf = Math.round(averageTime * (1 + waveDistribution)); + let timeStart = 90; + const waves: IBossLocationSpawn[] = []; + let maxSlotsReached = Math.round(1.3 * totalWaves); + + while (totalWaves > 0) { + const accelerate = totalWaves > 5 && waves.length < totalWaves / 3; + const stage = Math.round( + waves.length < Math.round(totalWaves * 0.5) + ? accelerate + ? firstHalf / 3 + : firstHalf + : secondHalf + ); + + if (waves.length >= 1) timeStart = timeStart + stage; + const main = getRandomZombieType(); + waves.push({ + BossChance, + BossDifficult: "normal", + BossEscortAmount: "0", + BossEscortDifficult: "normal", + BossEscortType: main, + BossName: main, + BossPlayer: false, + BossZone: "", + Delay: 0, + IgnoreMaxBots: true, + RandomTimeSpawn: false, + Time: timeStart, + Supports: new Array( + Math.round(Math.random() * 4) /* <= 4 AddthistoConfig */ + ) + .fill("") + .map(() => ({ + BossEscortType: getRandomZombieType(), + BossEscortDifficult: ["normal"], + BossEscortAmount: "1", + })), + TriggerId: "", + TriggerName: "", + spawnMode: ["regular", "pve"], + }); + + maxSlotsReached -= 1 + waves[waves.length - 1].Supports.length; + if (maxSlotsReached <= 0) break; + } + + return waves; +}; + +export interface MapSettings { + EscapeTimeLimit?: number; + maxBotPerZoneOverride?: number; + maxBotCapOverride?: number; + pmcHotZones?: string[]; + scavHotZones?: string[]; + pmcWaveCount: number; + scavWaveCount: number; + zombieWaveCount: number; +} + +export const getHealthBodyPartsByPercentage = (num: number) => { + const num35 = Math.round(35 * num); + const num100 = Math.round(100 * num); + const num70 = Math.round(70 * num); + const num80 = Math.round(80 * num); + return { + Head: { + min: num35, + max: num35, + }, + Chest: { + min: num100, + max: num100, + }, + Stomach: { + min: num100, + max: num100, + }, + LeftArm: { + min: num70, + max: num70, + }, + RightArm: { + min: num70, + max: num70, + }, + LeftLeg: { + min: num80, + max: num80, + }, + RightLeg: { + min: num80, + max: num80, + }, + }; +}; + +export interface MapConfigType { + spawnMinDistance: number; + pmcWaveCount: number; + scavWaveCount: number; + zombieWaveCount?: number; + scavHotZones?: string[]; + pmcHotZones?: string[]; + EscapeTimeLimitOverride?: number; +} + +export const setEscapeTimeOverrides = ( + locationList: ILocation[], + mapConfig: Record, + logger: ILogger, + config: typeof _config +) => { + for (let index = 0; index < locationList.length; index++) { + const mapSettingsList = Object.keys(mapConfig) as Array< + keyof typeof mapConfig + >; + + const map = mapSettingsList[index]; + const override = mapConfig[map].EscapeTimeLimitOverride; + const hardcodedEscapeLimitMax = 5; + + if ( + !override && + locationList[index].base.EscapeTimeLimit / defaultEscapeTimes[map] > + hardcodedEscapeLimitMax + ) { + const maxLimit = defaultEscapeTimes[map] * hardcodedEscapeLimitMax; + logger.warning( + `[MOAR] EscapeTimeLimit set too high on ${map}\nEscapeTimeLimit changed from ${locationList[index].base.EscapeTimeLimit} => ${maxLimit}\n` + ); + locationList[index].base.EscapeTimeLimit = maxLimit; + } + + if (override && locationList[index].base.EscapeTimeLimit !== override) { + console.log( + `[Moar] Set ${map}'s Escape time limit to ${override} from ${locationList[index].base.EscapeTimeLimit}\n` + ); + locationList[index].base.EscapeTimeLimit = override; + locationList[index].base.EscapeTimeLimitCoop = override; + locationList[index].base.EscapeTimeLimitPVE = override; + } + + if ( + config.startingPmcs && + locationList[index].base.EscapeTimeLimit / defaultEscapeTimes[map] > 2 + ) { + logger.warning( + `[MOAR] Average EscapeTimeLimit is too high (greater than 2x) to enable starting PMCS\nStarting PMCS has been turned off to prevent performance issues.\n` + ); + config.startingPmcs = false; + } + } +}; diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Tests/checkPresets.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Tests/checkPresets.ts new file mode 100644 index 0000000..57b753d --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Tests/checkPresets.ts @@ -0,0 +1,28 @@ +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { DependencyContainer } from "tsyringe"; +import config from "../../config/config.json"; +import presets from "../../config/Presets.json"; +import presetWeightings from "../../config/PresetWeightings.json"; + +export default function checkPresetLogic(container: DependencyContainer) { + const Logger = container.resolve("WinstonLogger"); + + for (const key in presetWeightings) { + if (presets[key] === undefined) { + Logger.error( + `\n[MOAR]: No preset found in PresetWeightings.json for preset "${key}" in Presets.json` + ); + } + } + + for (const key in presets) { + const preset = presets[key]; + for (const id in preset) { + if (config[id] === undefined) { + Logger.error( + `\n[MOAR]: No associated key found in config.json called "${id}" for preset "${key}" in Presets.json` + ); + } + } + } +} diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Zombies/Zombies.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Zombies/Zombies.ts new file mode 100644 index 0000000..1125559 --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/Zombies/Zombies.ts @@ -0,0 +1,160 @@ +import { DependencyContainer } from "tsyringe"; +import { + ISeasonalEventConfig, + ISeasonalEvent, +} from "@spt/models/spt/config/ISeasonalEventConfig.d"; + +import { ConfigServer } from "@spt/servers/ConfigServer"; +import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; +import { SeasonalEventService } from "@spt/services/SeasonalEventService"; +import { zombieTypesCaps } from "../Spawning/utils"; + +export const baseZombieSettings = (enabled: boolean, count: number) => + ({ + enabled, + name: "zombies", + type: "Zombies", + startDay: "1", + startMonth: "1", + endDay: "31", + endMonth: "12", + settings: { + enableSummoning: false, + removeEntryRequirement: [], + replaceBotHostility: true, + zombieSettings: { + enabled: true, + mapInfectionAmount: { + Interchange: count === -1 ? randomNumber100() : count, + Lighthouse: count === -1 ? randomNumber100() : count, + RezervBase: count === -1 ? randomNumber100() : count, + Sandbox: count === -1 ? randomNumber100() : count, + Shoreline: count === -1 ? randomNumber100() : count, + TarkovStreets: count === -1 ? randomNumber100() : count, + Woods: count === -1 ? randomNumber100() : count, + bigmap: count === -1 ? randomNumber100() : count, + factory4: count === -1 ? randomNumber100() : count, + laboratory: count === -1 ? randomNumber100() : count, + }, + disableBosses: [], + disableWaves: [], + }, + }, + } as unknown as ISeasonalEvent); + +const randomNumber100 = () => Math.round(Math.random() * 100); + +export const resetCurrentEvents = ( + container: DependencyContainer, + enabled: boolean, + zombieWaveQuantity: number, + random: boolean = false +) => { + const configServer = container.resolve("ConfigServer"); + const eventConfig = configServer.getConfig( + ConfigTypes.SEASONAL_EVENT + ); + + let percentToShow = random ? -1 : Math.round(zombieWaveQuantity * 100); + if (percentToShow > 100) percentToShow = 100; + + eventConfig.events = [baseZombieSettings(enabled, percentToShow)]; + + const seasonalEventService = container.resolve( + "SeasonalEventService" + ) as any; + + // First we need to clear any existing data + seasonalEventService.currentlyActiveEvents = []; + seasonalEventService.christmasEventActive = false; + seasonalEventService.halloweenEventActive = false; + // Then re-calculate the cached data + seasonalEventService.cacheActiveEvents(); + // seasonalEventService.addEventBossesToMaps("halloweenzombies"); +}; + +export const setUpZombies = (container: DependencyContainer) => { + const configServer = container.resolve("ConfigServer"); + const eventConfig = configServer.getConfig( + ConfigTypes.SEASONAL_EVENT + ); + + eventConfig.events = [baseZombieSettings(false, 100)]; + + // eventConfig.eventBossSpawns = { + // zombies: eventConfig.eventBossSpawns.halloweenzombies, + // }; + eventConfig.eventGear[eventConfig.events[0].name] = {}; + eventConfig.hostilitySettingsForEvent.zombies.default = + eventConfig.hostilitySettingsForEvent.zombies.default + .filter(({ BotRole }) => !["pmcBEAR", "pmcUSEC"].includes(BotRole)) + .map((host) => ({ + ...host, + AlwaysEnemies: [ + "infectedAssault", + "infectedPmc", + "infectedCivil", + "infectedLaborant", + "infectedTagilla", + "pmcBEAR", + "pmcUSEC", + ], + AlwaysNeutral: [ + "marksman", + "assault", + "bossTest", + "bossBully", + "followerTest", + "bossKilla", + "bossKojaniy", + "followerKojaniy", + "pmcBot", + "cursedAssault", + "bossGluhar", + "followerGluharAssault", + "followerGluharSecurity", + "followerGluharScout", + "followerGluharSnipe", + "followerSanitar", + "bossSanitar", + "test", + "assaultGroup", + "sectantWarrior", + "sectantPriest", + "bossTagilla", + "followerTagilla", + "exUsec", + "gifter", + "bossKnight", + "followerBigPipe", + "followerBirdEye", + "bossZryachiy", + "followerZryachiy", + "bossBoar", + "followerBoar", + "arenaFighter", + "arenaFighterEvent", + "bossBoarSniper", + "crazyAssaultEvent", + "peacefullZryachiyEvent", + "sectactPriestEvent", + "ravangeZryachiyEvent", + "followerBoarClose1", + "followerBoarClose2", + "bossKolontay", + "followerKolontayAssault", + "followerKolontaySecurity", + "shooterBTR", + "bossPartisan", + "spiritWinter", + "spiritSpring", + "peacemaker", + "skier", + ], + SavagePlayerBehaviour: "Neutral", + BearPlayerBehaviour: "AlwaysEnemies", + UsecPlayerBehaviour: "AlwaysEnemies", + })); + + // console.log(eventConfig.hostilitySettingsForEvent.zombies.default); +}; diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/mod.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/mod.ts new file mode 100644 index 0000000..ac6279f --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/mod.ts @@ -0,0 +1,29 @@ +import { DependencyContainer } from "tsyringe"; +import { IPostSptLoadMod } from "@spt/models/external/IPostSptLoadMod"; +import { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod"; +import { enableBotSpawning } from "../config/config.json"; +import { buildWaves } from "./Spawning/Spawning"; +import config from "../config/config.json"; +import { globalValues } from "./GlobalValues"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { setupRoutes } from "./Routes/routes"; +import checkPresetLogic from "./Tests/checkPresets"; + +class Moar implements IPostSptLoadMod, IPreSptLoadMod { + preSptLoad(container: DependencyContainer): void { + if (enableBotSpawning) setupRoutes(container); + } + + postSptLoad(container: DependencyContainer): void { + if (enableBotSpawning) { + checkPresetLogic(container); + globalValues.baseConfig = config; + globalValues.overrideConfig = {}; + const logger = container.resolve("WinstonLogger"); + logger.info("\n[MOAR]: Starting up, may the bots ever be in your favour!"); + buildWaves(container); + } + } +} + +module.exports = { mod: new Moar() }; diff --git a/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/utils.ts b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/utils.ts new file mode 100644 index 0000000..7f8a3bc --- /dev/null +++ b/mods/MOAR - Ultra Lite Spawn Mod_backup/user/mods/DewardianDev-MOAR/src/utils.ts @@ -0,0 +1,57 @@ +import PresetWeightings from "../config/PresetWeightings.json"; +import Presets from "../config/Presets.json"; +import { globalValues } from "./GlobalValues"; + +export const saveToFile = (data, filePath) => { + var fs = require("fs"); + let dir = __dirname; + let dirArray = dir.split("\\"); + const directory = `${dirArray[dirArray.length - 4]}/${ + dirArray[dirArray.length - 3] + }/${dirArray[dirArray.length - 2]}/`; + fs.writeFile( + directory + filePath, + JSON.stringify(data, null, 4), + function (err) { + if (err) throw err; + } + ); +}; + +export const cloneDeep = (objectToClone: any) => + JSON.parse(JSON.stringify(objectToClone)); + +export const getRandomPresetOrCurrentlySelectedPreset = () => { + switch (true) { + case globalValues.forcedPreset.toLowerCase() === "custom": + return {}; + case !globalValues.forcedPreset: + globalValues.forcedPreset = "random"; + break; + case globalValues.forcedPreset === "random": + break; + + default: + return Presets[globalValues.forcedPreset]; + } + + const all = []; + + const itemKeys = Object.keys(PresetWeightings); + + for (const key of itemKeys) { + for (let i = 0; i < PresetWeightings[key]; i++) { + all.push(key); + } + } + + const preset: string = all[Math.round(Math.random() * (all.length - 1))]; + globalValues.currentPreset = preset; + return Presets[preset]; +}; + +export const kebabToTitle = (str: string): string => + str + .split("-") + .map((word) => word.slice(0, 1).toUpperCase() + word.slice(1)) + .join(" "); diff --git a/profiles/Server/modlist.txt b/profiles/Server/modlist.txt index 6dce03a..8e57f86 100644 --- a/profiles/Server/modlist.txt +++ b/profiles/Server/modlist.txt @@ -1,4 +1,3 @@ -# This file was automatically generated by Mod Organizer. +Unsorted_separator -Version 1.28.6_separator -SWAG + DONUTS