Ports hardcoded wave-related settings to custom blocks
Discord: Stormpooper#3436
This documentation assumes the user is familiar with base game wave settings and population.
The Warmup rundown is used as a demonstration of this plugin.
Note that the heat system is explained in its own section.
Soft spawn cap. All aggressive enemies count towards this. The value of each enemy is set by EnemyTypeCostsTowardsCap
Max heat value used for weight calculations. The closer a type's heat is to max, the lower its spawn chance.
How much to decrease heat of all types (that are not filtered out) each time an enemy is picked.
This value is actually 1 right now in base game, which causes some bugs. It's intended to be 25, which is the default for the mod.
Array of 5 floats. Base chance to spawn each type.
Array of 5 floats. How much heat is added when this type is picked.
Note that before the type is picked, cooldown is applied.
Say, if the type had 50 heat, cooldown is 25, and heat on select is 150, you would end up with 175 heat afterwards.
Array of 5 floats. How much heat each type has when starting a level.
In base game this value is never reset and carries on to other levels and attempts.
Array of 5 floats. How much this type costs for wave populationPoints settings when picked.
Array of 5 floats. Type is taken from EnemyDataBlock and is added towards AllowedTotalCost when enemy turns aggressive.
The 5 floats here are actually the same field as EnemyTypeCostsForWave in base game but are separated in this mod, since the types are separate in base game.
Array of 5 ints. Currently broken, only matters if the value is above 0. This type can't spawn otherwise (as is the case for Boss role in base game).
When set to true, prevents heat of enemy types not present in the wave settings of current wave from cooling down.
Behind the scenes, there's a global grab count number for how many times total any enemy was picked. Each enemy heat type holds its own grab count, and when cooling down, multiplies the cooldown by the grab count difference. When set to true, the grab count for all types is always adjusted so the cooldown isn't multiplied.
Effectively the same as ID of normal blocks. Duplicates are ignored.
After first launching the game with the mod installed, a json file will generate in BepInEx\plugins\PersistentData\ConfigurableGlobalWaveSettings
This json is a pseudo-datablock containing the settings blocks, referenced from RundownDataBlock with PersistentID.
To select settings for a level, add the text "globalwavesettings_[ID]" (case-insensitive) to DevInfo of your level in RundownDataBlock. The mod searches for the pattern, not exact match, so it doesn't matter how much or what text is specified as long as this pattern exists in it.
If a level does not have anything specified, the block with ID 1 will be used unless it doesn't exist. These settings are close to real game settings but with some bug fixes.
Rundown developers should add this mod as a dependency and add the json file with this folder structure: plugins\PersistentData
\ConfigurableGlobalWaveSettings\GlobalWaveSettingsDataBlock.json
Heat is used for a weighted RNG system. Simply put, the higher the heat of a type is, the lower its spawn chance. Heat cannot go below 0.
The game picks enemies to spawn one-by-one following this process:
Filter the types using wave settings;
Filter out types that exceed spawn count limits (broken, only matters if limit is set to 0);
Apply cooldown to all remaining types;
Set weight of types exceeding max heat to 0;
For other types, calculate weight like so: (1 - heat / maxHeat) * baseWeight
Using weights, semi-randomly select a type;
If not all types are filtered out, but all weights are 0, the first type remaining after filtering is picked.
If selection failed, return type Standard
.
Note: thanks to the selection algorithm, heats can be driven way over max heat (e.g. if wave settings only allow MiniBoss spawns). The settings are global, so they persist to other waves, possibly distorting spawns until normal heat values are restored via cooldown.
So let's say we have MiniBoss with 150 heat and base chance 0.15. The cooldown is 25. Assuming it's not filtered out, its weight would be: (1 - 125 / 200) * 0.15 = 0.05625
This does not mean it has a 5.6% chance to spawn, to calculate that you would need to know the weights of other types as well.
Now let's say max heat is 200, cooldown is 25, and the initial heat of MiniBoss is set to 500. In this case even if it's not filtered out, MiniBoss cannot spawn until the ~12th pick. If it is filtered out, its heat won't decrease until we have an alarm (or scout wave) that does allow MiniBoss spawns.
Even with no settings selected, block ID 1 is picked, which is game defaults with 3 bugs fixed:
Cooldown speed is reverted to intended value (25 as opposed to 1 set by game at the time of writing)
Heat is reset on each level attempt
Boss role is enabled
When info logging is enabled, this mod will log ID selection.
When debug logging is enabled, this mod will log wave spawns.
Developer-managed plugin documentation
Overcomplicated and unmaintainable
Discord: Stormpooper#3436
User should be familiar with editing datablocks and willing to lose sanity.
The mod provides no music by default, the user should be able to build asset bundles with music clips.
The Warmup rundown is used as a demonstration of this plugin.
This mod can play different music based on the current situation - active events (reading wave settings), posted sound events, aggressive enemy count/types/distance. Enemy values are collected into a "threat" value, a score indicating how difficult the situation is.
The music is divided into 3 sections hierarchically: music entry, stage, and loop/clip. A music entry is essentially an entire datablock depicting one piece of music. An entry can hold any amount of stages, each of which specifies what to play for a certain threat level. Finally, each stage can hold any amount of loops/clips, which are simply separate audio clips that are part of the same threat level.
2 main PersistentData config files are used by the mod:
DynamicMusicData.json - defines music entries
ThreatData.json - defines threat entries
To specify what settings a level should read, modify the "DevInfo" field in RundownDataBlock for the level you need. Write "threatdata_x" for threat settings, where x is the ID, and the same way "dynamicmusicdata" for music settings. Only one threat value will be read for any level, but any amount of music settings is accepted.
To export this data with your rundown, refer to ConfigurableGlobalWaveSettings. The process is the same, simply adapt it to this mod's files and dependencies.
Remember to include the music asset bundle with your rundown.
Customizable stealth settings, mostly related to sleeper alerting
Discord: Stormpooper#3436
Most information about this mod is available on Thunderstore. This page is more about a reference of the fields and more detailed information.
As far as I know, there should be no problems running this together with any other existing mods, but for documentation's sake I have to mention that this plugin prefixes 2 methods with return false
: EnemyAgent.PropagateTargetLimited
and EnemyDetection.UpdateDetectionTimer
.
Plugin developers will know what this means.
This is another one of my mods that uses persistent data - a json file with default values is automatically generated on first launch. These values can be edited as you see fit and they can also be packaged with a separate mod (most likely a rundown) so the users of that mod will automatically use the included settings.
Vector2 here represents a min/max value
If the default value of a field is not mentioned, assume base game does not have an equivalent or the default is the same as it is in base game (can be seen in a fresh generated config file).
Multiplier for how fast sleepers build up detection to flashlights. This is also editable in datablocks.
By default in the game it takes ~2 seconds of light for the sleeper to alert, here it will take around 1 second.
Multiplier for how fast sleepers cool down from detection to flashlights. This is also editable in datablocks.
This value's cooldown being slow leads to sleepers repeatedly cycling from heartbeats to sleeping to heartbeats again, which just wastes players' time. By default, here it cools down 4 times faster.
Overrides the chance for a sleeper to alert another sleeper under conditions when it is normally rng. In the base game, the rng value varies by type of melee weapon used and damage inflicted, but here it is always the same and set to be guaranteed by default.
The max distance at which a sleeper can propagate to another. This value is squared - 169 means 13 meters.
The max distance at which a sleeper is considered in a cluster with another sleeper. This value is squared.
Same as above but for "low noise" weapons (knife). Defaults to 1 meter instead of the usual 3. This value is squared.
If this is set to true, when a sleeper alerts something in a cluster, it will continue scanning for more sleepers to alert within the detection distance.
The maximum number of sleepers to propagate to. Game default is effectively 1.
Whether cluster sleepers should add to the alert count.
Whether the number of alerted sleepers in a cluster is limited by the max alert count. Will not function if IncludeClusterInAlertCount
is set to false.
The additional window of time during which a sleeper cannot be propagated to. Starts when a sleeper has just started transitioning to "active" (started listening). Base game does not have such a window.
The player's noise system is a bit complicated. The player can make a bunch of different noise "types" and that noise stays on the player for a certain period of time. This can sometimes result in the player alerting a sleeper with noise even though the player is not actually making noise anymore, leading players to feel like the sleeper alerted for no reason.
To minimize the effect of this on normal stealth, this field was added and set to a negative value by default to decrease the "noisy" time of the player. This has some side-effects making sleepers not react to noise properly in some cases but I think it's worth it.
Most noises in the game linger for 0.55 seconds and the default value here is -0.4, meaning the noises instead linger for 0.15 seconds.
Same as above but for run and "Loud landing" noise types (loud landing is what killed bhop). This value is set to 0 by default, meaning there is no difference from the base game. Changing this to a negative value will potentially allow people to bhop again.
Transition fields are related to the 4 states of a hibernating sleeper's detection, where sleeping is "deactivated" and listening is "active", and the other 2 states are the in-between states.
This transition is from "deactivated" to "starts listening". At this point, noise detection is off (will not start pulsing/heartbeats to noise), but can already propagate, which is why AdditionalHibernationWindow
was added.
Game default is 2 seconds, mod default is 1.25 seconds, which means the sleeper will react to noises faster.
This duration is effectively the amount of time the sleeper will stay listening. As soon as this ends, noise detection and propagation are turned off.
Game default is 2-4 seconds, mod default is 1-4.
This duration is the amount of time it takes for the sleeper to be fully deactivated, but it has no mechanic difference from the sleeper being deactivated (sleeping). It basically just extends a few other timers, making the sleeper stay asleep longer.
Game and mod default is 1 second.
Essentially the amount of time a sleeper will stay asleep if undisturbed.
Game default is 2-15 seconds, mod default is 5-15 seconds.
Imagine getting 2 seconds of movement before red light again or something alerts despite the flashlight sync you just did.
I was actually going to expand this and some others to variable values depending on how well players perform in stealth (where more noise/time spent/messing up would force sleepers to be more awake) but decided against it.
This is the duration of time after a sleeper is deactivated when a sleeper will not noise-detect players sneaking right next to it (if you didn't know, yes they detect sneaking within 2 meters of the sleeper).
Game and mod default is 0.5-1.5 seconds.