Downgraded MOAR & Updated ModOrganizer.ini
This commit is contained in:
parent
3e8f90cd1a
commit
b3cab2e572
|
@ -1,5 +1,11 @@
|
||||||
[General]
|
[General]
|
||||||
|
gameName=SPT
|
||||||
|
gamePath=@ByteArray(replace_me)
|
||||||
selected_profile=@ByteArray(Multiplayer)
|
selected_profile=@ByteArray(Multiplayer)
|
||||||
|
version=2.5.2
|
||||||
|
first_start=false
|
||||||
|
previousSeparatorColor=@Variant(\0\0\0\x43\x1\xff\xff\x44\x44\x36\x36))\0\0)
|
||||||
|
backup_install=true
|
||||||
|
|
||||||
[Settings]
|
[Settings]
|
||||||
style=Dark Bronze.qss
|
style=Dark Bronze.qss
|
||||||
|
@ -7,10 +13,10 @@ style=Dark Bronze.qss
|
||||||
[customExecutables]
|
[customExecutables]
|
||||||
size=1
|
size=1
|
||||||
1\arguments=
|
1\arguments=
|
||||||
1\binary=D:/Games/Singleplayer Tarkov/SPT.Launcher.exe
|
1\binary=replace_me/SPT.Launcher.exe
|
||||||
1\hide=false
|
1\hide=false
|
||||||
1\ownicon=false
|
1\ownicon=false
|
||||||
1\steamAppID=
|
1\steamAppID=
|
||||||
1\title=Multiplayer
|
1\title=Multiplayer
|
||||||
1\toolbar=false
|
1\toolbar=false
|
||||||
1\workingDirectory=D:/Games/Singleplayer Tarkov
|
1\workingDirectory=replace_me
|
||||||
|
|
|
@ -1,23 +1,8 @@
|
||||||
## Settings file was created by plugin MOAR v2.6.2
|
## Settings file was created by plugin MOAR v2.5.6
|
||||||
## Plugin GUID: MOAR.settings
|
## Plugin GUID: MOAR.settings
|
||||||
|
|
||||||
[1. Main Settings]
|
[1. Main Settings]
|
||||||
|
|
||||||
## All Bots/Players (excluding bosses) can spawn anywhere (overrides PMC/Player openzone options)
|
|
||||||
# Setting type: Boolean
|
|
||||||
# Default value: false
|
|
||||||
PMC/Scav/Player OpenZones On/Off = false
|
|
||||||
|
|
||||||
## Adds a large number of zones (including all scav zones) to pmc bots spawn pool
|
|
||||||
# Setting type: Boolean
|
|
||||||
# Default value: true
|
|
||||||
PMC OpenZones On/Off = true
|
|
||||||
|
|
||||||
## Adds a large number of zones to the Player's (you) spawn pool
|
|
||||||
# Setting type: Boolean
|
|
||||||
# Default value: true
|
|
||||||
Player OpenZones On/Off = true
|
|
||||||
|
|
||||||
## Causes all PMCs to spawn in the first few minutes of the game (performance intensive)
|
## Causes all PMCs to spawn in the first few minutes of the game (performance intensive)
|
||||||
# Setting type: Boolean
|
# Setting type: Boolean
|
||||||
# Default value: false
|
# Default value: false
|
||||||
|
@ -25,15 +10,15 @@ Starting PMCS On/Off = false
|
||||||
|
|
||||||
## Works with SAIN or SPT to decide the bot's 'difficulty' preset (EASY: 0, easy-MEDIUM: 0.4, easy-MEDIUM-hard: 0.6, medium-hard: 0.85, HARD-impossible: 1, etc..)
|
## Works with SAIN or SPT to decide the bot's 'difficulty' preset (EASY: 0, easy-MEDIUM: 0.4, easy-MEDIUM-hard: 0.6, medium-hard: 0.85, HARD-impossible: 1, etc..)
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
# Default value: 0.6
|
# Default value: 0.3
|
||||||
# Acceptable value range: From 0 to 1.5
|
# Acceptable value range: From 0 to 1.5
|
||||||
Pmc difficulty = 0.6
|
Pmc difficulty = 0.3
|
||||||
|
|
||||||
## Works with SAIN or SPT to decide the bot's 'difficulty' preset (EASY: 0, easy-MEDIUM: 0.4, easy-MEDIUM-hard: 0.6, medium-hard: 0.85, HARD-impossible: 1, etc..)
|
## Works with SAIN or SPT to decide the bot's 'difficulty' preset (EASY: 0, easy-MEDIUM: 0.4, easy-MEDIUM-hard: 0.6, medium-hard: 0.85, HARD-impossible: 1, etc..)
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
# Default value: 0.4
|
# Default value: 0.3
|
||||||
# Acceptable value range: From 0 to 1.5
|
# Acceptable value range: From 0 to 1.5
|
||||||
Scav difficulty = 0.4
|
Scav difficulty = 0.3
|
||||||
|
|
||||||
## Preset to be used, random pulls a random weighted preset from the PresetWeights.json every time a raid ends
|
## Preset to be used, random pulls a random weighted preset from the PresetWeights.json every time a raid ends
|
||||||
# Setting type: String
|
# Setting type: String
|
||||||
|
@ -51,6 +36,12 @@ Preset Announce On/Off = true
|
||||||
# Default value:
|
# Default value:
|
||||||
FIKA DETECTED: ALWAYS PRESS THIS FIRST BEFORE MAKING CHANGES!! =
|
FIKA DETECTED: ALWAYS PRESS THIS FIRST BEFORE MAKING CHANGES!! =
|
||||||
|
|
||||||
|
PMC/Scav/Player OpenZones On/Off = false
|
||||||
|
|
||||||
|
PMC OpenZones On/Off = true
|
||||||
|
|
||||||
|
Player OpenZones On/Off = true
|
||||||
|
|
||||||
[2. Custom game Settings]
|
[2. Custom game Settings]
|
||||||
|
|
||||||
## Pushes settings to server
|
## Pushes settings to server
|
||||||
|
@ -146,9 +137,9 @@ moreScavGroups On/Off = false
|
||||||
|
|
||||||
## Max bots permitted in any particular spawn zone, recommend not to touch this.
|
## Max bots permitted in any particular spawn zone, recommend not to touch this.
|
||||||
# Setting type: Int32
|
# Setting type: Int32
|
||||||
# Default value: 5
|
# Default value: 7
|
||||||
# Acceptable value range: From 0 to 15
|
# Acceptable value range: From 0 to 15
|
||||||
MaxBotPerZone = 5
|
MaxBotPerZone = 7
|
||||||
|
|
||||||
## Max bots alive at one time
|
## Max bots alive at one time
|
||||||
# Setting type: Int32
|
# Setting type: Int32
|
||||||
|
@ -187,9 +178,9 @@ ScavWaveDistribution = 0.5
|
||||||
|
|
||||||
## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid
|
## Determines the weighting of spawns at the beginning (1) spread evenly throughout (0.5) or at the end(0) of the raid
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
# Default value: 0.7
|
# Default value: 0.8
|
||||||
# Acceptable value range: From 0 to 1
|
# Acceptable value range: From 0 to 1
|
||||||
PmcWaveDistribution = 0.7
|
PmcWaveDistribution = 0.8
|
||||||
|
|
||||||
## Multiplies wave counts seen in the server's mapConfig.json by this number
|
## Multiplies wave counts seen in the server's mapConfig.json by this number
|
||||||
# Setting type: Double
|
# Setting type: Double
|
||||||
|
|
|
@ -259,7 +259,7 @@ Centering On Player Zoom Level = 0.15
|
||||||
# Setting type: Single
|
# Setting type: Single
|
||||||
# Default value: 0
|
# Default value: 0
|
||||||
# Acceptable value range: From 0 to 15
|
# Acceptable value range: From 0 to 15
|
||||||
Main map zoom = 3.833475
|
Main map zoom = 15
|
||||||
|
|
||||||
## The keyboard shortcut to peek at the map
|
## The keyboard shortcut to peek at the map
|
||||||
# Setting type: KeyboardShortcut
|
# Setting type: KeyboardShortcut
|
||||||
|
|
Binary file not shown.
|
@ -1,11 +1,11 @@
|
||||||
[General]
|
[General]
|
||||||
gameName=spt
|
gameName=spt
|
||||||
modid=0
|
modid=0
|
||||||
version=d2025.1.2.0
|
version=d2024.12.31.0
|
||||||
newestVersion=
|
newestVersion=
|
||||||
category="1,"
|
category="1,"
|
||||||
nexusFileStatus=1
|
nexusFileStatus=1
|
||||||
installationFile=DewardianDev-MOAR-2.6.5.zip
|
installationFile=DewardianDev-MOAR-2.6.1.zip
|
||||||
repository=Nexus
|
repository=Nexus
|
||||||
ignoredVersion=
|
ignoredVersion=
|
||||||
comments=
|
comments=
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
{
|
{
|
||||||
"enableBotSpawning": true,
|
"enableBotSpawning": true,
|
||||||
|
|
||||||
"pmcDifficulty": 0.6,
|
"pmcDifficulty": 0.3,
|
||||||
"scavDifficulty": 0.4,
|
"scavDifficulty": 0.3,
|
||||||
|
|
||||||
"scavWaveDistribution": 0.5,
|
"scavWaveDistribution": 0.5,
|
||||||
"scavWaveQuantity": 1,
|
"scavWaveQuantity": 1,
|
||||||
|
|
||||||
"startingPmcs": false,
|
"startingPmcs": false,
|
||||||
|
|
||||||
"playerOpenZones": true,
|
"pmcWaveDistribution": 0.8,
|
||||||
"pmcOpenZones": true,
|
|
||||||
"allOpenZones": false,
|
|
||||||
|
|
||||||
"pmcWaveDistribution": 0.7,
|
|
||||||
"pmcWaveQuantity": 1,
|
"pmcWaveQuantity": 1,
|
||||||
|
|
||||||
"zombiesEnabled": false,
|
"zombiesEnabled": false,
|
||||||
|
@ -22,7 +18,7 @@
|
||||||
"zombieHealth": 1,
|
"zombieHealth": 1,
|
||||||
|
|
||||||
"maxBotCap": 25,
|
"maxBotCap": 25,
|
||||||
"maxBotPerZone": 5,
|
"maxBotPerZone": 7,
|
||||||
|
|
||||||
"moreScavGroups": false,
|
"moreScavGroups": false,
|
||||||
"morePmcGroups": false,
|
"morePmcGroups": false,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "MOAR",
|
"name": "MOAR",
|
||||||
"version": "2.6.5",
|
"version": "2.6.1",
|
||||||
"main": "src/mod.js",
|
"main": "src/mod.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "DewardianDev",
|
"author": "DewardianDev",
|
||||||
|
|
|
@ -7,11 +7,7 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
import { DependencyContainer } from "tsyringe";
|
import { DependencyContainer } from "tsyringe";
|
||||||
import { globalValues } from "../GlobalValues";
|
import { globalValues } from "../GlobalValues";
|
||||||
import {
|
import { cloneDeep, getRandomPresetOrCurrentlySelectedPreset } from "../utils";
|
||||||
cloneDeep,
|
|
||||||
getRandomPresetOrCurrentlySelectedPreset,
|
|
||||||
saveToFile,
|
|
||||||
} from "../utils";
|
|
||||||
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig.d";
|
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig.d";
|
||||||
import { originalMapList } from "./constants";
|
import { originalMapList } from "./constants";
|
||||||
import { buildBossWaves } from "./buildBossWaves";
|
import { buildBossWaves } from "./buildBossWaves";
|
||||||
|
@ -131,7 +127,7 @@ export const buildWaves = (container: DependencyContainer) => {
|
||||||
rezervbase: { pmcbot: { min: 0, max: 0 } },
|
rezervbase: { pmcbot: { min: 0, max: 0 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
updateSpawnLocations(locationList, config);
|
updateSpawnLocations(locationList);
|
||||||
|
|
||||||
setEscapeTimeOverrides(locationList, _mapConfig, Logger, config);
|
setEscapeTimeOverrides(locationList, _mapConfig, Logger, config);
|
||||||
|
|
||||||
|
|
|
@ -30,21 +30,25 @@ export default function buildPmcs(
|
||||||
.filter(
|
.filter(
|
||||||
({ Categories, BotZoneName }) =>
|
({ Categories, BotZoneName }) =>
|
||||||
!!BotZoneName &&
|
!!BotZoneName &&
|
||||||
!BotZoneName.includes("snipe") &&
|
(Categories.includes("Player") ||
|
||||||
(Categories.includes("Player") || Categories.includes("All")) &&
|
(map === "laboratory" &&
|
||||||
!BotZoneName.includes("BotZoneGate")
|
!BotZoneName.includes("BotZoneGate"))) &&
|
||||||
|
!BotZoneName.includes("snipe")
|
||||||
)
|
)
|
||||||
.map(({ BotZoneName, ...rest }) => {
|
.map(({ BotZoneName, ...rest }) => {
|
||||||
return BotZoneName;
|
return BotZoneName;
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
...pmcHotZones,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Make labs have only named zones
|
// Make labs have only named zones
|
||||||
if (map === "laboratory") {
|
if (map === "laboratory") {
|
||||||
pmcZones = new Array(10).fill(pmcZones).flat(1);
|
pmcZones = new Array(10).fill(pmcZones).flat(1);
|
||||||
|
// console.log(pmcZones);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||||
|
|
||||||
const { pmcWaveCount } = mapConfig[map];
|
const { pmcWaveCount } = mapConfig[map];
|
||||||
|
|
||||||
const escapeTimeLimitRatio = Math.round(
|
const escapeTimeLimitRatio = Math.round(
|
||||||
|
@ -54,12 +58,13 @@ export default function buildPmcs(
|
||||||
const totalWaves = Math.round(
|
const totalWaves = Math.round(
|
||||||
pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio
|
pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio
|
||||||
);
|
);
|
||||||
|
// console.log(pmcZones.length, totalWaves);
|
||||||
const numberOfZoneless = totalWaves - pmcZones.length;
|
const numberOfZoneless = totalWaves - pmcZones.length;
|
||||||
if (numberOfZoneless > 0) {
|
if (numberOfZoneless > 0) {
|
||||||
const addEmpty = new Array(numberOfZoneless).fill("");
|
const addEmpty = new Array(numberOfZoneless).fill("");
|
||||||
pmcZones = shuffle<string[]>([...pmcZones, ...addEmpty]);
|
pmcZones = shuffle<string[]>([...pmcZones, ...addEmpty]);
|
||||||
}
|
}
|
||||||
|
// if (map === "laboratory") console.log(numberOfZoneless, pmcZones);
|
||||||
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log(`${map} PMC count ${totalWaves} \n`);
|
console.log(`${map} PMC count ${totalWaves} \n`);
|
||||||
|
@ -70,16 +75,10 @@ export default function buildPmcs(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
const waves = buildPmcWaves(pmcWaveCount, timeLimit, config, pmcZones);
|
||||||
|
// if (map === "laboratory")
|
||||||
const waves = buildPmcWaves(
|
// console.log(waves.map(({ BossZone }) => BossZone));
|
||||||
totalWaves,
|
// apply our new waves
|
||||||
timeLimit,
|
|
||||||
config,
|
|
||||||
pmcZones,
|
|
||||||
pmcHotZones
|
|
||||||
);
|
|
||||||
|
|
||||||
locationList[index].base.BossLocationSpawn = [
|
locationList[index].base.BossLocationSpawn = [
|
||||||
...waves,
|
...waves,
|
||||||
...locationList[index].base.BossLocationSpawn,
|
...locationList[index].base.BossLocationSpawn,
|
||||||
|
|
|
@ -89,16 +89,19 @@ export default function buildScavMarksmanWaves(
|
||||||
const sniperLocations = new Set(
|
const sniperLocations = new Set(
|
||||||
[...locationList[index].base.SpawnPointParams]
|
[...locationList[index].base.SpawnPointParams]
|
||||||
.filter(
|
.filter(
|
||||||
({ Categories, DelayToCanSpawnSec, BotZoneName, Sides }) =>
|
({ Categories, Sides, BotZoneName }) =>
|
||||||
!Categories.includes("Boss") &&
|
!!BotZoneName &&
|
||||||
Sides[0] === "Savage" &&
|
Sides.includes("Savage") &&
|
||||||
(BotZoneName?.toLowerCase().includes("snipe") ||
|
!Categories.includes("Boss")
|
||||||
DelayToCanSpawnSec > 40)
|
|
||||||
)
|
)
|
||||||
.map(({ BotZoneName }) => BotZoneName || "")
|
.filter(
|
||||||
|
({ BotZoneName, DelayToCanSpawnSec }) =>
|
||||||
|
BotZoneName?.toLowerCase().includes("snipe") ||
|
||||||
|
DelayToCanSpawnSec > 300
|
||||||
|
)
|
||||||
|
.map(({ BotZoneName }) => BotZoneName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (sniperLocations.size) {
|
if (sniperLocations.size) {
|
||||||
locationList[index].base.MinMaxBots = [
|
locationList[index].base.MinMaxBots = [
|
||||||
{
|
{
|
||||||
|
@ -109,21 +112,32 @@ export default function buildScavMarksmanWaves(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
let scavZones = shuffle<string[]>([
|
const scavZones = shuffle<string[]>([
|
||||||
...new Set(
|
...new Set(
|
||||||
[...locationList[index].base.SpawnPointParams]
|
[...locationList[index].base.SpawnPointParams]
|
||||||
.filter(
|
.filter(
|
||||||
({ Categories, Sides, BotZoneName }) =>
|
({ Categories, Sides, BotZoneName }) =>
|
||||||
!!BotZoneName &&
|
!!BotZoneName &&
|
||||||
Categories.includes("Bot") &&
|
Sides.includes("Savage") &&
|
||||||
(Sides.includes("Savage") || Sides.includes("All"))
|
!Categories.includes("Boss")
|
||||||
)
|
)
|
||||||
.map(({ BotZoneName }) => BotZoneName)
|
.map(({ BotZoneName }) => BotZoneName)
|
||||||
.filter((name) => !sniperLocations.has(name))
|
.filter((name) => !sniperLocations.has(name))
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Reduced Zone Delay
|
||||||
|
locationList[index].base.SpawnPointParams = locationList[
|
||||||
|
index
|
||||||
|
].base.SpawnPointParams.map((spawn) => ({
|
||||||
|
...spawn,
|
||||||
|
DelayToCanSpawnSec:
|
||||||
|
spawn.DelayToCanSpawnSec > 20
|
||||||
|
? Math.round(spawn.DelayToCanSpawnSec / 10)
|
||||||
|
: spawn.DelayToCanSpawnSec,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||||
const { scavWaveCount } = mapConfig[map];
|
const { scavWaveCount } = mapConfig[map];
|
||||||
|
|
||||||
const escapeTimeLimitRatio = Math.round(
|
const escapeTimeLimitRatio = Math.round(
|
||||||
|
@ -135,19 +149,12 @@ export default function buildScavMarksmanWaves(
|
||||||
scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio
|
scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio
|
||||||
);
|
);
|
||||||
|
|
||||||
const numberOfZoneless = scavTotalWaveCount - scavZones.length;
|
|
||||||
// console.log(numberOfZoneless);
|
|
||||||
if (numberOfZoneless > 0) {
|
|
||||||
const addEmpty = new Array(numberOfZoneless).fill("");
|
|
||||||
scavZones = shuffle<string[]>([...scavZones, ...addEmpty]);
|
|
||||||
}
|
|
||||||
// console.log(scavZones);
|
|
||||||
config.debug &&
|
config.debug &&
|
||||||
escapeTimeLimitRatio !== 1 &&
|
escapeTimeLimitRatio !== 1 &&
|
||||||
console.log(
|
console.log(
|
||||||
`${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment`
|
`${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment`
|
||||||
);
|
);
|
||||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
|
||||||
let snipers = waveBuilder(
|
let snipers = waveBuilder(
|
||||||
sniperLocations.size,
|
sniperLocations.size,
|
||||||
Math.round(timeLimit / 4),
|
Math.round(timeLimit / 4),
|
||||||
|
@ -159,13 +166,14 @@ export default function buildScavMarksmanWaves(
|
||||||
[],
|
[],
|
||||||
shuffle([...sniperLocations]),
|
shuffle([...sniperLocations]),
|
||||||
80,
|
80,
|
||||||
true,
|
false,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (snipersHaveFriends)
|
if (snipersHaveFriends)
|
||||||
snipers = snipers.map((wave) => ({
|
snipers = snipers.map((wave) => ({
|
||||||
...wave,
|
...wave,
|
||||||
|
slots_min: 0,
|
||||||
...(snipersHaveFriends && wave.slots_max < 2
|
...(snipersHaveFriends && wave.slots_max < 2
|
||||||
? { slots_min: 1, slots_max: 2 }
|
? { slots_min: 1, slots_max: 2 }
|
||||||
: {}),
|
: {}),
|
||||||
|
|
|
@ -1,126 +1,36 @@
|
||||||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||||
import { configLocations } from "./constants";
|
import { configLocations } from "./constants";
|
||||||
import mapConfig from "../../config/mapConfig.json";
|
import mapConfig from "../../config/mapConfig.json";
|
||||||
import _config from "../../config/config.json";
|
|
||||||
|
|
||||||
export default function updateSpawnLocations(
|
export default function updateSpawnLocations(locationList: ILocation[]) {
|
||||||
locationList: ILocation[],
|
|
||||||
config: typeof _config
|
|
||||||
) {
|
|
||||||
for (let index = 0; index < locationList.length; index++) {
|
for (let index = 0; index < locationList.length; index++) {
|
||||||
const map = configLocations[index];
|
const map = configLocations[index];
|
||||||
// console.log(map);
|
|
||||||
const limit = mapConfig[map].spawnMinDistance;
|
const limit = mapConfig[map].spawnMinDistance;
|
||||||
|
|
||||||
const InfiltrationList = [
|
|
||||||
...new Set(
|
|
||||||
locationList[index].base.SpawnPointParams.filter(
|
|
||||||
({ Infiltration }) => Infiltration
|
|
||||||
).map(({ Infiltration }) => Infiltration)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
// console.log(map, InfiltrationList);
|
|
||||||
const getRandomInfil = (): string =>
|
|
||||||
InfiltrationList[Math.floor(Math.random() * InfiltrationList.length)];
|
|
||||||
// console.log(InfiltrationList);
|
|
||||||
// console.log("\n" + map);
|
// console.log("\n" + map);
|
||||||
locationList[index].base.SpawnPointParams.forEach(
|
locationList[index].base.SpawnPointParams.forEach(
|
||||||
(
|
(
|
||||||
{
|
{ ColliderParams, BotZoneName, DelayToCanSpawnSec, Categories, Sides },
|
||||||
ColliderParams,
|
|
||||||
BotZoneName,
|
|
||||||
DelayToCanSpawnSec,
|
|
||||||
Categories,
|
|
||||||
Sides,
|
|
||||||
Infiltration,
|
|
||||||
},
|
|
||||||
innerIndex
|
innerIndex
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
!Categories.includes("Boss") &&
|
ColliderParams?._props?.Radius !== undefined &&
|
||||||
|
ColliderParams?._props?.Radius < limit &&
|
||||||
!BotZoneName?.toLowerCase().includes("snipe") &&
|
!BotZoneName?.toLowerCase().includes("snipe") &&
|
||||||
DelayToCanSpawnSec < 41
|
DelayToCanSpawnSec < 300
|
||||||
) {
|
) {
|
||||||
// Make it so players/pmcs can spawn anywhere.
|
// console.log(
|
||||||
if (
|
// "----",
|
||||||
config.playerOpenZones &&
|
// ColliderParams._props.Radius,
|
||||||
!!Infiltration &&
|
// "=>",
|
||||||
(Sides.includes("Pmc") || Sides.includes("All"))
|
// limit,
|
||||||
) {
|
// BotZoneName
|
||||||
locationList[index].base.SpawnPointParams[innerIndex].Categories = [
|
// );
|
||||||
"Player",
|
|
||||||
"Coop",
|
|
||||||
innerIndex % 2 === 0 ? "Group" : "Opposite",
|
|
||||||
];
|
|
||||||
|
|
||||||
locationList[index].base.SpawnPointParams[innerIndex].Sides = [
|
locationList[index].base.SpawnPointParams[
|
||||||
"Pmc",
|
innerIndex
|
||||||
"All",
|
].ColliderParams._props.Radius = limit;
|
||||||
];
|
|
||||||
// console.log(
|
|
||||||
// BotZoneName || "none",
|
|
||||||
// locationList[index].base.SpawnPointParams[innerIndex].Categories,
|
|
||||||
// locationList[index].base.SpawnPointParams[innerIndex].Sides
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!config.allOpenZones &&
|
|
||||||
config.pmcOpenZones &&
|
|
||||||
Categories.includes("Bot") &&
|
|
||||||
Sides[0] === "Savage" &&
|
|
||||||
!Infiltration
|
|
||||||
) {
|
|
||||||
locationList[index].base.SpawnPointParams[innerIndex].Categories = [
|
|
||||||
"Player",
|
|
||||||
"Bot",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Infiltration && 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 (
|
|
||||||
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 min = !offset && waves.length < 1 ? 0 : timeStart;
|
||||||
const max = !offset && waves.length < 1 ? 0 : timeStart + 60;
|
const max = !offset && waves.length < 1 ? 0 : timeStart + 10;
|
||||||
|
|
||||||
if (waves.length >= 1 || offset) timeStart = timeStart + stage;
|
if (waves.length >= 1 || offset) timeStart = timeStart + stage;
|
||||||
const BotPreset = getDifficulty(difficulty);
|
const BotPreset = getDifficulty(difficulty);
|
||||||
|
@ -55,9 +55,8 @@ export const waveBuilder = (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (slotMax < 1) slotMax = 1;
|
if (slotMax < 1) slotMax = 1;
|
||||||
let slotMin = (Math.round(Math.random() * slotMax) || 1) - 1;
|
const slotMin = (Math.round(Math.random() * slotMax) || 1) - 1;
|
||||||
|
|
||||||
if (wildSpawnType === "marksman" && slotMin < 1) slotMin = 1;
|
|
||||||
waves.push({
|
waves.push({
|
||||||
BotPreset,
|
BotPreset,
|
||||||
BotSide: getBotSide(wildSpawnType),
|
BotSide: getBotSide(wildSpawnType),
|
||||||
|
@ -190,26 +189,11 @@ export const getRandomZombieType = () =>
|
||||||
zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())];
|
zombieTypesCaps[Math.round((zombieTypesCaps.length - 1) * Math.random())];
|
||||||
|
|
||||||
export const buildPmcWaves = (
|
export const buildPmcWaves = (
|
||||||
pmcTotal: number,
|
totalWaves: number,
|
||||||
escapeTimeLimit: number,
|
escapeTimeLimit: number,
|
||||||
config: typeof _config,
|
config: typeof _config,
|
||||||
bossZones: string[],
|
bossZones: string[]
|
||||||
hotZones: string[]
|
|
||||||
): IBossLocationSpawn[] => {
|
): IBossLocationSpawn[] => {
|
||||||
// console.log(pmcTotal)
|
|
||||||
if (!pmcTotal) return [];
|
|
||||||
const halfIndex = Math.round(bossZones.length * 0.75); //Put hotzones in the 2 - 4 spawns
|
|
||||||
// console.log(bossZones.length);
|
|
||||||
bossZones = [
|
|
||||||
...bossZones.slice(0, halfIndex),
|
|
||||||
...hotZones,
|
|
||||||
...bossZones.slice(halfIndex),
|
|
||||||
];
|
|
||||||
|
|
||||||
// console.log(bossZones.length, hotZones.length);
|
|
||||||
// console.log(bossZones);
|
|
||||||
pmcTotal = pmcTotal + hotZones.length;
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
pmcMaxGroupSize,
|
pmcMaxGroupSize,
|
||||||
pmcDifficulty,
|
pmcDifficulty,
|
||||||
|
@ -218,12 +202,14 @@ export const buildPmcWaves = (
|
||||||
pmcWaveDistribution,
|
pmcWaveDistribution,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
const averageTime = (escapeTimeLimit * 0.8) / pmcTotal;
|
const averageTime = escapeTimeLimit / totalWaves;
|
||||||
|
const firstHalf = Math.round(averageTime * (1 - pmcWaveDistribution));
|
||||||
|
const secondHalf = Math.round(averageTime * (1 + pmcWaveDistribution));
|
||||||
|
let timeStart = -1;
|
||||||
const waves: IBossLocationSpawn[] = [];
|
const waves: IBossLocationSpawn[] = [];
|
||||||
let maxSlotsReached = pmcTotal;
|
let maxSlotsReached = totalWaves;
|
||||||
|
|
||||||
while (pmcTotal > 0) {
|
while (totalWaves > 0) {
|
||||||
let bossEscortAmount = Math.round(
|
let bossEscortAmount = Math.round(
|
||||||
(morePmcGroups ? 1 : Math.random()) *
|
(morePmcGroups ? 1 : Math.random()) *
|
||||||
Math.random() *
|
Math.random() *
|
||||||
|
@ -231,24 +217,20 @@ export const buildPmcWaves = (
|
||||||
);
|
);
|
||||||
|
|
||||||
if (bossEscortAmount < 0) bossEscortAmount = 0;
|
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
|
||||||
|
);
|
||||||
|
|
||||||
// const totalCountThisWave = bossEscortAmount + 1;
|
if (waves.length >= 1) timeStart = timeStart + stage;
|
||||||
const totalCountThusFar = pmcTotal - maxSlotsReached;
|
|
||||||
|
|
||||||
const timeToUse =
|
|
||||||
totalCountThusFar < pmcTotal * pmcWaveDistribution
|
|
||||||
? Math.round(
|
|
||||||
averageTime * (1 - pmcWaveDistribution) * totalCountThusFar
|
|
||||||
)
|
|
||||||
: Math.round(
|
|
||||||
escapeTimeLimit * (1 - pmcWaveDistribution) +
|
|
||||||
(1 - pmcWaveDistribution) * totalCountThusFar * averageTime
|
|
||||||
);
|
|
||||||
|
|
||||||
let timeStart =
|
|
||||||
(startingPmcs ? totalCountThusFar * totalCountThusFar * 3 : timeToUse) ||
|
|
||||||
-1;
|
|
||||||
|
|
||||||
|
// console.log(timeStart, BossEscortAmount);
|
||||||
const side = Math.random() > 0.5 ? "pmcBEAR" : "pmcUSEC";
|
const side = Math.random() > 0.5 ? "pmcBEAR" : "pmcUSEC";
|
||||||
|
|
||||||
const BossDifficult = getDifficulty(pmcDifficulty);
|
const BossDifficult = getDifficulty(pmcDifficulty);
|
||||||
|
@ -278,10 +260,7 @@ export const buildPmcWaves = (
|
||||||
maxSlotsReached -= 1 + bossEscortAmount;
|
maxSlotsReached -= 1 + bossEscortAmount;
|
||||||
if (maxSlotsReached <= 0) break;
|
if (maxSlotsReached <= 0) break;
|
||||||
}
|
}
|
||||||
// console.log(
|
|
||||||
// escapeTimeLimit,
|
|
||||||
// waves.map(({ Time }) => Time)
|
|
||||||
// );
|
|
||||||
return waves;
|
return waves;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -291,7 +270,6 @@ export const buildZombie = (
|
||||||
waveDistribution: number,
|
waveDistribution: number,
|
||||||
BossChance: number = 100
|
BossChance: number = 100
|
||||||
): IBossLocationSpawn[] => {
|
): IBossLocationSpawn[] => {
|
||||||
if (!totalWaves) return [];
|
|
||||||
const averageTime = (escapeTimeLimit * 60) / totalWaves;
|
const averageTime = (escapeTimeLimit * 60) / totalWaves;
|
||||||
const firstHalf = Math.round(averageTime * (1 - waveDistribution));
|
const firstHalf = Math.round(averageTime * (1 - waveDistribution));
|
||||||
const secondHalf = Math.round(averageTime * (1 + waveDistribution));
|
const secondHalf = Math.round(averageTime * (1 + waveDistribution));
|
||||||
|
|
Binary file not shown.
|
@ -1,28 +0,0 @@
|
||||||
[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
|
|
|
@ -1,29 +0,0 @@
|
||||||
name: "tagged-release"
|
|
||||||
|
|
||||||
on: push
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tagged-release:
|
|
||||||
name: "Tagged Release"
|
|
||||||
runs-on: "ubuntu-latest"
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
- name: Print package.json version (before)
|
|
||||||
id: versionstep
|
|
||||||
run: |
|
|
||||||
echo "version=$(jq -r .version package.json)" >> $GITHUB_OUTPUT
|
|
||||||
working-directory: ${{ github.workspace }}
|
|
||||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
|
||||||
with:
|
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
automatic_release_tag: ${{ steps.versionstep.outputs.version }}
|
|
||||||
title: "MOAR ${{ steps.versionstep.outputs.version }}"
|
|
||||||
prerelease: false
|
|
||||||
files: |
|
|
||||||
./dist/*.zip
|
|
|
@ -1,15 +0,0 @@
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- name: staging
|
|
||||||
prerelease: true
|
|
||||||
debug: true
|
|
||||||
ci: true
|
|
||||||
dryRun: false
|
|
||||||
plugins:
|
|
||||||
- "@semantic-release/commit-analyzer"
|
|
||||||
- "@semantic-release/release-notes-generator"
|
|
||||||
- "@semantic-release/github"
|
|
||||||
- "@semantic-release/npm"
|
|
||||||
- - "@semantic-release/git"
|
|
||||||
- assets: ["package.json"]
|
|
||||||
message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
{
|
|
||||||
"enableBotSpawning": true,
|
|
||||||
|
|
||||||
"pmcDifficulty": 0.3,
|
|
||||||
"scavDifficulty": 0.3,
|
|
||||||
|
|
||||||
"scavWaveDistribution": 0.5,
|
|
||||||
"scavWaveQuantity": 1,
|
|
||||||
|
|
||||||
"startingPmcs": false,
|
|
||||||
|
|
||||||
"pmcWaveDistribution": 0.8,
|
|
||||||
"pmcWaveQuantity": 1,
|
|
||||||
|
|
||||||
"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
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
{
|
|
||||||
"customs": {
|
|
||||||
"spawnMinDistance": 30,
|
|
||||||
"pmcWaveCount": 12,
|
|
||||||
"scavWaveCount": 21,
|
|
||||||
"zombieWaveCount": 9,
|
|
||||||
"scavHotZones": [
|
|
||||||
"ZoneDormitory"
|
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"ZoneDormitory"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"laboratory": {
|
|
||||||
"spawnMinDistance": 20,
|
|
||||||
"pmcWaveCount": 10,
|
|
||||||
"scavWaveCount": 0,
|
|
||||||
"zombieWaveCount": 12
|
|
||||||
},
|
|
||||||
"lighthouse": {
|
|
||||||
"spawnMinDistance": 40,
|
|
||||||
"pmcWaveCount": 12,
|
|
||||||
"scavWaveCount": 20,
|
|
||||||
"zombieWaveCount": 10,
|
|
||||||
"scavHotZones": [
|
|
||||||
"Zone_LongRoad",
|
|
||||||
"Zone_LongRoad"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rezervbase": {
|
|
||||||
"spawnMinDistance": 40,
|
|
||||||
"pmcWaveCount": 11,
|
|
||||||
"scavWaveCount": 24,
|
|
||||||
"zombieWaveCount": 9,
|
|
||||||
"scavHotZones": [
|
|
||||||
"ZoneRailStrorage"
|
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"ZoneBarrack"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"shoreline": {
|
|
||||||
"spawnMinDistance": 40,
|
|
||||||
"pmcWaveCount": 14,
|
|
||||||
"scavWaveCount": 32,
|
|
||||||
"zombieWaveCount": 12,
|
|
||||||
"scavHotZones": [
|
|
||||||
"ZoneSanatorium1"
|
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"ZoneSanatorium2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"tarkovstreets": {
|
|
||||||
"spawnMinDistance": 40,
|
|
||||||
"pmcWaveCount": 16,
|
|
||||||
"scavWaveCount": 28,
|
|
||||||
"zombieWaveCount": 13
|
|
||||||
},
|
|
||||||
"woods": {
|
|
||||||
"spawnMinDistance": 40,
|
|
||||||
"pmcWaveCount": 14,
|
|
||||||
"scavWaveCount": 28,
|
|
||||||
"zombieWaveCount": 10,
|
|
||||||
"scavHotZones": [
|
|
||||||
"ZoneWoodCutter"
|
|
||||||
],
|
|
||||||
"pmcHotZones": [
|
|
||||||
"ZoneWoodCutter"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"gzLow": {
|
|
||||||
"spawnMinDistance": 30,
|
|
||||||
"pmcWaveCount": 10,
|
|
||||||
"scavWaveCount": 18,
|
|
||||||
"zombieWaveCount": 9
|
|
||||||
},
|
|
||||||
"gzHigh": {
|
|
||||||
"spawnMinDistance": 30,
|
|
||||||
"pmcWaveCount": 12,
|
|
||||||
"scavWaveCount": 18,
|
|
||||||
"zombieWaveCount": 9
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
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[]> = {};
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
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"
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,154 +0,0 @@
|
||||||
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];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,278 +0,0 @@
|
||||||
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`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,226 +0,0 @@
|
||||||
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 }));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
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,
|
|
||||||
};
|
|
|
@ -1,38 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,430 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
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`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
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);
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
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() };
|
|
|
@ -1,57 +0,0 @@
|
||||||
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(" ");
|
|
Loading…
Reference in New Issue