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
|
||||
|
||||
[1. Main Settings]
|
||||
|
||||
## All Bots/Players (excluding bosses) can spawn anywhere (overrides PMC/Player openzone options)
|
||||
# Setting type: Boolean
|
||||
# Default value: false
|
||||
PMC/Scav/Player OpenZones On/Off = false
|
||||
|
||||
## Adds a large number of zones (including all scav zones) to pmc bots spawn pool
|
||||
# Setting type: Boolean
|
||||
# Default value: true
|
||||
PMC OpenZones On/Off = true
|
||||
|
||||
## Adds a large number of zones to the Player's (you) spawn pool
|
||||
# Setting type: Boolean
|
||||
# Default value: false
|
||||
Player OpenZones On/Off = false
|
||||
|
||||
## Causes all PMCs to spawn in the first few minutes of the game (performance intensive)
|
||||
# Setting type: Boolean
|
||||
# Default value: false
|
||||
|
@ -16,9 +31,9 @@ Pmc difficulty = 0.6
|
|||
|
||||
## Works with SAIN or SPT to decide the bot's 'difficulty' preset (EASY: 0, easy-MEDIUM: 0.4, easy-MEDIUM-hard: 0.6, medium-hard: 0.85, HARD-impossible: 1, etc..)
|
||||
# Setting type: Double
|
||||
# Default value: 0.3
|
||||
# Default value: 0.4
|
||||
# Acceptable value range: From 0 to 1.5
|
||||
Scav difficulty = 0.3
|
||||
Scav difficulty = 0.4
|
||||
|
||||
## Preset to be used, random pulls a random weighted preset from the PresetWeights.json every time a raid ends
|
||||
# Setting type: String
|
||||
|
@ -36,12 +51,6 @@ Preset Announce On/Off = true
|
|||
# Default value:
|
||||
FIKA DETECTED: ALWAYS PRESS THIS FIRST BEFORE MAKING CHANGES!! =
|
||||
|
||||
PMC/Scav/Player OpenZones On/Off = false
|
||||
|
||||
PMC OpenZones On/Off = true
|
||||
|
||||
Player OpenZones On/Off = true
|
||||
|
||||
[2. Custom game Settings]
|
||||
|
||||
## Pushes settings to server
|
||||
|
@ -63,7 +72,7 @@ gradualBossInvasion On/Off = true
|
|||
# Setting type: Int32
|
||||
# Default value: 5
|
||||
# Acceptable value range: From 0 to 100
|
||||
bossInvasionSpawnChance = 5
|
||||
bossInvasionSpawnChance = 0
|
||||
|
||||
## Allows the main bosses (not knight,rogues,raiders) to invade other maps with a reduced retinue, by default they will spawn in native boss locations
|
||||
# Setting type: Boolean
|
||||
|
@ -123,23 +132,23 @@ scavMaxGroupSize = 4
|
|||
# Setting type: Int32
|
||||
# Default value: 4
|
||||
# Acceptable value range: From 0 to 10
|
||||
pmcMaxGroupSize = 4
|
||||
pmcMaxGroupSize = 5
|
||||
|
||||
## Increases chances of pmc groups spawning, doesn't dramatically increase quantity.
|
||||
# Setting type: Boolean
|
||||
# Default value: false
|
||||
morePmcGroups On/Off = false
|
||||
morePmcGroups On/Off = true
|
||||
|
||||
## Increases chances of scav groups spawning, doesn't dramatically increase quantity.
|
||||
# Setting type: Boolean
|
||||
# Default value: false
|
||||
moreScavGroups On/Off = false
|
||||
moreScavGroups On/Off = true
|
||||
|
||||
## Max bots permitted in any particular spawn zone, recommend not to touch this.
|
||||
# Setting type: Int32
|
||||
# Default value: 7
|
||||
# Default value: 5
|
||||
# Acceptable value range: From 0 to 15
|
||||
MaxBotPerZone = 7
|
||||
MaxBotPerZone = 5
|
||||
|
||||
## Max bots alive at one time
|
||||
# Setting type: Int32
|
||||
|
@ -157,34 +166,34 @@ ZombieHealth = 1
|
|||
# Setting type: Double
|
||||
# Default value: 1
|
||||
# Acceptable value range: From 0 to 10
|
||||
ZombieWaveQuantity = 1
|
||||
ZombieWaveQuantity = 1.5
|
||||
|
||||
## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid
|
||||
# Setting type: Double
|
||||
# Default value: 0.5
|
||||
# Acceptable value range: From 0 to 1
|
||||
ZombieWaveDistribution = 0.5
|
||||
ZombieWaveDistribution = 0.2
|
||||
|
||||
## Enables zombies to spawn
|
||||
# Setting type: Boolean
|
||||
# Default value: false
|
||||
zombiesEnabled On/Off = false
|
||||
zombiesEnabled On/Off = true
|
||||
|
||||
## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid
|
||||
# Setting type: Double
|
||||
# Default value: 0.3
|
||||
# Default value: 0.5
|
||||
# Acceptable value range: From 0 to 1
|
||||
ScavWaveDistribution = 0.3
|
||||
ScavWaveDistribution = 0.5
|
||||
|
||||
## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid
|
||||
# Setting type: Double
|
||||
# Default value: 0.8
|
||||
# Default value: 0.7
|
||||
# Acceptable value range: From 0 to 1
|
||||
PmcWaveDistribution = 0.8
|
||||
PmcWaveDistribution = 0.7
|
||||
|
||||
## Multiplies wave counts seen in the server's mapConfig.json by this number
|
||||
# Setting type: Double
|
||||
# Default value: 0.5
|
||||
# Default value: 1
|
||||
# Acceptable value range: From 0 to 10
|
||||
ScavWaveQuantity = 1
|
||||
|
||||
|
@ -192,7 +201,7 @@ ScavWaveQuantity = 1
|
|||
# Setting type: Double
|
||||
# Default value: 1
|
||||
# Acceptable value range: From 0 to 10
|
||||
PmcWaveQuantity = 1.2
|
||||
PmcWaveQuantity = 1.5
|
||||
|
||||
[3.Debug]
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"nextUpdateTime": 1736141944920,
|
||||
"selectedMap": "woods",
|
||||
"nextUpdateTime": 1736820722143,
|
||||
"selectedMap": "lighthouse",
|
||||
"lastRotationInterval": 180,
|
||||
"lastUpdateTime": 1736131144920
|
||||
"lastUpdateTime": 1736809922143
|
||||
}
|
Binary file not shown.
|
@ -1,11 +1,11 @@
|
|||
[General]
|
||||
gameName=spt
|
||||
modid=0
|
||||
version=d2024.12.31.0
|
||||
version=d2025.1.13.0
|
||||
newestVersion=
|
||||
category="1,"
|
||||
nexusFileStatus=1
|
||||
installationFile=DewardianDev-MOAR-2.6.1.zip
|
||||
installationFile=DewardianDev-MOAR-2.6.7.zip
|
||||
repository=Nexus
|
||||
ignoredVersion=
|
||||
comments=
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
"scavWaveQuantity": 1.2
|
||||
},
|
||||
"more-pmcs": {
|
||||
"scavWaveDistribution": 0.4,
|
||||
"morePmcGroups": true,
|
||||
"pmcMaxGroupSize": 5,
|
||||
"pmcWaveQuantity": 1.2
|
||||
},
|
||||
"more-scavs-and-pmcs": {
|
||||
"maxBotCap": 30,
|
||||
"scavWaveDistribution": 0.4,
|
||||
"moreScavGroups": true,
|
||||
"scavMaxGroupSize": 5,
|
||||
"morePmcGroups": true,
|
||||
|
@ -39,8 +40,6 @@
|
|||
"scavWaveDistribution": 0.4,
|
||||
"scavWaveQuantity": 1.3,
|
||||
"pmcWaveQuantity": 1.3,
|
||||
"maxBotCap": 30,
|
||||
"maxBotPerZone": 9,
|
||||
"moreScavGroups": true,
|
||||
"morePmcGroups": true,
|
||||
"pmcMaxGroupSize": 6,
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
"enableBotSpawning": true,
|
||||
|
||||
"pmcDifficulty": 0.6,
|
||||
"scavDifficulty": 0.3,
|
||||
"scavDifficulty": 0.4,
|
||||
|
||||
"scavWaveDistribution": 0.3,
|
||||
"scavWaveQuantity": 0.5,
|
||||
"scavWaveDistribution": 0.5,
|
||||
"scavWaveQuantity": 1,
|
||||
|
||||
"startingPmcs": false,
|
||||
|
||||
"pmcWaveDistribution": 0.8,
|
||||
"playerOpenZones": false,
|
||||
"pmcOpenZones": true,
|
||||
"allOpenZones": false,
|
||||
|
||||
"pmcWaveDistribution": 0.7,
|
||||
"pmcWaveQuantity": 1,
|
||||
|
||||
"zombiesEnabled": false,
|
||||
|
@ -18,7 +22,7 @@
|
|||
"zombieHealth": 1,
|
||||
|
||||
"maxBotCap": 25,
|
||||
"maxBotPerZone": 7,
|
||||
"maxBotPerZone": 5,
|
||||
|
||||
"moreScavGroups": false,
|
||||
"morePmcGroups": false,
|
||||
|
|
|
@ -5,14 +5,10 @@
|
|||
"scavWaveCount": 21,
|
||||
"zombieWaveCount": 9,
|
||||
"scavHotZones": [
|
||||
"ZoneDormitory",
|
||||
"ZoneCrossRoad",
|
||||
"ZoneGasStation"
|
||||
"ZoneDormitory"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneDormitory",
|
||||
"ZoneGasStation",
|
||||
"ZoneCustoms"
|
||||
"ZoneDormitory"
|
||||
]
|
||||
},
|
||||
"factoryDay": {
|
||||
|
@ -39,11 +35,6 @@
|
|||
"scavHotZones": [
|
||||
"ZoneCenterBot",
|
||||
"ZoneCenter"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneIDEA",
|
||||
"ZoneOLI",
|
||||
"ZoneCenter"
|
||||
]
|
||||
},
|
||||
"laboratory": {
|
||||
|
@ -59,12 +50,7 @@
|
|||
"zombieWaveCount": 10,
|
||||
"scavHotZones": [
|
||||
"Zone_LongRoad",
|
||||
"Zone_Village"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"Zone_DestroyedHouse",
|
||||
"Zone_Chalet",
|
||||
"Zone_Village"
|
||||
"Zone_LongRoad"
|
||||
]
|
||||
},
|
||||
"rezervbase": {
|
||||
|
@ -73,13 +59,10 @@
|
|||
"scavWaveCount": 24,
|
||||
"zombieWaveCount": 9,
|
||||
"scavHotZones": [
|
||||
"ZoneRailStrorage",
|
||||
"ZoneBunkerStorage",
|
||||
"ZoneBarrack"
|
||||
"ZoneRailStrorage"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneBarrack",
|
||||
"ZoneBunkerStorage"
|
||||
"ZoneBarrack"
|
||||
]
|
||||
},
|
||||
"shoreline": {
|
||||
|
@ -88,34 +71,17 @@
|
|||
"scavWaveCount": 32,
|
||||
"zombieWaveCount": 12,
|
||||
"scavHotZones": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneGasStation",
|
||||
"ZonePowerStation",
|
||||
"ZoneBusStation",
|
||||
"ZoneStartVillage"
|
||||
"ZoneSanatorium1"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneSanatorium2",
|
||||
"ZoneGasStation",
|
||||
"ZonePowerStation"
|
||||
"ZoneSanatorium2"
|
||||
]
|
||||
},
|
||||
"tarkovstreets": {
|
||||
"spawnMinDistance": 40,
|
||||
"pmcWaveCount": 16,
|
||||
"scavWaveCount": 28,
|
||||
"zombieWaveCount": 13,
|
||||
"scavHotZones": [
|
||||
"ZoneHotel_2",
|
||||
"ZoneHotel_1",
|
||||
"ZoneConstruction",
|
||||
"ZoneCarShowroom"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneSanatorium2",
|
||||
"ZoneCinema",
|
||||
"ZoneConcordiaParking"
|
||||
]
|
||||
"zombieWaveCount": 13
|
||||
},
|
||||
"woods": {
|
||||
"spawnMinDistance": 40,
|
||||
|
@ -123,15 +89,10 @@
|
|||
"scavWaveCount": 28,
|
||||
"zombieWaveCount": 10,
|
||||
"scavHotZones": [
|
||||
"ZoneWoodCutter",
|
||||
"ZoneClearVill",
|
||||
"ZoneScavBase2",
|
||||
"ZoneRedHouse"
|
||||
"ZoneWoodCutter"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneWoodCutter",
|
||||
"ZoneBigRocks",
|
||||
"ZoneHighRocks"
|
||||
"ZoneWoodCutter"
|
||||
]
|
||||
},
|
||||
"gzLow": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "MOAR",
|
||||
"version": "2.6.1",
|
||||
"version": "2.6.7",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "DewardianDev",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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 { DynamicRouterModService } from "@spt/services/mod/dynamicRouter/DynamicRouterModService";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
import { kebabToTitle } from "../utils";
|
||||
import PresetWeightingsConfig from "../../config/PresetWeightings.json";
|
||||
|
@ -11,9 +11,9 @@ export const setupRoutes = (container: DependencyContainer) => {
|
|||
"StaticRouterModService"
|
||||
);
|
||||
|
||||
const dynamicRouterModService = container.resolve<DynamicRouterModService>(
|
||||
"DynamicRouterModService"
|
||||
);
|
||||
// const dynamicRouterModService = container.resolve<DynamicRouterModService>(
|
||||
// "DynamicRouterModService"
|
||||
// );
|
||||
|
||||
// Make buildwaves run on game end
|
||||
staticRouterModService.registerStaticRouter(
|
||||
|
|
|
@ -7,7 +7,11 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
|||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
import { cloneDeep, getRandomPresetOrCurrentlySelectedPreset } from "../utils";
|
||||
import {
|
||||
cloneDeep,
|
||||
getRandomPresetOrCurrentlySelectedPreset,
|
||||
saveToFile,
|
||||
} from "../utils";
|
||||
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig.d";
|
||||
import { originalMapList } from "./constants";
|
||||
import { buildBossWaves } from "./buildBossWaves";
|
||||
|
@ -63,7 +67,7 @@ export const buildWaves = (container: DependencyContainer) => {
|
|||
}
|
||||
});
|
||||
|
||||
config.debug &&
|
||||
// config.debug &&
|
||||
console.log(
|
||||
globalValues.forcedPreset === "custom"
|
||||
? "custom"
|
||||
|
@ -127,7 +131,7 @@ export const buildWaves = (container: DependencyContainer) => {
|
|||
rezervbase: { pmcbot: { min: 0, max: 0 } },
|
||||
};
|
||||
|
||||
updateSpawnLocations(locationList);
|
||||
updateSpawnLocations(locationList, config);
|
||||
|
||||
setEscapeTimeOverrides(locationList, _mapConfig, Logger, config);
|
||||
|
||||
|
|
|
@ -139,36 +139,20 @@ export function buildBossWaves(
|
|||
|
||||
for (let key = 0; key < locationList.length; key++) {
|
||||
//Gather bosses to avoid duplicating.
|
||||
let bossLocations = "";
|
||||
|
||||
const duplicateBosses = [
|
||||
...locationList[key].base.BossLocationSpawn.filter(
|
||||
({ BossName, BossZone }) => {
|
||||
bossLocations += BossZone + ",";
|
||||
return bossList.includes(BossName);
|
||||
}
|
||||
({ BossName, BossZone }) => bossList.includes(BossName)
|
||||
).map(({ BossName }) => BossName),
|
||||
"bossKnight", // So knight doesn't invade
|
||||
];
|
||||
|
||||
const uniqueBossZones = bossOpenZones
|
||||
? ""
|
||||
: [
|
||||
...new Set(
|
||||
bossLocations
|
||||
.split(",")
|
||||
.filter(
|
||||
(zone) => !!zone && !zone.toLowerCase().includes("snipe")
|
||||
)
|
||||
),
|
||||
].join(",");
|
||||
|
||||
//Build bosses to add
|
||||
const bossesToAdd = shuffle<IBossLocationSpawn[]>(Object.values(bosses))
|
||||
.filter(({ BossName }) => !duplicateBosses.includes(BossName))
|
||||
.map((boss, j) => ({
|
||||
...boss,
|
||||
BossZone: uniqueBossZones,
|
||||
BossZone: "",
|
||||
BossEscortAmount:
|
||||
boss.BossEscortAmount === "0" ? boss.BossEscortAmount : "1",
|
||||
...(gradualBossInvasion ? { Time: j * 20 + 1 } : {}),
|
||||
|
@ -268,7 +252,22 @@ export function buildBossWaves(
|
|||
configLocations[index]
|
||||
}: ${bossesToAdd.map(({ BossName }) => BossName)}`
|
||||
);
|
||||
// console.log(locationList[index].base.BossLocationSpawn.length);
|
||||
|
||||
// Apply the percentages on all bosses, cull those that won't spawn, make all bosses 100 chance that remain.
|
||||
locationList[index].base.BossLocationSpawn = locationList[
|
||||
index
|
||||
].base.BossLocationSpawn.filter(({ BossChance, BossName }, bossIndex) => {
|
||||
if (BossChance < 100 && BossChance / 100 < Math.random()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map((boss) => ({ ...boss, ...{ BossChance: 100 } }));
|
||||
|
||||
// if (mapName === "customs")
|
||||
// console.log(mapName, locationList[index].base.BossLocationSpawn);
|
||||
});
|
||||
|
||||
if (hasChangedBossSpawns) {
|
||||
console.log(
|
||||
`[MOAR]: --- Adjusting default boss spawn rates complete --- \n`
|
||||
|
|
|
@ -30,25 +30,21 @@ export default function buildPmcs(
|
|||
.filter(
|
||||
({ Categories, BotZoneName }) =>
|
||||
!!BotZoneName &&
|
||||
(Categories.includes("Player") ||
|
||||
(map === "laboratory" &&
|
||||
!BotZoneName.includes("BotZoneGate"))) &&
|
||||
!BotZoneName.includes("snipe")
|
||||
!BotZoneName.includes("snipe") &&
|
||||
(Categories.includes("Player") || Categories.includes("All")) &&
|
||||
!BotZoneName.includes("BotZoneGate")
|
||||
)
|
||||
.map(({ BotZoneName, ...rest }) => {
|
||||
return BotZoneName;
|
||||
})
|
||||
),
|
||||
...pmcHotZones,
|
||||
]);
|
||||
|
||||
// Make labs have only named zones
|
||||
if (map === "laboratory") {
|
||||
pmcZones = new Array(10).fill(pmcZones).flat(1);
|
||||
// console.log(pmcZones);
|
||||
}
|
||||
|
||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||
|
||||
const { pmcWaveCount } = mapConfig[map];
|
||||
|
||||
const escapeTimeLimitRatio = Math.round(
|
||||
|
@ -58,13 +54,12 @@ export default function buildPmcs(
|
|||
const totalWaves = Math.round(
|
||||
pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio
|
||||
);
|
||||
// console.log(pmcZones.length, totalWaves);
|
||||
|
||||
const numberOfZoneless = totalWaves - pmcZones.length;
|
||||
if (numberOfZoneless > 0) {
|
||||
const addEmpty = new Array(numberOfZoneless).fill("");
|
||||
pmcZones = shuffle<string[]>([...pmcZones, ...addEmpty]);
|
||||
}
|
||||
// if (map === "laboratory") console.log(numberOfZoneless, pmcZones);
|
||||
|
||||
if (config.debug) {
|
||||
console.log(`${map} PMC count ${totalWaves} \n`);
|
||||
|
@ -75,10 +70,16 @@ export default function buildPmcs(
|
|||
);
|
||||
}
|
||||
|
||||
const waves = buildPmcWaves(pmcWaveCount, timeLimit, config, pmcZones);
|
||||
// if (map === "laboratory")
|
||||
// console.log(waves.map(({ BossZone }) => BossZone));
|
||||
// apply our new waves
|
||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||
|
||||
const waves = buildPmcWaves(
|
||||
totalWaves,
|
||||
timeLimit,
|
||||
config,
|
||||
pmcZones,
|
||||
pmcHotZones
|
||||
);
|
||||
|
||||
locationList[index].base.BossLocationSpawn = [
|
||||
...waves,
|
||||
...locationList[index].base.BossLocationSpawn,
|
||||
|
|
|
@ -89,19 +89,16 @@ export default function buildScavMarksmanWaves(
|
|||
const sniperLocations = new Set(
|
||||
[...locationList[index].base.SpawnPointParams]
|
||||
.filter(
|
||||
({ Categories, Sides, BotZoneName }) =>
|
||||
!!BotZoneName &&
|
||||
Sides.includes("Savage") &&
|
||||
!Categories.includes("Boss")
|
||||
({ Categories, DelayToCanSpawnSec, BotZoneName, Sides }) =>
|
||||
!Categories.includes("Boss") &&
|
||||
Sides[0] === "Savage" &&
|
||||
(BotZoneName?.toLowerCase().includes("snipe") ||
|
||||
DelayToCanSpawnSec > 40)
|
||||
)
|
||||
.filter(
|
||||
({ BotZoneName, DelayToCanSpawnSec }) =>
|
||||
BotZoneName?.toLowerCase().includes("snipe") ||
|
||||
DelayToCanSpawnSec > 300
|
||||
)
|
||||
.map(({ BotZoneName }) => BotZoneName)
|
||||
.map(({ BotZoneName }) => BotZoneName || "")
|
||||
);
|
||||
|
||||
|
||||
if (sniperLocations.size) {
|
||||
locationList[index].base.MinMaxBots = [
|
||||
{
|
||||
|
@ -112,32 +109,21 @@ export default function buildScavMarksmanWaves(
|
|||
];
|
||||
}
|
||||
|
||||
const scavZones = shuffle<string[]>([
|
||||
let scavZones = shuffle<string[]>([
|
||||
...new Set(
|
||||
[...locationList[index].base.SpawnPointParams]
|
||||
.filter(
|
||||
({ Categories, Sides, BotZoneName }) =>
|
||||
!!BotZoneName &&
|
||||
Sides.includes("Savage") &&
|
||||
!Categories.includes("Boss")
|
||||
Categories.includes("Bot") &&
|
||||
(Sides.includes("Savage") || Sides.includes("All"))
|
||||
)
|
||||
.map(({ BotZoneName }) => BotZoneName)
|
||||
.filter((name) => !sniperLocations.has(name))
|
||||
),
|
||||
]);
|
||||
|
||||
// Reduced Zone Delay
|
||||
locationList[index].base.SpawnPointParams = locationList[
|
||||
index
|
||||
].base.SpawnPointParams.map((spawn) => ({
|
||||
...spawn,
|
||||
DelayToCanSpawnSec:
|
||||
spawn.DelayToCanSpawnSec > 20
|
||||
? Math.round(spawn.DelayToCanSpawnSec / 10)
|
||||
: spawn.DelayToCanSpawnSec,
|
||||
}));
|
||||
|
||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||
const { scavWaveCount } = mapConfig[map];
|
||||
|
||||
const escapeTimeLimitRatio = Math.round(
|
||||
|
@ -149,12 +135,19 @@ export default function buildScavMarksmanWaves(
|
|||
scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio
|
||||
);
|
||||
|
||||
const numberOfZoneless = scavTotalWaveCount - scavZones.length;
|
||||
// console.log(numberOfZoneless);
|
||||
if (numberOfZoneless > 0) {
|
||||
const addEmpty = new Array(numberOfZoneless).fill("");
|
||||
scavZones = shuffle<string[]>([...scavZones, ...addEmpty]);
|
||||
}
|
||||
// console.log(scavZones);
|
||||
config.debug &&
|
||||
escapeTimeLimitRatio !== 1 &&
|
||||
console.log(
|
||||
`${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment`
|
||||
);
|
||||
|
||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||
let snipers = waveBuilder(
|
||||
sniperLocations.size,
|
||||
Math.round(timeLimit / 4),
|
||||
|
@ -166,14 +159,13 @@ export default function buildScavMarksmanWaves(
|
|||
[],
|
||||
shuffle([...sniperLocations]),
|
||||
80,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
if (snipersHaveFriends)
|
||||
snipers = snipers.map((wave) => ({
|
||||
...wave,
|
||||
slots_min: 0,
|
||||
...(snipersHaveFriends && wave.slots_max < 2
|
||||
? { slots_min: 1, slots_max: 2 }
|
||||
: {}),
|
||||
|
|
|
@ -1,37 +1,138 @@
|
|||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import { configLocations } from "./constants";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import _config from "../../config/config.json";
|
||||
|
||||
export default function updateSpawnLocations(locationList: ILocation[]) {
|
||||
export default function updateSpawnLocations(
|
||||
locationList: ILocation[],
|
||||
config: typeof _config
|
||||
) {
|
||||
for (let index = 0; index < locationList.length; index++) {
|
||||
const map = configLocations[index];
|
||||
|
||||
// console.log(map);
|
||||
const limit = mapConfig[map].spawnMinDistance;
|
||||
|
||||
const InfiltrationList = [
|
||||
...new Set(
|
||||
locationList[index].base.SpawnPointParams.filter(
|
||||
({ Infiltration }) => Infiltration
|
||||
).map(({ Infiltration }) => Infiltration)
|
||||
),
|
||||
];
|
||||
|
||||
// console.log(map, InfiltrationList);
|
||||
const getRandomInfil = (): string =>
|
||||
InfiltrationList[Math.floor(Math.random() * InfiltrationList.length)];
|
||||
// console.log(InfiltrationList);
|
||||
// console.log("\n" + map);
|
||||
locationList[index].base.SpawnPointParams.forEach(
|
||||
(
|
||||
{ ColliderParams, BotZoneName, DelayToCanSpawnSec, Categories, Sides },
|
||||
{
|
||||
ColliderParams,
|
||||
BotZoneName,
|
||||
DelayToCanSpawnSec,
|
||||
Categories,
|
||||
Sides,
|
||||
Infiltration,
|
||||
},
|
||||
innerIndex
|
||||
) => {
|
||||
if (
|
||||
ColliderParams?._props?.Radius !== undefined &&
|
||||
ColliderParams?._props?.Radius < limit &&
|
||||
!Categories.includes("Boss") &&
|
||||
!BotZoneName?.toLowerCase().includes("snipe") &&
|
||||
DelayToCanSpawnSec < 300
|
||||
DelayToCanSpawnSec < 41
|
||||
) {
|
||||
// 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(
|
||||
// "----",
|
||||
// ColliderParams._props.Radius,
|
||||
// "=>",
|
||||
// limit,
|
||||
// BotZoneName
|
||||
// BotZoneName || "none",
|
||||
// locationList[index].base.SpawnPointParams[innerIndex].Categories,
|
||||
// locationList[index].base.SpawnPointParams[innerIndex].Sides
|
||||
// );
|
||||
}
|
||||
if (!Infiltration) {
|
||||
if (
|
||||
!config.allOpenZones &&
|
||||
config.pmcOpenZones &&
|
||||
Categories.includes("Bot") &&
|
||||
Sides[0] === "Savage"
|
||||
) {
|
||||
// if (BotZoneName === "Zone_LongRoad") console.log("yes");
|
||||
locationList[index].base.SpawnPointParams[innerIndex].Categories =
|
||||
["Player", "Bot"];
|
||||
|
||||
locationList[index].base.SpawnPointParams[
|
||||
innerIndex
|
||||
].Infiltration = getRandomInfil();
|
||||
}
|
||||
|
||||
if (config.allOpenZones) {
|
||||
locationList[index].base.SpawnPointParams[innerIndex].Categories =
|
||||
[
|
||||
"Bot",
|
||||
"Player",
|
||||
"Coop",
|
||||
innerIndex % 2 === 0 ? "Group" : "Opposite",
|
||||
];
|
||||
|
||||
locationList[index].base.SpawnPointParams[
|
||||
innerIndex
|
||||
].Infiltration = getRandomInfil();
|
||||
// console.log(
|
||||
// locationList[index].base.SpawnPointParams[innerIndex].Infiltration
|
||||
// );
|
||||
locationList[index].base.SpawnPointParams[innerIndex].Sides = [
|
||||
"Pmc",
|
||||
"Savage",
|
||||
"All",
|
||||
];
|
||||
}
|
||||
|
||||
if (config.bossOpenZones && Categories.includes("Bot")) {
|
||||
locationList[index].base.SpawnPointParams[
|
||||
innerIndex
|
||||
].Categories.push("Boss");
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
ColliderParams?._props?.Radius !== undefined &&
|
||||
ColliderParams?._props?.Radius < limit
|
||||
) {
|
||||
locationList[index].base.SpawnPointParams[
|
||||
innerIndex
|
||||
].ColliderParams._props.Radius = limit;
|
||||
}
|
||||
} else {
|
||||
if (!Categories.includes("Boss") && DelayToCanSpawnSec > 40) {
|
||||
locationList[index].base.SpawnPointParams[
|
||||
innerIndex
|
||||
].DelayToCanSpawnSec = Math.round(
|
||||
DelayToCanSpawnSec * Math.random() * Math.random() * 0.5
|
||||
);
|
||||
|
||||
// console.log(
|
||||
// BotZoneName,
|
||||
// DelayToCanSpawnSec,
|
||||
// locationList[index].base.SpawnPointParams[innerIndex]
|
||||
// .DelayToCanSpawnSec
|
||||
// );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export const waveBuilder = (
|
|||
);
|
||||
|
||||
const min = !offset && waves.length < 1 ? 0 : timeStart;
|
||||
const max = !offset && waves.length < 1 ? 0 : timeStart + 10;
|
||||
const max = !offset && waves.length < 1 ? 0 : timeStart + 60;
|
||||
|
||||
if (waves.length >= 1 || offset) timeStart = timeStart + stage;
|
||||
const BotPreset = getDifficulty(difficulty);
|
||||
|
@ -55,8 +55,9 @@ export const waveBuilder = (
|
|||
);
|
||||
|
||||
if (slotMax < 1) slotMax = 1;
|
||||
const slotMin = (Math.round(Math.random() * slotMax) || 1) - 1;
|
||||
let slotMin = (Math.round(Math.random() * slotMax) || 1) - 1;
|
||||
|
||||
if (wildSpawnType === "marksman" && slotMin < 1) slotMin = 1;
|
||||
waves.push({
|
||||
BotPreset,
|
||||
BotSide: getBotSide(wildSpawnType),
|
||||
|
@ -189,11 +190,26 @@ export const getRandomZombieType = () =>
|
|||
zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())];
|
||||
|
||||
export const buildPmcWaves = (
|
||||
totalWaves: number,
|
||||
pmcTotal: number,
|
||||
escapeTimeLimit: number,
|
||||
config: typeof _config,
|
||||
bossZones: string[]
|
||||
bossZones: string[],
|
||||
hotZones: string[]
|
||||
): IBossLocationSpawn[] => {
|
||||
// console.log(pmcTotal)
|
||||
if (!pmcTotal) return [];
|
||||
const halfIndex = Math.round(bossZones.length * 0.75); //Put hotzones in the 2 - 4 spawns
|
||||
// console.log(bossZones.length);
|
||||
bossZones = [
|
||||
...bossZones.slice(0, halfIndex),
|
||||
...hotZones,
|
||||
...bossZones.slice(halfIndex),
|
||||
];
|
||||
|
||||
// console.log(bossZones.length, hotZones.length);
|
||||
// console.log(bossZones);
|
||||
pmcTotal = pmcTotal + hotZones.length;
|
||||
|
||||
let {
|
||||
pmcMaxGroupSize,
|
||||
pmcDifficulty,
|
||||
|
@ -202,14 +218,12 @@ export const buildPmcWaves = (
|
|||
pmcWaveDistribution,
|
||||
} = config;
|
||||
|
||||
const averageTime = escapeTimeLimit / totalWaves;
|
||||
const firstHalf = Math.round(averageTime * (1 - pmcWaveDistribution));
|
||||
const secondHalf = Math.round(averageTime * (1 + pmcWaveDistribution));
|
||||
let timeStart = -1;
|
||||
const waves: IBossLocationSpawn[] = [];
|
||||
let maxSlotsReached = totalWaves;
|
||||
const averageTime = (escapeTimeLimit * 0.8) / pmcTotal;
|
||||
|
||||
while (totalWaves > 0) {
|
||||
const waves: IBossLocationSpawn[] = [];
|
||||
let maxSlotsReached = pmcTotal;
|
||||
|
||||
while (pmcTotal > 0) {
|
||||
let bossEscortAmount = Math.round(
|
||||
(morePmcGroups ? 1 : Math.random()) *
|
||||
Math.random() *
|
||||
|
@ -217,20 +231,24 @@ export const buildPmcWaves = (
|
|||
);
|
||||
|
||||
if (bossEscortAmount < 0) bossEscortAmount = 0;
|
||||
const accelerate = totalWaves > 5 && waves.length < totalWaves / 3;
|
||||
const stage = startingPmcs
|
||||
? 10
|
||||
|
||||
// const totalCountThisWave = bossEscortAmount + 1;
|
||||
const totalCountThusFar = pmcTotal - maxSlotsReached;
|
||||
|
||||
const timeToUse =
|
||||
totalCountThusFar < pmcTotal * pmcWaveDistribution
|
||||
? Math.round(
|
||||
averageTime * (1 - pmcWaveDistribution) * totalCountThusFar
|
||||
)
|
||||
: Math.round(
|
||||
waves.length < Math.round(totalWaves * 0.5)
|
||||
? accelerate
|
||||
? firstHalf / 3
|
||||
: firstHalf
|
||||
: secondHalf
|
||||
escapeTimeLimit * (1 - pmcWaveDistribution) +
|
||||
(1 - pmcWaveDistribution) * totalCountThusFar * averageTime
|
||||
);
|
||||
|
||||
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 BossDifficult = getDifficulty(pmcDifficulty);
|
||||
|
@ -260,7 +278,10 @@ export const buildPmcWaves = (
|
|||
maxSlotsReached -= 1 + bossEscortAmount;
|
||||
if (maxSlotsReached <= 0) break;
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// escapeTimeLimit,
|
||||
// waves.map(({ Time }) => Time)
|
||||
// );
|
||||
return waves;
|
||||
};
|
||||
|
||||
|
@ -270,6 +291,7 @@ export const buildZombie = (
|
|||
waveDistribution: number,
|
||||
BossChance: number = 100
|
||||
): IBossLocationSpawn[] => {
|
||||
if (!totalWaves) return [];
|
||||
const averageTime = (escapeTimeLimit * 60) / totalWaves;
|
||||
const firstHalf = Math.round(averageTime * (1 - waveDistribution));
|
||||
const secondHalf = Math.round(averageTime * (1 + waveDistribution));
|
||||
|
|
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
|
||||
-Version 1.28.6_separator
|
||||
-SWAG + DONUTS
|
||||
|
|
Loading…
Reference in New Issue