Updated MOAR & Tweaked Config
This commit is contained in:
parent
c99136d826
commit
6eecae59f6
|
@ -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
|
## Plugin GUID: MOAR.settings
|
||||||
|
|
||||||
[1. Main 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)
|
## Causes all PMCs to spawn in the first few minutes of the game (performance intensive)
|
||||||
# Setting type: Boolean
|
# Setting type: Boolean
|
||||||
# Default value: false
|
# 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..)
|
## 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
|
# Setting type: Double
|
||||||
# Default value: 0.3
|
# Default value: 0.4
|
||||||
# Acceptable value range: From 0 to 1.5
|
# 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
|
## Preset to be used, random pulls a random weighted preset from the PresetWeights.json every time a raid ends
|
||||||
# Setting type: String
|
# Setting type: String
|
||||||
|
@ -36,12 +51,6 @@ Preset Announce On/Off = true
|
||||||
# Default value:
|
# Default value:
|
||||||
FIKA DETECTED: ALWAYS PRESS THIS FIRST BEFORE MAKING CHANGES!! =
|
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]
|
[2. Custom game Settings]
|
||||||
|
|
||||||
## Pushes settings to server
|
## Pushes settings to server
|
||||||
|
@ -63,7 +72,7 @@ gradualBossInvasion On/Off = true
|
||||||
# Setting type: Int32
|
# Setting type: Int32
|
||||||
# Default value: 5
|
# Default value: 5
|
||||||
# Acceptable value range: From 0 to 100
|
# 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
|
## 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
|
# Setting type: Boolean
|
||||||
|
@ -123,23 +132,23 @@ scavMaxGroupSize = 4
|
||||||
# Setting type: Int32
|
# Setting type: Int32
|
||||||
# Default value: 4
|
# Default value: 4
|
||||||
# Acceptable value range: From 0 to 10
|
# Acceptable value range: From 0 to 10
|
||||||
pmcMaxGroupSize = 4
|
pmcMaxGroupSize = 5
|
||||||
|
|
||||||
## Increases chances of pmc groups spawning, doesn't dramatically increase quantity.
|
## Increases chances of pmc groups spawning, doesn't dramatically increase quantity.
|
||||||
# Setting type: Boolean
|
# Setting type: Boolean
|
||||||
# Default value: false
|
# Default value: false
|
||||||
morePmcGroups On/Off = false
|
morePmcGroups On/Off = true
|
||||||
|
|
||||||
## Increases chances of scav groups spawning, doesn't dramatically increase quantity.
|
## Increases chances of scav groups spawning, doesn't dramatically increase quantity.
|
||||||
# Setting type: Boolean
|
# Setting type: Boolean
|
||||||
# Default value: false
|
# Default value: false
|
||||||
moreScavGroups On/Off = false
|
moreScavGroups On/Off = true
|
||||||
|
|
||||||
## Max bots permitted in any particular spawn zone, recommend not to touch this.
|
## Max bots permitted in any particular spawn zone, recommend not to touch this.
|
||||||
# Setting type: Int32
|
# Setting type: Int32
|
||||||
# Default value: 7
|
# Default value: 5
|
||||||
# Acceptable value range: From 0 to 15
|
# Acceptable value range: From 0 to 15
|
||||||
MaxBotPerZone = 7
|
MaxBotPerZone = 5
|
||||||
|
|
||||||
## Max bots alive at one time
|
## Max bots alive at one time
|
||||||
# Setting type: Int32
|
# Setting type: Int32
|
||||||
|
@ -157,34 +166,34 @@ ZombieHealth = 1
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
# Default value: 1
|
# Default value: 1
|
||||||
# Acceptable value range: From 0 to 10
|
# 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
|
## 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
|
# Setting type: Double
|
||||||
# Default value: 0.5
|
# Default value: 0.5
|
||||||
# Acceptable value range: From 0 to 1
|
# Acceptable value range: From 0 to 1
|
||||||
ZombieWaveDistribution = 0.5
|
ZombieWaveDistribution = 0.2
|
||||||
|
|
||||||
## Enables zombies to spawn
|
## Enables zombies to spawn
|
||||||
# Setting type: Boolean
|
# Setting type: Boolean
|
||||||
# Default value: false
|
# 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
|
## 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
|
# Setting type: Double
|
||||||
# Default value: 0.3
|
# Default value: 0.5
|
||||||
# Acceptable value range: From 0 to 1
|
# 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
|
## 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
|
# Setting type: Double
|
||||||
# Default value: 0.8
|
# Default value: 0.7
|
||||||
# Acceptable value range: From 0 to 1
|
# 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
|
## Multiplies wave counts seen in the server's mapConfig.json by this number
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
# Default value: 0.5
|
# Default value: 1
|
||||||
# Acceptable value range: From 0 to 10
|
# Acceptable value range: From 0 to 10
|
||||||
ScavWaveQuantity = 1
|
ScavWaveQuantity = 1
|
||||||
|
|
||||||
|
@ -192,7 +201,7 @@ ScavWaveQuantity = 1
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
# Default value: 1
|
# Default value: 1
|
||||||
# Acceptable value range: From 0 to 10
|
# Acceptable value range: From 0 to 10
|
||||||
PmcWaveQuantity = 1.2
|
PmcWaveQuantity = 1.5
|
||||||
|
|
||||||
[3.Debug]
|
[3.Debug]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"nextUpdateTime": 1736141944920,
|
"nextUpdateTime": 1736820722143,
|
||||||
"selectedMap": "woods",
|
"selectedMap": "lighthouse",
|
||||||
"lastRotationInterval": 180,
|
"lastRotationInterval": 180,
|
||||||
"lastUpdateTime": 1736131144920
|
"lastUpdateTime": 1736809922143
|
||||||
}
|
}
|
Binary file not shown.
|
@ -1,11 +1,11 @@
|
||||||
[General]
|
[General]
|
||||||
gameName=spt
|
gameName=spt
|
||||||
modid=0
|
modid=0
|
||||||
version=d2024.12.31.0
|
version=d2025.1.13.0
|
||||||
newestVersion=
|
newestVersion=
|
||||||
category="1,"
|
category="1,"
|
||||||
nexusFileStatus=1
|
nexusFileStatus=1
|
||||||
installationFile=DewardianDev-MOAR-2.6.1.zip
|
installationFile=DewardianDev-MOAR-2.6.7.zip
|
||||||
repository=Nexus
|
repository=Nexus
|
||||||
ignoredVersion=
|
ignoredVersion=
|
||||||
comments=
|
comments=
|
||||||
|
|
|
@ -6,12 +6,13 @@
|
||||||
"scavWaveQuantity": 1.2
|
"scavWaveQuantity": 1.2
|
||||||
},
|
},
|
||||||
"more-pmcs": {
|
"more-pmcs": {
|
||||||
|
"scavWaveDistribution": 0.4,
|
||||||
"morePmcGroups": true,
|
"morePmcGroups": true,
|
||||||
"pmcMaxGroupSize": 5,
|
"pmcMaxGroupSize": 5,
|
||||||
"pmcWaveQuantity": 1.2
|
"pmcWaveQuantity": 1.2
|
||||||
},
|
},
|
||||||
"more-scavs-and-pmcs": {
|
"more-scavs-and-pmcs": {
|
||||||
"maxBotCap": 30,
|
"scavWaveDistribution": 0.4,
|
||||||
"moreScavGroups": true,
|
"moreScavGroups": true,
|
||||||
"scavMaxGroupSize": 5,
|
"scavMaxGroupSize": 5,
|
||||||
"morePmcGroups": true,
|
"morePmcGroups": true,
|
||||||
|
@ -39,8 +40,6 @@
|
||||||
"scavWaveDistribution": 0.4,
|
"scavWaveDistribution": 0.4,
|
||||||
"scavWaveQuantity": 1.3,
|
"scavWaveQuantity": 1.3,
|
||||||
"pmcWaveQuantity": 1.3,
|
"pmcWaveQuantity": 1.3,
|
||||||
"maxBotCap": 30,
|
|
||||||
"maxBotPerZone": 9,
|
|
||||||
"moreScavGroups": true,
|
"moreScavGroups": true,
|
||||||
"morePmcGroups": true,
|
"morePmcGroups": true,
|
||||||
"pmcMaxGroupSize": 6,
|
"pmcMaxGroupSize": 6,
|
||||||
|
|
|
@ -2,14 +2,18 @@
|
||||||
"enableBotSpawning": true,
|
"enableBotSpawning": true,
|
||||||
|
|
||||||
"pmcDifficulty": 0.6,
|
"pmcDifficulty": 0.6,
|
||||||
"scavDifficulty": 0.3,
|
"scavDifficulty": 0.4,
|
||||||
|
|
||||||
"scavWaveDistribution": 0.3,
|
"scavWaveDistribution": 0.5,
|
||||||
"scavWaveQuantity": 0.5,
|
"scavWaveQuantity": 1,
|
||||||
|
|
||||||
"startingPmcs": false,
|
"startingPmcs": false,
|
||||||
|
|
||||||
"pmcWaveDistribution": 0.8,
|
"playerOpenZones": false,
|
||||||
|
"pmcOpenZones": true,
|
||||||
|
"allOpenZones": false,
|
||||||
|
|
||||||
|
"pmcWaveDistribution": 0.7,
|
||||||
"pmcWaveQuantity": 1,
|
"pmcWaveQuantity": 1,
|
||||||
|
|
||||||
"zombiesEnabled": false,
|
"zombiesEnabled": false,
|
||||||
|
@ -18,7 +22,7 @@
|
||||||
"zombieHealth": 1,
|
"zombieHealth": 1,
|
||||||
|
|
||||||
"maxBotCap": 25,
|
"maxBotCap": 25,
|
||||||
"maxBotPerZone": 7,
|
"maxBotPerZone": 5,
|
||||||
|
|
||||||
"moreScavGroups": false,
|
"moreScavGroups": false,
|
||||||
"morePmcGroups": false,
|
"morePmcGroups": false,
|
||||||
|
|
|
@ -5,14 +5,10 @@
|
||||||
"scavWaveCount": 21,
|
"scavWaveCount": 21,
|
||||||
"zombieWaveCount": 9,
|
"zombieWaveCount": 9,
|
||||||
"scavHotZones": [
|
"scavHotZones": [
|
||||||
"ZoneDormitory",
|
"ZoneDormitory"
|
||||||
"ZoneCrossRoad",
|
|
||||||
"ZoneGasStation"
|
|
||||||
],
|
],
|
||||||
"pmcHotZones": [
|
"pmcHotZones": [
|
||||||
"ZoneDormitory",
|
"ZoneDormitory"
|
||||||
"ZoneGasStation",
|
|
||||||
"ZoneCustoms"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"factoryDay": {
|
"factoryDay": {
|
||||||
|
@ -39,11 +35,6 @@
|
||||||
"scavHotZones": [
|
"scavHotZones": [
|
||||||
"ZoneCenterBot",
|
"ZoneCenterBot",
|
||||||
"ZoneCenter"
|
"ZoneCenter"
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"ZoneIDEA",
|
|
||||||
"ZoneOLI",
|
|
||||||
"ZoneCenter"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"laboratory": {
|
"laboratory": {
|
||||||
|
@ -59,12 +50,7 @@
|
||||||
"zombieWaveCount": 10,
|
"zombieWaveCount": 10,
|
||||||
"scavHotZones": [
|
"scavHotZones": [
|
||||||
"Zone_LongRoad",
|
"Zone_LongRoad",
|
||||||
"Zone_Village"
|
"Zone_LongRoad"
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"Zone_DestroyedHouse",
|
|
||||||
"Zone_Chalet",
|
|
||||||
"Zone_Village"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rezervbase": {
|
"rezervbase": {
|
||||||
|
@ -73,13 +59,10 @@
|
||||||
"scavWaveCount": 24,
|
"scavWaveCount": 24,
|
||||||
"zombieWaveCount": 9,
|
"zombieWaveCount": 9,
|
||||||
"scavHotZones": [
|
"scavHotZones": [
|
||||||
"ZoneRailStrorage",
|
"ZoneRailStrorage"
|
||||||
"ZoneBunkerStorage",
|
|
||||||
"ZoneBarrack"
|
|
||||||
],
|
],
|
||||||
"pmcHotZones": [
|
"pmcHotZones": [
|
||||||
"ZoneBarrack",
|
"ZoneBarrack"
|
||||||
"ZoneBunkerStorage"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"shoreline": {
|
"shoreline": {
|
||||||
|
@ -88,34 +71,17 @@
|
||||||
"scavWaveCount": 32,
|
"scavWaveCount": 32,
|
||||||
"zombieWaveCount": 12,
|
"zombieWaveCount": 12,
|
||||||
"scavHotZones": [
|
"scavHotZones": [
|
||||||
"ZoneSanatorium1",
|
"ZoneSanatorium1"
|
||||||
"ZoneGasStation",
|
|
||||||
"ZonePowerStation",
|
|
||||||
"ZoneBusStation",
|
|
||||||
"ZoneStartVillage"
|
|
||||||
],
|
],
|
||||||
"pmcHotZones": [
|
"pmcHotZones": [
|
||||||
"ZoneSanatorium2",
|
"ZoneSanatorium2"
|
||||||
"ZoneGasStation",
|
|
||||||
"ZonePowerStation"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tarkovstreets": {
|
"tarkovstreets": {
|
||||||
"spawnMinDistance": 40,
|
"spawnMinDistance": 40,
|
||||||
"pmcWaveCount": 16,
|
"pmcWaveCount": 16,
|
||||||
"scavWaveCount": 28,
|
"scavWaveCount": 28,
|
||||||
"zombieWaveCount": 13,
|
"zombieWaveCount": 13
|
||||||
"scavHotZones": [
|
|
||||||
"ZoneHotel_2",
|
|
||||||
"ZoneHotel_1",
|
|
||||||
"ZoneConstruction",
|
|
||||||
"ZoneCarShowroom"
|
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"ZoneSanatorium2",
|
|
||||||
"ZoneCinema",
|
|
||||||
"ZoneConcordiaParking"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"woods": {
|
"woods": {
|
||||||
"spawnMinDistance": 40,
|
"spawnMinDistance": 40,
|
||||||
|
@ -123,15 +89,10 @@
|
||||||
"scavWaveCount": 28,
|
"scavWaveCount": 28,
|
||||||
"zombieWaveCount": 10,
|
"zombieWaveCount": 10,
|
||||||
"scavHotZones": [
|
"scavHotZones": [
|
||||||
"ZoneWoodCutter",
|
"ZoneWoodCutter"
|
||||||
"ZoneClearVill",
|
|
||||||
"ZoneScavBase2",
|
|
||||||
"ZoneRedHouse"
|
|
||||||
],
|
],
|
||||||
"pmcHotZones": [
|
"pmcHotZones": [
|
||||||
"ZoneWoodCutter",
|
"ZoneWoodCutter"
|
||||||
"ZoneBigRocks",
|
|
||||||
"ZoneHighRocks"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gzLow": {
|
"gzLow": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "MOAR",
|
"name": "MOAR",
|
||||||
"version": "2.6.1",
|
"version": "2.6.7",
|
||||||
"main": "src/mod.js",
|
"main": "src/mod.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "DewardianDev",
|
"author": "DewardianDev",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { DependencyContainer } from "tsyringe";
|
import { DependencyContainer } from "tsyringe";
|
||||||
import { buildWaves } from "../Spawning/Spawning";
|
import { buildWaves } from "../Spawning/Spawning";
|
||||||
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||||
import { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService";
|
// import { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService";
|
||||||
import { globalValues } from "../GlobalValues";
|
import { globalValues } from "../GlobalValues";
|
||||||
import { kebabToTitle } from "../utils";
|
import { kebabToTitle } from "../utils";
|
||||||
import PresetWeightingsConfig from "../../config/PresetWeightings.json";
|
import PresetWeightingsConfig from "../../config/PresetWeightings.json";
|
||||||
|
@ -11,9 +11,9 @@ export const setupRoutes = (container: DependencyContainer) => {
|
||||||
"StaticRouterModService"
|
"StaticRouterModService"
|
||||||
);
|
);
|
||||||
|
|
||||||
const dynamicRouterModService = container.resolve<DynamicRouterModService>(
|
// const dynamicRouterModService = container.resolve<DynamicRouterModService>(
|
||||||
"DynamicRouterModService"
|
// "DynamicRouterModService"
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Make buildwaves run on game end
|
// Make buildwaves run on game end
|
||||||
staticRouterModService.registerStaticRouter(
|
staticRouterModService.registerStaticRouter(
|
||||||
|
|
|
@ -7,7 +7,11 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
import { DependencyContainer } from "tsyringe";
|
import { DependencyContainer } from "tsyringe";
|
||||||
import { globalValues } from "../GlobalValues";
|
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 { ILocationConfig } from "@spt/models/spt/config/ILocationConfig.d";
|
||||||
import { originalMapList } from "./constants";
|
import { originalMapList } from "./constants";
|
||||||
import { buildBossWaves } from "./buildBossWaves";
|
import { buildBossWaves } from "./buildBossWaves";
|
||||||
|
@ -63,7 +67,7 @@ export const buildWaves = (container: DependencyContainer) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
config.debug &&
|
// config.debug &&
|
||||||
console.log(
|
console.log(
|
||||||
globalValues.forcedPreset === "custom"
|
globalValues.forcedPreset === "custom"
|
||||||
? "custom"
|
? "custom"
|
||||||
|
@ -127,7 +131,7 @@ export const buildWaves = (container: DependencyContainer) => {
|
||||||
rezervbase: { pmcbot: { min: 0, max: 0 } },
|
rezervbase: { pmcbot: { min: 0, max: 0 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
updateSpawnLocations(locationList);
|
updateSpawnLocations(locationList, config);
|
||||||
|
|
||||||
setEscapeTimeOverrides(locationList, _mapConfig, Logger, config);
|
setEscapeTimeOverrides(locationList, _mapConfig, Logger, config);
|
||||||
|
|
||||||
|
|
|
@ -139,36 +139,20 @@ export function buildBossWaves(
|
||||||
|
|
||||||
for (let key = 0; key < locationList.length; key++) {
|
for (let key = 0; key < locationList.length; key++) {
|
||||||
//Gather bosses to avoid duplicating.
|
//Gather bosses to avoid duplicating.
|
||||||
let bossLocations = "";
|
|
||||||
|
|
||||||
const duplicateBosses = [
|
const duplicateBosses = [
|
||||||
...locationList[key].base.BossLocationSpawn.filter(
|
...locationList[key].base.BossLocationSpawn.filter(
|
||||||
({ BossName, BossZone }) => {
|
({ BossName, BossZone }) => bossList.includes(BossName)
|
||||||
bossLocations += BossZone + ",";
|
|
||||||
return bossList.includes(BossName);
|
|
||||||
}
|
|
||||||
).map(({ BossName }) => BossName),
|
).map(({ BossName }) => BossName),
|
||||||
"bossKnight", // So knight doesn't invade
|
"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
|
//Build bosses to add
|
||||||
const bossesToAdd = shuffle<IBossLocationSpawn[]>(Object.values(bosses))
|
const bossesToAdd = shuffle<IBossLocationSpawn[]>(Object.values(bosses))
|
||||||
.filter(({ BossName }) => !duplicateBosses.includes(BossName))
|
.filter(({ BossName }) => !duplicateBosses.includes(BossName))
|
||||||
.map((boss, j) => ({
|
.map((boss, j) => ({
|
||||||
...boss,
|
...boss,
|
||||||
BossZone: uniqueBossZones,
|
BossZone: "",
|
||||||
BossEscortAmount:
|
BossEscortAmount:
|
||||||
boss.BossEscortAmount === "0" ? boss.BossEscortAmount : "1",
|
boss.BossEscortAmount === "0" ? boss.BossEscortAmount : "1",
|
||||||
...(gradualBossInvasion ? { Time: j * 20 + 1 } : {}),
|
...(gradualBossInvasion ? { Time: j * 20 + 1 } : {}),
|
||||||
|
@ -268,7 +252,22 @@ export function buildBossWaves(
|
||||||
configLocations[index]
|
configLocations[index]
|
||||||
}: ${bossesToAdd.map(({ BossName }) => BossName)}`
|
}: ${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) {
|
if (hasChangedBossSpawns) {
|
||||||
console.log(
|
console.log(
|
||||||
`[MOAR]: --- Adjusting default boss spawn rates complete --- \n`
|
`[MOAR]: --- Adjusting default boss spawn rates complete --- \n`
|
||||||
|
|
|
@ -30,25 +30,21 @@ export default function buildPmcs(
|
||||||
.filter(
|
.filter(
|
||||||
({ Categories, BotZoneName }) =>
|
({ Categories, BotZoneName }) =>
|
||||||
!!BotZoneName &&
|
!!BotZoneName &&
|
||||||
(Categories.includes("Player") ||
|
!BotZoneName.includes("snipe") &&
|
||||||
(map === "laboratory" &&
|
(Categories.includes("Player") || Categories.includes("All")) &&
|
||||||
!BotZoneName.includes("BotZoneGate"))) &&
|
!BotZoneName.includes("BotZoneGate")
|
||||||
!BotZoneName.includes("snipe")
|
|
||||||
)
|
)
|
||||||
.map(({ BotZoneName, ...rest }) => {
|
.map(({ BotZoneName, ...rest }) => {
|
||||||
return BotZoneName;
|
return BotZoneName;
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
...pmcHotZones,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Make labs have only named zones
|
// Make labs have only named zones
|
||||||
if (map === "laboratory") {
|
if (map === "laboratory") {
|
||||||
pmcZones = new Array(10).fill(pmcZones).flat(1);
|
pmcZones = new Array(10).fill(pmcZones).flat(1);
|
||||||
// console.log(pmcZones);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
|
||||||
|
|
||||||
const { pmcWaveCount } = mapConfig[map];
|
const { pmcWaveCount } = mapConfig[map];
|
||||||
|
|
||||||
const escapeTimeLimitRatio = Math.round(
|
const escapeTimeLimitRatio = Math.round(
|
||||||
|
@ -58,13 +54,12 @@ export default function buildPmcs(
|
||||||
const totalWaves = Math.round(
|
const totalWaves = Math.round(
|
||||||
pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio
|
pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio
|
||||||
);
|
);
|
||||||
// console.log(pmcZones.length, totalWaves);
|
|
||||||
const numberOfZoneless = totalWaves - pmcZones.length;
|
const numberOfZoneless = totalWaves - pmcZones.length;
|
||||||
if (numberOfZoneless > 0) {
|
if (numberOfZoneless > 0) {
|
||||||
const addEmpty = new Array(numberOfZoneless).fill("");
|
const addEmpty = new Array(numberOfZoneless).fill("");
|
||||||
pmcZones = shuffle<string[]>([...pmcZones, ...addEmpty]);
|
pmcZones = shuffle<string[]>([...pmcZones, ...addEmpty]);
|
||||||
}
|
}
|
||||||
// if (map === "laboratory") console.log(numberOfZoneless, pmcZones);
|
|
||||||
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log(`${map} PMC count ${totalWaves} \n`);
|
console.log(`${map} PMC count ${totalWaves} \n`);
|
||||||
|
@ -75,10 +70,16 @@ export default function buildPmcs(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const waves = buildPmcWaves(pmcWaveCount, timeLimit, config, pmcZones);
|
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||||
// if (map === "laboratory")
|
|
||||||
// console.log(waves.map(({ BossZone }) => BossZone));
|
const waves = buildPmcWaves(
|
||||||
// apply our new waves
|
totalWaves,
|
||||||
|
timeLimit,
|
||||||
|
config,
|
||||||
|
pmcZones,
|
||||||
|
pmcHotZones
|
||||||
|
);
|
||||||
|
|
||||||
locationList[index].base.BossLocationSpawn = [
|
locationList[index].base.BossLocationSpawn = [
|
||||||
...waves,
|
...waves,
|
||||||
...locationList[index].base.BossLocationSpawn,
|
...locationList[index].base.BossLocationSpawn,
|
||||||
|
|
|
@ -89,19 +89,16 @@ export default function buildScavMarksmanWaves(
|
||||||
const sniperLocations = new Set(
|
const sniperLocations = new Set(
|
||||||
[...locationList[index].base.SpawnPointParams]
|
[...locationList[index].base.SpawnPointParams]
|
||||||
.filter(
|
.filter(
|
||||||
({ Categories, Sides, BotZoneName }) =>
|
({ Categories, DelayToCanSpawnSec, BotZoneName, Sides }) =>
|
||||||
!!BotZoneName &&
|
!Categories.includes("Boss") &&
|
||||||
Sides.includes("Savage") &&
|
Sides[0] === "Savage" &&
|
||||||
!Categories.includes("Boss")
|
(BotZoneName?.toLowerCase().includes("snipe") ||
|
||||||
|
DelayToCanSpawnSec > 40)
|
||||||
)
|
)
|
||||||
.filter(
|
.map(({ BotZoneName }) => BotZoneName || "")
|
||||||
({ BotZoneName, DelayToCanSpawnSec }) =>
|
|
||||||
BotZoneName?.toLowerCase().includes("snipe") ||
|
|
||||||
DelayToCanSpawnSec > 300
|
|
||||||
)
|
|
||||||
.map(({ BotZoneName }) => BotZoneName)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (sniperLocations.size) {
|
if (sniperLocations.size) {
|
||||||
locationList[index].base.MinMaxBots = [
|
locationList[index].base.MinMaxBots = [
|
||||||
{
|
{
|
||||||
|
@ -112,32 +109,21 @@ export default function buildScavMarksmanWaves(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const scavZones = shuffle<string[]>([
|
let scavZones = shuffle<string[]>([
|
||||||
...new Set(
|
...new Set(
|
||||||
[...locationList[index].base.SpawnPointParams]
|
[...locationList[index].base.SpawnPointParams]
|
||||||
.filter(
|
.filter(
|
||||||
({ Categories, Sides, BotZoneName }) =>
|
({ Categories, Sides, BotZoneName }) =>
|
||||||
!!BotZoneName &&
|
!!BotZoneName &&
|
||||||
Sides.includes("Savage") &&
|
Categories.includes("Bot") &&
|
||||||
!Categories.includes("Boss")
|
(Sides.includes("Savage") || Sides.includes("All"))
|
||||||
)
|
)
|
||||||
.map(({ BotZoneName }) => BotZoneName)
|
.map(({ BotZoneName }) => BotZoneName)
|
||||||
.filter((name) => !sniperLocations.has(name))
|
.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 { scavWaveCount } = mapConfig[map];
|
||||||
|
|
||||||
const escapeTimeLimitRatio = Math.round(
|
const escapeTimeLimitRatio = Math.round(
|
||||||
|
@ -149,12 +135,19 @@ export default function buildScavMarksmanWaves(
|
||||||
scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio
|
scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const numberOfZoneless = scavTotalWaveCount - scavZones.length;
|
||||||
|
// console.log(numberOfZoneless);
|
||||||
|
if (numberOfZoneless > 0) {
|
||||||
|
const addEmpty = new Array(numberOfZoneless).fill("");
|
||||||
|
scavZones = shuffle<string[]>([...scavZones, ...addEmpty]);
|
||||||
|
}
|
||||||
|
// console.log(scavZones);
|
||||||
config.debug &&
|
config.debug &&
|
||||||
escapeTimeLimitRatio !== 1 &&
|
escapeTimeLimitRatio !== 1 &&
|
||||||
console.log(
|
console.log(
|
||||||
`${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment`
|
`${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment`
|
||||||
);
|
);
|
||||||
|
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||||
let snipers = waveBuilder(
|
let snipers = waveBuilder(
|
||||||
sniperLocations.size,
|
sniperLocations.size,
|
||||||
Math.round(timeLimit / 4),
|
Math.round(timeLimit / 4),
|
||||||
|
@ -166,14 +159,13 @@ export default function buildScavMarksmanWaves(
|
||||||
[],
|
[],
|
||||||
shuffle([...sniperLocations]),
|
shuffle([...sniperLocations]),
|
||||||
80,
|
80,
|
||||||
false,
|
true,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (snipersHaveFriends)
|
if (snipersHaveFriends)
|
||||||
snipers = snipers.map((wave) => ({
|
snipers = snipers.map((wave) => ({
|
||||||
...wave,
|
...wave,
|
||||||
slots_min: 0,
|
|
||||||
...(snipersHaveFriends && wave.slots_max < 2
|
...(snipersHaveFriends && wave.slots_max < 2
|
||||||
? { slots_min: 1, slots_max: 2 }
|
? { slots_min: 1, slots_max: 2 }
|
||||||
: {}),
|
: {}),
|
||||||
|
|
|
@ -1,37 +1,138 @@
|
||||||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||||
import { configLocations } from "./constants";
|
import { configLocations } from "./constants";
|
||||||
import mapConfig from "../../config/mapConfig.json";
|
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++) {
|
for (let index = 0; index < locationList.length; index++) {
|
||||||
const map = configLocations[index];
|
const map = configLocations[index];
|
||||||
|
// console.log(map);
|
||||||
const limit = mapConfig[map].spawnMinDistance;
|
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);
|
// console.log("\n" + map);
|
||||||
locationList[index].base.SpawnPointParams.forEach(
|
locationList[index].base.SpawnPointParams.forEach(
|
||||||
(
|
(
|
||||||
{ ColliderParams, BotZoneName, DelayToCanSpawnSec, Categories, Sides },
|
{
|
||||||
|
ColliderParams,
|
||||||
|
BotZoneName,
|
||||||
|
DelayToCanSpawnSec,
|
||||||
|
Categories,
|
||||||
|
Sides,
|
||||||
|
Infiltration,
|
||||||
|
},
|
||||||
innerIndex
|
innerIndex
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
ColliderParams?._props?.Radius !== undefined &&
|
!Categories.includes("Boss") &&
|
||||||
ColliderParams?._props?.Radius < limit &&
|
|
||||||
!BotZoneName?.toLowerCase().includes("snipe") &&
|
!BotZoneName?.toLowerCase().includes("snipe") &&
|
||||||
DelayToCanSpawnSec < 300
|
DelayToCanSpawnSec < 41
|
||||||
) {
|
) {
|
||||||
|
// 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].Sides = [
|
||||||
|
"Pmc",
|
||||||
|
"All",
|
||||||
|
];
|
||||||
// console.log(
|
// console.log(
|
||||||
// "----",
|
// BotZoneName || "none",
|
||||||
// ColliderParams._props.Radius,
|
// locationList[index].base.SpawnPointParams[innerIndex].Categories,
|
||||||
// "=>",
|
// locationList[index].base.SpawnPointParams[innerIndex].Sides
|
||||||
// limit,
|
|
||||||
// BotZoneName
|
|
||||||
// );
|
// );
|
||||||
|
}
|
||||||
|
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[
|
locationList[index].base.SpawnPointParams[
|
||||||
innerIndex
|
innerIndex
|
||||||
].ColliderParams._props.Radius = limit;
|
].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
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ export const waveBuilder = (
|
||||||
);
|
);
|
||||||
|
|
||||||
const min = !offset && waves.length < 1 ? 0 : timeStart;
|
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;
|
if (waves.length >= 1 || offset) timeStart = timeStart + stage;
|
||||||
const BotPreset = getDifficulty(difficulty);
|
const BotPreset = getDifficulty(difficulty);
|
||||||
|
@ -55,8 +55,9 @@ export const waveBuilder = (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (slotMax < 1) slotMax = 1;
|
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({
|
waves.push({
|
||||||
BotPreset,
|
BotPreset,
|
||||||
BotSide: getBotSide(wildSpawnType),
|
BotSide: getBotSide(wildSpawnType),
|
||||||
|
@ -189,11 +190,26 @@ export const getRandomZombieType = () =>
|
||||||
zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())];
|
zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())];
|
||||||
|
|
||||||
export const buildPmcWaves = (
|
export const buildPmcWaves = (
|
||||||
totalWaves: number,
|
pmcTotal: number,
|
||||||
escapeTimeLimit: number,
|
escapeTimeLimit: number,
|
||||||
config: typeof _config,
|
config: typeof _config,
|
||||||
bossZones: string[]
|
bossZones: string[],
|
||||||
|
hotZones: string[]
|
||||||
): IBossLocationSpawn[] => {
|
): 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 {
|
let {
|
||||||
pmcMaxGroupSize,
|
pmcMaxGroupSize,
|
||||||
pmcDifficulty,
|
pmcDifficulty,
|
||||||
|
@ -202,14 +218,12 @@ export const buildPmcWaves = (
|
||||||
pmcWaveDistribution,
|
pmcWaveDistribution,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
const averageTime = escapeTimeLimit / totalWaves;
|
const averageTime = (escapeTimeLimit * 0.8) / pmcTotal;
|
||||||
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) {
|
const waves: IBossLocationSpawn[] = [];
|
||||||
|
let maxSlotsReached = pmcTotal;
|
||||||
|
|
||||||
|
while (pmcTotal > 0) {
|
||||||
let bossEscortAmount = Math.round(
|
let bossEscortAmount = Math.round(
|
||||||
(morePmcGroups ? 1 : Math.random()) *
|
(morePmcGroups ? 1 : Math.random()) *
|
||||||
Math.random() *
|
Math.random() *
|
||||||
|
@ -217,20 +231,24 @@ export const buildPmcWaves = (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (bossEscortAmount < 0) bossEscortAmount = 0;
|
if (bossEscortAmount < 0) bossEscortAmount = 0;
|
||||||
const accelerate = totalWaves > 5 && waves.length < totalWaves / 3;
|
|
||||||
const stage = startingPmcs
|
// const totalCountThisWave = bossEscortAmount + 1;
|
||||||
? 10
|
const totalCountThusFar = pmcTotal - maxSlotsReached;
|
||||||
|
|
||||||
|
const timeToUse =
|
||||||
|
totalCountThusFar < pmcTotal * pmcWaveDistribution
|
||||||
|
? Math.round(
|
||||||
|
averageTime * (1 - pmcWaveDistribution) * totalCountThusFar
|
||||||
|
)
|
||||||
: Math.round(
|
: Math.round(
|
||||||
waves.length < Math.round(totalWaves * 0.5)
|
escapeTimeLimit * (1 - pmcWaveDistribution) +
|
||||||
? accelerate
|
(1 - pmcWaveDistribution) * totalCountThusFar * averageTime
|
||||||
? firstHalf / 3
|
|
||||||
: firstHalf
|
|
||||||
: secondHalf
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (waves.length >= 1) timeStart = timeStart + stage;
|
let timeStart =
|
||||||
|
(startingPmcs ? totalCountThusFar * totalCountThusFar * 3 : timeToUse) ||
|
||||||
|
-1;
|
||||||
|
|
||||||
// console.log(timeStart, BossEscortAmount);
|
|
||||||
const side = Math.random() > 0.5 ? "pmcBEAR" : "pmcUSEC";
|
const side = Math.random() > 0.5 ? "pmcBEAR" : "pmcUSEC";
|
||||||
|
|
||||||
const BossDifficult = getDifficulty(pmcDifficulty);
|
const BossDifficult = getDifficulty(pmcDifficulty);
|
||||||
|
@ -260,7 +278,10 @@ export const buildPmcWaves = (
|
||||||
maxSlotsReached -= 1 + bossEscortAmount;
|
maxSlotsReached -= 1 + bossEscortAmount;
|
||||||
if (maxSlotsReached <= 0) break;
|
if (maxSlotsReached <= 0) break;
|
||||||
}
|
}
|
||||||
|
// console.log(
|
||||||
|
// escapeTimeLimit,
|
||||||
|
// waves.map(({ Time }) => Time)
|
||||||
|
// );
|
||||||
return waves;
|
return waves;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,6 +291,7 @@ export const buildZombie = (
|
||||||
waveDistribution: number,
|
waveDistribution: number,
|
||||||
BossChance: number = 100
|
BossChance: number = 100
|
||||||
): IBossLocationSpawn[] => {
|
): IBossLocationSpawn[] => {
|
||||||
|
if (!totalWaves) return [];
|
||||||
const averageTime = (escapeTimeLimit * 60) / totalWaves;
|
const averageTime = (escapeTimeLimit * 60) / totalWaves;
|
||||||
const firstHalf = Math.round(averageTime * (1 - waveDistribution));
|
const firstHalf = Math.round(averageTime * (1 - waveDistribution));
|
||||||
const secondHalf = Math.round(averageTime * (1 + waveDistribution));
|
const secondHalf = Math.round(averageTime * (1 + waveDistribution));
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,28 @@
|
||||||
|
[General]
|
||||||
|
gameName=spt
|
||||||
|
modid=0
|
||||||
|
version=d2024.12.31.0
|
||||||
|
newestVersion=
|
||||||
|
category="1,"
|
||||||
|
nexusFileStatus=1
|
||||||
|
installationFile=DewardianDev-MOAR-2.6.1.zip
|
||||||
|
repository=Nexus
|
||||||
|
ignoredVersion=
|
||||||
|
comments=
|
||||||
|
notes=
|
||||||
|
nexusDescription=
|
||||||
|
url=
|
||||||
|
hasCustomURL=false
|
||||||
|
lastNexusQuery=
|
||||||
|
lastNexusUpdate=
|
||||||
|
nexusLastModified=2024-12-16T06:46:30Z
|
||||||
|
nexusCategory=0
|
||||||
|
converted=false
|
||||||
|
validated=false
|
||||||
|
color=@Variant(\0\0\0\x43\0\xff\xff\0\0\0\0\0\0\0\0)
|
||||||
|
tracked=0
|
||||||
|
|
||||||
|
[installedFiles]
|
||||||
|
1\modid=0
|
||||||
|
1\fileid=0
|
||||||
|
size=1
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Dushaoan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"live-like": 25,
|
||||||
|
"more-scavs": 8,
|
||||||
|
"more-pmcs": 8,
|
||||||
|
"more-scavs-and-pmcs": 5,
|
||||||
|
"main-boss-roaming": 5,
|
||||||
|
"sniper-buddies": 4,
|
||||||
|
"boss-invasion": 2,
|
||||||
|
"rogue-invasion": 0,
|
||||||
|
"raider-invasion": 0,
|
||||||
|
"insanity": 0
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"live-like": {},
|
||||||
|
"more-scavs": {
|
||||||
|
"moreScavGroups": true,
|
||||||
|
"scavMaxGroupSize": 5,
|
||||||
|
"scavWaveQuantity": 1.2
|
||||||
|
},
|
||||||
|
"more-pmcs": {
|
||||||
|
"morePmcGroups": true,
|
||||||
|
"pmcMaxGroupSize": 5,
|
||||||
|
"pmcWaveQuantity": 1.2
|
||||||
|
},
|
||||||
|
"more-scavs-and-pmcs": {
|
||||||
|
"maxBotCap": 30,
|
||||||
|
"moreScavGroups": true,
|
||||||
|
"scavMaxGroupSize": 5,
|
||||||
|
"morePmcGroups": true,
|
||||||
|
"pmcMaxGroupSize": 5,
|
||||||
|
"scavWaveQuantity": 1.2,
|
||||||
|
"pmcWaveQuantity": 1.2,
|
||||||
|
"mainBossChanceBuff": 25
|
||||||
|
},
|
||||||
|
"boss-invasion": {
|
||||||
|
"bossOpenZones": true,
|
||||||
|
"bossInvasion": true,
|
||||||
|
"bossInvasionSpawnChance": 10,
|
||||||
|
"mainBossChanceBuff": 25,
|
||||||
|
"gradualBossInvasion": true
|
||||||
|
},
|
||||||
|
"rogue-invasion": {
|
||||||
|
"randomRaiderGroup": true,
|
||||||
|
"randomRaiderGroupChance": 50
|
||||||
|
},
|
||||||
|
"raider-invasion": {
|
||||||
|
"randomRaiderGroup": true,
|
||||||
|
"randomRaiderGroupChance": 50
|
||||||
|
},
|
||||||
|
"insanity": {
|
||||||
|
"scavWaveDistribution": 0.4,
|
||||||
|
"scavWaveQuantity": 1.3,
|
||||||
|
"pmcWaveQuantity": 1.3,
|
||||||
|
"maxBotCap": 30,
|
||||||
|
"maxBotPerZone": 9,
|
||||||
|
"moreScavGroups": true,
|
||||||
|
"morePmcGroups": true,
|
||||||
|
"pmcMaxGroupSize": 6,
|
||||||
|
"scavMaxGroupSize": 6,
|
||||||
|
"snipersHaveFriends": true,
|
||||||
|
"bossOpenZones": true,
|
||||||
|
"randomRaiderGroup": true,
|
||||||
|
"randomRaiderGroupChance": 50,
|
||||||
|
"randomRogueGroup": true,
|
||||||
|
"randomRogueGroupChance": 50,
|
||||||
|
"mainBossChanceBuff": 50,
|
||||||
|
"bossInvasion": true,
|
||||||
|
"bossInvasionSpawnChance": 10
|
||||||
|
},
|
||||||
|
"main-boss-roaming": {
|
||||||
|
"bossOpenZones": true,
|
||||||
|
"mainBossChanceBuff": 35
|
||||||
|
},
|
||||||
|
"sniper-buddies": {
|
||||||
|
"snipersHaveFriends": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"ADD_THESE_TO_A_MAP_TO_OVERRIDE_OR_ADD_A_BOSS_TO_A_MAP": {
|
||||||
|
"BOSS_NAME_EXAMPLE": "CHANCE_OF_SPAWNING_PERCENT",
|
||||||
|
"sectantPriest": 0,
|
||||||
|
"arenaFighterEvent": 0,
|
||||||
|
"bossBoarSniper": 0,
|
||||||
|
"pmcBot": 0,
|
||||||
|
"bossZryachiy": 0,
|
||||||
|
"exUsec": 0,
|
||||||
|
"crazyAssaultEvent": 0,
|
||||||
|
"peacemaker": 0,
|
||||||
|
"bossKojaniy": 0,
|
||||||
|
"bossGluhar": 0,
|
||||||
|
"bossSanitar": 0,
|
||||||
|
"bossKilla": 0,
|
||||||
|
"bossTagilla": 0,
|
||||||
|
"bossKnight": 0,
|
||||||
|
"bossBoar": 0,
|
||||||
|
"bossKolontay": 0,
|
||||||
|
"bossPartisan": 0,
|
||||||
|
"bossBully": 0
|
||||||
|
},
|
||||||
|
"customs": {
|
||||||
|
"bossKnight": 30,
|
||||||
|
"bossPartisan": 30,
|
||||||
|
"bossBully": 30
|
||||||
|
},
|
||||||
|
"factoryDay": {
|
||||||
|
"bossTagilla": 30
|
||||||
|
},
|
||||||
|
"factoryNight": {
|
||||||
|
"bossTagilla": 30
|
||||||
|
},
|
||||||
|
"interchange": {
|
||||||
|
"bossKilla": 30
|
||||||
|
},
|
||||||
|
"laboratory": {},
|
||||||
|
"lighthouse": {
|
||||||
|
"bossKnight": 30,
|
||||||
|
"bossPartisan": 30
|
||||||
|
},
|
||||||
|
"rezervbase": {
|
||||||
|
"bossGluhar": 30
|
||||||
|
},
|
||||||
|
"shoreline": {
|
||||||
|
"bossKnight": 30,
|
||||||
|
"bossPartisan": 30,
|
||||||
|
"bossSanitar": 30
|
||||||
|
},
|
||||||
|
"tarkovstreets": {
|
||||||
|
"bossBoar": 30,
|
||||||
|
"bossKolontay": 30
|
||||||
|
},
|
||||||
|
"woods": {
|
||||||
|
"bossKojaniy": 30,
|
||||||
|
"bossKnight": 30,
|
||||||
|
"bossPartisan": 30
|
||||||
|
},
|
||||||
|
"gzLow": {},
|
||||||
|
"gzHigh": {
|
||||||
|
"bossKolontay": 30
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"enableBotSpawning": true,
|
||||||
|
|
||||||
|
"pmcDifficulty": 0.6,
|
||||||
|
"scavDifficulty": 0.3,
|
||||||
|
|
||||||
|
"scavWaveDistribution": 0.3,
|
||||||
|
"scavWaveQuantity": 0.5,
|
||||||
|
|
||||||
|
"startingPmcs": false,
|
||||||
|
|
||||||
|
"pmcWaveDistribution": 0.8,
|
||||||
|
"pmcWaveQuantity": 1.6,
|
||||||
|
|
||||||
|
"zombiesEnabled": false,
|
||||||
|
"zombieWaveDistribution": 0.5,
|
||||||
|
"zombieWaveQuantity": 1,
|
||||||
|
"zombieHealth": 1,
|
||||||
|
|
||||||
|
"maxBotCap": 25,
|
||||||
|
"maxBotPerZone": 7,
|
||||||
|
|
||||||
|
"moreScavGroups": false,
|
||||||
|
"morePmcGroups": false,
|
||||||
|
"pmcMaxGroupSize": 4,
|
||||||
|
"scavMaxGroupSize": 4,
|
||||||
|
|
||||||
|
"snipersHaveFriends": false,
|
||||||
|
|
||||||
|
"bossOpenZones": false,
|
||||||
|
|
||||||
|
"randomRaiderGroup": false,
|
||||||
|
"randomRaiderGroupChance": 10,
|
||||||
|
|
||||||
|
"randomRogueGroup": false,
|
||||||
|
"randomRogueGroupChance": 10,
|
||||||
|
|
||||||
|
"disableBosses": false,
|
||||||
|
"mainBossChanceBuff": 0,
|
||||||
|
|
||||||
|
"bossInvasion": false,
|
||||||
|
"bossInvasionSpawnChance": 5,
|
||||||
|
"gradualBossInvasion": true,
|
||||||
|
|
||||||
|
"debug": false
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
{
|
||||||
|
"customs": {
|
||||||
|
"spawnMinDistance": 30,
|
||||||
|
"pmcWaveCount": 12,
|
||||||
|
"scavWaveCount": 21,
|
||||||
|
"zombieWaveCount": 9,
|
||||||
|
"scavHotZones": [
|
||||||
|
"ZoneDormitory",
|
||||||
|
"ZoneCrossRoad",
|
||||||
|
"ZoneGasStation"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"ZoneDormitory",
|
||||||
|
"ZoneGasStation",
|
||||||
|
"ZoneCustoms"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"factoryDay": {
|
||||||
|
"spawnMinDistance": 20,
|
||||||
|
"maxBotCapOverride": 12,
|
||||||
|
"maxBotPerZoneOverride": 10,
|
||||||
|
"pmcWaveCount": 8,
|
||||||
|
"scavWaveCount": 9,
|
||||||
|
"zombieWaveCount": 6
|
||||||
|
},
|
||||||
|
"factoryNight": {
|
||||||
|
"spawnMinDistance": 20,
|
||||||
|
"maxBotCapOverride": 12,
|
||||||
|
"maxBotPerZoneOverride": 10,
|
||||||
|
"pmcWaveCount": 8,
|
||||||
|
"scavWaveCount": 9,
|
||||||
|
"zombieWaveCount": 6
|
||||||
|
},
|
||||||
|
"interchange": {
|
||||||
|
"spawnMinDistance": 40,
|
||||||
|
"pmcWaveCount": 14,
|
||||||
|
"scavWaveCount": 32,
|
||||||
|
"zombieWaveCount": 12,
|
||||||
|
"scavHotZones": [
|
||||||
|
"ZoneCenterBot",
|
||||||
|
"ZoneCenter"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"ZoneIDEA",
|
||||||
|
"ZoneOLI",
|
||||||
|
"ZoneCenter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"laboratory": {
|
||||||
|
"spawnMinDistance": 20,
|
||||||
|
"pmcWaveCount": 10,
|
||||||
|
"scavWaveCount": 0,
|
||||||
|
"zombieWaveCount": 12
|
||||||
|
},
|
||||||
|
"lighthouse": {
|
||||||
|
"spawnMinDistance": 40,
|
||||||
|
"pmcWaveCount": 12,
|
||||||
|
"scavWaveCount": 20,
|
||||||
|
"zombieWaveCount": 10,
|
||||||
|
"scavHotZones": [
|
||||||
|
"Zone_LongRoad",
|
||||||
|
"Zone_Village"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"Zone_DestroyedHouse",
|
||||||
|
"Zone_Chalet",
|
||||||
|
"Zone_Village"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rezervbase": {
|
||||||
|
"spawnMinDistance": 40,
|
||||||
|
"pmcWaveCount": 11,
|
||||||
|
"scavWaveCount": 24,
|
||||||
|
"zombieWaveCount": 9,
|
||||||
|
"scavHotZones": [
|
||||||
|
"ZoneRailStrorage",
|
||||||
|
"ZoneBunkerStorage",
|
||||||
|
"ZoneBarrack"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"ZoneBarrack",
|
||||||
|
"ZoneBunkerStorage"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shoreline": {
|
||||||
|
"spawnMinDistance": 40,
|
||||||
|
"pmcWaveCount": 14,
|
||||||
|
"scavWaveCount": 32,
|
||||||
|
"zombieWaveCount": 12,
|
||||||
|
"scavHotZones": [
|
||||||
|
"ZoneSanatorium1",
|
||||||
|
"ZoneGasStation",
|
||||||
|
"ZonePowerStation",
|
||||||
|
"ZoneBusStation",
|
||||||
|
"ZoneStartVillage"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"ZoneSanatorium2",
|
||||||
|
"ZoneGasStation",
|
||||||
|
"ZonePowerStation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tarkovstreets": {
|
||||||
|
"spawnMinDistance": 40,
|
||||||
|
"pmcWaveCount": 16,
|
||||||
|
"scavWaveCount": 28,
|
||||||
|
"zombieWaveCount": 13,
|
||||||
|
"scavHotZones": [
|
||||||
|
"ZoneHotel_2",
|
||||||
|
"ZoneHotel_1",
|
||||||
|
"ZoneConstruction",
|
||||||
|
"ZoneCarShowroom"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"ZoneSanatorium2",
|
||||||
|
"ZoneCinema",
|
||||||
|
"ZoneConcordiaParking"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"woods": {
|
||||||
|
"spawnMinDistance": 40,
|
||||||
|
"pmcWaveCount": 14,
|
||||||
|
"scavWaveCount": 28,
|
||||||
|
"zombieWaveCount": 10,
|
||||||
|
"scavHotZones": [
|
||||||
|
"ZoneWoodCutter",
|
||||||
|
"ZoneClearVill",
|
||||||
|
"ZoneScavBase2",
|
||||||
|
"ZoneRedHouse"
|
||||||
|
],
|
||||||
|
"pmcHotZones": [
|
||||||
|
"ZoneWoodCutter",
|
||||||
|
"ZoneBigRocks",
|
||||||
|
"ZoneHighRocks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gzLow": {
|
||||||
|
"spawnMinDistance": 30,
|
||||||
|
"pmcWaveCount": 10,
|
||||||
|
"scavWaveCount": 18,
|
||||||
|
"zombieWaveCount": 9
|
||||||
|
},
|
||||||
|
"gzHigh": {
|
||||||
|
"spawnMinDistance": 30,
|
||||||
|
"pmcWaveCount": 12,
|
||||||
|
"scavWaveCount": 18,
|
||||||
|
"zombieWaveCount": 9
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "MOAR",
|
||||||
|
"version": "2.6.1",
|
||||||
|
"main": "src/mod.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "DewardianDev",
|
||||||
|
"sptVersion": "^3.10.x",
|
||||||
|
"scripts": {
|
||||||
|
"setup": "npm i",
|
||||||
|
"build": "node ./packageBuild.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@semantic-release/git": "^10.0.1",
|
||||||
|
"@types/node": "16.18.10",
|
||||||
|
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||||
|
"@typescript-eslint/parser": "5.46.1",
|
||||||
|
"bestzip": "2.2.1",
|
||||||
|
"eslint": "8.30.0",
|
||||||
|
"fs-extra": "11.1.0",
|
||||||
|
"glob": "8.0.3",
|
||||||
|
"semantic-release": "^24.2.0",
|
||||||
|
"tsyringe": "4.7.0",
|
||||||
|
"typescript": "4.9.4"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import config from "../config/config.json";
|
||||||
|
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
|
||||||
|
export class globalValues {
|
||||||
|
public static baseConfig: typeof config = undefined;
|
||||||
|
public static overrideConfig: Partial<typeof config> = undefined;
|
||||||
|
public static locationsBase: ILocationBase[] = undefined;
|
||||||
|
public static currentPreset: string = "";
|
||||||
|
public static forcedPreset: string = "custom";
|
||||||
|
public static addedMapZones: Record<string, string[]> = {};
|
||||||
|
}
|
|
@ -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>(
|
||||||
|
"StaticRouterModService"
|
||||||
|
);
|
||||||
|
|
||||||
|
const dynamicRouterModService = container.resolve<DynamicRouterModService>(
|
||||||
|
"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"
|
||||||
|
);
|
||||||
|
};
|
|
@ -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>("ConfigServer");
|
||||||
|
const Logger = container.resolve<ILogger>("WinstonLogger");
|
||||||
|
const pmcConfig = configServer.getConfig<IPmcConfig>(ConfigTypes.PMC);
|
||||||
|
const botConfig = configServer.getConfig<IBotConfig>(ConfigTypes.BOT);
|
||||||
|
|
||||||
|
const locationConfig = configServer.getConfig<ILocationConfig>(
|
||||||
|
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>("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];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -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<string, IBossLocationSpawn> = {};
|
||||||
|
for (const key in locationList) {
|
||||||
|
locationList[key].base.BossLocationSpawn.forEach((boss) => {
|
||||||
|
if (!allBosses[boss.BossName]) {
|
||||||
|
allBosses[boss.BossName] = boss;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBossList
|
||||||
|
const bosses: Record<string, IBossLocationSpawn> = {};
|
||||||
|
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<IBossLocationSpawn[]>(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<string, number> = cloneDeep(
|
||||||
|
bossConfig[mapName] || {}
|
||||||
|
);
|
||||||
|
// if (Object.keys(mapBossConfig).length === 0) console.log(name, "empty");
|
||||||
|
const adjusted = new Set<string>([]);
|
||||||
|
|
||||||
|
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`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string[]>([
|
||||||
|
...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<string[]>([...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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string[]>([
|
||||||
|
...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 }));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = <n>(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<string, MapConfigType>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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<ILogger>("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`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>("ConfigServer");
|
||||||
|
const eventConfig = configServer.getConfig<ISeasonalEventConfig>(
|
||||||
|
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>(
|
||||||
|
"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>("ConfigServer");
|
||||||
|
const eventConfig = configServer.getConfig<ISeasonalEventConfig>(
|
||||||
|
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);
|
||||||
|
};
|
|
@ -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<ILogger>("WinstonLogger");
|
||||||
|
logger.info("\n[MOAR]: Starting up, may the bots ever be in your favour!");
|
||||||
|
buildWaves(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { mod: new Moar() };
|
|
@ -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(" ");
|
|
@ -1,4 +1,3 @@
|
||||||
# This file was automatically generated by Mod Organizer.
|
|
||||||
+Unsorted_separator
|
+Unsorted_separator
|
||||||
-Version 1.28.6_separator
|
-Version 1.28.6_separator
|
||||||
-SWAG + DONUTS
|
-SWAG + DONUTS
|
||||||
|
|
Loading…
Reference in New Issue