feat(moar)
This commit is contained in:
parent
e334736730
commit
ff3ab9a974
32 changed files with 6960 additions and 0 deletions
BIN
BepInEx/plugins/MOAR.dll
Normal file
BIN
BepInEx/plugins/MOAR.dll
Normal file
Binary file not shown.
21
user/mods/DewardianDev-MOAR/LICENSE.md
Normal file
21
user/mods/DewardianDev-MOAR/LICENSE.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Dushaoan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
19
user/mods/DewardianDev-MOAR/config/PresetWeightings.json
Normal file
19
user/mods/DewardianDev-MOAR/config/PresetWeightings.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"live-like": 45,
|
||||
"quiet-raids": 8,
|
||||
"more-scavs": 5,
|
||||
"more-pmcs": 4,
|
||||
"more-scavs-and-pmcs": 3,
|
||||
"delayed-scavs": 2,
|
||||
"delayed-pmcs": 4,
|
||||
"arrived-early": 4,
|
||||
"rogue-invasion": 1,
|
||||
"raider-invasion": 1,
|
||||
"insanity": 1,
|
||||
"minor-boss-invasion": 4,
|
||||
"less-pmcs-less-scavs-boss-invasion": 3,
|
||||
"low-pmcs-low-scavs-major-boss-invasion": 1,
|
||||
"main-boss-guaranteed": 5,
|
||||
"main-boss-guaranteed-roaming": 3,
|
||||
"sniper-buddies": 5
|
||||
}
|
||||
107
user/mods/DewardianDev-MOAR/config/Presets.json
Normal file
107
user/mods/DewardianDev-MOAR/config/Presets.json
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"live-like": {},
|
||||
"quiet-raids": {
|
||||
"spawnSmoothing": false,
|
||||
"randomSpawns": true,
|
||||
"scavGroupChance": 0.1,
|
||||
"scavMaxGroupSize": 2,
|
||||
"pmcGroupChance": 0.1,
|
||||
"pmcMaxGroupSize": 2,
|
||||
"scavWaveQuantity": 0.7,
|
||||
"pmcWaveQuantity": 0.5,
|
||||
"mainBossChanceBuff": 0
|
||||
},
|
||||
"more-scavs": {
|
||||
"scavGroupChance": 0.5,
|
||||
"scavMaxGroupSize": 5,
|
||||
"scavWaveQuantity": 1.5,
|
||||
"pmcWaveQuantity": 0.7
|
||||
},
|
||||
"more-pmcs": {
|
||||
"pmcGroupChance": 0.5,
|
||||
"pmcWaveQuantity": 1.3,
|
||||
"scavWaveQuantity": 0.7
|
||||
},
|
||||
"more-scavs-and-pmcs": {
|
||||
"scavGroupChance": 0.3,
|
||||
"scavMaxGroupSize": 5,
|
||||
"pmcGroupChance": 0.3,
|
||||
"pmcMaxGroupSize": 5,
|
||||
"scavWaveQuantity": 1.4,
|
||||
"pmcWaveQuantity": 1.2
|
||||
},
|
||||
"delayed-scavs": {
|
||||
"scavWaveDistribution": 1.5
|
||||
},
|
||||
"delayed-pmcs": {
|
||||
"pmcWaveDistribution": 1.2
|
||||
},
|
||||
"arrived-early": {
|
||||
"spawnSmoothing": false,
|
||||
"scavWaveDistribution": 1.2,
|
||||
"pmcWaveDistribution": 1.2
|
||||
},
|
||||
"rogue-invasion": {
|
||||
"randomRaiderGroup": true,
|
||||
"randomRaiderGroupChance": 100
|
||||
},
|
||||
"raider-invasion": {
|
||||
"randomRaiderGroup": true,
|
||||
"randomRaiderGroupChance": 100
|
||||
},
|
||||
"insanity": {
|
||||
"scavWaveQuantity": 1.3,
|
||||
"pmcWaveQuantity": 1.3,
|
||||
"scavGroupChance": 0.7,
|
||||
"pmcGroupChance": 0.8,
|
||||
"pmcMaxGroupSize": 6,
|
||||
"scavMaxGroupSize": 6,
|
||||
"sniperGroupChance": 0.5,
|
||||
"bossOpenZones": true,
|
||||
"randomRaiderGroup": true,
|
||||
"randomRaiderGroupChance": 50,
|
||||
"randomRogueGroup": true,
|
||||
"randomRogueGroupChance": 50,
|
||||
"mainBossChanceBuff": 50,
|
||||
"bossInvasion": true,
|
||||
"bossInvasionSpawnChance": 10
|
||||
},
|
||||
"minor-boss-invasion": {
|
||||
"bossOpenZones": true,
|
||||
"bossInvasion": true,
|
||||
"bossInvasionSpawnChance": 10,
|
||||
"mainBossChanceBuff": 25,
|
||||
"gradualBossInvasion": true
|
||||
},
|
||||
"less-pmcs-less-scavs-boss-invasion": {
|
||||
"maxBotCap": 20,
|
||||
"scavWaveQuantity": 0.7,
|
||||
"pmcWaveQuantity": 0.7,
|
||||
"bossOpenZones": true,
|
||||
"bossInvasion": true,
|
||||
"bossInvasionSpawnChance": 25,
|
||||
"mainBossChanceBuff": 35,
|
||||
"gradualBossInvasion": true
|
||||
},
|
||||
"low-pmcs-low-scavs-major-boss-invasion": {
|
||||
"maxBotCap": 12,
|
||||
"scavWaveQuantity": 0.3,
|
||||
"pmcWaveQuantity": 0.1,
|
||||
"bossOpenZones": true,
|
||||
"bossInvasion": true,
|
||||
"bossInvasionSpawnChance": 90,
|
||||
"mainBossChanceBuff": 80,
|
||||
"gradualBossInvasion": true
|
||||
},
|
||||
"main-boss-guaranteed": {
|
||||
"mainBossChanceBuff": 100
|
||||
},
|
||||
"main-boss-guaranteed-roaming": {
|
||||
"bossOpenZones": true,
|
||||
"mainBossChanceBuff": 100
|
||||
},
|
||||
"sniper-buddies": {
|
||||
"sniperMaxGroupSize": 4,
|
||||
"sniperGroupChance": 1
|
||||
}
|
||||
}
|
||||
262
user/mods/DewardianDev-MOAR/config/Spawns/playerSpawns.json
Normal file
262
user/mods/DewardianDev-MOAR/config/Spawns/playerSpawns.json
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
{
|
||||
"bigmap": [
|
||||
{
|
||||
"x": -237.231491,
|
||||
"y": 1.75956392,
|
||||
"z": -120.84491
|
||||
},
|
||||
{
|
||||
"x": -333.538757,
|
||||
"y": 1.2834585,
|
||||
"z": -193.426743
|
||||
},
|
||||
{
|
||||
"x": -330.967346,
|
||||
"y": 0.7311232,
|
||||
"z": -148.138031
|
||||
},
|
||||
{
|
||||
"x": -320.449768,
|
||||
"y": -0.1416501,
|
||||
"z": -67.51158
|
||||
},
|
||||
{
|
||||
"x": 494.7378,
|
||||
"y": 2.0099400000000003,
|
||||
"z": 214.363235
|
||||
},
|
||||
{
|
||||
"x": 406.431763,
|
||||
"y": 13.6794415,
|
||||
"z": 205.624054
|
||||
},
|
||||
{
|
||||
"x": 565.3794,
|
||||
"y": 13.6622076,
|
||||
"z": 176.73848
|
||||
},
|
||||
{
|
||||
"x": 670.381958,
|
||||
"y": 4.553419,
|
||||
"z": 116.672173
|
||||
},
|
||||
{
|
||||
"x": 648.4502,
|
||||
"y": 0.884521127,
|
||||
"z": 110.214828
|
||||
},
|
||||
{
|
||||
"x": 648.0046,
|
||||
"y": 1.60148263,
|
||||
"z": -144.927017
|
||||
},
|
||||
{
|
||||
"x": 607.7813,
|
||||
"y": 1.9404974,
|
||||
"z": -131.76033
|
||||
},
|
||||
{
|
||||
"x": 353.7437,
|
||||
"y": 1.67306828,
|
||||
"z": -182.096039
|
||||
}
|
||||
],
|
||||
"factory4_day": [],
|
||||
"factory4_night": [],
|
||||
"interchange": [
|
||||
{
|
||||
"x": 281.41922,
|
||||
"y": 21.82544,
|
||||
"z": 350.742
|
||||
},
|
||||
{
|
||||
"x": 485.793152,
|
||||
"y": 19.02228,
|
||||
"z": -376.709045
|
||||
},
|
||||
{
|
||||
"x": -244.620956,
|
||||
"y": 21.9201584,
|
||||
"z": -249.0072
|
||||
}
|
||||
],
|
||||
"laboratory": [],
|
||||
"lighthouse": [
|
||||
{
|
||||
"x": -6.39022541,
|
||||
"y": 0.612077177,
|
||||
"z": 582.3435
|
||||
},
|
||||
{
|
||||
"x": 151.168228,
|
||||
"y": 1.59125161,
|
||||
"z": 332.439056
|
||||
},
|
||||
{
|
||||
"x": 146.495163,
|
||||
"y": 0.8794734479999999,
|
||||
"z": -160.836212
|
||||
}
|
||||
],
|
||||
"rezervbase": [
|
||||
{
|
||||
"x": 233.31601,
|
||||
"y": -10.2675419,
|
||||
"z": -4.18630457
|
||||
},
|
||||
{
|
||||
"x": 139.089874,
|
||||
"y": -10.305027,
|
||||
"z": 23.3623848
|
||||
},
|
||||
{
|
||||
"x": 56.89732,
|
||||
"y": -6.44232559,
|
||||
"z": 137.955032
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"x": 257.83,
|
||||
"y": -53.64759,
|
||||
"z": -104.635
|
||||
},
|
||||
{
|
||||
"x": 155.2863,
|
||||
"y": -33.8709831,
|
||||
"z": -282.204285
|
||||
},
|
||||
{
|
||||
"x": 249.29483,
|
||||
"y": -64.13083,
|
||||
"z": 450.0022
|
||||
},
|
||||
{
|
||||
"x": -529.4264,
|
||||
"y": -18.0687866,
|
||||
"z": -338.916351
|
||||
}
|
||||
],
|
||||
"tarkovstreets": [],
|
||||
"woods": [
|
||||
{
|
||||
"x": -255.085876,
|
||||
"y": 18.7673836,
|
||||
"z": -657.3163
|
||||
},
|
||||
{
|
||||
"x": 216.3761,
|
||||
"y": 11.7787333,
|
||||
"z": -831.962036
|
||||
},
|
||||
{
|
||||
"x": -629.5677,
|
||||
"y": 13.3888264,
|
||||
"z": -257.016571
|
||||
},
|
||||
{
|
||||
"x": -602.1799,
|
||||
"y": 24.6817856,
|
||||
"z": -154.038223
|
||||
},
|
||||
{
|
||||
"x": 253.6307,
|
||||
"y": -10.6299782,
|
||||
"z": 331.7935
|
||||
}
|
||||
],
|
||||
"sandbox": [
|
||||
{
|
||||
"x": 12.19389,
|
||||
"y": 23.9751911,
|
||||
"z": 157.1094
|
||||
},
|
||||
{
|
||||
"x": 109.844994,
|
||||
"y": 23.96955,
|
||||
"z": 304.835266
|
||||
},
|
||||
{
|
||||
"x": 139.231842,
|
||||
"y": 23.92805,
|
||||
"z": 275.822052
|
||||
},
|
||||
{
|
||||
"x": 120.96067,
|
||||
"y": 28.6914253,
|
||||
"z": 285.82135
|
||||
},
|
||||
{
|
||||
"x": 147.699371,
|
||||
"y": 23.2307129,
|
||||
"z": 255.266312
|
||||
},
|
||||
{
|
||||
"x": 5.82757664,
|
||||
"y": 23.2639179,
|
||||
"z": 330.428345
|
||||
},
|
||||
{
|
||||
"x": -11.9459629,
|
||||
"y": 24.33598,
|
||||
"z": -57.9175034
|
||||
},
|
||||
{
|
||||
"x": 207.106964,
|
||||
"y": 16.67053,
|
||||
"z": 37.7381
|
||||
},
|
||||
{
|
||||
"x": 181.100342,
|
||||
"y": 16.74511,
|
||||
"z": 125.971664
|
||||
}
|
||||
],
|
||||
"sandbox_high": [
|
||||
{
|
||||
"x": 12.19389,
|
||||
"y": 23.9751911,
|
||||
"z": 157.1094
|
||||
},
|
||||
{
|
||||
"x": 109.844994,
|
||||
"y": 23.96955,
|
||||
"z": 304.835266
|
||||
},
|
||||
{
|
||||
"x": 139.231842,
|
||||
"y": 23.92805,
|
||||
"z": 275.822052
|
||||
},
|
||||
{
|
||||
"x": 120.96067,
|
||||
"y": 28.6914253,
|
||||
"z": 285.82135
|
||||
},
|
||||
{
|
||||
"x": 147.699371,
|
||||
"y": 23.2307129,
|
||||
"z": 255.266312
|
||||
},
|
||||
{
|
||||
"x": 5.82757664,
|
||||
"y": 23.2639179,
|
||||
"z": 330.428345
|
||||
},
|
||||
{
|
||||
"x": -11.9459629,
|
||||
"y": 24.33598,
|
||||
"z": -57.9175034
|
||||
},
|
||||
{
|
||||
"x": 207.106964,
|
||||
"y": 16.67053,
|
||||
"z": 37.7381
|
||||
},
|
||||
{
|
||||
"x": 181.100342,
|
||||
"y": 16.74511,
|
||||
"z": 125.971664
|
||||
}
|
||||
]
|
||||
}
|
||||
1034
user/mods/DewardianDev-MOAR/config/Spawns/pmcSpawns.json
Normal file
1034
user/mods/DewardianDev-MOAR/config/Spawns/pmcSpawns.json
Normal file
File diff suppressed because it is too large
Load diff
2049
user/mods/DewardianDev-MOAR/config/Spawns/scavSpawns.json
Normal file
2049
user/mods/DewardianDev-MOAR/config/Spawns/scavSpawns.json
Normal file
File diff suppressed because it is too large
Load diff
14
user/mods/DewardianDev-MOAR/config/Spawns/sniperSpawns.json
Normal file
14
user/mods/DewardianDev-MOAR/config/Spawns/sniperSpawns.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"bigmap": [],
|
||||
"factory4_day": [],
|
||||
"factory4_night": [],
|
||||
"interchange": [],
|
||||
"laboratory": [],
|
||||
"lighthouse": [],
|
||||
"rezervbase": [],
|
||||
"shoreline": [],
|
||||
"tarkovstreets": [],
|
||||
"woods": [],
|
||||
"sandbox": [],
|
||||
"sandbox_high": []
|
||||
}
|
||||
8
user/mods/DewardianDev-MOAR/config/advancedConfig.json
Normal file
8
user/mods/DewardianDev-MOAR/config/advancedConfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"-------------JUST LEAVE THESE ALONE---------------": "",
|
||||
"ActivateSpawnCullingOnServerStart": false,
|
||||
"MarksmanDifficultyChanges": true,
|
||||
"EnableBossPerformanceImprovements": true,
|
||||
"SpawnpointAreaTarget": 1,
|
||||
"showMapCullingDebug": false
|
||||
}
|
||||
63
user/mods/DewardianDev-MOAR/config/bossConfig.json
Normal file
63
user/mods/DewardianDev-MOAR/config/bossConfig.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"ADD_THESE_TO_A_MAP_TO_OVERRIDE_OR_ADD_A_BOSS_TO_A_MAP": {
|
||||
"BOSS_NAME_EXAMPLE": "CHANCE_OF_SPAWNING_PERCENT",
|
||||
"sectantPriest": 0,
|
||||
"arenaFighterEvent": 0,
|
||||
"bossBoarSniper": 0,
|
||||
"pmcBot": 0,
|
||||
"bossZryachiy": 0,
|
||||
"exUsec": 0,
|
||||
"crazyAssaultEvent": 0,
|
||||
"peacemaker": 0,
|
||||
"bossKojaniy": 0,
|
||||
"bossGluhar": 0,
|
||||
"bossSanitar": 0,
|
||||
"bossKilla": 0,
|
||||
"bossTagilla": 0,
|
||||
"bossKnight": 0,
|
||||
"bossBoar": 0,
|
||||
"bossKolontay": 0,
|
||||
"bossPartisan": 0,
|
||||
"bossBully": 0
|
||||
},
|
||||
"customs": {
|
||||
"bossKnight": 18,
|
||||
"bossPartisan": 8,
|
||||
"bossBully": 30
|
||||
},
|
||||
"factoryDay": {
|
||||
"bossTagilla": 30
|
||||
},
|
||||
"factoryNight": {
|
||||
"bossTagilla": 30
|
||||
},
|
||||
"interchange": {
|
||||
"bossKilla": 30
|
||||
},
|
||||
"laboratory": {},
|
||||
"lighthouse": {
|
||||
"bossKnight": 18,
|
||||
"bossPartisan": 8
|
||||
},
|
||||
"rezervbase": {
|
||||
"bossGluhar": 30
|
||||
},
|
||||
"shoreline": {
|
||||
"bossKnight": 18,
|
||||
"bossPartisan": 8,
|
||||
"bossSanitar": 30
|
||||
},
|
||||
"tarkovstreets": {
|
||||
"bossBoar": 30,
|
||||
"bossKolontay": 30
|
||||
},
|
||||
"woods": {
|
||||
"bossKojaniy": 30,
|
||||
"bossKnight": 18,
|
||||
"bossPartisan": 8
|
||||
},
|
||||
"gzLow": {},
|
||||
"gzHigh": {
|
||||
"bossKolontay": 30
|
||||
}
|
||||
}
|
||||
49
user/mods/DewardianDev-MOAR/config/config.json
Normal file
49
user/mods/DewardianDev-MOAR/config/config.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"enableBotSpawning": true,
|
||||
"spawnSmoothing": true,
|
||||
|
||||
"pmcDifficulty": 0.6,
|
||||
"scavDifficulty": 0.4,
|
||||
|
||||
"scavWaveDistribution": 0.6,
|
||||
"scavWaveQuantity": 1,
|
||||
|
||||
"startingPmcs": false,
|
||||
"pmcWaveDistribution": 0.3,
|
||||
"pmcWaveQuantity": 1,
|
||||
|
||||
"randomSpawns": false,
|
||||
|
||||
"zombiesEnabled": false,
|
||||
"zombieWaveDistribution": 0.8,
|
||||
"zombieWaveQuantity": 1,
|
||||
"zombieHealth": 1,
|
||||
|
||||
"maxBotCap": 28,
|
||||
"maxBotPerZone": 7,
|
||||
|
||||
"sniperGroupChance": 0.2,
|
||||
"scavGroupChance": 0.2,
|
||||
"pmcGroupChance": 0.2,
|
||||
|
||||
"pmcMaxGroupSize": 4,
|
||||
"scavMaxGroupSize": 3,
|
||||
"sniperMaxGroupSize": 1,
|
||||
|
||||
"bossOpenZones": false,
|
||||
|
||||
"randomRaiderGroup": false,
|
||||
"randomRaiderGroupChance": 10,
|
||||
|
||||
"randomRogueGroup": false,
|
||||
"randomRogueGroupChance": 10,
|
||||
|
||||
"disableBosses": false,
|
||||
"mainBossChanceBuff": 0,
|
||||
|
||||
"bossInvasion": false,
|
||||
"bossInvasionSpawnChance": 5,
|
||||
"gradualBossInvasion": true,
|
||||
|
||||
"debug": false
|
||||
}
|
||||
185
user/mods/DewardianDev-MOAR/config/mapConfig.json
Normal file
185
user/mods/DewardianDev-MOAR/config/mapConfig.json
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
{
|
||||
"customs": {
|
||||
"sniperQuantity": 3,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 12,
|
||||
"mapCullingNearPointValuePmc": 12,
|
||||
"mapCullingNearPointValueScav": 22,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 12,
|
||||
"scavWaveCount": 24,
|
||||
"zombieWaveCount": 9,
|
||||
"scavHotZones": [
|
||||
"ZoneDormitory"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneDormitory"
|
||||
]
|
||||
},
|
||||
"factoryDay": {
|
||||
"sniperQuantity": 0,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.6,
|
||||
"mapCullingNearPointValuePlayer": 2,
|
||||
"mapCullingNearPointValuePmc": 2,
|
||||
"mapCullingNearPointValueScav": 1,
|
||||
"spawnMinDistance": 15,
|
||||
"maxBotCapOverride": 12,
|
||||
"maxBotPerZoneOverride": 12,
|
||||
"pmcWaveCount": 8,
|
||||
"scavWaveCount": 12,
|
||||
"zombieWaveCount": 6
|
||||
},
|
||||
"factoryNight": {
|
||||
"sniperQuantity": 0,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.6,
|
||||
"mapCullingNearPointValuePlayer": 2,
|
||||
"mapCullingNearPointValuePmc": 2,
|
||||
"mapCullingNearPointValueScav": 1,
|
||||
"spawnMinDistance": 15,
|
||||
"maxBotCapOverride": 12,
|
||||
"maxBotPerZoneOverride": 12,
|
||||
"pmcWaveCount": 8,
|
||||
"scavWaveCount": 12,
|
||||
"zombieWaveCount": 6
|
||||
},
|
||||
"interchange": {
|
||||
"sniperQuantity": 0,
|
||||
"initialSpawnDelay": 20,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 12,
|
||||
"mapCullingNearPointValuePmc": 4,
|
||||
"mapCullingNearPointValueScav": 1,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 14,
|
||||
"scavWaveCount": 36,
|
||||
"zombieWaveCount": 12,
|
||||
"scavHotZones": [
|
||||
"ZoneCenterBot",
|
||||
"ZoneCenter"
|
||||
]
|
||||
},
|
||||
"laboratory": {
|
||||
"sniperQuantity": 0,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 3,
|
||||
"mapCullingNearPointValuePmc": 3,
|
||||
"mapCullingNearPointValueScav": 2,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 12,
|
||||
"scavWaveCount": 0,
|
||||
"zombieWaveCount": 12
|
||||
},
|
||||
"lighthouse": {
|
||||
"sniperQuantity": 1,
|
||||
"initialSpawnDelay": 20,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 6,
|
||||
"mapCullingNearPointValuePmc": 5,
|
||||
"mapCullingNearPointValueScav": 8,
|
||||
"maxBotCapOverride": 22,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 12,
|
||||
"scavWaveCount": 24,
|
||||
"zombieWaveCount": 10,
|
||||
"scavHotZones": [
|
||||
"Zone_LongRoad"
|
||||
]
|
||||
},
|
||||
"rezervbase": {
|
||||
"sniperQuantity": 0,
|
||||
"initialSpawnDelay": 20,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 12,
|
||||
"mapCullingNearPointValuePmc": 6,
|
||||
"mapCullingNearPointValueScav": 1,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 11,
|
||||
"scavWaveCount": 28,
|
||||
"zombieWaveCount": 9,
|
||||
"scavHotZones": [
|
||||
"ZoneRailStrorage"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneBarrack"
|
||||
]
|
||||
},
|
||||
"shoreline": {
|
||||
"sniperQuantity": 3,
|
||||
"initialSpawnDelay": 20,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 12,
|
||||
"mapCullingNearPointValuePmc": 6,
|
||||
"mapCullingNearPointValueScav": 15,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 14,
|
||||
"scavWaveCount": 36,
|
||||
"zombieWaveCount": 12,
|
||||
"scavHotZones": [
|
||||
"ZoneSanatorium1"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneSanatorium2"
|
||||
]
|
||||
},
|
||||
"tarkovstreets": {
|
||||
"sniperQuantity": 5,
|
||||
"initialSpawnDelay": 20,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 12,
|
||||
"mapCullingNearPointValuePmc": 6,
|
||||
"mapCullingNearPointValueScav": 6,
|
||||
"maxBotCapOverride": 18,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 10,
|
||||
"scavWaveCount": 20,
|
||||
"zombieWaveCount": 13
|
||||
},
|
||||
"woods": {
|
||||
"sniperQuantity": 1,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.9,
|
||||
"mapCullingNearPointValuePlayer": 12,
|
||||
"mapCullingNearPointValuePmc": 30,
|
||||
"mapCullingNearPointValueScav": 36,
|
||||
"spawnMinDistance": 30,
|
||||
"pmcWaveCount": 14,
|
||||
"scavWaveCount": 36,
|
||||
"zombieWaveCount": 10,
|
||||
"scavHotZones": [
|
||||
"ZoneWoodCutter"
|
||||
],
|
||||
"pmcHotZones": [
|
||||
"ZoneWoodCutter"
|
||||
]
|
||||
},
|
||||
"gzLow": {
|
||||
"sniperQuantity": 1,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.7,
|
||||
"mapCullingNearPointValuePlayer": 6,
|
||||
"mapCullingNearPointValuePmc": 5,
|
||||
"mapCullingNearPointValueScav": 1,
|
||||
"maxBotCapOverride": 15,
|
||||
"spawnMinDistance": 15,
|
||||
"pmcWaveCount": 10,
|
||||
"scavWaveCount": 18,
|
||||
"zombieWaveCount": 9
|
||||
},
|
||||
"gzHigh": {
|
||||
"sniperQuantity": 1,
|
||||
"initialSpawnDelay": 15,
|
||||
"smoothingDistribution": 0.7,
|
||||
"mapCullingNearPointValuePlayer": 6,
|
||||
"mapCullingNearPointValuePmc": 5,
|
||||
"mapCullingNearPointValueScav": 1,
|
||||
"maxBotCapOverride": 15,
|
||||
"spawnMinDistance": 15,
|
||||
"pmcWaveCount": 12,
|
||||
"scavWaveCount": 18,
|
||||
"zombieWaveCount": 9
|
||||
}
|
||||
}
|
||||
25
user/mods/DewardianDev-MOAR/package.json
Normal file
25
user/mods/DewardianDev-MOAR/package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "MOAR",
|
||||
"version": "3.1.6",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "DewardianDev",
|
||||
"sptVersion": "^3.11.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"
|
||||
}
|
||||
}
|
||||
17
user/mods/DewardianDev-MOAR/src/GlobalValues.ts
Normal file
17
user/mods/DewardianDev-MOAR/src/GlobalValues.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Ixyz } from "@spt/models/eft/common/Ixyz";
|
||||
import config from "../config/config.json";
|
||||
import {
|
||||
ILocationBase,
|
||||
ISpawnPointParam,
|
||||
} 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 = "random";
|
||||
public static addedMapZones: Record<number, string[]> = {};
|
||||
public static indexedMapSpawns: Record<number, ISpawnPointParam[]> = {};
|
||||
public static playerSpawn: ISpawnPointParam;
|
||||
}
|
||||
213
user/mods/DewardianDev-MOAR/src/Routes/routes.ts
Normal file
213
user/mods/DewardianDev-MOAR/src/Routes/routes.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import { DependencyContainer } from "tsyringe";
|
||||
import { buildWaves } from "../Spawning/Spawning";
|
||||
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
import { kebabToTitle } from "../utils";
|
||||
import PresetWeightingsConfig from "../../config/PresetWeightings.json";
|
||||
import { Ixyz } from "@spt/models/eft/common/Ixyz";
|
||||
import {
|
||||
deleteBotSpawn,
|
||||
updateBotSpawn,
|
||||
} from "../SpawnZoneChanges/updateUtils";
|
||||
|
||||
export const setupRoutes = (container: DependencyContainer) => {
|
||||
const staticRouterModService = container.resolve<StaticRouterModService>(
|
||||
"StaticRouterModService"
|
||||
);
|
||||
|
||||
interface AddSpawnRequest {
|
||||
map: string;
|
||||
position: Ixyz;
|
||||
type: "player" | "pmc" | "scav" | "sniper";
|
||||
}
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`moarAddBotSpawn`,
|
||||
[
|
||||
{
|
||||
url: "/moar/addBotSpawn",
|
||||
action: async (
|
||||
url: string,
|
||||
req: AddSpawnRequest,
|
||||
sessionID,
|
||||
output
|
||||
) => {
|
||||
updateBotSpawn(req.map, req.position, req.type);
|
||||
return "success";
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarAddBotSpawn"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`moarDeleteBotSpawn`,
|
||||
[
|
||||
{
|
||||
url: "/moar/deleteBotSpawn",
|
||||
action: async (
|
||||
url: string,
|
||||
req: AddSpawnRequest,
|
||||
sessionID,
|
||||
output
|
||||
) => {
|
||||
// console.log(req);
|
||||
deleteBotSpawn(req.map, req.position, req.type);
|
||||
return "success";
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarDeleteBotSpawn"
|
||||
);
|
||||
|
||||
// Make buildwaves run on game end
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`moarUpdater`,
|
||||
[
|
||||
{
|
||||
url: "/client/match/local/end",
|
||||
action: async (_url, info, sessionId, output) => {
|
||||
buildWaves(container);
|
||||
return output;
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarUpdater"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`moarGetCurrentPreset`,
|
||||
[
|
||||
{
|
||||
url: "/moar/currentPreset",
|
||||
action: async () => {
|
||||
return globalValues.forcedPreset || "random";
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarGetCurrentPreset"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`moarGetAnnouncePreset`,
|
||||
[
|
||||
{
|
||||
url: "/moar/announcePreset",
|
||||
action: async () => {
|
||||
if (globalValues.forcedPreset?.toLowerCase() === "random") {
|
||||
return globalValues.currentPreset;
|
||||
}
|
||||
return globalValues.forcedPreset || globalValues.currentPreset;
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarGetAnnouncePreset"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`getDefaultConfig`,
|
||||
[
|
||||
{
|
||||
url: "/moar/getDefaultConfig",
|
||||
action: async () => {
|
||||
return JSON.stringify(globalValues.baseConfig);
|
||||
},
|
||||
},
|
||||
],
|
||||
"getDefaultConfig"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`getServerConfigWithOverrides`,
|
||||
[
|
||||
{
|
||||
url: "/moar/getServerConfigWithOverrides",
|
||||
action: async () => {
|
||||
return JSON.stringify({
|
||||
...(globalValues.baseConfig || {}),
|
||||
...(globalValues.overrideConfig || {}),
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
"getServerConfigWithOverrides"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`getServerConfigWithOverrides`,
|
||||
[
|
||||
{
|
||||
url: "/moar/getServerConfigWithOverrides",
|
||||
action: async () => {
|
||||
return JSON.stringify({
|
||||
...globalValues.baseConfig,
|
||||
...globalValues.overrideConfig,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
"getServerConfigWithOverrides"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`moarGetPresetsList`,
|
||||
[
|
||||
{
|
||||
url: "/moar/getPresets",
|
||||
action: async () => {
|
||||
let result = [
|
||||
...Object.keys(PresetWeightingsConfig).map((preset) => ({
|
||||
Name: kebabToTitle(preset),
|
||||
Label: preset,
|
||||
})),
|
||||
{ Name: "Random", Label: "random" },
|
||||
{ Name: "Custom", Label: "custom" },
|
||||
];
|
||||
|
||||
return JSON.stringify({ data: result });
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarGetPresetsList"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
"setOverrideConfig",
|
||||
[
|
||||
{
|
||||
url: "/moar/setOverrideConfig",
|
||||
action: async (
|
||||
url: string,
|
||||
overrideConfig: typeof globalValues.overrideConfig = {},
|
||||
sessionID,
|
||||
output
|
||||
) => {
|
||||
globalValues.overrideConfig = overrideConfig;
|
||||
|
||||
buildWaves(container);
|
||||
|
||||
return "Success";
|
||||
},
|
||||
},
|
||||
],
|
||||
"setOverrideConfig"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
"moarSetPreset",
|
||||
[
|
||||
{
|
||||
url: "/moar/setPreset",
|
||||
action: async (url: string, { Preset }, sessionID, output) => {
|
||||
globalValues.forcedPreset = Preset;
|
||||
buildWaves(container);
|
||||
|
||||
return `Current Preset: ${kebabToTitle(
|
||||
globalValues.forcedPreset || "Random"
|
||||
)}`;
|
||||
},
|
||||
},
|
||||
],
|
||||
"moarSetPreset"
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// context/index.js
|
||||
export { default as PlayerSpawns } from "../../config/Spawns/playerSpawns.json";
|
||||
export { default as ScavSpawns } from "../../config/Spawns/scavSpawns.json";
|
||||
export { default as SniperSpawns } from "../../config/Spawns/sniperSpawns.json";
|
||||
export { default as PmcSpawns } from "../../config/Spawns/pmcSpawns.json";
|
||||
243
user/mods/DewardianDev-MOAR/src/SpawnZoneChanges/setupSpawn.ts
Normal file
243
user/mods/DewardianDev-MOAR/src/SpawnZoneChanges/setupSpawn.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
import { DatabaseServer } from "@spt/servers/DatabaseServer";
|
||||
import { configLocations, originalMapList } from "../Spawning/constants";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import advancedConfig from "../../config/advancedConfig.json";
|
||||
import { ISpawnPointParam } from "@spt/models/eft/common/ILocationBase";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
import {
|
||||
AddCustomBotSpawnPoints,
|
||||
BuildCustomPlayerSpawnPoints,
|
||||
AddCustomPmcSpawnPoints,
|
||||
AddCustomSniperSpawnPoints,
|
||||
cleanClosest,
|
||||
getClosestZone,
|
||||
removeClosestSpawnsFromCustomBots,
|
||||
} from "../Spawning/spawnZoneUtils";
|
||||
import { shuffle } from "../Spawning/utils";
|
||||
import { PlayerSpawns, PmcSpawns, ScavSpawns, SniperSpawns } from ".";
|
||||
import { updateAllBotSpawns } from "./updateUtils";
|
||||
import { showMapCullingDebug } from "../../config/advancedConfig.json"
|
||||
|
||||
export const setupSpawns = (container: DependencyContainer) => {
|
||||
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||
const { locations } = databaseServer.getTables();
|
||||
|
||||
const indexedMapSpawns: Record<number, ISpawnPointParam[]> = {};
|
||||
|
||||
const mapsToExcludeFromPlayerCulling = new Set([
|
||||
"factory4_day",
|
||||
"factory4_night",
|
||||
"laboratory",
|
||||
]);
|
||||
|
||||
originalMapList.forEach((map, mapIndex) => {
|
||||
const configMap = configLocations[mapIndex];
|
||||
const allZones = [
|
||||
...new Set(
|
||||
locations[map].base.SpawnPointParams.filter(
|
||||
({ BotZoneName }: ISpawnPointParam) => !!BotZoneName
|
||||
).map(({ BotZoneName }: ISpawnPointParam) => BotZoneName)
|
||||
),
|
||||
];
|
||||
|
||||
locations[map].base.OpenZones = allZones.join(",");
|
||||
|
||||
let bossSpawns: ISpawnPointParam[] = [];
|
||||
let scavSpawns: ISpawnPointParam[] = [];
|
||||
let sniperSpawns: ISpawnPointParam[] = [];
|
||||
|
||||
let pmcSpawns: ISpawnPointParam[] = [];
|
||||
|
||||
const bossZoneList = new Set([
|
||||
"Zone_Blockpost",
|
||||
"Zone_RoofRocks",
|
||||
"Zone_RoofContainers",
|
||||
"Zone_RoofBeach",
|
||||
"Zone_TreatmentRocks",
|
||||
"Zone_TreatmentBeach",
|
||||
"Zone_Hellicopter",
|
||||
"Zone_Island",
|
||||
"BotZoneGate1",
|
||||
"BotZoneGate2",
|
||||
"BotZoneBasement",
|
||||
]);
|
||||
|
||||
const isGZ = map.includes("sandbox");
|
||||
|
||||
shuffle<ISpawnPointParam[]>(locations[map].base.SpawnPointParams).forEach(
|
||||
(point) => {
|
||||
switch (true) {
|
||||
case point.Categories.includes("Boss") ||
|
||||
bossZoneList.has(point.BotZoneName):
|
||||
bossSpawns.push(point);
|
||||
break;
|
||||
|
||||
case point.BotZoneName?.toLowerCase().includes("snipe") ||
|
||||
(map !== "lighthouse" && point.DelayToCanSpawnSec > 40):
|
||||
sniperSpawns.push(point);
|
||||
break;
|
||||
|
||||
case !!point.Infiltration || point.Categories.includes("Coop"):
|
||||
pmcSpawns.push(point);
|
||||
break;
|
||||
default:
|
||||
scavSpawns.push(point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// fix GZ
|
||||
if (isGZ) {
|
||||
sniperSpawns.map((point, index) => {
|
||||
if (index < 2) {
|
||||
point.BotZoneName = Math.random()
|
||||
? "ZoneSandSnipeCenter"
|
||||
: "ZoneSandSnipeCenter2";
|
||||
} else {
|
||||
point.BotZoneName = ["ZoneSandSnipeCenter", "ZoneSandSnipeCenter2"][
|
||||
index
|
||||
];
|
||||
}
|
||||
return point;
|
||||
});
|
||||
}
|
||||
|
||||
if (advancedConfig.ActivateSpawnCullingOnServerStart) {
|
||||
ScavSpawns[map] =
|
||||
removeClosestSpawnsFromCustomBots(
|
||||
ScavSpawns,
|
||||
scavSpawns,
|
||||
map,
|
||||
configLocations[mapIndex]
|
||||
) || [];
|
||||
PmcSpawns[map] =
|
||||
removeClosestSpawnsFromCustomBots(
|
||||
PmcSpawns,
|
||||
pmcSpawns,
|
||||
map,
|
||||
configLocations[mapIndex]
|
||||
) || [];
|
||||
PlayerSpawns[map] =
|
||||
removeClosestSpawnsFromCustomBots(
|
||||
PlayerSpawns,
|
||||
pmcSpawns,
|
||||
map,
|
||||
configLocations[mapIndex]
|
||||
) || [];
|
||||
SniperSpawns[map] =
|
||||
removeClosestSpawnsFromCustomBots(
|
||||
SniperSpawns,
|
||||
sniperSpawns,
|
||||
map,
|
||||
configLocations[mapIndex]
|
||||
) || [];
|
||||
}
|
||||
|
||||
const { spawnMinDistance: limit } = mapConfig[configLocations[mapIndex]];
|
||||
|
||||
let playerSpawns = BuildCustomPlayerSpawnPoints(
|
||||
map,
|
||||
locations[map].base.SpawnPointParams
|
||||
);
|
||||
|
||||
playerSpawns = cleanClosest(
|
||||
playerSpawns,
|
||||
mapIndex,
|
||||
mapConfig[configMap].mapCullingNearPointValuePlayer
|
||||
);
|
||||
|
||||
scavSpawns = cleanClosest(
|
||||
AddCustomBotSpawnPoints(scavSpawns, map),
|
||||
mapIndex,
|
||||
mapConfig[configMap].mapCullingNearPointValueScav
|
||||
).map((point, botIndex) => {
|
||||
if (point.ColliderParams?._props?.Radius < limit) {
|
||||
point.ColliderParams._props.Radius = limit;
|
||||
}
|
||||
|
||||
return !!point.Categories.length
|
||||
? {
|
||||
...point,
|
||||
BotZoneName: isGZ ? "ZoneSandbox" : point?.BotZoneName,
|
||||
Categories: ["Bot"],
|
||||
Sides: ["Savage"],
|
||||
CorePointId: 1,
|
||||
}
|
||||
: point;
|
||||
});
|
||||
|
||||
pmcSpawns = cleanClosest(
|
||||
AddCustomPmcSpawnPoints(pmcSpawns, map),
|
||||
mapIndex,
|
||||
mapConfig[configMap].mapCullingNearPointValuePmc
|
||||
).map((point, pmcIndex) => {
|
||||
if (point.ColliderParams?._props?.Radius < limit) {
|
||||
point.ColliderParams._props.Radius = limit;
|
||||
}
|
||||
|
||||
return !!point.Categories.length
|
||||
? {
|
||||
...point,
|
||||
BotZoneName: isGZ
|
||||
? "ZoneSandbox"
|
||||
: getClosestZone(
|
||||
scavSpawns,
|
||||
point.Position.x,
|
||||
point.Position.y,
|
||||
point.Position.z
|
||||
),
|
||||
Categories: ["Coop", Math.random() ? "Group" : "Opposite"],
|
||||
Sides: ["Pmc"],
|
||||
CorePointId: 0,
|
||||
}
|
||||
: point;
|
||||
});
|
||||
|
||||
sniperSpawns = AddCustomSniperSpawnPoints(sniperSpawns, map);
|
||||
|
||||
indexedMapSpawns[mapIndex] = [
|
||||
...sniperSpawns.map((point) => ({ ...point, type: "sniper" })),
|
||||
...bossSpawns.map((point) => ({ ...point, type: "boss" })),
|
||||
...scavSpawns.map((point) => ({ ...point, type: "scav" })),
|
||||
...pmcSpawns.map((point) => ({ ...point, type: "pmc" })),
|
||||
...playerSpawns.map((point) => ({ ...point, type: "player" })),
|
||||
];
|
||||
showMapCullingDebug &&
|
||||
console.log(
|
||||
"sniperSpawns",
|
||||
sniperSpawns.length,
|
||||
"bossSpawns",
|
||||
bossSpawns.length,
|
||||
"scavSpawns",
|
||||
scavSpawns.length,
|
||||
"pmcSpawns",
|
||||
pmcSpawns.length,
|
||||
"playerSpawns",
|
||||
playerSpawns.length,
|
||||
map
|
||||
);
|
||||
|
||||
locations[map].base.SpawnPointParams = indexedMapSpawns[mapIndex];
|
||||
|
||||
const listToAddToOpenZones = [
|
||||
...new Set(
|
||||
locations[map].base.SpawnPointParams.map(
|
||||
({ BotZoneName }) => BotZoneName
|
||||
).filter((zone) => !!zone)
|
||||
),
|
||||
];
|
||||
|
||||
locations[map].base.OpenZones = listToAddToOpenZones.join(",");
|
||||
});
|
||||
|
||||
// PlayerSpawns, PmcSpawns, ScavSpawns, SniperSpawns
|
||||
if (advancedConfig.ActivateSpawnCullingOnServerStart) {
|
||||
updateAllBotSpawns(PlayerSpawns, "playerSpawns");
|
||||
updateAllBotSpawns(PmcSpawns, "pmcSpawns");
|
||||
updateAllBotSpawns(ScavSpawns, "scavSpawns");
|
||||
updateAllBotSpawns(SniperSpawns, "sniperSpawns");
|
||||
}
|
||||
globalValues.indexedMapSpawns = indexedMapSpawns;
|
||||
};
|
||||
112
user/mods/DewardianDev-MOAR/src/SpawnZoneChanges/updateUtils.ts
Normal file
112
user/mods/DewardianDev-MOAR/src/SpawnZoneChanges/updateUtils.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { Ixyz } from "@spt/models/eft/common/Ixyz";
|
||||
import { getDistance } from "../Spawning/spawnZoneUtils";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const currentDirectory = process.cwd();
|
||||
// Function to update JSON file
|
||||
export const updateJsonFile = <T>(
|
||||
filePath: string,
|
||||
callback: (jsonData) => void,
|
||||
successMessage: string
|
||||
) => {
|
||||
// Read the JSON file
|
||||
fs.readFile(filePath, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
console.error("Error reading the file:", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the JSON data
|
||||
let jsonData;
|
||||
try {
|
||||
jsonData = JSON.parse(data);
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing JSON data:", parseError);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(jsonData);
|
||||
|
||||
// Update the JSON object
|
||||
|
||||
// Write the updated JSON object back to the file
|
||||
fs.writeFile(
|
||||
filePath,
|
||||
JSON.stringify(jsonData, null, 2),
|
||||
"utf8",
|
||||
(writeError) => {
|
||||
if (writeError) {
|
||||
console.error("Error writing the file:", writeError);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(successMessage);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const updateBotSpawn = (
|
||||
map: string,
|
||||
value: Ixyz,
|
||||
type: "player" | "pmc" | "scav" | "sniper"
|
||||
) => {
|
||||
map = map.toLowerCase();
|
||||
updateJsonFile<Ixyz>(
|
||||
`${currentDirectory}/user/mods/DewardianDev-MOAR/config/Spawns/${type}Spawns.json`,
|
||||
(jsonData) => {
|
||||
value.y = value.y + 0.5;
|
||||
if (jsonData[map]) {
|
||||
jsonData[map].push(value);
|
||||
} else {
|
||||
jsonData[map] = [value];
|
||||
}
|
||||
},
|
||||
"Successfully added one bot spawn to " + map
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteBotSpawn = (
|
||||
map: string,
|
||||
value: Ixyz,
|
||||
type: "player" | "pmc" | "scav" | "sniper"
|
||||
) => {
|
||||
map = map.toLowerCase();
|
||||
updateJsonFile<Ixyz>(
|
||||
`${currentDirectory}/user/mods/DewardianDev-MOAR/config/Spawns/${type}Spawns.json`,
|
||||
(jsonData) => {
|
||||
if (jsonData[map]) {
|
||||
const { x: X, y: Y, z: Z } = value;
|
||||
let nearest = undefined;
|
||||
let nearDist = Infinity;
|
||||
jsonData[map].forEach(({ x, y, z }, index) => {
|
||||
const dist = getDistance(x, y, z, X, Y, Z);
|
||||
if (dist < nearDist) {
|
||||
nearest = index;
|
||||
nearDist = dist;
|
||||
}
|
||||
});
|
||||
|
||||
if (nearest) {
|
||||
(jsonData[map] as Ixyz[]).splice(nearest, 1);
|
||||
} else {
|
||||
console.log("No nearest spawn on " + map);
|
||||
}
|
||||
}
|
||||
},
|
||||
"Successfully removed one bot spawn from "
|
||||
);
|
||||
};
|
||||
|
||||
export const updateAllBotSpawns = (
|
||||
values: Record<string, Ixyz[]>,
|
||||
targetType: string
|
||||
) =>
|
||||
updateJsonFile<Ixyz>(
|
||||
`${currentDirectory}/user/mods/DewardianDev-MOAR/config/Spawns/${targetType}.json`,
|
||||
(jsonData) => {
|
||||
Object.keys(jsonData).forEach((map) => (jsonData[map] = values[map]));
|
||||
},
|
||||
"Successfully updated all Spawns"
|
||||
);
|
||||
173
user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts
Normal file
173
user/mods/DewardianDev-MOAR/src/Spawning/Spawning.ts
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
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,
|
||||
saveToFile,
|
||||
} 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 { enforceSmoothing, setEscapeTimeOverrides } from "./utils";
|
||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import updateSpawnLocations from "./updateSpawnLocations";
|
||||
import marksmanChanges from "./marksmanChanges";
|
||||
import advancedConfig from "../../config/advancedConfig.json";
|
||||
|
||||
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 } = 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.forcedPreset
|
||||
? globalValues.forcedPreset
|
||||
: 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.removeExistingPmcWaves = true;
|
||||
|
||||
Object.keys(pmcConfig.customPmcWaves).forEach(key => {
|
||||
pmcConfig.customPmcWaves[key] = []
|
||||
})
|
||||
|
||||
|
||||
if (config.startingPmcs && (!config.randomSpawns || config.spawnSmoothing)) {
|
||||
Logger.warning(
|
||||
`[MOAR] Starting pmcs turned on, turning off cascade system and smoothing.\n`
|
||||
);
|
||||
config.spawnSmoothing = false;
|
||||
config.randomSpawns = true;
|
||||
}
|
||||
|
||||
if (advancedConfig.MarksmanDifficultyChanges) {
|
||||
marksmanChanges(bots);
|
||||
}
|
||||
|
||||
updateSpawnLocations(locationList, config);
|
||||
|
||||
setEscapeTimeOverrides(locationList, _mapConfig, Logger, config);
|
||||
|
||||
// BOSS RELATED STUFF!
|
||||
buildBossWaves(config, locationList);
|
||||
|
||||
//Zombies
|
||||
if (config.zombiesEnabled) {
|
||||
buildZombieWaves(config, locationList, bots);
|
||||
}
|
||||
|
||||
buildPmcs(config, locationList);
|
||||
|
||||
// Make main waves
|
||||
buildScavMarksmanWaves(config, locationList, botConfig);
|
||||
|
||||
// enableSmoothing
|
||||
if (config.spawnSmoothing) {
|
||||
enforceSmoothing(locationList);
|
||||
}
|
||||
|
||||
// saveToFile(locations.bigmap.base.SpawnPointParams, "spawns.json");
|
||||
|
||||
originalMapList.forEach((name, index) => {
|
||||
if (!locations[name]) {
|
||||
console.log("[MOAR] OH CRAP we have a problem!", name);
|
||||
} else {
|
||||
locations[name] = locationList[index];
|
||||
}
|
||||
});
|
||||
};
|
||||
341
user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts
Normal file
341
user/mods/DewardianDev-MOAR/src/Spawning/buildBossWaves.ts
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import _config from "../../config/config.json";
|
||||
import bossConfig from "../../config/bossConfig.json";
|
||||
import advancedConfig from "../../config/advancedConfig.json";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import {
|
||||
bossesToRemoveFromPool,
|
||||
bossPerformanceHash,
|
||||
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)
|
||||
);
|
||||
|
||||
// Performance changes
|
||||
if (advancedConfig.EnableBossPerformanceImprovements) {
|
||||
locationList[indx].base.BossLocationSpawn.forEach((Boss, bIndex) => {
|
||||
if (Boss.BossChance < 1) return;
|
||||
if (!!bossPerformanceHash[Boss.BossName || ""]) {
|
||||
|
||||
const varsToUpdate: Record<string, any> =
|
||||
bossPerformanceHash[Boss.BossName];
|
||||
// make it so bossPartisan has a random spawn time
|
||||
if (Boss.BossName === "bossPartisan") {
|
||||
const max = locationList[
|
||||
indx
|
||||
].base.EscapeTimeLimit
|
||||
|
||||
varsToUpdate.Time = Math.floor(Math.random() * 50 * max)
|
||||
// console.log(varsToUpdate, max * 60)
|
||||
}
|
||||
|
||||
locationList[indx].base.BossLocationSpawn[bIndex] = {
|
||||
...Boss,
|
||||
...varsToUpdate,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
const duplicateBosses = [
|
||||
...locationList[key].base.BossLocationSpawn.filter(
|
||||
({ BossName, BossZone }) => bossList.includes(BossName)
|
||||
).map(({ BossName }) => BossName),
|
||||
"bossKnight", // So knight doesn't invade
|
||||
];
|
||||
|
||||
//Build bosses to add
|
||||
const bossesToAdd = shuffle<IBossLocationSpawn[]>(Object.values(bosses))
|
||||
.filter(({ BossName }) => !duplicateBosses.includes(BossName))
|
||||
.map((boss, j) => ({
|
||||
...boss,
|
||||
BossZone: "",
|
||||
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 from bossConfig.json --- `
|
||||
);
|
||||
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)}`
|
||||
);
|
||||
// console.log(locationList[index].base.BossLocationSpawn.length);
|
||||
|
||||
const bossesToSkip = new Set(["sectantPriest", "pmcBot"]);
|
||||
// Apply the percentages on all bosses, cull those that won't spawn, make all bosses 100 chance that remain.
|
||||
locationList[index].base.BossLocationSpawn = locationList[
|
||||
index
|
||||
].base.BossLocationSpawn.map(
|
||||
({ BossChance, BossName, TriggerId }, bossIndex) => {
|
||||
if (BossChance < 1) {
|
||||
return locationList[index].base.BossLocationSpawn[bossIndex];
|
||||
}
|
||||
if (
|
||||
!TriggerId &&
|
||||
!bossesToSkip.has(BossName) &&
|
||||
BossChance < 100
|
||||
) {
|
||||
if (
|
||||
BossChance / 100 < Math.random()) {
|
||||
locationList[index].base.BossLocationSpawn[
|
||||
bossIndex
|
||||
].BossChance = 0;
|
||||
|
||||
locationList[index].base.BossLocationSpawn[bossIndex].ForceSpawn =
|
||||
false;
|
||||
|
||||
locationList[index].base.BossLocationSpawn[
|
||||
bossIndex
|
||||
].IgnoreMaxBots = false;
|
||||
} else {
|
||||
locationList[index].base.BossLocationSpawn[
|
||||
bossIndex
|
||||
].BossChance = 100;
|
||||
}
|
||||
}
|
||||
return locationList[index].base.BossLocationSpawn[bossIndex];
|
||||
}
|
||||
).filter(({ BossChance, BossName, ...rest }) => {
|
||||
if (BossChance < 1) {
|
||||
return false;
|
||||
}
|
||||
return true
|
||||
});
|
||||
|
||||
// if (mapName === "lighthouse") {
|
||||
// console.log(
|
||||
// locationList[index].base.BossLocationSpawn.map(
|
||||
// ({ BossName, BossChance }) => ({ BossName, BossChance })
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
});
|
||||
|
||||
if (hasChangedBossSpawns) {
|
||||
console.log(
|
||||
`[MOAR]: --- Adjusting default boss spawn rates complete --- \n`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts
Normal file
142
user/mods/DewardianDev-MOAR/src/Spawning/buildPmcs.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import _config from "../../config/config.json";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import { defaultEscapeTimes, defaultHostility } from "./constants";
|
||||
import { buildBotWaves, looselyShuffle, MapSettings, shuffle } from "./utils";
|
||||
import { saveToFile } from "../utils";
|
||||
import getSortedSpawnPointList from "./spawnZoneUtils";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
|
||||
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];
|
||||
|
||||
// Set pmcs hostile to everything
|
||||
locationList[index].base.BotLocationModifier.AdditionalHostilitySettings =
|
||||
defaultHostility;
|
||||
|
||||
const {
|
||||
pmcHotZones = [],
|
||||
pmcWaveCount,
|
||||
initialSpawnDelay,
|
||||
} = (mapConfig?.[map] as MapSettings) || {};
|
||||
|
||||
const {
|
||||
Position: { x, y, z },
|
||||
} = globalValues.playerSpawn;
|
||||
|
||||
let pmcZones = getSortedSpawnPointList(
|
||||
locationList[index].base.SpawnPointParams.filter(
|
||||
(point) => point["type"] === "pmc"
|
||||
),
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
0.05
|
||||
).map(({ BotZoneName }) => BotZoneName);
|
||||
|
||||
looselyShuffle(pmcZones, 3);
|
||||
|
||||
// console.log(pmcZones);
|
||||
|
||||
if (map === "laboratory") {
|
||||
pmcZones = new Array(10).fill(pmcZones).flat(1);
|
||||
}
|
||||
|
||||
if (config.randomSpawns) pmcZones = shuffle<string[]>(pmcZones);
|
||||
|
||||
const escapeTimeLimitRatio = Math.round(
|
||||
locationList[index].base.EscapeTimeLimit / defaultEscapeTimes[map]
|
||||
);
|
||||
|
||||
let totalWaves = Math.round(
|
||||
pmcWaveCount * config.pmcWaveQuantity * escapeTimeLimitRatio
|
||||
);
|
||||
|
||||
if (!!pmcHotZones.length && totalWaves > 0) {
|
||||
totalWaves = totalWaves + pmcHotZones.length;
|
||||
}
|
||||
|
||||
while (totalWaves - pmcZones.length > 0) {
|
||||
console.log(
|
||||
`${map} ran out of appropriate zones for pmcs, duplicating zones`
|
||||
);
|
||||
// const addEmpty = new Array(numberOfZoneless).fill("");
|
||||
pmcZones = [...pmcZones, ...pmcZones];
|
||||
if (pmcZones.length === 0) {
|
||||
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 timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||
|
||||
const half = Math.round(
|
||||
totalWaves % 2 === 0 ? totalWaves / 2 : (totalWaves + 1) / 2
|
||||
);
|
||||
|
||||
const usecSpawns = pmcZones.filter((_, i) => i % 2 === 0);
|
||||
const bearSpawns = pmcZones.filter((_, i) => i % 2 !== 0);
|
||||
|
||||
const pmcUSEC = buildBotWaves(
|
||||
half,
|
||||
config.startingPmcs ? Math.round(0.2 * timeLimit) : timeLimit,
|
||||
config.pmcMaxGroupSize - 1,
|
||||
config.pmcGroupChance,
|
||||
usecSpawns,
|
||||
config.pmcDifficulty,
|
||||
"pmcUSEC",
|
||||
false,
|
||||
config.pmcWaveDistribution,
|
||||
initialSpawnDelay + Math.round(10 * Math.random())
|
||||
);
|
||||
|
||||
const pmcBEAR = buildBotWaves(
|
||||
half,
|
||||
config.startingPmcs ? Math.round(0.1 * timeLimit) : timeLimit,
|
||||
config.pmcMaxGroupSize - 1,
|
||||
config.pmcGroupChance,
|
||||
bearSpawns,
|
||||
config.pmcDifficulty,
|
||||
"pmcBEAR",
|
||||
false,
|
||||
config.pmcWaveDistribution,
|
||||
initialSpawnDelay + Math.round(10 * Math.random())
|
||||
);
|
||||
|
||||
const pmcs = [...pmcUSEC, ...pmcBEAR];
|
||||
// console.log(pmcs.map(({ Time }) => Time));
|
||||
if (pmcs.length) {
|
||||
// Add hotzones if exist
|
||||
pmcHotZones.forEach((hotzone) => {
|
||||
const index = Math.floor(pmcs.length * Math.random());
|
||||
pmcs[index].BossZone = hotzone;
|
||||
// console.log(pmcs[index]);
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// map,
|
||||
// pmcs.map(({ BossZone }) => BossZone)
|
||||
// );
|
||||
|
||||
locationList[index].base.BossLocationSpawn = [
|
||||
...pmcs,
|
||||
...locationList[index].base.BossLocationSpawn,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import _config from "../../config/config.json";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import { defaultEscapeTimes, originalMapList } from "./constants";
|
||||
import { buildBotWaves, looselyShuffle, MapSettings, shuffle } from "./utils";
|
||||
import { WildSpawnType } from "@spt/models/eft/common/ILocationBase";
|
||||
import { IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
||||
import { saveToFile } from "../utils";
|
||||
import getSortedSpawnPointList from "./spawnZoneUtils";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
|
||||
export default function buildScavMarksmanWaves(
|
||||
config: typeof _config,
|
||||
locationList: ILocation[],
|
||||
botConfig: IBotConfig
|
||||
) {
|
||||
let {
|
||||
maxBotCap,
|
||||
scavWaveQuantity,
|
||||
scavWaveDistribution,
|
||||
sniperMaxGroupSize,
|
||||
maxBotPerZone,
|
||||
scavMaxGroupSize,
|
||||
scavDifficulty,
|
||||
sniperGroupChance,
|
||||
scavGroupChance,
|
||||
} = 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.waves = [];
|
||||
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 = [],
|
||||
sniperQuantity = 1,
|
||||
scavWaveCount,
|
||||
initialSpawnDelay,
|
||||
} = (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;
|
||||
locationList[index].base.BotMaxPlayer = 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, DelayToCanSpawnSec, BotZoneName, Sides }) =>
|
||||
// !Categories.includes("Boss") &&
|
||||
// Sides[0] === "Savage" &&
|
||||
// (BotZoneName?.toLowerCase().includes("snipe") ||
|
||||
// DelayToCanSpawnSec > 40)
|
||||
// )
|
||||
// .map(({ BotZoneName }) => BotZoneName || "")
|
||||
// );
|
||||
|
||||
const {
|
||||
Position: { x, y, z },
|
||||
} = globalValues.playerSpawn;
|
||||
|
||||
const sniperSpawns = getSortedSpawnPointList(
|
||||
locationList[index].base.SpawnPointParams.filter(
|
||||
(point) => point["type"] === "sniper"
|
||||
),
|
||||
x,
|
||||
y,
|
||||
z
|
||||
);
|
||||
|
||||
let sniperLocations = sniperSpawns.map(({ BotZoneName }) => BotZoneName);
|
||||
// console.log(sniperLocations);
|
||||
|
||||
const sniperDelay = 25;
|
||||
// Make sure that the sniper spawns permit snipers to actually spawn early.
|
||||
const sniperIds = new Set(sniperSpawns.map(({ Id }) => Id));
|
||||
|
||||
locationList[index].base.SpawnPointParams.forEach((point, snipeIndex) => {
|
||||
if (sniperIds.has(point.Id)) {
|
||||
locationList[index].base.SpawnPointParams[
|
||||
snipeIndex
|
||||
].DelayToCanSpawnSec = 20;
|
||||
}
|
||||
});
|
||||
|
||||
if (sniperLocations.length) {
|
||||
locationList[index].base.MinMaxBots = [
|
||||
{
|
||||
WildSpawnType: "marksman",
|
||||
max: sniperLocations.length * 5,
|
||||
min: sniperLocations.length,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let scavZones = getSortedSpawnPointList(
|
||||
locationList[index].base.SpawnPointParams.filter(
|
||||
(point) => point["type"] === "scav"
|
||||
),
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
0.05
|
||||
).map(({ BotZoneName }) => BotZoneName);
|
||||
|
||||
looselyShuffle(scavZones, 3);
|
||||
|
||||
const escapeTimeLimitRatio = Math.round(
|
||||
locationList[index].base.EscapeTimeLimit / defaultEscapeTimes[map]
|
||||
);
|
||||
|
||||
// Scavs
|
||||
let scavTotalWaveCount = Math.round(
|
||||
scavWaveCount * scavWaveQuantity * escapeTimeLimitRatio
|
||||
);
|
||||
|
||||
if (scavHotZones.length && scavTotalWaveCount > 0) {
|
||||
scavTotalWaveCount = scavTotalWaveCount + scavHotZones.length;
|
||||
}
|
||||
|
||||
while (scavTotalWaveCount - scavZones.length > 0) {
|
||||
console.log(
|
||||
`${map} ran out of appropriate zones for scavs, duplicating zones`
|
||||
);
|
||||
// const addEmpty = new Array(numberOfZoneless).fill("");
|
||||
scavZones = [...scavZones, ...scavZones];
|
||||
if (scavZones.length === 0) {
|
||||
scavZones = [""];
|
||||
}
|
||||
}
|
||||
|
||||
config.debug &&
|
||||
escapeTimeLimitRatio !== 1 &&
|
||||
console.log(
|
||||
`${map} Scav wave count changed from ${scavWaveCount} to ${scavTotalWaveCount} due to escapeTimeLimit adjustment`
|
||||
);
|
||||
|
||||
const timeLimit = locationList[index].base.EscapeTimeLimit * 60;
|
||||
|
||||
// if (config.randomSpawns)
|
||||
// sniperLocations = shuffle<string[]>(sniperLocations);
|
||||
// console.log(map);
|
||||
const snipers = buildBotWaves(
|
||||
Math.min(sniperQuantity, sniperLocations.length),
|
||||
timeLimit, ///30,
|
||||
sniperMaxGroupSize,
|
||||
sniperGroupChance,
|
||||
sniperLocations,
|
||||
0.8,
|
||||
WildSpawnType.MARKSMAN,
|
||||
true,
|
||||
0.3,
|
||||
sniperDelay
|
||||
);
|
||||
|
||||
if (config.randomSpawns) scavZones = shuffle<string[]>(scavZones);
|
||||
const scavWaves = buildBotWaves(
|
||||
scavTotalWaveCount,
|
||||
timeLimit,
|
||||
scavMaxGroupSize,
|
||||
scavGroupChance,
|
||||
scavZones,
|
||||
scavDifficulty,
|
||||
WildSpawnType.ASSAULT,
|
||||
false,
|
||||
scavWaveDistribution,
|
||||
initialSpawnDelay + Math.round(10 * Math.random())
|
||||
);
|
||||
|
||||
// Add hotzones if exist
|
||||
if (scavWaves.length) {
|
||||
scavHotZones.forEach((hotzone) => {
|
||||
const index = Math.floor(scavWaves.length * Math.random());
|
||||
scavWaves[index].BossZone = hotzone;
|
||||
// console.log(scavWaves[index].BossZone);
|
||||
});
|
||||
}
|
||||
// if (map === "shoreline") console.log(scavWaves.map(({ Time }) => Time));
|
||||
// console.log(snipers, scavWaves)
|
||||
locationList[index].base.BossLocationSpawn = [
|
||||
...snipers,
|
||||
...scavWaves,
|
||||
...locationList[index].base.BossLocationSpawn,
|
||||
];
|
||||
}
|
||||
}
|
||||
80
user/mods/DewardianDev-MOAR/src/Spawning/buildZombieWaves.ts
Normal file
80
user/mods/DewardianDev-MOAR/src/Spawning/buildZombieWaves.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import _config from "../../config/config.json";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import { configLocations, defaultEscapeTimes } from "./constants";
|
||||
import {
|
||||
buildZombie,
|
||||
getHealthBodyPartsByPercentage,
|
||||
zombieTypes,
|
||||
} from "./utils";
|
||||
import { IBots } from "@spt/models/spt/bots/IBots";
|
||||
|
||||
export default function buildZombieWaves(
|
||||
config: typeof _config,
|
||||
locationList: ILocation[],
|
||||
bots: IBots
|
||||
) {
|
||||
let { debug, zombieWaveDistribution, zombieWaveQuantity, zombieHealth } =
|
||||
config;
|
||||
|
||||
const zombieBodyParts = getHealthBodyPartsByPercentage(zombieHealth);
|
||||
zombieTypes.forEach((type) => {
|
||||
bots.types?.[type]?.health?.BodyParts?.forEach((_, index) => {
|
||||
bots.types[type].health.BodyParts[index] = zombieBodyParts;
|
||||
});
|
||||
});
|
||||
|
||||
for (let indx = 0; indx < locationList.length; indx++) {
|
||||
const location = locationList[indx].base;
|
||||
const mapSettingsList = Object.keys(mapConfig) as Array<
|
||||
keyof typeof mapConfig
|
||||
>;
|
||||
const map = mapSettingsList[indx];
|
||||
|
||||
const { zombieWaveCount } = mapConfig?.[configLocations[indx]];
|
||||
|
||||
// if (location.Events?.Halloween2024?.MaxCrowdAttackSpawnLimit)
|
||||
// location.Events.Halloween2024.MaxCrowdAttackSpawnLimit = 100;
|
||||
// if (location.Events?.Halloween2024?.CrowdCooldownPerPlayerSec)
|
||||
// location.Events.Halloween2024.CrowdCooldownPerPlayerSec = 60;
|
||||
// if (location.Events?.Halloween2024?.CrowdCooldownPerPlayerSec)
|
||||
// location.Events.Halloween2024.CrowdsLimit = 10;
|
||||
// if (location.Events?.Halloween2024?.CrowdAttackSpawnParams)
|
||||
// location.Events.Halloween2024.CrowdAttackSpawnParams = [];
|
||||
|
||||
if (!zombieWaveCount) return;
|
||||
|
||||
const escapeTimeLimitRatio = Math.round(
|
||||
locationList[indx].base.EscapeTimeLimit / defaultEscapeTimes[map]
|
||||
);
|
||||
|
||||
const zombieTotalWaveCount = Math.round(
|
||||
zombieWaveCount * zombieWaveQuantity * escapeTimeLimitRatio
|
||||
);
|
||||
|
||||
config.debug &&
|
||||
escapeTimeLimitRatio !== 1 &&
|
||||
console.log(
|
||||
`${map} Zombie wave count changed from ${zombieWaveCount} to ${zombieTotalWaveCount} due to escapeTimeLimit adjustment`
|
||||
);
|
||||
|
||||
const zombieWaves = buildZombie(
|
||||
zombieTotalWaveCount,
|
||||
location.EscapeTimeLimit * 60,
|
||||
zombieWaveDistribution,
|
||||
9999
|
||||
);
|
||||
|
||||
debug &&
|
||||
console.log(
|
||||
configLocations[indx],
|
||||
" generated ",
|
||||
zombieWaves.length,
|
||||
"Zombies"
|
||||
);
|
||||
|
||||
location.BossLocationSpawn.push(...zombieWaves);
|
||||
|
||||
// console.log(zombieWaves[0], zombieWaves[7]);
|
||||
}
|
||||
}
|
||||
232
user/mods/DewardianDev-MOAR/src/Spawning/constants.ts
Normal file
232
user/mods/DewardianDev-MOAR/src/Spawning/constants.ts
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
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",
|
||||
"bossPartisan",
|
||||
"spiritWinter",
|
||||
"spiritSpring",
|
||||
"peacemaker",
|
||||
"skier",
|
||||
"assault",
|
||||
"marksman",
|
||||
"pmcUSEC",
|
||||
"exUsec",
|
||||
"pmcBot",
|
||||
"bossBully",
|
||||
],
|
||||
AlwaysFriends: [
|
||||
"bossZryachiy",
|
||||
"followerZryachiy",
|
||||
"peacefullZryachiyEvent",
|
||||
"ravangeZryachiyEvent",
|
||||
"gifter",
|
||||
],
|
||||
BearEnemyChance: 100,
|
||||
BearPlayerBehaviour: "AlwaysEnemies",
|
||||
BotRole: "pmcBEAR",
|
||||
ChancedEnemies: [],
|
||||
Neutral: ["shooterBTR"],
|
||||
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",
|
||||
"bossPartisan",
|
||||
"spiritWinter",
|
||||
"spiritSpring",
|
||||
"peacemaker",
|
||||
"skier",
|
||||
"assault",
|
||||
"marksman",
|
||||
"pmcBEAR",
|
||||
"exUsec",
|
||||
"pmcBot",
|
||||
"bossBully",
|
||||
],
|
||||
AlwaysFriends: [
|
||||
"bossZryachiy",
|
||||
"followerZryachiy",
|
||||
"peacefullZryachiyEvent",
|
||||
"ravangeZryachiyEvent",
|
||||
"gifter",
|
||||
],
|
||||
BearEnemyChance: 100,
|
||||
BearPlayerBehaviour: "AlwaysEnemies",
|
||||
BotRole: "pmcUSEC",
|
||||
ChancedEnemies: [],
|
||||
Neutral: ["shooterBTR"],
|
||||
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,
|
||||
};
|
||||
|
||||
export const bossPerformanceHash = {
|
||||
bossZryachiy: {
|
||||
BossChance: 50,
|
||||
BossEscortAmount: "0",
|
||||
},
|
||||
exUsec: {
|
||||
BossEscortAmount: "1",
|
||||
BossChance: 40,
|
||||
},
|
||||
bossBully: {
|
||||
BossEscortAmount: "2,3",
|
||||
},
|
||||
bossBoar: {
|
||||
BossEscortAmount: "1,2,2,2",
|
||||
},
|
||||
bossBoarSniper: {
|
||||
BossEscortAmount: "1",
|
||||
},
|
||||
bossKojaniy: {
|
||||
BossEscortAmount: "1,2,2",
|
||||
},
|
||||
bossPartisan: {
|
||||
TriggerId: "",
|
||||
TriggerName: "",
|
||||
RandomTimeSpawn: false,
|
||||
Time:120,
|
||||
},
|
||||
// bossSanitar: {
|
||||
// BossEscortAmount: "1,2,3",
|
||||
// },
|
||||
};
|
||||
31
user/mods/DewardianDev-MOAR/src/Spawning/marksmanChanges.ts
Normal file
31
user/mods/DewardianDev-MOAR/src/Spawning/marksmanChanges.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { IDifficultyCategories } from "@spt/models/eft/common/tables/IBotType";
|
||||
import { IBots } from "@spt/models/spt/bots/IBots";
|
||||
import { saveToFile } from "../utils";
|
||||
|
||||
export default function marksmanChanges(bots: IBots) {
|
||||
// saveToFile(bots.types.marksman.difficulty, "marksmanDifficulty.json");
|
||||
for (const diff in bots.types.marksman.difficulty) {
|
||||
(bots.types.marksman.difficulty[diff] as IDifficultyCategories).Core = {
|
||||
...bots.types.marksman.difficulty[diff].Core,
|
||||
VisibleAngle: 300,
|
||||
VisibleDistance: 245,
|
||||
ScatteringPerMeter: 0.1,
|
||||
HearingSense: 2.85,
|
||||
};
|
||||
|
||||
(bots.types.marksman.difficulty[diff] as IDifficultyCategories).Mind = {
|
||||
...bots.types.marksman.difficulty[diff].Mind,
|
||||
BULLET_FEEL_DIST: 360,
|
||||
CHANCE_FUCK_YOU_ON_CONTACT_100: 10,
|
||||
};
|
||||
|
||||
(bots.types.marksman.difficulty[diff] as IDifficultyCategories).Hearing = {
|
||||
...bots.types.marksman.difficulty[diff].Hearing,
|
||||
CHANCE_TO_HEAR_SIMPLE_SOUND_0_1: 0.7,
|
||||
DISPERSION_COEF: 3.6,
|
||||
CLOSE_DIST: 10,
|
||||
FAR_DIST: 30,
|
||||
};
|
||||
}
|
||||
// saveToFile(bots.types.marksman.difficulty, "marksmanDifficulty2.json");
|
||||
}
|
||||
410
user/mods/DewardianDev-MOAR/src/Spawning/spawnZoneUtils.ts
Normal file
410
user/mods/DewardianDev-MOAR/src/Spawning/spawnZoneUtils.ts
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
import _config from "../../config/config.json";
|
||||
import { ISpawnPointParam } from "@spt/models/eft/common/ILocationBase";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import { Ixyz } from "@spt/models/eft/common/Ixyz";
|
||||
import { configLocations } from "./constants";
|
||||
import {
|
||||
ScavSpawns,
|
||||
PlayerSpawns,
|
||||
SniperSpawns,
|
||||
PmcSpawns,
|
||||
} from "../SpawnZoneChanges";
|
||||
import { MapConfigType } from "./utils";
|
||||
|
||||
function sq(n: number) {
|
||||
return n * n;
|
||||
}
|
||||
|
||||
function pt(a: number, b: number) {
|
||||
return Math.sqrt(sq(a) + sq(b));
|
||||
}
|
||||
|
||||
export const getDistance = (
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
mX: number,
|
||||
mY: number,
|
||||
mZ: number
|
||||
) => {
|
||||
(x = Math.abs(x - mX)), (y = Math.abs(y - mY)), (z = Math.abs(z - mZ));
|
||||
|
||||
return pt(pt(x, z), y);
|
||||
};
|
||||
|
||||
export default function getSortedSpawnPointList(
|
||||
SpawnPointParams: ISpawnPointParam[],
|
||||
mX: number,
|
||||
my: number,
|
||||
mZ: number,
|
||||
cull?: number
|
||||
): ISpawnPointParam[] {
|
||||
let culledAmount = 0;
|
||||
|
||||
const sorted = SpawnPointParams.sort((a, b) => {
|
||||
const a1 = getDistance(
|
||||
a.Position.x,
|
||||
a.Position.y,
|
||||
a.Position.z,
|
||||
mX,
|
||||
my,
|
||||
mZ
|
||||
);
|
||||
const b1 = getDistance(
|
||||
b.Position.x,
|
||||
b.Position.y,
|
||||
b.Position.z,
|
||||
mX,
|
||||
my,
|
||||
mZ
|
||||
);
|
||||
return a1 - b1;
|
||||
}).filter((_, index) => {
|
||||
if (!cull) return true;
|
||||
const result = index > SpawnPointParams.length * cull;
|
||||
if (!result) culledAmount++;
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
if (_config.debug && culledAmount > 0) {
|
||||
console.log(
|
||||
"Reduced to " +
|
||||
Math.round((sorted.length / SpawnPointParams.length) * 100) +
|
||||
"% of original spawns",
|
||||
SpawnPointParams.length,
|
||||
">",
|
||||
sorted.length,
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export function cleanClosest(
|
||||
SpawnPointParams: ISpawnPointParam[],
|
||||
mapIndex: number,
|
||||
mapCullingNearPointValue: number
|
||||
): ISpawnPointParam[] {
|
||||
const map = configLocations[mapIndex];
|
||||
|
||||
const okayList = new Set();
|
||||
const filteredParams = SpawnPointParams.map((point) => {
|
||||
const {
|
||||
Position: { x: X, y: Y, z: Z },
|
||||
} = point;
|
||||
const result = !SpawnPointParams.some(({ Position: { z, x, y }, Id }) => {
|
||||
const dist = getDistance(X, Y, Z, x, y, z);
|
||||
return mapCullingNearPointValue > dist && dist !== 0 && !okayList.has(Id);
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
okayList.add(point.Id);
|
||||
}
|
||||
|
||||
return result
|
||||
? point
|
||||
: {
|
||||
...point,
|
||||
DelayToCanSpawnSec: 9999999,
|
||||
CorePointId: 99999,
|
||||
Categories: [],
|
||||
Sides: [],
|
||||
};
|
||||
});
|
||||
|
||||
if (_config.debug) {
|
||||
const actualCulled = filteredParams.filter(
|
||||
({ Categories }) => !!Categories.length
|
||||
);
|
||||
console.log(
|
||||
map,
|
||||
filteredParams.length,
|
||||
">",
|
||||
actualCulled.length,
|
||||
"Reduced to " +
|
||||
Math.round((actualCulled.length / filteredParams.length) * 100) +
|
||||
"% of original spawns",
|
||||
// player ? "player" : "bot"
|
||||
); // high, low}
|
||||
}
|
||||
|
||||
return filteredParams.filter((point) => !!point.Categories.length);
|
||||
|
||||
// if (!_config.debug) {
|
||||
// const actualCulled = culled.filter(({ Categories }) => !!Categories.length);
|
||||
// console.log(
|
||||
// map,
|
||||
// "Reduced to " +
|
||||
// Math.round((actualCulled.length / culled.length) * 100) +
|
||||
// "% of original spawns",
|
||||
// culled.length,
|
||||
// ">",
|
||||
// actualCulled.length
|
||||
// // "\n"
|
||||
// ); // high, low}
|
||||
// }
|
||||
}
|
||||
|
||||
export function uuidv4() {
|
||||
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
|
||||
(
|
||||
+c ^
|
||||
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))
|
||||
).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
export const AddCustomPmcSpawnPoints = (
|
||||
SpawnPointParams: ISpawnPointParam[],
|
||||
map: string
|
||||
) => {
|
||||
if (!PmcSpawns[map] || !PmcSpawns[map].length) {
|
||||
_config.debug && console.log("no custom Bot spawns for " + map);
|
||||
return SpawnPointParams;
|
||||
}
|
||||
|
||||
const playerSpawns = PmcSpawns[map].map((coords: Ixyz, index: number) => ({
|
||||
BotZoneName: getClosestZone(SpawnPointParams, coords.x, coords.y, coords.z),
|
||||
Categories: ["Coop", Math.random() ? "Group" : "Opposite"],
|
||||
Sides: ["Pmc"],
|
||||
CorePointId: 0,
|
||||
ColliderParams: {
|
||||
_parent: "SpawnSphereParams",
|
||||
_props: {
|
||||
Center: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
Radius: 20,
|
||||
},
|
||||
},
|
||||
DelayToCanSpawnSec: 4,
|
||||
Id: uuidv4(),
|
||||
Infiltration: "",
|
||||
Position: coords,
|
||||
Rotation: random360(),
|
||||
}));
|
||||
|
||||
return [...SpawnPointParams, ...playerSpawns];
|
||||
};
|
||||
|
||||
export const AddCustomBotSpawnPoints = (
|
||||
SpawnPointParams: ISpawnPointParam[],
|
||||
map: string
|
||||
) => {
|
||||
if (!ScavSpawns[map] || !ScavSpawns[map].length) {
|
||||
_config.debug && console.log("no custom Bot spawns for " + map);
|
||||
return SpawnPointParams;
|
||||
}
|
||||
|
||||
const scavSpawns = ScavSpawns[map].map((coords: Ixyz) => ({
|
||||
BotZoneName: getClosestZone(SpawnPointParams, coords.x, coords.y, coords.z),
|
||||
Categories: ["Bot"],
|
||||
ColliderParams: {
|
||||
_parent: "SpawnSphereParams",
|
||||
_props: {
|
||||
Center: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
Radius: 20,
|
||||
},
|
||||
},
|
||||
CorePointId: 1,
|
||||
DelayToCanSpawnSec: 4,
|
||||
Id: uuidv4(),
|
||||
Infiltration: "",
|
||||
Position: coords,
|
||||
Rotation: random360(),
|
||||
Sides: ["Savage"],
|
||||
}));
|
||||
|
||||
return [...SpawnPointParams, ...scavSpawns];
|
||||
};
|
||||
|
||||
export const AddCustomSniperSpawnPoints = (
|
||||
SpawnPointParams: ISpawnPointParam[],
|
||||
map: string
|
||||
) => {
|
||||
if (!SniperSpawns[map] || !SniperSpawns[map].length) {
|
||||
_config.debug && console.log("no custom Player spawns for " + map);
|
||||
return SpawnPointParams;
|
||||
}
|
||||
|
||||
const sniperSpawns = SniperSpawns[map].map((coords: Ixyz, index: number) => ({
|
||||
BotZoneName:
|
||||
getClosestZone(SpawnPointParams, coords.x, coords.y, coords.z) ||
|
||||
"custom_snipe_" + index,
|
||||
Categories: ["Bot"],
|
||||
ColliderParams: {
|
||||
_parent: "SpawnSphereParams",
|
||||
_props: {
|
||||
Center: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
Radius: 20,
|
||||
},
|
||||
},
|
||||
CorePointId: 1,
|
||||
DelayToCanSpawnSec: 4,
|
||||
Id: uuidv4(),
|
||||
Infiltration: "",
|
||||
Position: coords,
|
||||
Rotation: random360(),
|
||||
Sides: ["Savage"],
|
||||
}));
|
||||
|
||||
return [...SpawnPointParams, ...sniperSpawns];
|
||||
};
|
||||
|
||||
export const random360 = () => Math.random() * 360;
|
||||
|
||||
export const BuildCustomPlayerSpawnPoints = (
|
||||
map: string,
|
||||
refSpawns: ISpawnPointParam[]
|
||||
) => {
|
||||
const playerOnlySpawns = refSpawns
|
||||
.filter((item) => !!item.Infiltration && item.Categories[0] === "Player")
|
||||
.map((point) => {
|
||||
point.ColliderParams._props.Radius = 1;
|
||||
point.Position.y = point.Position.y + 0.5;
|
||||
return {
|
||||
...point,
|
||||
BotZoneName: "",
|
||||
isCustom: true,
|
||||
Id: uuidv4(),
|
||||
Sides: ["Pmc"],
|
||||
};
|
||||
});
|
||||
|
||||
// console.log(map, playerOnlySpawns.length);
|
||||
if (!PlayerSpawns[map] || !PlayerSpawns[map].length) {
|
||||
_config.debug && console.log("no custom Player spawns for " + map);
|
||||
return playerOnlySpawns;
|
||||
}
|
||||
|
||||
const getClosestInfil = (X: number, Y: number, Z: number) => {
|
||||
let closest = Infinity;
|
||||
let selectedInfil = "";
|
||||
|
||||
playerOnlySpawns.forEach(({ Infiltration, Position: { x, y, z } }) => {
|
||||
const dist = getDistance(X, Y, Z, x, y, z);
|
||||
if (!!Infiltration && dist < closest) {
|
||||
closest = dist;
|
||||
selectedInfil = Infiltration;
|
||||
}
|
||||
});
|
||||
|
||||
return selectedInfil;
|
||||
};
|
||||
|
||||
const playerSpawns = PlayerSpawns[map].map((coords: Ixyz, index) => ({
|
||||
BotZoneName: "",
|
||||
Categories: ["Player"],
|
||||
ColliderParams: {
|
||||
_parent: "SpawnSphereParams",
|
||||
_props: {
|
||||
Center: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
Radius: 1,
|
||||
},
|
||||
},
|
||||
isCustom: true,
|
||||
CorePointId: 0,
|
||||
DelayToCanSpawnSec: 4,
|
||||
Id: uuidv4(),
|
||||
Infiltration: getClosestInfil(coords.x, coords.y, coords.z),
|
||||
Position: coords,
|
||||
Rotation: random360(),
|
||||
Sides: ["Pmc"],
|
||||
}));
|
||||
|
||||
// TODO: Check infils
|
||||
// console.log(map);
|
||||
// console.log(playerOnlySpawns[0], playerSpawns[0]);
|
||||
|
||||
return [...playerOnlySpawns, ...playerSpawns];
|
||||
};
|
||||
|
||||
export const getClosestZone = (
|
||||
params: ISpawnPointParam[],
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
) => {
|
||||
if (
|
||||
Array.isArray(params) &&
|
||||
!params.filter(({ BotZoneName }) => BotZoneName).length
|
||||
)
|
||||
return "";
|
||||
|
||||
return (
|
||||
getSortedSpawnPointList(params, x, y, z).find(
|
||||
({ BotZoneName }) => !!BotZoneName
|
||||
)?.BotZoneName || ""
|
||||
);
|
||||
};
|
||||
|
||||
export const removeClosestSpawnsFromCustomBots = (
|
||||
CustomBots: Record<string, Ixyz[]>,
|
||||
SpawnPointParams: ISpawnPointParam[],
|
||||
map: string,
|
||||
mapConfigMap: string
|
||||
) => {
|
||||
if (!CustomBots[map] || !CustomBots[map].length) {
|
||||
console.log(map, "Is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
const coords: Ixyz[] = CustomBots[map];
|
||||
|
||||
const { mapCullingNearPointValuePlayer,
|
||||
mapCullingNearPointValuePmc,
|
||||
mapCullingNearPointValueScav } = (mapConfig[mapConfigMap] as MapConfigType)
|
||||
|
||||
const mapCullingNearPointValue = (mapCullingNearPointValuePlayer +
|
||||
mapCullingNearPointValuePmc +
|
||||
mapCullingNearPointValueScav) / 3
|
||||
|
||||
|
||||
let filteredCoords = coords.filter(
|
||||
({ x: X, y: Y, z: Z }) =>
|
||||
!SpawnPointParams.some(({ Position: { z, x, y } }) => {
|
||||
return mapCullingNearPointValue > getDistance(X, Y, Z, x, y, z);
|
||||
})
|
||||
);
|
||||
|
||||
const okayList = new Set();
|
||||
|
||||
filteredCoords = [...coords].filter(({ x: X, y: Y, z: Z }, index) => {
|
||||
const result = !coords.some(({ z, x, y }) => {
|
||||
const dist = getDistance(X, Y, Z, x, y, z);
|
||||
return (
|
||||
mapCullingNearPointValue * 1.3 > dist &&
|
||||
dist !== 0 &&
|
||||
!okayList.has("" + x + y + z)
|
||||
);
|
||||
});
|
||||
if (!result) okayList.add("" + X + Y + Z);
|
||||
return result;
|
||||
});
|
||||
|
||||
console.log(
|
||||
map,
|
||||
coords.length,
|
||||
">",
|
||||
filteredCoords.length,
|
||||
"culled",
|
||||
coords.length - filteredCoords.length,
|
||||
"spawns"
|
||||
);
|
||||
return filteredCoords;
|
||||
};
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import { configLocations } from "./constants";
|
||||
import _config from "../../config/config.json";
|
||||
import { getRandomInArray, shuffle } from "./utils";
|
||||
import advancedConfig from "../../config/advancedConfig.json";
|
||||
import { ISpawnPointParam } from "@spt/models/eft/common/ILocationBase";
|
||||
import { globalValues } from "../GlobalValues";
|
||||
import getSortedSpawnPointList, {
|
||||
getClosestZone,
|
||||
getDistance,
|
||||
uuidv4,
|
||||
} from "./spawnZoneUtils";
|
||||
|
||||
export default function updateSpawnLocations(
|
||||
locationList: ILocation[],
|
||||
config: typeof _config
|
||||
) {
|
||||
for (let index = 0; index < locationList.length; index++) {
|
||||
const map = configLocations[index];
|
||||
const mapSpawns = [...globalValues.indexedMapSpawns[index]];
|
||||
|
||||
const playerSpawns = mapSpawns.filter(
|
||||
(point) => point?.["type"] === "player"
|
||||
);
|
||||
|
||||
const playerSpawn: ISpawnPointParam = getRandomInArray(playerSpawns);
|
||||
globalValues.playerSpawn = playerSpawn;
|
||||
|
||||
const { x, y, z } = playerSpawn.Position;
|
||||
|
||||
const sortedSpawnPointList = getSortedSpawnPointList(mapSpawns, x, y, z);
|
||||
|
||||
const possibleSpawnList: ISpawnPointParam[] = [];
|
||||
|
||||
sortedSpawnPointList.forEach((point) => {
|
||||
if (
|
||||
possibleSpawnList.length < advancedConfig.SpawnpointAreaTarget &&
|
||||
point?.["type"] === "player"
|
||||
) {
|
||||
point.ColliderParams._props.Radius = 1
|
||||
possibleSpawnList.push(point);
|
||||
}
|
||||
});
|
||||
|
||||
// const possibleSpawnListSet = new Set(possibleSpawnList.map(({ Id }) => Id));
|
||||
|
||||
locationList[index].base.SpawnPointParams = [
|
||||
...possibleSpawnList,
|
||||
...sortedSpawnPointList.filter((point) => point["type"] !== "player"),
|
||||
];
|
||||
|
||||
// {
|
||||
// if (point["type"] === "player" && !possibleSpawnListSet.has(point.Id)) {
|
||||
// point.Categories = [];
|
||||
// point.Sides = [];
|
||||
// }
|
||||
|
||||
// return point;
|
||||
// }
|
||||
|
||||
// console.log(
|
||||
// map,
|
||||
// locationList[index].base.SpawnPointParams.filter(
|
||||
// (point) => point?.["type"] === "player"
|
||||
// ).length,
|
||||
// locationList[index].base.SpawnPointParams.filter(
|
||||
// (point) => point?.Categories[0] === "Player"
|
||||
// ).length
|
||||
// );
|
||||
}
|
||||
}
|
||||
538
user/mods/DewardianDev-MOAR/src/Spawning/utils.ts
Normal file
538
user/mods/DewardianDev-MOAR/src/Spawning/utils.ts
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
import {
|
||||
IBossLocationSpawn,
|
||||
IWave,
|
||||
WildSpawnType,
|
||||
} from "@spt/models/eft/common/ILocationBase";
|
||||
import _config from "../../config/config.json";
|
||||
import mapConfig from "../../config/mapConfig.json";
|
||||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||
import { configLocations, 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 + 60;
|
||||
|
||||
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;
|
||||
let slotMin = (Math.round(Math.random() * slotMax) || 1) - 1;
|
||||
|
||||
if (wildSpawnType === "marksman" && slotMin < 1) slotMin = 1;
|
||||
waves.push({
|
||||
BotPreset,
|
||||
BotSide: getBotSide(wildSpawnType),
|
||||
SpawnPoints: getZone(
|
||||
specialZones,
|
||||
combinedZones,
|
||||
waves.length >= totalWaves
|
||||
),
|
||||
isPlayers: isPlayer,
|
||||
slots_max: slotMax,
|
||||
slots_min: slotMin,
|
||||
time_min: min,
|
||||
time_max: 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 buildBotWaves = (
|
||||
botTotal: number,
|
||||
escapeTimeLimit: number,
|
||||
maxGroup: number,
|
||||
groupChance: number,
|
||||
bossZones: string[],
|
||||
difficulty: number,
|
||||
botType: string,
|
||||
ForceSpawn: boolean,
|
||||
botDistribution: number,
|
||||
spawnDelay = 0
|
||||
): IBossLocationSpawn[] => {
|
||||
if (!botTotal) return [];
|
||||
const pushToEnd = botDistribution > 1;
|
||||
const pullFromEnd = botDistribution < 1;
|
||||
const botToZoneTotal = bossZones.length / botTotal;
|
||||
const isMarksman = botType === "marksman";
|
||||
const isPMC = botType === "pmcUSEC" || botType === "pmcBEAR";
|
||||
|
||||
let startTime = pushToEnd
|
||||
? Math.round((botDistribution - 1) * escapeTimeLimit)
|
||||
: spawnDelay;
|
||||
|
||||
escapeTimeLimit = pullFromEnd
|
||||
? Math.round(escapeTimeLimit * botDistribution)
|
||||
: Math.round(escapeTimeLimit - startTime);
|
||||
|
||||
const averageTime = Math.round(escapeTimeLimit / botTotal);
|
||||
|
||||
const waves: IBossLocationSpawn[] = [];
|
||||
let maxSlotsReached = botTotal;
|
||||
if (maxGroup < 1) maxGroup = 1;
|
||||
while (botTotal > 0) {
|
||||
const allowGroup = groupChance > Math.random();
|
||||
let bossEscortAmount = allowGroup
|
||||
? Math.round(maxGroup * Math.random())
|
||||
: 0;
|
||||
|
||||
if (
|
||||
bossEscortAmount < 0 ||
|
||||
(bossEscortAmount > 0 && bossEscortAmount + 1 > maxSlotsReached)
|
||||
) {
|
||||
bossEscortAmount = 0;
|
||||
}
|
||||
|
||||
const totalCountThisWave = isMarksman ? 1 : bossEscortAmount + 1;
|
||||
const totalCountThusFar = botTotal - maxSlotsReached;
|
||||
|
||||
const BossDifficult = getDifficulty(difficulty);
|
||||
|
||||
waves.push({
|
||||
BossChance: 100,
|
||||
BossDifficult,
|
||||
BossEscortAmount: bossEscortAmount.toString(),
|
||||
BossEscortDifficult: BossDifficult,
|
||||
BossEscortType: botType,
|
||||
BossName: botType,
|
||||
BossPlayer: false,
|
||||
BossZone: bossZones[Math.floor(totalCountThusFar * botToZoneTotal)] || "",
|
||||
ForceSpawn,
|
||||
IgnoreMaxBots: ForceSpawn,
|
||||
RandomTimeSpawn: false,
|
||||
Time: startTime,
|
||||
Supports: null,
|
||||
TriggerId: "",
|
||||
TriggerName: "",
|
||||
SpawnMode: isPMC ? ["pve"] : ["regular", "pve"],
|
||||
});
|
||||
|
||||
startTime += Math.round(totalCountThisWave * averageTime);
|
||||
|
||||
maxSlotsReached -= 1 + (isMarksman ? 0 : bossEscortAmount);
|
||||
if (maxSlotsReached <= 0) break;
|
||||
}
|
||||
// isMarksman &&
|
||||
// console.log(
|
||||
// // bossZones,
|
||||
// botType,
|
||||
// bossZones.length,
|
||||
// waves.map(({ Time, BossZone }) => ({ Time, BossZone }))
|
||||
// );
|
||||
return waves;
|
||||
};
|
||||
|
||||
export const buildZombie = (
|
||||
botTotal: number,
|
||||
escapeTimeLimit: number,
|
||||
botDistribution: number,
|
||||
BossChance: number = 100
|
||||
): IBossLocationSpawn[] => {
|
||||
if (!botTotal) return [];
|
||||
const pushToEnd = botDistribution > 1;
|
||||
const pullFromEnd = botDistribution < 1;
|
||||
|
||||
let startTime = pushToEnd
|
||||
? Math.round((botDistribution - 1) * escapeTimeLimit)
|
||||
: 0;
|
||||
|
||||
escapeTimeLimit = pullFromEnd
|
||||
? Math.round(escapeTimeLimit * botDistribution)
|
||||
: Math.round(escapeTimeLimit - startTime);
|
||||
|
||||
const averageTime = Math.round(escapeTimeLimit / botTotal);
|
||||
|
||||
const waves: IBossLocationSpawn[] = [];
|
||||
let maxSlotsReached = botTotal;
|
||||
|
||||
while (botTotal > 0) {
|
||||
const allowGroup = 0.2 > Math.random();
|
||||
let bossEscortAmount = allowGroup ? Math.round(4 * Math.random()) : 0;
|
||||
|
||||
if (bossEscortAmount < 0) bossEscortAmount = 0;
|
||||
|
||||
const totalCountThisWave = bossEscortAmount + 1;
|
||||
|
||||
const main = getRandomZombieType();
|
||||
waves.push({
|
||||
BossChance,
|
||||
BossDifficult: "normal",
|
||||
BossEscortAmount: "0",
|
||||
BossEscortDifficult: "normal",
|
||||
BossEscortType: main,
|
||||
BossName: main,
|
||||
BossPlayer: false,
|
||||
BossZone: "",
|
||||
Delay: 0,
|
||||
IgnoreMaxBots: false,
|
||||
RandomTimeSpawn: false,
|
||||
Time: startTime,
|
||||
Supports: new Array(bossEscortAmount).fill("").map(() => ({
|
||||
BossEscortType: getRandomZombieType(),
|
||||
BossEscortDifficult: ["normal"],
|
||||
BossEscortAmount: "1",
|
||||
})),
|
||||
TriggerId: "",
|
||||
TriggerName: "",
|
||||
SpawnMode: ["regular", "pve"],
|
||||
});
|
||||
|
||||
startTime += Math.round(totalCountThisWave * averageTime);
|
||||
|
||||
maxSlotsReached -= 1 + bossEscortAmount;
|
||||
if (maxSlotsReached <= 0) break;
|
||||
}
|
||||
// console.log(waves)
|
||||
return waves;
|
||||
};
|
||||
|
||||
export interface MapSettings {
|
||||
sniperQuantity?: number;
|
||||
initialSpawnDelay: number;
|
||||
smoothingDistribution: number;
|
||||
mapCullingNearPointValuePlayer: number;
|
||||
mapCullingNearPointValuePmc: number;
|
||||
mapCullingNearPointValueScav: number;
|
||||
spawnMinDistance: number;
|
||||
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;
|
||||
mapCullingNearPointValuePlayer: number;
|
||||
mapCullingNearPointValuePmc: number;
|
||||
mapCullingNearPointValueScav: 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getRandomInArray = <T>(arr: T[]): T =>
|
||||
arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
export const enforceSmoothing = (locationList: ILocation[]) => {
|
||||
for (let index = 0; index < locationList.length; index++) {
|
||||
const waves = locationList[index].base.BossLocationSpawn;
|
||||
|
||||
const Bosses: IBossLocationSpawn[] = [];
|
||||
let notBosses: IBossLocationSpawn[] = [];
|
||||
|
||||
const notBossesSet = new Set([
|
||||
"infectedLaborant",
|
||||
"infectedAssault",
|
||||
"infectedCivil",
|
||||
WildSpawnType.ASSAULT,
|
||||
WildSpawnType.MARKSMAN,
|
||||
"pmcBEAR",
|
||||
"pmcUSEC",
|
||||
]);
|
||||
|
||||
for (const wave of waves) {
|
||||
if (notBossesSet.has(wave.BossName)) {
|
||||
notBosses.push(wave);
|
||||
} else {
|
||||
Bosses.push(wave);
|
||||
}
|
||||
}
|
||||
|
||||
let first = Infinity,
|
||||
last = -Infinity;
|
||||
|
||||
notBosses.forEach((notBoss) => {
|
||||
first = Math.min(notBoss.Time, first);
|
||||
last = Math.max(notBoss.Time, last);
|
||||
});
|
||||
|
||||
if (first < 15) first = 15;
|
||||
|
||||
notBosses = notBosses.sort((a, b) => a.Time - b.Time);
|
||||
|
||||
// console.log(notBosses.map(({ Time }) => Time))
|
||||
|
||||
let start = first;
|
||||
const smoothingDistribution = (mapConfig[configLocations[index]] as any)
|
||||
.smoothingDistribution as number;
|
||||
|
||||
const increment =
|
||||
(Math.round((last - first) / notBosses.length) * 2) * smoothingDistribution;
|
||||
|
||||
for (let index = 0; index < notBosses.length; index++) {
|
||||
const ratio = (index + 1) / notBosses.length;
|
||||
// console.log(ratio);
|
||||
notBosses[index].Time = start;
|
||||
let inc = Math.round(increment * ratio);
|
||||
if (inc < 10) inc = 5;
|
||||
start += inc;
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// configLocations[index],
|
||||
// last,
|
||||
// notBosses.map(({ Time, BossName }) => ({ BossName, Time }))
|
||||
// );
|
||||
|
||||
locationList[index].base.BossLocationSpawn = [...Bosses, ...notBosses];
|
||||
}
|
||||
};
|
||||
|
||||
export const looselyShuffle = <T>(arr: T[], shuffleStep: number = 3): T[] => {
|
||||
const n = arr.length;
|
||||
const halfN = Math.floor(n / 2);
|
||||
for (let i = shuffleStep - 1; i < halfN; i += shuffleStep) {
|
||||
// Pick a random index from the second half of the array to swap with the current index
|
||||
const randomIndex = halfN + Math.floor(Math.random() * (n - halfN));
|
||||
// Swap the elements at the current index and the random index
|
||||
const temp = arr[i];
|
||||
arr[i] = arr[randomIndex];
|
||||
arr[randomIndex] = temp;
|
||||
}
|
||||
|
||||
return arr;
|
||||
};
|
||||
28
user/mods/DewardianDev-MOAR/src/Tests/checkPresets.ts
Normal file
28
user/mods/DewardianDev-MOAR/src/Tests/checkPresets.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import config from "../../config/config.json";
|
||||
import presets from "../../config/Presets.json";
|
||||
import presetWeightings from "../../config/PresetWeightings.json";
|
||||
|
||||
export default function checkPresetLogic(container: DependencyContainer) {
|
||||
const Logger = container.resolve<ILogger>("WinstonLogger");
|
||||
|
||||
for (const key in presetWeightings) {
|
||||
if (presets[key] === undefined) {
|
||||
Logger.error(
|
||||
`\n[MOAR]: No preset found in PresetWeightings.json for preset "${key}" in Presets.json`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in presets) {
|
||||
const preset = presets[key];
|
||||
for (const id in preset) {
|
||||
if (config[id] === undefined) {
|
||||
Logger.error(
|
||||
`\n[MOAR]: No associated key found in config.json called "${id}" for preset "${key}" in Presets.json`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
user/mods/DewardianDev-MOAR/src/Zombies/Zombies.ts
Normal file
160
user/mods/DewardianDev-MOAR/src/Zombies/Zombies.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import { DependencyContainer } from "tsyringe";
|
||||
import {
|
||||
ISeasonalEventConfig,
|
||||
ISeasonalEvent,
|
||||
} from "@spt/models/spt/config/ISeasonalEventConfig.d";
|
||||
|
||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||
import { zombieTypesCaps } from "../Spawning/utils";
|
||||
|
||||
export const baseZombieSettings = (enabled: boolean, count: number) =>
|
||||
({
|
||||
enabled,
|
||||
name: "zombies",
|
||||
type: "Zombies",
|
||||
startDay: "1",
|
||||
startMonth: "1",
|
||||
endDay: "31",
|
||||
endMonth: "12",
|
||||
settings: {
|
||||
enableSummoning: false,
|
||||
removeEntryRequirement: [],
|
||||
replaceBotHostility: true,
|
||||
zombieSettings: {
|
||||
enabled: true,
|
||||
mapInfectionAmount: {
|
||||
Interchange: count === -1 ? randomNumber100() : count,
|
||||
Lighthouse: count === -1 ? randomNumber100() : count,
|
||||
RezervBase: count === -1 ? randomNumber100() : count,
|
||||
Sandbox: count === -1 ? randomNumber100() : count,
|
||||
Shoreline: count === -1 ? randomNumber100() : count,
|
||||
TarkovStreets: count === -1 ? randomNumber100() : count,
|
||||
Woods: count === -1 ? randomNumber100() : count,
|
||||
bigmap: count === -1 ? randomNumber100() : count,
|
||||
factory4: count === -1 ? randomNumber100() : count,
|
||||
laboratory: count === -1 ? randomNumber100() : count,
|
||||
},
|
||||
disableBosses: [],
|
||||
disableWaves: [],
|
||||
},
|
||||
},
|
||||
} as unknown as ISeasonalEvent);
|
||||
|
||||
const randomNumber100 = () => Math.round(Math.random() * 100);
|
||||
|
||||
export const resetCurrentEvents = (
|
||||
container: DependencyContainer,
|
||||
enabled: boolean,
|
||||
zombieWaveQuantity: number,
|
||||
random: boolean = false
|
||||
) => {
|
||||
const configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
const eventConfig = configServer.getConfig<ISeasonalEventConfig>(
|
||||
ConfigTypes.SEASONAL_EVENT
|
||||
);
|
||||
|
||||
let percentToShow = random ? -1 : Math.round(zombieWaveQuantity * 100);
|
||||
if (percentToShow > 100) percentToShow = 100;
|
||||
|
||||
eventConfig.events = [baseZombieSettings(enabled, percentToShow)];
|
||||
|
||||
const seasonalEventService = container.resolve<SeasonalEventService>(
|
||||
"SeasonalEventService"
|
||||
) as any;
|
||||
|
||||
// First we need to clear any existing data
|
||||
seasonalEventService.currentlyActiveEvents = [];
|
||||
seasonalEventService.christmasEventActive = false;
|
||||
seasonalEventService.halloweenEventActive = false;
|
||||
// Then re-calculate the cached data
|
||||
seasonalEventService.cacheActiveEvents();
|
||||
// seasonalEventService.addEventBossesToMaps("halloweenzombies");
|
||||
};
|
||||
|
||||
export const setUpZombies = (container: DependencyContainer) => {
|
||||
const configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
const eventConfig = configServer.getConfig<ISeasonalEventConfig>(
|
||||
ConfigTypes.SEASONAL_EVENT
|
||||
);
|
||||
|
||||
eventConfig.events = [baseZombieSettings(false, 100)];
|
||||
|
||||
// eventConfig.eventBossSpawns = {
|
||||
// zombies: eventConfig.eventBossSpawns.halloweenzombies,
|
||||
// };
|
||||
eventConfig.eventGear[eventConfig.events[0].name] = {};
|
||||
eventConfig.hostilitySettingsForEvent.zombies.default =
|
||||
eventConfig.hostilitySettingsForEvent.zombies.default
|
||||
.filter(({ BotRole }) => !["pmcBEAR", "pmcUSEC"].includes(BotRole))
|
||||
.map((host) => ({
|
||||
...host,
|
||||
AlwaysEnemies: [
|
||||
"infectedAssault",
|
||||
"infectedPmc",
|
||||
"infectedCivil",
|
||||
"infectedLaborant",
|
||||
"infectedTagilla",
|
||||
"pmcBEAR",
|
||||
"pmcUSEC",
|
||||
],
|
||||
AlwaysNeutral: [
|
||||
"marksman",
|
||||
"assault",
|
||||
"bossTest",
|
||||
"bossBully",
|
||||
"followerTest",
|
||||
"bossKilla",
|
||||
"bossKojaniy",
|
||||
"followerKojaniy",
|
||||
"pmcBot",
|
||||
"cursedAssault",
|
||||
"bossGluhar",
|
||||
"followerGluharAssault",
|
||||
"followerGluharSecurity",
|
||||
"followerGluharScout",
|
||||
"followerGluharSnipe",
|
||||
"followerSanitar",
|
||||
"bossSanitar",
|
||||
"test",
|
||||
"assaultGroup",
|
||||
"sectantWarrior",
|
||||
"sectantPriest",
|
||||
"bossTagilla",
|
||||
"followerTagilla",
|
||||
"exUsec",
|
||||
"gifter",
|
||||
"bossKnight",
|
||||
"followerBigPipe",
|
||||
"followerBirdEye",
|
||||
"bossZryachiy",
|
||||
"followerZryachiy",
|
||||
"bossBoar",
|
||||
"followerBoar",
|
||||
"arenaFighter",
|
||||
"arenaFighterEvent",
|
||||
"bossBoarSniper",
|
||||
"crazyAssaultEvent",
|
||||
"peacefullZryachiyEvent",
|
||||
"sectactPriestEvent",
|
||||
"ravangeZryachiyEvent",
|
||||
"followerBoarClose1",
|
||||
"followerBoarClose2",
|
||||
"bossKolontay",
|
||||
"followerKolontayAssault",
|
||||
"followerKolontaySecurity",
|
||||
"shooterBTR",
|
||||
"bossPartisan",
|
||||
"spiritWinter",
|
||||
"spiritSpring",
|
||||
"peacemaker",
|
||||
"skier",
|
||||
],
|
||||
SavagePlayerBehaviour: "Neutral",
|
||||
BearPlayerBehaviour: "AlwaysEnemies",
|
||||
UsecPlayerBehaviour: "AlwaysEnemies",
|
||||
}));
|
||||
|
||||
// console.log(eventConfig.hostilitySettingsForEvent.zombies.default);
|
||||
};
|
||||
41
user/mods/DewardianDev-MOAR/src/mod.ts
Normal file
41
user/mods/DewardianDev-MOAR/src/mod.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { DependencyContainer } from "tsyringe";
|
||||
import { IPostSptLoadMod } from "@spt/models/external/IPostSptLoadMod";
|
||||
import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
|
||||
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";
|
||||
import { setupSpawns } from "./SpawnZoneChanges/setupSpawn";
|
||||
|
||||
class Moar implements IPostSptLoadMod, IPreSptLoadMod, IPostDBLoadMod {
|
||||
preSptLoad(container: DependencyContainer): void {
|
||||
if (enableBotSpawning) {
|
||||
setupRoutes(container);
|
||||
}
|
||||
}
|
||||
|
||||
postDBLoad(container: DependencyContainer): void {
|
||||
if (enableBotSpawning) {
|
||||
setupSpawns(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() };
|
||||
56
user/mods/DewardianDev-MOAR/src/utils.ts
Normal file
56
user/mods/DewardianDev-MOAR/src/utils.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
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…
Add table
Add a link
Reference in a new issue