Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
List of datablocks that modders rarely edit
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
This guide goes over setting up a development environment using Visual Studio and compiling your first plugin
A Plugin is a compiled C# assembly file that is loaded by BepInEx to allow for custom code to be executed, such as patches or custom behaviour
This guide assumes you are running windows 10 or 11, own GTFO on steam, and have r2modman installed
Is this missing something you want help with? Try asking in our Discord server.
How to retrieve sounds, assets, models, etc. from the game files
This section is still in development
How to fix the numerous errors when importing asset files from GTFO
Please for the love of god expand this section
GameData_EnemySFXDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_EnemyDetectionDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
Welcome to hell
Be aware that game updates often break datablocks, especially levels and rundowns, to the point where most rundowns get abandoned.
This page is for people who want to get started with modding GTFO but don't know anything about it. If you're a programmer and you want to write actual mods, check out the introduction to plugins.
Always remember that the official discord server is not the place to discuss anything modding related. Talk about it in the modding discord instead.
Datablocks are all stored in JSON, you need to get familiar with this text format. Basically it's a way to have data as text stored in a way that can be read by both humans and computers. There's an infinite number of guides and ways to learn JSON. One place to start is w3schools.
The text files can be edited even with notepad, but for your sanity I recommend using something more advanced. The most common option here is vscode. It allows you to easily manage entire folders of files at once, provides syntax checking, highlighting, easy searching, replacing, and lots of other good stuff. You can also add some vscode extensions for yourself such as indent-rainbow to further make your life easier. Either way you'll be looking at massive amounts of JSON so best be prepared.
Knowledge of C# or programming in general can definitely help but is not required for modding using datablocks.
RegEx is very helpful for quickly finding and replacing data.
Datablocks (also referred to as blocks or db) are GTFO's way of storing various information about the game. While I'm positive they don't directly edit the files ever, them choosing JSON is a convenient way for modders to read and change that information. What is stored in datablocks is completely in devs' control. Sometimes it may seem arbitrary what is stored there and what isn't, so some modders make plugins that allow even more control over the game.
Some examples of what we can find in datablocks:
The stats of weapons, enemies, and players (HP, speed, damage etc);
Terminal logs and passwords;
Enemy population in levels;
The objectives of levels;
Level layouts themselves;
Entire rundowns.
Everything that is stored in datablocks can be edited. As previously stated, the developers control what is stored in datablocks, making them limited in some aspects. Some things have set rules that you cannot see in datablocks. Some things may be very hard to get to work as expected. Some things cannot be done using only datablocks. Learning to use them can take a lot of effort, and you will have questions, so don't hesitate to ask in the datablock development channel.
For a quick, hassle-free setup it's recommended that you use R2Modman, a mod manager built to work with Thunderstore.
Install the latest version of the mod manager
Launch the mod manager and select GTFO
Create a new profile and name it
Select "Online" and install MTFO
Open Settings -> Locations -> Change GTFO directory
. Locate and select the install location of GTFO. By default this is C:\Program Files (x86)\Steam\steamapps\common\GTFO
Hit the "Play Modded" button
Best practice is to create one profile per rundown so you can switch between them easily.
You can find the profile folder by going through the mod manager to Settings -> All -> Browse Profile Folder
Run the game normally. To play vanilla again, you'd have to manually delete the mods (or remove/rename some specific files)
Now that MTFO is ready, there are still a few things left to do to get datablocks
After installing the mods, launch the game once for MTFO config to generate
Move to BepInEx\config and open MTFO.cfg
Set "Dump GameData" to true, save the file
Launch and close the game
You should now see a folder like BepInEx\GameData-Dump\31829
(version might be changed), this is your datablocks folder
Copy the folder to BepInEx\plugins
so you'll for example have BepInEx\plugins\31829
(folder name doesn't matter)
You can now edit the blocks in plugins and they'll be loaded by MTFO
A few things to note:
You can always regenerate the datablocks by just deleting them from gamedata-dump and launching the game again
Disabling the dump setting is optional
Only keep one folder of datablocks to load, otherwise you have no control over what's loaded
You don't need to keep all datablocks to have them load. In fact, it's recommended to only keep the ones you're going to edit
To get some familiarity with editing datablocks, we're going to do 3 things in this section:
Change a weapon's stats;
Enable a weapon from a previous rundown;
Switch the stats of an enemy to those of another.
You can test either after every step or all at once. A few tips before we get started:
Datablock reference can be used to get a lot of information about blocks.
LocalizedText
is a special type that can both point to a text block and be read as a plain string.
All enums can be passed as numbers or strings. I personally recommend using strings since they often give more information than plain numbers, but both work equally fine.
Remember, after initial generation, all datablocks should be located in GTFO\BepInEx\plugins\GameData_XXXXX\
When looking for the blocks you see in the guide, you should try to match either name or persistentID. Other fields can point you to the right blocks, but they don't have to be unique. For example, there are several versions of the pistol.
Let's keep this one simple. We're going to find the pistol's stats and change its damage to 100 and firemode to automatic. Weapon stats are kept in Archetype datablock. There are multiple ways to determine which block belongs to the pistol, but for now let's "assume" that the correct block is the one with the name "GEAR_Pistol_Semi_v2".
Note: lines under 3155 and 3184 are collapsed in vscode editor to reduce clutter. You'll see this used a lot.
Among the fields we can see the ones named "Damage" and "FireMode" (the latter is an enum and will appear as a number if you're not using enums as strings). Change the damage to 100
and firemode to auto
. Additionally, let's change the public name to "Balanced pistol" so we can see the custom blocks were loaded without having to drop into a level.
After saving, you can launch the game and test, or you can test after all the changes.
All datablocks share some fields. One of these fields is called "internalEnabled" and basically determines whether that block exists for the game or not. To enable a previous weapon, the only thing we need to do (assuming the old blocks have not been broken by game updates) is set this field to true
in the correct places.
For now we are going to enable the combat shotgun. Open PlayerOfflineGear datablock and find the block named "ShotgunAuto". Set "internalEnabled" to true
on that block.
When you save this and launch the game, the combat shotgun should be available under special weapons.
Aside from "internalEnabled" there are 2 other fields that are shared by all blocks. One of them is "name" which you don't see used in-game, but it does help identify the block you're looking at. The last shared field is "persistentID". This is the field used by blocks to reference other blocks. Note that "persistentID" and "name" cannot repeat in the same file.
Let's change a striker's stats to those of a big striker's using these references. The main block of an enemy is Enemy datablock and the stats we're looking for are stored in EnemyBalancing datablock. The reference field is named "BalancingDataId".
To change the stats we just need to find the big striker's balancing data, get its persistentID (in this case it's 16), and set it on the striker data.
And that's it, wave strikers now have the balancing stats of a big striker. If you want, you can also change the balancing data of "Striker_Hibernate" as well for easier testing and more suffering.
If you want to undo all your changes, you can delete the edited files and let them regenerate.
If you're wondering what fields do what, you can either interpret the name or check out the datablock reference.
Most fields that hold references to other blocks are of the type "uint" aka "UInt32".
If you edit datablocks enough, you're bound to run into errors, also called exceptions. There's an infinite number of causes so it's impossible to know all of them, but it's important to know where to start looking for information, at the very least so you can pass on more when looking for help. If you do run into something you can't fix yourself, you can ask in the tech support channel in the modding discord.
When launching the game with mods enabled, you'll see the BepInEx console as a separate window. This console is useful for many things, but most importantly for users, it shows errors in red text.
All logged messages have the type and source as a prefix. E.g. [Error: MTFO] error message
shows that there was an error caused by the mod MTFO. Most of the time errors are caused by the game itself, in which case you will see [Error: Unity]
as the prefix. In this case I would always recommend to look for the error in the game logs instead of BepInEx logs. These are located in %userprofile%\AppData\LocalLow\10 Chambers Collective\GTFO
If you closed the game already, their name will be of the following format: GTFO.[Date].[Time]_[PlayerNick]_[NetworkStatus]
. If the game is still open, it'll instead be GTFO.[Date].[Time]_NICKNAME_NETSTATUS
. For the most part you will be looking for the most recent file created.
While in bepinex console the error message is one line, the game logs provide the stack trace, which is the full source of the error and often provides much more information than just the error message.
Let's take a look at an example. BepInEx console shows this error message:
While the game logs show this:
NullReferenceException
is one of the most common errors that says next to nothing about the cause. All we know from BepInEx console is that an error happened. But the game logs show that the source is from melee weapons. Even if this error doesn't give enough information to you, it will allow other people to find the cause if you ask for help.
Also note that some errors can cause even more errors. When debugging, check the oldest errors first.
GameDataBlockBase, trying to save x without being setup
error is most likely caused by errors in your JSON. Check your syntax and if you pass incorrect types, such as string instead of int.
You can use git to track changes. This allows you to easily see everything you changed at any time, which is useful for many purposes and especially debugging.
If something doesn't work correctly, check if your mods are up to date. Rundown updates often break them.
Game logs are useful for more than just finding exceptions, they provide information about what's going on in the game. You can make a desktop shortcut to the logs folder to access them faster.
Remember, game updates can break existing datablocks, so be careful when using old blocks as reference (and especially careful when enabling disabled blocks).
Check out the Datablocks reference main page for general information about datablocks. Learn to use datablocks reference to get information about specific datablocks.
Once you have some experience, you can check out the Newbie Level Guide if you're interested in making levels/rundowns.
Don't be fooled by the fact that this guide is 5 parts, this right here is half of it
This is the meat of the guide. We're going to add a zone, add sleepers, alarms, blood doors, spitters, respawns, resources, a generator puzzle, a pitch black zone, fog.. there's tons of stuff we can do here.
However, as always, we can't explore everything or we'll be here all week (and this part is already really long). I'll try not to go into too much detail on the meaning of fields this time either, since you can check the relevant blocks' reference to see what all the fields do.
In practice I would just use the more readable blocks from the typelist (or some people opt to make levels using the datablockeditor), but we started with MTFO generated blocks so let's continue with them.
There's a block we can change once for the whole rundown to make level editing life a bit easier in the long run.
The datablock in question is ExpeditionBalance. We won't cover all of it, only the relevant parts.
In the Rundown datablock, ExpeditionBalance is always usually set to 1. If you want to make changes specific to levels, you can make a new expedition balance block and set the ID just for the level.
Yes, we're about to screw up the balance of the current level, but when making your own levels you shouldn't start with preset resources and sleeper spawns.
The fields in question:
These are the base multipliers for resources. Say, with Health being 1.0, setting "HealthMulti" in a zone to 0.8 will result in 4 uses of health, or 80% health for a player. This basically requires no calculation.
The values we don't like - the ones that complicate the math - are weapon and tool ammo.
Set both of those fields to 1 so you don't have to think about how many uses of ammo/tool you'll spawn.
Artifact progress is not saved when using mods, so unless you have special mods that make use of it, it's just an annoyance. What's more, they can even cause your layout to reroll if there's not enough space to place all of them, affecting not only one zone, but the rest of the layout generated afterwards.
Set these 2 fields to 0 to avoid artifact spawns:
Sleeper spawning system is convoluted, you can read about it here. The relevant part here is that when we're going with relative value, the points assigned are calculated like this: EnemyPopulationPerZone * DistributionValue
(from expedition balance and level layout).
So with a distribution value of 1, we'll have 25 points. But if we want to have exactly 12 points for example, we have to do some math.
Since we don't like math, let's set EnemyPopulationPerZone to 10. Now all we have to do to get 12 points is assign 1.2 in DistributionValue. Why not just go with 1? Because devs decided to automatically fail if it's set to less than 5 regardless of DistributionValue.
Reminder that we have just screwed up the resources and especially sleepers in our level, but in reality we wouldn't start with resources and sleepers already set anyway.
Now that that's taken care of, we can start messing with the level layout itself.
Let's add a new zone to the existing level. We're going to do this in the following order:
Copy the first zone and paste it at the end;
Change the local index;
Setup build parameters - where it's built from, the direction, size, altitude, subcomplex;
Adjust terminals;
Adjust lighting;
Set sleepers;
Set resources and consumables.
There's more we can do, but these 7 are plenty to get a full zone. And this is already too much to do at once without testing.
Go to level layout, copy the first zone, and paste it after the last zone. If you collapse all the zones now, you should see 12 in total:
An easier way to see is by using Breadcrumbs:
Anyway, the last zone had localindex 10, so this one should have 11. You can verify by searching for localIndex 11:
Remember, if you don't know what a field does, check the datablocks reference to see if it's documented.
SubComplex is 2, so this would be a Storage zone. Let's change it to DigSite (0) instead.
BuildFromLocalIndex is 0, so the source zone here will be the elevator zone. This is fine.
StartPosition is 1 (From_Start). The elevator zone is pretty small so this might not have impact, but let's change it to 2 (From_AverageCenter).
StartExpansion is 1. Let's change it to 3, hoping it will generate a security door to the right.
ZoneExpansion is 3. Let's also change this to go right, setting it to 5.
AltitudeData is 3. I would normally allow all here, but this level has infection fog, so let's go with 5. Medium-high should generate mostly above infection fog (of course that depends on fog settings but we already have existing medium zones and we know those are above fog).
CoverageMinMax is 40-45. That should result in a zone around medium-ish size. Let's set it to 65-65. We'll hopefully get a huge room and around 2 larges or a mix.
This is all guesswork. If we wanted precision, we'd have to try mods or keep rerolling the layout until we get what we want. But for now let's roll with what we get, even if it turns out completely different from my expectations.
The generated zone should be decently big now, so let's have 2 terminals.
Find TerminalPlacements, we'll set the placement weights to 0, leaving the terminal placement up to rng. Then copy-paste that block.
The results:
Lights are decided by "LightSettings" field. If this is set to 0, the light settings in rundown db will be chosen instead.
Right now it's set to 56. If we have a look at the LightSettings datablock and find that entry, we'll see it's named "AlmostWhite_1". Let's try 71 "RustyRedToBrown_1" instead. If you want, you can pick something different or even make your own light settings.
The sleeper spawning system is convoluted, you can read about it here. Yes I already said it before in this page.
We'll need the EnemyPopulation and EnemyGroup datablocks, and we'll also still need to make use of LevelLayout. Since we'll have to alternate between several files here, you might want to use split editor.
We're going to create 1 randomized group of either strikers, shooters, or big strikers, and 1 group with forced scout. We can reuse existing groups and populations but let's make our own for practice. A bottom-up approach is more appropriate here, so let's start with the EnemyPopulation datablock.
Deleting all existing blocks and remaking from scratch would introduce a few problems with hardcoded ID uses and the current level spawns so let's not do that (but when making full custom rundowns that might be better in the long run). Instead we'll be using numbers that are guaranteed not to mix up with base game blocks.
I'll follow the "rules" set in enemy spawning system. Considering the special enum values, limitations, and the total size of the enums in base game (ensuring scout entry doesn't step on one), here are the 4 new "RoleDatas" for our population entry:
These will go inside the "RoleDatas" of the population block that our expedition is using (you can see which population your expedition is using in the Rundown datablock). In our case, our expedition is using an "EnemyPopulation" of 1 (pretty much every level does). So we'll place these new "RoleDatas" at the end of this population, like so:
Population entries don't have a "Comment" field. But you can add any fields to JSON and as long as there are no syntax errors the game will ignore them. Comment fields have been added for clarity.
As we can see the role-difficulty pairs are unique so these entries won't be randomized at population-level, only at group-level.
Now to add enemy groups, these will just go below all the existing enemy groups in the EnemyGroup datablock:
1 forced scout group, and 4 mixed groups of the 3 enemies. Considering the relative weights, this should be a striker-dominant zone. MaxScores are the same all around so these will be a bit more predictable sizes. If we wanted more randomized sizes we could go with a range of 2-6 for example. It's just best not to give really big sizes to groups since a whole group always spawns in the same room, possibly resulting in high concentration in one area and rest zone empty.
Note I included an update to the LastPersistentID value. That's just so the logs don't clutter saying blocks with IDs above last were found.
Also remember MaxScore has that obnoxious hardcoded random multiplier on it, screwing with our spawns for no good reason.
Finally, we can set the spawns in level layout:
Comments again for clarity.
30 score spawns + 1 forced scout sounds fair for this zone size, but that of course depends on balancing.
Also normally you would take a look at the generated layout and settle on it before starting to spawn sleepers.
All that's left before we finally drop in is resources and consumables.
Before setting any spawns, always make sure these 2 are set to true in our LevelLayout block:
Resources are fairly straight-forward and purely dependent on balance so let's just use them to verify ExpeditionBalance values, setting all to 2 - should be 10 uses of each resource.
Consumables are set by just one field - "ConsumableDistributionInZone". If we tried to make something specific we'd have to constantly look at ItemDataBlock. Let's do the same as we did with lights and judge by names here instead. We'll have a peek at the names in the ConsumableDistribution datablock.
It's currently set to 45, "OnlyFogReps_Alt". Fog repeller balancing is important for an infection level, but our zone specifically is set to medium-high altitude data. While it can still generate something under infection fog, it should mostly stick to above that. Our options are combatting darkness, fog, giving foam/tripmines which are very powerful, or just generic consumable distribution that doesn't focus on anything in particular. Let's go with the generic one, setting it to 1.
A more proper level-making process could be something like this for example:
The entire layout;
Objective;
Resources;
Consumables and other spawns;
Sleepers;
Alarms.
But since we're just learning we simply focused on a single zone and finally made it to testing.
In lobby, remember to select the biotracker if you don't have mods that'll let you count and avoid enemies easier (you really should be using some though).
Before we drop in, we need to be ready for errors. We have 2 options here.
If bepinex console is enabled and unity log listening is enabled in bepinex config.
While dropping in, look at the console to check if you don't see tons of errors (red text) looping, meaning we've entered an infinite cage drop.
This is if we can't see game logs in bepinex console.
Open the game logs folder %userprofile%\AppData\LocalLow\10 Chambers Collective\GTFO
and locate the current log. Monitor it while dropping in. If the level generation fails, the latest log can start rapidly rising in size, meaning we entered an infinite error loop and will never finish cage drop.
If infinite cage drop happens, exit the game and relaunch after debugging and coming up with a fix. Exiting just the level can work but we can't guarantee no after-effects will carry over to the next drop.
Even if we drop in successfully we'll check for exceptions to see if anything failed without causing infinite cage drop. During the whole testing process and after exiting the level you should still look at the logs to verify no errors appeared.
Immediately upon dropping in and revealing the map I can something new at the right side of the map generated. Let's compare before/after.
We can see the map is the same apart from something new generated at the top right, which would be our zone. However, even without going in to test, it's clearly visible that the zone is not generated from the elevator zone. Verifying this we can see it generated from the zone to the right of elevator, and the source entry is to the north (forward):
Why did this happen?
There's no errors in the bepinex console and no particular errors in the game logs, but let's look closer.
The "BlockAndCleanFailedAreasFromZone" part is ok, it happens. I'm not an LG expert, but if I had to guess I'd say it either collided with other zones or ran out of plugs to connect areas.
Line 6 explains the cause. Zone 0 has nowhere to generate a security door so it moved on to another zone.
I'd say LG handled this well but we still need to fix it. A few options here:
Expand the elevator zone - screw up the whole existing layout.
Move source to zone 103 (right-most zone) - that is a fog zone and there are other balance shenanigans.
Move source to zone 102 - this is what LG did.
Let's go with option 3. We should also change the entrance to north like LG generated because there's likely no entrance to generate on the right side, but we can still try that and see what happens. Spoilers - it still generated from the north. Let's go ahead and change both fields now:
Here's our map now:
We can see it still generated in the same spot, as expected, and this time the layout of the zone is different. This is the exact same map as before changing entrance direction to north.
Time to explore.
The zone is heading right indeed, it's above fog everywhere except in parts of the last room, which is the huge one so that's expected. The lighting is also as expected. Here's a view of the last room from its entrance:
If it seems relatively dark, it's because I'm using the lights adjustment mod.
Two terminals generated, one near the middle, another at the very end.
Overall, I would say the layout is a great success.
24 total sleepers spawned - 2 big strikers, 1 scout, 11 strikers and 10 shooters. The spread is fair, only one area got 3 groups which is still very manageable. The spawns are very easy in this zone. I would say we can easily double the distribution here unless there's some special reason not to (like an error alarm), but we're not trying to balance the level. The spawns are all there, the count and spread is as expected. The sleepers part is successful.
10 uses of each spawned in various pack sizes. Right on target. If you get a bit more it might still be fine, because there's a base game bug where a resource pack spawns bigger than it should be.
With consumables we weren't going for anything special, so there's not much to verify apart from the fact that the usual stuff spawned.
We've seen everything and after leaving the level there are still no errors to see. We've successfully added a new zone.
Unfortunately, we're still not done here. Out of the things we said we would do in the overview, we still have these to do: alarms, blood doors, spitters, respawns, a generator puzzle, a pitch black zone, and fog.
Might as well go for some sort of world record in page length.
Or speedrun the rest of these topics.
Ah yes, the best difficulty filler in the game, abused on every single level. Even the error shenanigans in R4 are technically all alarms.
We'll eventually add our alarm by changing our "ChainedPuzzleToEnter" for our new zone in the LevelLayout block, but we'll do things from the ground up again.
Chained puzzles use 3 other datablocks (in addition to the ChainedPuzzle datablock itself):
SurvivalWaveSettings - the wave settings. Defines what, when, how many, and where;
SurvivalWavePopulation - wave settings specify what roles can spawn, this block maps roles to enemies, making settings more reusable;
ChainedPuzzleType - the types of scans, like full team, big, small, cluster etc. Mostly you just see the names and decide what scan to pick. I don't know any reason to edit this datablock.
In this example we'll only be editing ChainedPuzzle and SurvivalWaveSettings, though we'll reference things in the other blocks.
Survival wave population ID 1 will give us striker in standard, shooter in special, and big striker in miniboss. As basic an alarm setup as it gets.
For survival wave settings let's make something new. Something the base game doesn't do for alarms.
A little bit of analysis:
Updating LastPersistentID to match our new highest PersistentID.
Wave pause settings set to basically enforce 30-ish seconds between waves. The little variation is because something bugs if min-max are equal.
Population filter (with some help from base game boss bug) will allow only the 3 roles to spawn.
The most notable thing here is the limited population. Combined with points per wave, we get exactly 2 waves, with group spawns being more intense on the 2nd wave.
This alarm should keep the players occupied for at least a minute and depending on their clearing power, not much longer than that.
Tip: Whenever adding your own custom blocks, always check that the "persistentID"s that you choose aren't already in use.
Moving on to chained puzzle:
We're using the blocks we settled on - 400 for our new survival wave setting, and 1 for the basic survival wave population. For scans, one type 33 - the geo scan (introduced in R6.5) (highly likely not to function well in a random geo, but let's try it) - should be enough and we're not disabling the waves when the scan is completed, so the players will have to kill the 2 full waves.
Now they could just cheese it by running away and mining wherever until the population runs out, but from my experience they don't do that:
Barely anybody even notices something out of the ordinary with spawns when they're made well. If they do, they don't realize the specifics.
People hate wasting time. Even with cheese they'd have to sit in a sustain full-team scan for a while doing absolutely nothing.
All that's left is to set "ChainedPuzzleToEnter": 300
in our level layout and test.
Unsurprisingly the geo scan is screwed up but it's mostly functional so it's fine. Moving on.
As noted here, only hunter groups work properly for blood doors.
The new zone has suffered enough so let's leave it alone for now. Let's add a blood door to the zone left from the elevator - localIndex 1.
Setting up a blood door is fairly easy as long as you know your enemy groups. It's controlled by "ActiveEnemyWave" in level layout. Let's skip making hunter groups this time and just see what's in stock. I see "Hunter Easy Bullrush" in groups with ID 32. MaxScore 8 makes me guess it should be about 8 spawns, but it seems the cost is 2 in population so it's actually 4. Let's spawn one group on the door and 2 in the area for a total of 12 chargers:
A quick test here shows the predictions were spot on. On to the next topic.
Spitters, respawns, a generator puzzle, a pitch black zone, fog - we've got 5 things left to do, and at this point I feel like we've had enough practice to tackle them without too many details.
Spitters are static spawn enemies, controlled by "StaticSpawnDataContainers". This references StaticSpawnDataBlock, but it's rare to edit that directly in my experience. We can tackle just the level layout datablock after seeing that spitters are ID 1 in static spawns. Here's our block, placed in the first zone:
We're spawning just a few because I hate spitters. Most fields besides the ID and Count are for location randomization.
Respawner sacks are also a static spawn I believe, but they seem to be a special one, you only set up enemy respawning and they'll spawn automatically. So let's do that, again placing them in the first zone because we hate everyone who plays our levels:
The fields are all straight-forward here, but as always you can check the datablock reference for explanations. We don't need any respawn exclusions here so we don't even need to have that field.
Now when we messed with expedition balancing we reduced the first zone's population to barely anything, and there will be few respawn sacks as well because of that. Still enough to verify it's there and respawn works though.
I was going to make a keycard puzzle but we already have that example in the early zones. And generators have a little optional trick - we can spawn cells using big pickups distribution instead of ZonePlacementData. Scientific research suggests that thanks to how markers are set up, we'll get more possible placements inside our zone using big pickups instead of zone placement.
In zone 0, set ForceBigPickupsAllocation to true and BigPickupDistributionInZone to 5. In zone 1, set PuzzleType to 2 and PlacementCount to 0.
Now the zone to the left of spawn should require a generator to be activated which spawns in its entrance zone, and in elevator zone we spawned a cell using big pickups.
This one's just a matter of light settings, and there already seem to be 2 settings for darkness. Let's set LightSettings to 73 in zone 1.
This can't be set per zone, we can either edit the level's fog settings or setup some warden objective stuff. From rundown db we can see the level uses FogSettings with an ID of 90. We should copy that to a new persistent ID and set it, then edit the settings. But let's just edit ID 90 directly in the FogSettings datablock.
If we don't want to flip the balance of the level, we can only do something boring, which means we're flipping the balance of the level. Here are the new settings:
Fog density has been increased, altitude moved way up to 5, range and max boost changed to 0. Now the fog is upside-down and cancer is in the high zones instead of low. Flip successful.
If you didn't test these 5 parts yet, now's the time. We're all done with the changes. Here's a picture where you can see the fog, generator, spitters, and respawners:
Bonus points if you get hit by a spitter as soon as you drop in.
I would like to thank all my fans, my mother, and everyone that supported me. I finally got to finish this damn page.
Jokes aside, we can finally move on to warden objective. That topic's not simple either but fortunately I just have to settle on one thing and I get to choose a simple objective.
I mentioned typelist blocks before so I thought I'd throw in a few examples of how our blocks would look like then:
If we were in R4, this would be a guide on "optional" error alarms
Finally, the last part of the guide. level guide using as many mods as possible when?
We have already settled on the objective, but we have to make the sector before we apply it. We're not going to generate a third sector because it's the exact same process.
Start with making a new level layout. For a placeholder let's copy the block we have, delete all zones but 0, and clean up all the nightmares we added. Change the alias start to 200, and remember to rename and change ID.
Here's our placeholder layout:
We already have a placeholder objective, so let's move straight to rundown db.
Enable the secondary layer, set layout, set to build from zone 4 in main layer, set datablock ID to the unsolvable objective.
If you want to lock it with a bulkhead key, all you have to do is add an entry to main layer bulkhead door controller placements and set it to spawn in the same zone as the bulkhead. Remember to also take care of keys in that case. I'll go with secondary always unlocked.
Quick test to see if secondary did spawn and it did. Time to generate the proper layout.
Let's keep this small, to reactor basics. The first zone is fine. We're adding a bridge and a reactor geo for a total of 3 zones.
First thing to do here is find the custom geos. Remember you can only spawn something as custom geo if it is in that complex resource set's custom geos. In rundown db we can see we're using ComplexResourceData 1. Open ComplexResourceSetDataBlock, find ID 1. To make it easier to find what geos we have, I'm going to use breadcrumbs to find the custom geos:
For the most part we care about the Objective block. I'll copy that to a new file to make search easier.
I can see reactor gives 3 results and bridge 0, and I'm not sure the 3 reactor results are actual reactors anyway. Time to consult the Geomorph sheet (although the search in google docs doesn't seem to be the best search ever).
As suspected, the 3rd result was not an actual reactor geo. I'll take the first reactor (geo_64x64_mining_reactor_open_HA_01) and the classic reactor bridge (geo_64x64_mining_refinery_I_HA_05). I can see that both of these are in complex resource set 1 so I don't have to modify it.
If you're wondering whether you could spawn geomorphs from other complexes by messing with complex resource set, the answer is no. Only certain geos are loaded based on the level. The only way to mix complexes is to use mods.
Right, so, copy the first zone 2 more times, fix local indexes, add custom geos, and test.
Now this took me a few times with LG being finicky, but I got both geos to spawn in the correct directions. If you want to try for yourself with just pointers, check out how the map generates and adjust start positions and coverage. As I understand (I don't), LG will quit trying if it can't generate what it wants, and it can also skip generating the custom geos if it fills up the coverage size too early (normally when custom geo is set, that's the only thing that generates in the zone, but parts of random-generated geos can carry over from previous zones, filling up the size).
LG is rocket science that deserves its own guide if any poor fool ever bothered to make one, and on top of that it's still changing, so we won't dive too deep into why things happened here.
Anyway here's the level layout I got:
Some results:
If you have your own working layout, you don't have to copy mine. Note that there are some questionable terminal placements in there.
We've already made the objective, so we just need to apply it in rundown db and test it out. If it works, we don't have to do anything here.
Future me says it works fine. Excellent. The secondary layer is complete.
With the nightmare fully assembled, it's time for the last test. Go beat the whole level.
I'd love to insert a full video of me clearing it with Calle QA recording in the background, game muted (except for the combat music mod playing looming dread), cheats enabled, but I'm not gonna do that.
Finally done, feels like this took forever to write. Hopefully it wasn't so bad to get through as a reader. I hope you learned something, especially something about how to learn from existing examples and debug. Remember, you're not even limited to base game blocks. If you ask nicely, I'm sure most modders will let you use their blocks as examples.
I also highly recommend checking out the various different mods that expand datablock limitations.
Now if you'll excuse me, I need to go cast this abomination of a level into oblivion and then turn myself in. Good luck on your future endeavors.
If you're looking for a guide that explains events, you've come to the wrong place
Warden objectives are a core part of any level and they're quite convoluted complex just by themselves, especially with the new event system (that we won't look at). Our level is quite a nightmare by now but we're still not done torturing it for science. It's time to change the warden objective.
But seeing how in the next part we're adding a secondary sector, here we're going for 3 objectives:
Uplink terminal for main;
Empty (unsolvable) objective for secondary;
Reactor objective for secondary.
Alright, so, as you might guess, the zone we'll be adding this to is the zone we created, and we're looking for it to land in the 2nd terminal (further down the zone).
Funny enough we just yeeted an uplink objective with the original level's secondary layer, so let's steal that and also steal the text info from an uplink made before localized text became a thing (because we're not looking to deal with localization right now).
Remember, using existing blocks is often better than starting from 0, but be careful about which blocks you pick - some might become outdated with game updates. If you can, try to go for the current rundown.
We'll open up the WardenObjective datablock, and start by copying objective 274 (the original secondary objective) and the Header - GoToWinCondition_ToMainLayer fields from 19 (an uplink objective without localized text). After changing also changing the ID and name we get this:
Let's go through a bit of analysis before we change it. Know that not all fields are relevant and not all fields behave the same on different objectives.
Type is eWardenObjectiveType. Here it's 8, terminal uplink.
The info fields are straight-forward and can be easily checked in-game. Pay attention to strings in capitals in [] clauses, these are special values that get converted in code.
Waves during uplinks are set to settings 44, population 1. Population is standard, settings are set to standard and special only, single wave (10000 points), 5 enemies per group and 8 seconds between groups.
WaveOnGotoWinTrigger trigger is 0 (when objective is completed) but WavesOnGotoWin is empty, so there's no extraction alarm.
ChainedPuzzleToActive is 10 so a team scan is required before uplink starts.
ChainedPuzzleAtExit is 11 (always is) and speed multiplier is 0.2 (at 1.0 speed I believe it takes around 20 seconds to scan).
Rounds per uplink is 4 and there's 2 terminals.
Wave spawn type is 1 (InSuppliedCourseNodeZone), meaning they'll spawn in uplink zone.
The terminal picking and exit condition (elevator or exit geo) are set elsewhere, we'll look at that later.
Pretty much all of these settings are fair enough, we just need to change the uplink count to 1 and increase the exit scan speed.
Now let's configure the objective in rundown db.
Only 2 things to change here - the datablock id and the local index, the rest are already correct (make sure you edit the ObjectiveData inside MainLayerData).
Note that ZonePlacementDatas is a list of lists. If I recall correctly, the outer list is for different uplinks (if we had kept 2 uplinks for example), the inner list is for different locations for the same uplink (rng between runs).
The only other thing to note is WinCondition. 1 means exit geo. This field only affects info fields to my understanding, and only for the main layer. The actual exit location is always the exit geo if there is one.
You can have exit geos in layers, but that means you'll always have to do that layer.
You cannot alternate between several exit geos and/or elevator in the same level.
A quick test shows that all went according to plan, just one thing was forgotten - a door towards exit was previously locked until the objective was completed. We can copy that for our uplink objective and edit the text or we can disable the lock. I'll go with the latter and set PuzzleType to 0 on zone 9.
This is used for times when you don't want to deal with creating an objective but still need one (i.e. while you're still making a level) or when you have other means of completing the objective.
We're looking to make an objective as simple and universal as possible and in my experience the easiest way is a gather small items objective that spawns no items. The only thing you need to do once you have this block is set the objective ID in rundown db and you're done.
The block in question:
We'll try this out once we get to making the secondary sector.
Reminder that you shouldn't make objectives before you have the level.
Before we get into any specifics, remember that reactor is an objective heavily relying on waves. If you've read the heat explanation in the mod ConfigurableGlobalWaveSettings, you'll know there's a heat bug in base game and reactors will definitely mess with that, so you not only have to build your waves around the bug, it'll also affect the rest of the level and all other attempts until the game is restarted.
Let's steal R6D1 objective and text info from elsewhere. Reactor blocks are pretty huge thanks to each reactor wave defined separately, and this one also has a bunch of events. We'll have to analyze it in parts. Here's the whole block before editing:
Reactor objective type is 1; text fields are self-explanatory so we'll skip them.
Immediately on landing (EventsOnElevatorLand) we have 2 events, the first turns the lights off and the second adds a subobjective. We don't need either so we'll remove those events.
Then we have EventsOnActivate, I believe run upon completing the scan after inputting the startup command. Weirdly enough only one event seems relevant here - turning the lights on. The others don't seem like they would do anything - the 2nd event is type 0 (none) and the last is type 10 (StopEnemyWaves) but there are no waves to stop as far as I know. Maybe I misremember, or maybe there have been some changes to the level during development and they didn't get cleaned up properly. Either way we need none of these so we'll remove them as well.
Since we're on events, let's check the last remaining event in this block as well - "Events" under the 4th reactor wave. Usually the zone-unlock events for verification would be here, but R6D1 doesn't do that. Here we have one event - type 5 with trigger 1 - sound on start, with a timed delay. Looks like this is the tank spawn roar. We're removing it.
One last thing before we look at reactor waves - DoNotSolveObjectiveOnReactorComplete. This is used to make R6D1 seem like it's a terminal command objective when it's actually a reactor. Set this to false so the objective is completed after reactor.
Alright, reactor waves it is. Here's the first block:
The first settings are all about time. Time for warmup, wave, and verify phases, as well as for repeat phases marked as "fail".
VerifyInOtherZone determines if you get the code on HUD or if you have to find it on a terminal. ZoneForVerification is the local index of the zone to place the code in. No placement data here, which means we don't get to choose weights.
There's 4 waves total here - settings, population, and spawn type are all familiar at this point. The only thing new is "SpawnTimeRel" - when to start spawning the wave. Say, 0.55 would result in 80 * 0.55 = at 44 seconds into the wave. Remember that settings can delay spawn, groups don't all spawn instantly, spawn cap exists etc. so you have to be careful about not overdoing the waves, otherwise people will have to fight through the verify time.
I won't dive deep into the settings and population. I can guess that they'll mostly be very specific in spawn timing, enemies, and especially population points.
The other 3 blocks are the exact same thing, just different numbers, so there's no need to analyze them for me. This is all up to level design and balancing, and we don't do that here. I'll delete the 4th wave and leave everything as-is.
Here's the final block:
Time to finish this.
GameData_GearDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_PlayerDataBlock_bin.json (filled)
Defines player stats.
While this is a datablock, currently its only reference is hardcoded ID 1.
The max health of a player.
How long it takes for health to start regenerating after taking damage.
Relative max hp to regenerate.
E.g. 0.5 from 100 base would be 50.
Delay between health regeneration updates.
How much health is regenerated per second.
Friendly fire damage multiplier.
Currently broken for tools.
Minimum height a player has to fall to take fall damage.
Falling height where a player takes maximum fall damage.
Minimum fall damage a player can take.
Maximum fall damage a player can take.
Anywhere between min and max, fall damage scales linearly.
Unknown, most likely related to the unreleased nanoswarm tool and likely to change.
Unknown, most likely related to the unreleased nanoswarm tool and likely to change.
Unknown, most likely related to the unreleased nanoswarm tool and likely to change.
Seems unused.
Seems unused.
Seems unused.
Seems unused.
Player move speed while walking.
Player move speed when running/sprinting.
Affects move speed in the air. Seems to have little effect on slowing down and turning, mostly impacts speeding up. However, unless set to high values, makes no notable impact.
Player move speed while crouching.
Player move speed when going up and down ladders.
Only seems to have an impact on when player footstep audio is played.
Only seems to have an impact on when player footstep audio is played.
Only seems to have an impact on when player footstep audio is played.
Multiplier for smooth acceleration. At low values players will have trouble speeding up.
Multiplier for smooth stopping. At low values players will have trouble stopping in place (can still reverse direction normally depending on acc value).
Seems to have no effect.
Initial vertical velocity gained from a jump.
Gravity mults affect how fast falling accelerates (rising decelerates).
Gravity mult when rising and holding the jump button.
Gravity mult when rising and not holding the jump button.
Gravity mult after jump peak.
Gravity mult when falling without jumping.
Maximum vertical movement speed.
Default player camera position offset from player's feet.
Player camera position offset while crouched.
Added fov while player is running.
Default depth of field settings.
Depth of field settings in elevator.
Depth of field settings in terminal.
Seems unused.
Mouselook settings are probably something you shouldn't mess with.
Seems to affect aim sensitivity when in sights.
Seems to affect aim sensitivity when in sights.
FPS settings are probably something you shouldn't mess with.
FPS model setting.
FPS model setting.
FPS model setting.
FPS model setting.
FPS model setting.
FPS model setting.
FPS model setting.
Affects maximum possible height above default of sights.
These settings are not literal bullet counts but rather a base value for calculations.
Initial standard weapon ammo when starting the level.
Seems unused.
Max reserve ammo for standard weapons.
Affects how much ammo you get from a refill pack.
Initial special weapon ammo when starting the level.
Seems unused.
Max reserve ammo for special weapons.
Affects how much ammo you get from a refill pack.
Initial tool ammo.
Seems unused.
Max tool ammo.
Affects how much ammo you get from a refill pack.
There is an additional multiplier for sentries.
Seems unused.
Seems unused.
The following item settings seem to be either unused or have no impact worth editing for modders, therefore they have no descriptions.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
Determines whether audible breathing from stamina and infection is enabled.
Determines whether audible breathing from stamina is enabled.
Seems unused or unknown.
When enabled, prints some additional debug logs regarding breathing.
Base audible breathing sound volume.
Health limit for switching to low health breathing sound.
Time without spending stamina before player starts resting.
Stamina regeneration rate when resting in combat.
Stamina regeneration rate when not resting in combat.
Stamina regeneration rate when resting out of combat.
Stamina regeneration rate when not resting out of combat.
Whether stamina can affect movement speed.
Movement speed multiplier when stamina is high.
Movement speed multiplier when stamina is low.
Stamina speed modifier is a lerp between high and low with tiredness (0 - full stamina, 1 - empty stamina) as the value used to interpolate.
This value is the exponent for tiredness. Higher value means stamina will slow down players less.
Whether stamina should affect melee charge speed.
Melee charge speed multiplier when stamina is high.
Melee charge speed multiplier when stamina is low.
Sfx Audio threshold for stamina exhausted loop.
Threshold for setting an audio switch for playing was tired breathing audio later.
Stamina threshold for playing rested audio after player was tired.
Effectively decides whether InCombat values will be used during combat.
Minimum allowed stamina when not in combat.
Maximum allowed stamina when in combat.
How fast should stamina fall to combat maximum cap during combat.
Default 0 in base game, which means stamina will not fall unless players use it.
Jump stamina action cost.
Crouching movement stamina action cost.
Walking stamina action cost.
Running/sprinting stamina action cost.
Crouch enter stamina action cost.
Delay between separate dialog lines.
To our knowledge the rest of these fields are either unused or not useful for modders so most of them probably will not have useful descriptions.
How long a short dialog is blocked after playing.
How long a medium dialog is blocked after playing.
How long a long dialog is blocked after playing.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
A separate page for this struct was not generated because whoever made it is an idiot (but actually probably just a new person who doesn't know how datablocks are supposed to work).
For rundown developers, ActionCost is essentially the same thing as any other nested type.
Stamina cost of this action in combat.
Stamina cost of this action out of combat.
Whether this action should reset resting timer in combat.
Whether this action should reset resting timer out of combat.
GameData_SurvivalWaveSettingsDataBlock_bin.json (filled)
Provides the settings for alarms, scout waves and similar types of waves (all referred to as "alarm" in sections below).
PersistentID value range is changed from default to 1-255
All time-related settings are specified in seconds.
Delay before waves start spawning after alarm start.
Delay between enemy groups.
Minimum score boundary for pauses between waves.
Maximum score boundary for pauses between waves.
Above this threshold, the timer for a new wave doesn't move.
Anywhere in-between min and max, the timer speed is lerped.
Delay between waves at or below minimum score boundary.
Delay between waves at maximum score boundary.
List of enemy types in filter.
Whether to spawn only, or spawn all but the types included in population filter.
Chance for spawn direction to change between waves.
Change for spawn direction to change between groups.
Whether to override spawn type set in code.
The spawn type when override is set to true.
The total population points for waves. The alarm automatically stops if this runs out. -1 is infinite.
Population points for a wave at start ramp.
Population points for a wave at end ramp.
Minimum required cost for a group to spawn. This setting is related to the soft cap of enemies.
Population points for a group at start ramp.
Population points for a group at end ramp.
Lerp over time for start-end population point settings.
By default the game has some hardcoded values set that are used for score settings - cost of an enemy and soft cap.
Enemy costs per type are the following: 0.75 1 1 2 2.
Soft cap (MaxAllowedCost) is 25.
The enemy type for wave population point cost is determined by wave settings.
GameData_CloudsDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
Shows where you can see the finished datablocks and the changes made since then
Since R6 is gone now and so are the source blocks, some things have changed and the guide may be hard to follow. You may want to see what blocks were used when originally developing the guide, or perhaps you just want to check/copy a specific block. Either way, the guide blocks are now stored on git:
You can also check out the commits to see any changes made since initial release. I will also cover some changes here as well.
Since MTFO now supports loading only specific blocks (also to fix some crap broken by R7 like players' shoes, shooters with pouncer stance and so on), the blocks have been minimized.
Additionally, the exit zone size has been changed so the max size isn't reached before exit geomorph is generated.
Here's the new map:
Now that devs have confirmed they're officially killing the game (and keeping old rundowns), code-wise it shouldn't change much, which means less maintenance work for rundown developers.
With this update, there's only 2 things to do to get old levels to work:
Change GameSetup rundown ids to load into a list
Add the tutorial rundown (its reference is hardcoded so we can't get rid of it)
That's it. The layout doesn't even reroll. Unless you were using some mods that are now broken (this guide only uses MTFO), the level should be fully functional.
It seems no changes are required.
Since this is likely to continue, the notes will only be updated if there are relevant changes.
The start of the guide has been reworked to include the R1 ALT update required changes. Additionally some ID numbers have been changed to avoid collisions.
I just changed something and now the level's not working AND I CAN'T FIX IT FOR 2 HOURS AAAAAHHHHHHHHHHH
This guide only introduces the very fundamentals of Git in a simplified fashion.
This guide assumes you're using VS Code but Git is fully usable without it.
Git is a version control system. That basically means you get to see all the changes you (or someone else if you're collaborating) have made to files in a repository. Git is commonly used for storing files online and even publicly for anyone to see, such as on GitHub, but it's fully functional for offline use as well. For now, we're only covering offline use.
In VS Code, the main view for Git is the source control view, seen at the top left side of the window:
The changes are shown on the files themselves:
...and their contents, marked as red for deletions and green for additions:
How useful this is depends on how you use it, for example:
You can easily return to a version of your content that you know for sure is working.
You can undo one little change at a time to see where exactly things went wrong.
You can keep it as a form of documentation for the changes made over time or the changes required for certain things to work.
Using Git is completely optional but (speaking from experience) it can save hours wasted debugging and even dropped/restarted levels (for rundown developers) when no fix for problems is found.
If you haven't installed git yet, the source control view will show something like this:
Download and launch git installer from there. Most users should use 64-bit standalone installer.
When going through the installation, most default options should be fine. Just one change you might want to make:
Either way, for basic git use none of these options should matter much.
First step when working with a project offline is to initialize the repository. If you open the source control view now, you should see something like this:
We're almost ready to start using git, but first there's 2 commands to run. We have to set a user name and email for commits author tracking. For offline repositories this shouldn't matter much and can be changed later, but remember that you can't change author information on existing commits. To run the commands, open a new terminal in VS Code:
In the terminal, input the commands with your own values:
git config --global user.name "somename"
git config --global user.email "somename@gmail.com"
Now you're ready to start using Git.
VS Code has extensions that bring additional functionality to the editor. GitLens is one such extension for Git:
It will not be covered here, but if you see some functionality in the guide that you don't see in your own VS Code, it likely comes from this extension.
Now that we're ready, let's initialize the repository. Once it's done, the source control view should list all your files as new since we haven't started tracking them yet:
Let's stage and commit all of them as the initial commit:
Press the + icon to stage all the changes (make sure you press it to the right of the "Changes" text instead of a specific file) - you're selecting the files to include in the commit
Write the commit message - "initial commit"
Press the commit button to finalize the commit.
Now the source control window should only show the "Publish Branch" button. This is an offline project so we're not going to do that.
Stage (verb) - to stage a change is to prepare it for a commit, i.e. include in the commit. Git allows you to select just the changes you want instead of everything that's been edited (including selecting only specific parts of files).
Commit - a snapshot of the hierarchy and the contents of the files in a Git repository; a single point in the Git history. A commit is like a specific version of your project.
Commit message - a description of the changes made in a specific commit.
Branch, "master" branch - a "branch" is a line of development. Essentially, it allows you to isolate your changes and commits from others based on people working on them or their purpose. Branching is a more advanced topic that you likely won't run into when working alone. The master branch is the default, the main branch in a project.
So now we kind of know how to work with Git and it does track the changes in our project correctly, but how do we make use of it? For starters, when do we commit? And what commit messages do we use?
That's completely up to you. If you are developing a rundown on thunderstore, one thunderstore version can be one commit, with commits like "V 1.0.1", "added C1" etc. You can make it more detailed, for example "adjusted ammo balancing for C1", "added shadow baby charger", "added hel hammer launcher". There's probably just 2 things I could mention:
Generally, smaller commits are better than large commits - keep them more focused, easier to define and less progress lost when reverting;
Make sure your changes work before you commit. A commit with a broken project version is the last thing you want.
Now let's say that we did indeed break something (but didn't commit) and want to use Git to help fix it. The first straightforward way is to see the specific changes you made and change them back. We don't want to revert the entire commit or entire files, just some one line that's probably broken. We have 2 easy ways to do that.
For the first, go to source control view and select a file with changes, then find some specific change:
Click the arrow in the middle to revert the change:
The change should be reverted, just remember to save the file.
You can't always easily undo reverted changes.
For the second method, go directly to the changed file and line. You should see a blue line near line numbers:
Click it. You should now see the changes inline. At the top right there should be a revert icon:
Click it to revert the change.
In 2 other common cases where you want to revert a whole file or all the changes since the last commit, it's easily done through source control view. All you need to do is click the revert icon on a file or next to changes:
Reverting that specific change would restore the deleted file.
Now if you break your project and don't remember what was different when it worked, you can simply look and see. Hopefully this prevents some painful debugging for you.
Git keeps the entire history of the project since the repository was initialized, so you could go back to a version of your project from years back if you wanted to. You can also look into storing a project privately or publicly online as well and even working with other people.
How to set up your project for editing and compiling
We will be creating a Library project written in C# targeting all platforms
First create a new project and choose "Class Library" - "A project for creating a classlibrary that targets .NET or .NET Standard"
Then write a name into the Project name field, such as "MyFirstPlugin" and click "Next"
Next ensure you have ".NET 6.0 (Long-term support)" selected as your Framework and click "Create"
When the project is finished creating you should see "Class1.cs" open
Click Project > Edit Project File and replace it with the following
The following project settings assume you have r2modman installed in the Default location with BepInEx installed and run at least once
You can change which r2modman profile is used by editing the Profile property Additionally, you can also change the BepInEx path to your preferred location by editing the BepInEx property
Finally save the changes to the csproj file
You should now be ready to create your Plugin class
The following Patch class will use HarmonyX to allow us to modify how GTFO behaves
First, in Visual Studio, click Project > Add Class and change Class1.cs to Patch.cs
Next replace the pre-generated code with the following
After that go back to our Plugin class and add the following line to your Load method
You can now compile the plugin and when you click the About Rundown button in-game you will unlock a new rundown
The line we added to our load method means that when the plugin loads it will create a Harmony instance and apply all the patches it can find (note we supply Harmony with the same GUID we saw earlier). The [HarmonyPatch]
attribute we gave our Patch class helps Harmony find it (though it isn't always required depending on how you go about patching).
When Harmony finds our Patch class it'll then look for any patching methods. The attributes we gave to the MyPatch method label it as such a patching method. The [HarmonyPostfix]
attribute tells Harmony a little about how we want to do our patch while the [HarmonyPatch(...)]
attribute tells Harmony what we want to patch. In this case it specifies that we want to patch the Setup method in the CM_PageRundown_New class. Our patch replaces the action of the about rundown button with our own code.
If multiple patch classes are desired first create a harmony instance and then use the types
Step 1: Download the Wwise launcher from the link below. From inside the launcher, download Wwise version 2017.1.9.6501.
You also probably want to use audacity to convert audio files to the .wav file format, because that's what Wwise requires. The download link is below.
Step 2: Using Wwise version 2017.1.9.6501 create a new project, and uncheck all of the asset groups, none are required for our SoundBanks.
Step 1: In the project explorer, right-click on the Default Work Unit in the Actor-Mixer Hierarchy, and in "New Child", select "Actor-Mixer". Name this Actor-Mixer "SFXmixer"
Step 2: Right-click SFXmixer and create a new folder, and name it whatever you want. We will be putting all our sounds into this folder.
Step 3: Drag your .wav files directly into this folder in the hierarchy inside Wwise. It should prompt you with import settings, click import.
Step 4: Click on your sound, and in the Sound Property Editor, click on the "Positioning" tab. Check the "Override parent" box.
Step 6: Change the positioning to "3D". (Note: You can change the volume of your sound, or make your sound loop continuously in the "General Settings" tab)
How to take a rundown and reduce it to one level
This step isn't necessary (and in some ways it's better not to do this at all), but for learning purposes it's good to understand exactly what we'll be working with.
For this step we're going to trim the main datablocks that we'll be working with (specifically Rundown and LevelLayout) so that they only contain one level.
This also doubles as an exercise in using VS Code.
To start, we'll grab a copy of the unedited Rundown, LevelLayout and GameSetup datablocks. You can follow along with how to generate them using MTFO from or grab them from the archived versions at . You can also view the final version of our edited datablocks here: . Put these inside a folder in the GameData
directory of BepInEx. You'll have something like this:
MTFO can load datablocks directly from the GameData
folder, but it is better to keep them organized in a separate folder. MTFO also used to support loading datablocks from the plugins
folder instead, which is what you'll see in many mods and other guides (and even in other parts of this wiki!). Placing datablocks in the GameData
folder is now preferred.
Open your GameData
folder with VS Code.
Open the Rundown datablock and delete the headers.
Headers have no impact on modded datablocks, deleting them is optional. They're used only by the devs in their editor tools.
Now we'll delete all but one rundown from the "Blocks"
part of the datablock.
First, find the "Tutorial Holder"
rundown's block. This contains the game's tutorial, and its presence is hardcoded so we can't delete it.
Go to the start of the block, collapse it, and copy it. (Or, if using VS Code, you can use Control + Shift + [
to collapse the block your cursor is in).
Paste it at the bottom of the blocks.
Find the rundown that contains whichever level you'd like to use as a foundation. In this case, we'll use Rundown 6.
Collapse it, copy it and paste it at the bottom of the blocks.
Delete all the other blocks, aside from our two at the bottom (it's easiest to do this by collapsing them all). Once we're down to just our two blocks you might need to fix some formatting. There should be a comma between our two blocks, but not one at the end.
We only have one rundown and we want to keep only one level in it. Let's go with B2.
Collapse all the tier blocks.
Delete everything but B tier.
Make sure you don't delete the tiers themselves, just leave them empty. Otherwise the rundown will not load correctly.
Confirm that the 2nd object in B tier is B2 "Contaminant" and collapse the tier blocks
Delete all but the 2nd block.
B2 has now become B1 and is the only level in the rundown.
At the bottom of the rundown's block there is the "persistentID"
. When the rundown's ID is not 1, GTFO API ensures all levels are unlocked, so we don't have to take care of that ourselves. If you want to include level progression, you'll have to set the ID to 1 and then use the "Accessibility", "UnlockedByExpedition", and "CustomProgressionLock" fields to control levels being unlocked.
Contaminant is the only level in our rundown, but it still has a secondary layer. Let's thoroughly remove the secondary layer from the Rundown datablock. We'll have to remove quite a few sections, see the image below for what we have to remove. We need to remove the bulkhead information from the main layer, and also a bunch of stuff from the secondary layer. After we're done, the secondary layer should look the same as the third layer.
Now we have to do the same to the LevelLayout datablock. We'll find the correct level layout ID and delete the rest.
In the picture above from our rundown block, you can see that LevelLayoutData
is set to 162. That's our main layer level layout.
Open the LevelLayout datablock and find the block with this ID.
Repeat the same process as for our rundown blocks: collapse it, copy it, collapse all blocks, delete them, paste the copied block, delete the comma.
Remember VS Code has to process over 200k lines here so don't be too harsh if it lags a bit. Deleting at least a part of the blocks does have practical use here as VS Code and its plugins won't take time to load when editing huge files like this one.
There should only be a few thousand lines left in this file.
Most of the work is now done, and we only have to do a minor tweak to the GameSetup datablock. Open it up and change the "RundownIdsToLoad"
to only contain the rundown ID for your rundown. The rundown's ID is the "PersistentID"
number at the bottom of the rundown's block.
We talked briefly about changing the ID of our rundown block earlier. While this is not a necessary step anymore thanks to GTFO API, it was originally agreed with devs to change all custom rundowns' ID to 1. If you did do this, you'll obviously have to change the "RundownIdsToLoad"
to match.
You're also allowed to change the LevelLayout block IDs. If you do so, then you'll have to change the "LevelLayoutData"
from the rundown's block to match.
Remember to save everything.
Launch the game. You should now see only B1 in the rundown menu.
Drop into the level. You should now have only the one rundown, and the level itself should load just fine. If you have freecam or some other mods, you can use them to check what the level looks like more easily. See if anything has changed.
You should see some marker (all sorts of objects placed in the level, a whole different topic) placement differences, also the lack of the secondary layer.
Oh and we deleted the bulkhead key, controller, and the bulkhead door (which is now just a security door) from the main layer. If you want to restore that, you can do so by restoring these 3 fields from the original datablock we kept as a backup (you did keep some as a backup, right?):
ZonesWithBulkheadEntrance - zones in same layer (main) that have bulkhead entrances.
BulkheadDoorControllerPlacements - where to place bulkhead door controllers. If a zone with a bulkhead entrance does not have a door controller, the entrance will not require a bulkhead key.
BulkheadKeyPlacements - where to place bulkhead keys in the layer.
Make sure you restore the ones under MainLayerData
and not secondary. You can also just copy the whole MainLayerData
block.
Later sections of the guide will assume you restored these (or didn't delete them to begin with).
If you did actually restore and verify again, the terminal near the bulkhead might be near the zone door in front of the door controller now.
The Rundown, LevelLayout and GameSetup blocks are now cleaned up and we can move onto the 2nd step in the guide.
The one stop shop for all content GTFO modding wiki related! Datablock and guides oh my.
This wiki is connected to the .
Current Wiki Status: About as good as it'll get
We certainly don't have everything here, but there is plenty of useful information.
Mr Bro will teach you how to create soundbanks to add custom sounds
Audiokinetic Wwise is a software development kit (SDK) for audio content creation and integration in games and other interactive media, the tool used by 10CC to integrate audio into GTFO. Wwise also includes a soundbank system, which allows developers to organize and access their audio content, using sound events. To add custom sounds to GTFO, we will have to use Wwise and create SoundBanks within the application.
A SoundBank is a container of sound events, and the objects and audio data required to play them. GTFO uses SoundBanks created by Wwise to play all of its audio in-game, ranging from its dynamic music system to enemy sound effects.
Who is Mr Bro?
Hi there, my name is Mr. Bro and I'm a big fan of the video game GTFO. Everyone has been asking for ways to add custom sound and here is a guide on how to do so! Looking into it I have found a way how to use Wwise to create custom soundbanks and have created a plugin to put these soundbanks into GTFO. Personally it was very difficult at first, but I look forward to hearing feedback from other players and continuing to improve my creations!
Step 1: Click on the "SoundBanks" tab, right-click the "Default Work Unit", and create a new Soundbank. After doing this, press F7 to open the SoundBank workstation, or press the "Layouts" tab in the window menu, and selected "SoundBanks". You can press F5 to close the Manager, or use the aforementioned tab.
Step 2: Click on the "Events" tab, and drag your "Play" sound event into the SoundBank Editor.
If you want to create more sound events, you can drag them into the same SoundBank. This includes "Stop" events, which will stop a sound playing, which is necessary for stopping looping sounds. (e.g. Door alarms)
Step 3: To export the SoundBank, right-click the SoundBank in the SoundBank Manager, and "Generate SoundBank(s) for current platform", or press Shift-F7.
Step 4: To view your SoundBank file, right-click the SoundBank in the SoundBank Manager, and select the last option in the drop down menu, "Open Containing Folder", for the SoundBank. (Make sure not to select the second last option that opens the folder for the Work Unit.)
You should end up with two files, the .bnk file (the SoundBank itself), and a .txt file, containing your Event IDs. This .txt file is necessary for using your SoundBank.
Import GTFO asset files into Unity
Unity version is required to create a project for GTFO
Open Unity Hub and in the Projects tab click the drop-down button next to Open
Select Add project from disk and navigate to your exported assets folder
Choose the "ExportedProject" folder and ensure the editor version is correct
Open the project in Unity Hub and wait for Half-Life 3
Enjoy the numerous errors
Step 1: In the "Events tab", click on the "Default Work Unit" in the "Events" folder, and create a new "Play" sound event. Click on the "Play" sound event you created.
Step 2: Click back onto the "Audio" tab, and drag your sound from the hierarchy into the "Target" box in the Event Editor. (Note: This Event Editor will only display given you clicked on the "Play" event in the Events tab)
You will run into some if you play enough modded. You might have already run into some without even knowing it. You have already run into some you don't even know about.
Originally covered in , but it deserves its own page. Besides, it's useful for all users.
Errors and exceptions often mean something went wrong either with the game or a mod. There's an infinite number of causes so it's impossible to know all of them, but it's important to know where to start looking for information. At the very least you can pass on more when looking for help. If you do run into something you can't fix yourself, you can ask in the in the modding discord.
Error/Exception - while in a way they mean the same thing to a user, the distinct difference is that if you see "error", it is printed by programmers, whereas "exception" shows that it came straight from code.
BepInEx logs/game logs - BepinEx logs are shown in the console window and LogOutput.log, meanwhile game logs are stored in %userprofile%\AppData\LocalLow\10 Chambers Collective\GTFO
. While some information overlaps, game logs show more useful base game error information, and show a good amount of non-error information as well.
When launching the game with mods enabled, you'll see the BepInEx console as a separate window.
This console is useful for many things, but most importantly for users, it shows most errors in red text. If it's not a red text error, it can be relevant to rundown developers specifically, so it's not covered here.
Not all red text means you have to do something about it. Some mods post meaningless errors on purpose (although they really shouldn't), and some base game errors are meaningless as well.
In the case of mods, the message is probably something intentionally stupid.
In the case of game errors, check the game logs to see if they contain that error. If they don't, you can probably ignore it.
All logged messages have the type and source as a prefix. E.g. [Error: MTFO] error message
shows that there was an error caused by the mod MTFO. Most of the time errors are thrown by the game itself (but the underlying cause can still be a mod), in which case you will see [Error: Unity]
as the prefix. In this case it is recommended to look for the error in the game logs instead of BepInEx logs. These are located in %userprofile%\AppData\LocalLow\10 Chambers Collective\GTFO
If you closed the game already, their name will be of the following format: GTFO.[Date].[Time]_[PlayerNick]_[NetworkStatus]
. If the game is still open (or crashed), it'll instead be GTFO.[Date].[Time]_NICKNAME_NETSTATUS
. For the most part you will be looking for the most recent file created.
It is also possible to see an error coming from something that doesn't match any mod names nor base game (such as Preloader or Detour). Treat it as an error coming from mods in that case.
While in BepInEx console the error message is one line, the game logs provide the stack trace, which is the full source of the error and often provides much more information than just the error message.
Let's take a look at an example. BepInEx console shows this error message:
While the game logs show this:
NullReferenceException
is one of the most common errors that says next to nothing about the cause. All we know from BepInEx console is that an error happened, but the game logs show that the source is from melee weapons. Even if this error doesn't give enough information to you, it will allow other people to find the cause faster if you ask for help.
Also note that some errors can cause even more errors. When debugging, check the oldest errors first.
Editor features that make working with datablocks easier
This may seem very basic but it can come up more often than expected. There's 2 quick ways to save files and you should save often.
Ctrl + s
- save the current file
Ctrl + k, s
- save all opened files (press ctrl and k together, release both, and press s)
This is a neat function to have, it allows you to easily open any folder from file explorer with VS Code. You can enable this when installing. If you didn't, you can reinstall VS Code without deleting it and select the option (or you can go mess with registry files but I wouldn't recommend it).
If VS Code detects an error in your file, it'll mark that file red.
It might not be obvious what causes this or what the exact error is, so you can open the problems view to see it by pressing ctrl + shift + m
:
From here, you can click on the problem and you'll be taken straight to it. And if you're lucky, the error message will accurately explain the problem.
This allows you to see several files at once. To use it, simply drag a file in the top view to the side of the screen.
You can collapse blocks by clicking the arrow next to them at the left side of the screen. This can be useful to hide unnecessary information or quickly select entire blocks.
Useful when navigating big lists, such as levels in a tier. Pressing ctrl + shift + \
anywhere inside a block will take you to the end of it, and pressing it again while at the end of a block will take you back to the start.
Breadcrumbs tell you where you are inside a file in terms of blocks:
You can click the breadcrumbs to navigate and even find out what block you're in.
Sometimes it's possible that you know the line you need but scrolling would take some effort. To quickly go to the line you want, press ctrl + g
. This will open an input field at the top of the screen. Input the line number you need and press enter.
When working with datablocks, it's common to open and close a lot of files. The explorer view at the left side of the screen is a good way to see all the files but it can take a bit of effort to get the one you want. A better way is to use the top menu to go to the file you want, similarly to going between lines. Open the menu with ctrl + p
and start typing the name of the file you want, then press enter to open the file. This works for any file in the folder you have open and its subfolders.
This one might seem obvious but there can be features you're not aware of.
The basic shortcut for search is ctrl + f
but afterwards you can press alt + l
or manually click the icon to limit the search to selected text.
For example, if i want to search for tier D in rundowndatablock, i get this:
But if i want to find tier d in a specific rundown, i could use breadcrumbs to get to the start/end of the block, collapse it, select it and then find in selection instead, showing 2 matches:
This may seem trivial but imagine you're trying to find a specific chained puzzle use in a certain level and have to go through hundreds of matches instead. Then it ends up saving tons of time.
Available either with ctrl + shift + f
or with 2nd menu at the left side of the screen, this function allows search/replace to work across all files in the opened folder. You can also search in a subset of files.
RegEx is a powerful search & replace tool, not limited to VS Code, and is quite a complex topic. For now let's just say that it allows you to find text based on patterns.
Let's say I want to find enemy with persistent id 13, but I'm lazy to enter the whole text in the search. Using RegEx, i can search for pattern pers.+13
and find what I want.
Here "pers" and "13" are taken as literal text to search for, but ".+" is a pattern saying "match any character at least once" (except line breaks), and this ends up matching the rest of the missing text in-between "pers" and "13".
RegEx (as well as search without regex) can be used not only to find, but to replace text, performing massive amounts of editing in no time at all.
Let's say we want to take ALL the enemy groups in base game and rename them to mark as such.
The search pattern is "name": "(.+)"
. All of these characters are matched literally, except for "(.+)
". The ".+" pattern is the same as before, but "()" marks a capturing group. That means we're going to reuse it later.
The replace pattern is "name": "OBSOLETE_$1"
. Here almost everything is literal, except for $1. That means the first capturing group should be inserted here.
The result:
Depending on creativity and circumstances, this can be used to great effect. In fact, RegEx was used to generate a big part of this wiki.
A simple but neat feature, this allows you to create a new file simply by double-clicking next to the list of existing files. You can use this to store a block for editing temporarily, back up certain information, mark notes and so on. VS Code will keep the file even if you close and reopen the app.
Comments are created purely to give additional context/information for the reader. JSON technically does not have comments, but there are workarounds for this.
One way is to make a new JSON field. The game ignores whatever fields it doesn't know, so it won't break anything.
But this can get annoying, and there is a way to use comments normally with //
. If you do it in a plain JSON file, VS Code will likely complain about it.
But the game ignores comments so it can be left as-is. However, the red text triggers our OCD and we want it gone. Well, there is a way. JSONC (json with comments) exists, and while mods do not usually use it, we can configure VS Code to take any JSON file as JSONC.
To do this, open the command menu with ctrl + shift + p
and start typing "language". "Change language mode" should pop up. Select it, then select "Configure file association", then type in "jsonc" and press enter.
Now VS Code will assume JSONC for all JSON files and you won't see it complaining about comments.
Step 1: Add BepInExPack_GTFO, MTFO (if you don't have them already) as a dependency to your profile/mod.
Step 2: In your profile, add the .bnk and .txt file to the path:
(Create the "Assets" and "SoundBank" folder if they don't exist)
Step 3: Use the sound event ID inside the .txt file to put into MTFO datablocks to use your sound ingame. Make sure to use the sound event ID (highlighted below) which is located on the upper half of the file, not the audio ID which is located on the lower half of the file.
This sound event ID can be used in in-game events, as well as:
GameData_EnemySFXDataBlock_bin
GameData_ChainedPuzzleDataBlock_bin
GameData_MeleeSFXDataBlock_bin
There's also a DataBlock for weapon sound effects, but ¯\_(ツ)_/¯
Mr Bro out
Hot reloading can let you test your changes without having to restart your game, it's great!
Hot reloading may not work for every datablock. If you make a change and nothing happens, you may need to restart the game instead. Additionally, hot reloading isn't quite the same as restarting your game, and may cause bugs. If in doubt, restart your game.
Hot reloading is a MTFO feature that allows you to reload datablocks without restarting the game. It can speed up your ability to test your changes quite considerably, and is definitely something you should strongly consider before trying to make your own datablock mods.
If you're using r2modman to manage your mods, then after creating a profile with MTFO (and launching the modded profile at least once to generate the config files) you can go to Config Editor in the r2modman profile, and then edit MTFO's config file.
Then simply set EnableHotReload to true and save.
You can also edit the config file with any text editor as well.
Don't mind the description stating it only works for a few datablocks, it's outdated.
Now when you're in the game's level selection menus, a button will appear on the left that will reload your datablocks without having to restart the game.
If there's something useful for modding but not stored on the wiki, we'll reference it here.
Partly an explanation on how level generation works, mostly a list of all geomorphs and what they look like.
Explains most things about how weapons work and how to make them.
A way to make level layout blocks online instead of working with JSON.
Mostly focused on how to setup unity for making geomorphs, but works for datamining unity assets and making other things in unity as well.
Major update soon.
Stores all datablocks and their type information as well as enums. Can be used to view all changes to datablocks starting from mid-R4.
basically like it's done on windows
make sure your gfx drivers are good (other games, glxgears work etc)
install linux-native steam
login to steam
install GTFO
launch GTFO, make sure it launches and works okay
if "not responding" after trying to start a level, try x11 instead of wayland (on ubuntu you log out, select user, on password screen you go bottom right of screen gears -> ubuntu on xorg)
download r2modman for your distro from
launch r2modman, select GTFO, should detect your GTFO directory, can be set from settings if not
create a profile, install the mods you want in it
click on launch modded, modded launches, enjoy
Tested to work with r2modman 3.1.34, earlier versions needed some extra steps to launch modded properly (like creating a bat-file to launch modded and a doorstop_config.ini file with a full path to the BepInEx.Unity.IL2CPP.dll file etc)
How to setup foobar2000 with the vgmstream component to play and extract the audio files from GTFO
Download and install
Download and open
Open foobar2000 and choose File > Add Folder
Find and add the following folder:
~\Steam\steamapps\common\GTFO\GTFO_Data\StreamingAssets\GeneratedSoundBanks\Windows
Open the SoundbanksInfo.xml
in a text editor to see what audio files the game uses
In foobar2000 search for the file id with Ctrl + F
Double click the audio to listen to it
In foobar2000 right click a track
Choose Convert > Quick Convert
Choose an output setting and click Convert
Select an output path and save
GameData_ArchetypeDataBlock_bin.json (filled)
Defines weapon stats and metadata, including sentries.
This datablock is not referenced directly, but rather selected from component firemode paired with specified component gear category from . Sentry archetypes are hardcoded.
Weapons technically have 2 names. This is the one used as a more generic name, e.g. "Assault Rifle".
The other name is specified in GearJSON in .
Weapon description displayed in lobby.
Firing mode of this weapon. Can affect which other fields are used.
SemiBurst is an unused but functional firemode. It's basically a semi auto where you can fire x shots before triggering a cooldown, unless your delay between any 2 shots is big enough for it to reset.
Recoil data ID of this weapon.
Which booster types should affect this weapon.
Seems functional but currently unused.
Base damage of this weapon.
Linear damage falloff for this weapon.
X determines where it starts, Y determines where it ends.
Anywhere in-between X and Y the damage falls off linearly via a multiplier to 0, but there's a lowest value for the multiplier.
Lowest value for the multiplier is currently 0.1.
Stagger damage multiplier for base damage.
Precision damage multiplier for base damage. This multiplier is only applied when a weakspot is hit (back damage doesn't count).
Default mag size.
Time in seconds for the reload animation to complete. Note that reload can be completed before the animation is done.
We will have:
30 initial (mag included) ammo when starting a level.
OnDropin seems unused.
46 max reserve ammo.
450 * 0.2 / 10 = 9 bullets per refill (0.2 comes from the fact that you use 0.2 of a refill at any given time (e.g. 4 uses of a refill is 0.8)
Time in seconds between shots.
Sentries don't immediately detect that an enemy died so they're more likely to waste ammo on dead enemies with low delay.
Shell casing size multiplier.
Shell casing ejection speed range.
Whether this weapon can pierce.
Pierce is currently broken for sentries.
How many enemies can a piercing bullet hit. 1 means it will not pierce.
5 is technically max, although in a roundabout way - for piercing weapons, a loop is performed a maximum of 5 times before exiting. Each collider hit consumes one iteration. It can often happen that the same enemy is hit several times (although damage is applied only once), making high pierce give diminishing returns. In practice, even with 4-5 pierce you'll most likely only hit up to 3 enemies.
Hipfire random spread.
Spread is hard to judge, but a weapon with 1 spread is fairly accurate even at mid-long range (~15m).
Behind the scenes this is just a multiplier for a random point inside a unit circle.
ADS random spread.
How long equip animation takes. Also affects when it can be fired after selecting.
Animation sequence to play when equipping a weapon.
How long ADS animation takes.
Animation sequence to play when aiming with a weapon.
For burst weapons, delay between separate bursts.
For burst weapons, how many shots a single burst fires.
For shotguns, pellet count in a shot.
For shotguns, the cone size. Note that this value is Int32, only accepting whole numbers.
Cone size affects pellet spread, but it is not random. It fires one pellet in the middle and the rest in a circle around the middle based on a sin/cos function.
Shotgun random pellet spread. Also a whole number.
This doesn't scale well with cone size and can result in shots going way further from the middle than expected.
How long it takes for the weapon to charge before firing.
Delay before a weapon can charge up again when it stops firing.
Currently broken for auto weapons.
Time for semiburst weapon shot count to reset.
Delay before a sentry starts firing when detecting an enemy.
Sentry rotation speed.
Sentry detection max range.
Currently broken (sort of) - the noise range the sentry causes around itself directly depends on this value.
Max angle to either side at which a sentry can detect enemies.
Note that sentries can only cover 180 degrees (value 90) total as they physically cannot turn around.
By default sentries shoot where their barrel is aiming, which can cause them to miss when they shouldn't.
Setting this to true will make them basically an aimbot, always shooting towards the target.
Unfortunately, the target position is also currently broken on some enemies and so they can miss even as aimbot.
Distance for long range boosters.
Distance for short range boosters.
Seems to determine how sentries should target enemies.
Practical difference is unknown.
Should sentry only target biotracker-tagged enemies.
Should sentry prioritize targetting biotracker-tagged enemies.
Start fire delay multiplier when targetting biotracker-tagged enemies.
Rotation speed multiplier when targetting biotracker-tagged enemies.
Damage multiplier when targetting biotracker-tagged enemies.
Stagger damage multiplier when targetting biotracker-tagged enemies.
Cost of bullet multiplier when targetting biotracker-tagged enemies.
Makes perfect sense.
Shot delay multiplier when targetting biotracker-tagged enemies.
A list of every datablock in the game, their fields and if you're lucky, what they do!
Datablocks are groups of fields that form all the customizable parts of rundowns, weapons, enemies, levels, waves, and more. Everything needed to create a custom rundown can be created utilizing datablocks. This reference will explain all of the fields within each datablock. Check out the guides for more information about how to use them.
Datablocks and their change history is kept on .
All datablocks share 3 main fields, each of them has special meaning.
The ID of the block, used to reference by all other blocks. If you see any other field with type UInt32, chances are it's a reference to some other block (but that type is also used to reference wwise audio IDs).
Must be unique.
Although persistentID type is UInt32, the actual value range is often much smaller because it is changed to other types in networking for example. Sticking to a safe range of 1-65535 is recommended, but datablocks with even smaller ranges do exist ( for example). If we notice any blocks with a range smaller than that, we will make a note about it in the relevant reference page.
Whether this block is enabled. Disabled blocks are ignored by the game.
The name of the block. This seems to be only for readability and is not used by the game, but it may be used by mods.
Must be unique.
This field is only used by devs in their editor.
The last persistentID found in datablocks. Serves no purpose but can clutter the logs if not set correctly.
in all datablocks documentation, you see a specific format of fields:
name - type
or name - type (special meaning)
The name is just that, the name of the field. The type is what data should be specified there.
These are the most basic types:
None - 0
Force_One - 1
Rel_Value - 2
In JSON, enums can be specified both as underlying type and as plain text.
An example value you could see here is "EnemyRunner, EnemyLow, EnemyCrawl", or 84. This combines 3 values and the game processes it in a way that understands the value set as all 3.
In datablocks reference, nested type refers to a class that's defined by the game. Each nested type has its own page to reduce clutter when the same type is referenced several times.
Every datablock file defines one object containing several fields, including a list of objects - Blocks.
All 3 of these never go by themselves, they contain other types (and sometimes can be nested).
Lists and Arrays always start and end with [ ]
symbols. For example, a "List Int32" looks like this: [ 1, 2, 3 ]
instead of a plain Int32 value 1
. a "List List Int32" follows the same principle: [ [ 1, 2, 3 ], [ 1, 2, 3 ] ]
. Arrays look exactly the same as Lists in JSON.
Dictionaries are a more complex type and are rarely found in datablocks. A dictionary is an object that maps 2 types, the first as key, the 2nd as value. In JSON, dictionaries look the same as nested objects.
Here you can see a dictionary with 2 keys "Glue" and "Explosive" mapped to objects. In JSON, an object with 2 fields "Glue" and "Explosive" could look exactly the same.
Sometimes you see fields set to seemingly invalid values such as an empty string or -1, or just not there at all. In these cases you can think of the fields as unset, which means the game provides some default value or behaviour for it.
In datablocks, Vectors are messed up right now and generate with some fields that don't actually do anything:
You can ignore or delete these.
Fields related to time/length/duration are specified in seconds unless stated otherwise.
GameData_EnemyGroupDataBlock_bin.json (filled)
This datablock is used for setting enemy spawns with a focus on randomization.
Spawning enemies is explained in .
EnemyGroup blocks 37 and 38 are used by birther and birther boss respectively. This can only be changed with mods.
Type match of this group.
There are special types, explained in .
Difficulty match of this group.
Defines where enemies are placed in the selected area.
The score (or population points/cost) of this group.
Multiplier for MaxScore when adding to total placed score in area.
The higher the area's score, the less likely it is to be picked for another enemy group.
Relative weight of this group. Affects how likely it is to be selected when several are matched.
List of roles in this group. Each role will spawn one type of enemy when this group is selected.
GameData_EnemyBehaviorDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_EnemyMovementDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_EnemyBalancingDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_FogSettingsDataBlock_bin.json (filled)
This datablock edits fog settings, which include fog's visual effects (color, opacity, etc.), height, and controls if the fog is flipped (inversed fog, like the one in R5E1), infectious, etc.
As one of the most important features of the game, fog serves to occlude player's sight, limit zones' reachability and make decisions on whether to 'toss a Fog Repeller and combat amid' or to 'flee'. Also, colored fog (for example, Green Fog in R7C2, etc.) would affect lighting of the entire level, making your level able to give players impressions of tension, horror, anxiety, sound, and so on.
Unused.
Determine the color of the fog. This can affect the lighting of the entire level and create some preferable visual effect.
It seems that starting in Rundown 7, the way fog occludes sight and interacts with lights has changed. Colored fog may not block sight well, so it's best to use white fog for that purpose.
For example, here's extremely dense red fog:
The base fog density in the entire level.
To create inversed fog (e.g. R5E1), you have to set FogDensity
and DensityHeightMaxBoost
such that FogDensity
< DensityHeightMaxBoost
Unused.
Noise
are those floating particles right above the fog plane. These particles make the fog plane resemble a tide.
This field controls the floating direction of those particles / tide pattern of the fog plane.
Controls how fast noise particles float / how uneven the tide pattern of the fog plane is.
Seems to be the size of the noise particles. Higher values can make them look visibly separated, kind of like dust clouds.
The lowest point for the fog height. For non-inversed fog, everything with altitude lower than this value would be fully submerged by the fog.
As a reference for zone altitude: Low
: -4.0, Mid
: 0.0, High
: 4.0
Distance above lowest point for fog height, used in calculating the highest point for fog height.
This is the actual field for controlling the density of (non-inversed) fog. The larger, the more occluding.
The maximum value for how fast infection accumulates from fog. For example, 0.1
would take you 10 seconds to get fully infected. 0.03
is a common, average value to set to.
Note that how much infection you get depends on how deep in the fog you are with linear scaling, where the highest point of infection (and lowest actual infection gain rate) is at DensityHeightAltitude + DensityHeightRange
and lowest point is DensityHeightAltitude
GameData_EnemyPopulationDataBlock_bin.json (filled)
GameData_LevelLayoutDataBlock_bin.json (filled)
Level layout is the main block defining what the level looks like and what you can find in it, including resources, enemies, terminals etc.
The number of the first zone as seen in-game. The rest are derived from this and LocalIndex.
List containing the data of each zone. Each entry is one zone in the map. The first zone in the list is the entrance/elevator (NOT localindex 0).
GameData_ChainedPuzzleDataBlock_bin.json (filled)
Defines Chained Puzzle settings (e.g. Security Door alarm).
The Alarm name. For example, Class S Surge Alarm
Whether to trigger alarm when the puzzle starts. Typically set to false
for scans without an alarm and enemy wave.
However, you can set this field to true
even if you don't specify the enemy wave for the puzzle via the following fields. In that case, there will still be alarm sound but no enemy waves.
Determine how the wave spawns. You set this field to make the wave either a relatively regular, surge, diminished, or ://ERROR! alarm wave.
You may take those wave settings already available in the vanilla datablocks into good use.
Determine what type(s) of enemy would spawn.
How many areas away from players should enemies spawn, with 2
being the default value if left unset.
Used in R7 to prevent enemy waves spawning too far away from the scan zone.
Specify whether to stop the wave after Chained Puzzle Completion. Typically set to false
for ://ERROR! alarm.
Whether to use random position for each scan. Usually set to true
.
By setting this field to false
, the scan position for each puzzle would be relatively static, i.e. it's not static all the time.
As explained by the field name :)
Also as explained by the field name :)
Determines the count and types of scans.
If set to true
, the HUD won't show up if you are a certain distance (seems to be 25m) away from the scan.
Typically set to false
for regular scan, and true
for extraction scan.
Alarm sound when the puzzle starts. Changing the sound properly may give the scan a different impression.
Usually this sound starts the alarm sound loop.
Referenced to SoundID.
Alarm sound when the puzzle stop. Could use this field to set the ambient ERROR alarm sound.
Usually this sound stops the alarm sound loop. If not set correctly, the alarm sound can go on forever.
Referenced to SoundID.
GameData_GearCategoryDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_MeleeArchetypeDataBlock_bin.json
Defines melee weapon stats and metadata.
This datablock is not referenced directly, but instead specified by the given to a melee weapon.
Datablock name. Unused elsewhere.
Normal: Wakes up sleepers in 7m from hitting locks or the ground. Quiet: Wakes up sleepers in 4m from hitting locks or the ground. Also has reduced chance to trigger long range aggro (i.e. glowing enemy wakes up far away from killing another enemy).
Damage dealt by fully charged attacks. Partial charge interpolates the damage from light to fully charged by the charge progress cubed.
Damage dealt by light attacks.
Stagger damage multiplier for light attacks.
Stagger damage multiplier for fully charged attacks. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.
Precision damage multiplier for light attacks.
Precision damage multiplier for fully charged attacks. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.
Environment damage multiplier for light attacks (i.e. vs. locks and doors).
Environment damage multiplier for fully charged attacks (i.e. vs. locks and doors). Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.
Backstab damage multiplier for light attacks. Stacks multiplicatively with the default backstab bonus. Does not apply to enemies without the default backstab bonus.
Backstab damage multiplier for light attacks. Stacks multiplicatively with the default backstab bonus. Does not apply to enemies without the default backstab bonus. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.
Damage multiplier to sleeping enemies for light attacks. Does not apply to Scouts.
Damage multiplier to sleeping enemies for fully charged attacks. Does not apply to Scouts. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.
Prevents the weapon from breaking enemy limbs.
Distance in meters that the weapon hits what is directly in view. Does not affect the model hitbox.
Additional distance in meters around the model hitbox that hits can occur in. Does not affect wall collision. Remains inactive while something is directly in view. Appears to be twice as large as the value written in practice.
Distance in meters around the player that pushes can affect targets in.
Allows the weapon to hit with both the forward damage ray (hitting a target directly in view) and the model hitbox. Also allows the weapon to hit additional targets after the first hit if the hit animation can deal damage. Can still damage multiple enemies simultaneously with the model hitbox even if set to false.
If true, waits until the attack button is released to trigger a light attack. Otherwise, immediately begins the light attack animation on mouse click, though it can still transition to a charge attack.
Should cause the impact particle effect when hitting terrain.
Allows the user to sprint while charging. If false, will stop and prevent sprinting while charging.
Multiplier to user sprint speed while charging. Cannot cause the user to exceed their sprint speed, but can increase the diagonal sprint speed to match the forward sprint speed.
Melee animation set ID of this weapon.
Melee SFX set ID of this weapon.
Stamina cost for fully charged attacks. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.
Stamina cost for light attacks.
Stamina cost for pushes.
GameData_RecoilDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_SurvivalWavePopulationDataBlock_bin.json (filled)
This block is used for assigning types from to actual enemies using PersistentID from EnemyDataBlock.
EnemyDataBlock PersistentID of Weakling for this population.
EnemyDataBlock PersistentID of Standard for this population.
EnemyDataBlock PersistentID of Special for this population.
EnemyDataBlock PersistentID of MiniBoss for this population.
EnemyDataBlock PersistentID of Boss for this population.
Note that Boss is currently broken in base game.
GameData_PlayerOfflineGearDataBlock_bin.json (filled)
Defines mostly various weapon parts (references to various gear datablocks) and some other data.
Probably the worst datablock in the game.
The only known use and difference here is "RundownSpecificInventory" which simply changes the text displayed in lobby.
This innocent little string here contains an absurd amount of information in an even more absurd format. It's most likely this way because it's sent over networking and devs didn't care enough to make it more readable in datablocks.
This JSON and its meaning (including various related datablocks) is covered in the weapons guide in .
GameData_WardenObjectiveDataBlock_bin.json (filled)
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
All expeditions require at least 1 Warden Objective (for Main specifically), which determines if the objective for the level (or one of the layers within the level) is Reactor Startup, Central Generator Cluster and all that.
To begin with, you can find any of the DBs in GameData_WardenObjectiveDatablock.json
as your base reference.
At first glance it looks quite intimidating, right? Don’t worry. Let’s do some categorizations and split those fields up. To make it more succinct, I will use one of the datablock copy-pasted from GameData_WardenObjectiveDatablock.json
, but remove some redundant parts and add some comments where necessary.
These fields tells the objective manager to spawn enemy waves and execute events at given point.
Literally on elevator land (upon cage drop). The fields are all self-clarifying.
At first glance it's rather perplexing that what is "On Activate". Sadly, I have no idea how to describe it clearly ; )
For now, you may refer to its usage in different objective type get some hints. "WavesOnActivate" and "EventsOnActivate" are not used as frequently as "WavesOnGotoWin" and "EventsOnGotoWin"; in addition, the first part, which is the explanation for field "OnActivateOnSolveItem", is quite long. So if you feel hard to understand this part, I suggest you skip this part and read "On Goto Win" first
By "Goto Win", it means objective completion.
The following fields are all Objective Specific. You can remove those irrelevant fields given your objective type.
Some objective types don't have their own objective-specific fields.
Some may require you to spawn certain item(s) in certain zone(s) in LevelLayoutDatablock
to make the objective work properly.
Objective Type for R6C1 and R6D4.
Objective Type for R6A1 and R6C2 Secondary.
Note: for zones that spawn those Big Pickup Items (no matter if they are objective item or not), don't forget to set "BigPickupDistribution"
in LevelLayoutDatablock
to "true"
Geomorph-tied objective type. To make a Reactor Startup / Shutdown work properly, you must set certain geomorph in the level.
Here's a list of the name of Reactor geomorph (use Ctrl + F in the doc 'All Geomorph' to see what they look like):
Mining Reactor Open HA 1 ****"Assets/AssetPrefabs/Complex/Mining/Geomorphs/geo_64x64_mining_reactor_open_HA_01.prefab"
Mining Reactor HA 2 ****"Assets/AssetPrefabs/Complex/Mining/Geomorphs/geo_64x64_mining_reactor_HA_02.prefab"
Lab Reactor HA 1 ****"Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_lab_reactor_HA_01.prefab"
Lab Reactor HA 2 ****"Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_lab_reactor_HA_02.prefab"
Note: For Reactor Shutdown, you can only use the Mining Reactor HA 2 as your Reactor Geomorph. Without aid of a plugin, Reactor Shutdown would be broken if using the other 3 geomorphs.
Finally, there's no Floodway Reactor so far.
The most phenomenal use of this objective type is probably R5C2 Overload, in which "WavesOnActivate", "EventsOnActivate", "ChainedPuzzleMidObjective" are all well used.
Also R5B3 Secondary (or Extreme) is an example for how to use this objective type.
R6C2 Main Objective.
Note:
If you are using this objective type as your Main Layer objective, the required number of powercells will be given in the elevator cargo container.
Otherwise (you are using it as your Secondary/Overload objective),
the powercells will spawn in the first zone of the layer.
Don't forget to set those zones' "ForceBigpickupDistribution"
to "true"
If you don't want the powercell to be given in the elevator cargo, you will need the help of Tweaker
, a plugin that enables you to manipulate your elevator cargo.
In addition, you need to spawn the required number of PowerGenerator
(s) in LevelLayoutDatablock
in the desired zone(s). The objective manager would choose one of the unselected PowerGenerator
(s) spawned in the zone as the objective generator.
R6B2 Secondary / R6D2 Secondary
To use this objective types, you need to spawn required number of terminals for each uplink in LevelLayoutDatablock
.
Also, for Corrupted Uplink, sadly, for each Uplink, you CAN'T spawn the 2 terminals for the uplink in 2 different zone.
Geomorph-tied objective type (same as Reactor Startup/Shutdown).
Here's a list of the name of valid generator geomorph (in which you can spawn a generator cluster) . Use Ctrl + F in the doc 'All Geomorph' to see what they look like:
Digsite hub HA 1 ****"Assets/AssetPrefabs/Complex/Mining/Geomorphs/Digsite/geo_64x64_mining_dig_site_hub_HA_01.prefab"
Digsite hub HA 2 ****"Assets/AssetPrefabs/Complex/Mining/Geomorphs/Digsite/geo_64x64_mining_dig_site_hub_HA_02.prefab"
Lab hub HA 2 ****"Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_tech_lab_hub_HA_02.prefab"
Lab HA 2 [Standard Geomorph] ****"Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_tech_lab_HA_02.prefab"
Lab HA 3 [Standard Geomorph] "Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_tech_lab_HA_03.prefab"
Note that I've marked the last 2 geomorph with [Standard Geomorph]. If you directly copy-paste them into "CustomGeomorph"
in LevelLayoutDatablock
, your console will flood with Errors. Without editing ComplexResourceSetDatablock
, if you want to use the 2 standard geomorphs, you will need to roll subseed
to change the Zone's layout and MarkerSubseed
to finally get a Generator Cluster. I suggest you read the 'All Geomorph' doc to find out why.
Oh, finally, in LevelLayoutDatablock
, don't forget to set "GeneratorClustersInZone"
to 1
in the desired zone.
Geomorph-tied objective type (same as Reactor Startup/Shutdown).
Here's a list of the name of available geomorph. Use Ctrl + F in the doc 'All Geomorph' to see what they look like:
Lab Dead End Room 1 ****"Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_Lab_dead_end_room_02.prefab"
Lab Dead End Room 2 ****"Assets/AssetPrefabs/Complex/Tech/Geomorphs/geo_64x64_Lab_dead_end_room_01.prefab"
Refinery Dead End HA 1 ****"Assets/AssetPrefabs/Complex/Mining/Geomorphs/Refinery/geo_64x64_mining_refinery_dead_end_HA_01.prefab"
This type of objective begins upon entering the layer.
For example, if you set the Secondary objective as Survival, the timer won't starts until any of your teammates pace into the Secondary Bulkhead Door.
The objective completes upon the timer ends.
You can end the timer in advance by using a "ForceWin"
event.
After the "prepare time" ends, the objective manager will start executing events in "EventsOnActivate"
, and spawn waves in "WavesOnActivate"
.
First, let's take a look at an event copy-pasted from the vanilla datablock:
A bit intimidating, right? Luckily, for a certain event type only few fields are used, which means we can remove unused fields to make our file look cleaner.
Here's a list of EventType:
0 - None
1 - OpenSecurityDoor
2 - UnlockSecurityDoor
3 - AllLightsOff
4 - AllLightsOn
5 - PlaySound
6 - SetFogSetting
7 - DimensionFlashTeam
8 - DimensionWarpTeam
9 - SpawnEnemyWave
10 - StopEnemyWaves
11 - UpdateCustomSubObjective
12 - ForceCompleteObjective
999 - EventBreak
In the following I will try to explain what each event type does, and which fields are used. Also an example is provided for you to refer to and copy-paste.
Does nothing. You can use it to display Warden Intel.
Open a given Sec-Door, even the door is locked with a security alarm.
Note: Without aid of a plugin, you can't open a sec-door that has already been "approached".
Works similarly as OpenSecurityDoor.
Shutdown all lights in the current level.
Turn on all lights in the current level.
Play certain sound in/from specific zone. You will need to collect the sound ID from vanilla data.
Changed the fog for the entire level.
[Not 100% sure if my explanation is correct. Rectify if anywhere wrong]
Teleport your team to a given dimension, and teleport back after a certain duration.
This event is used in R6A1 and R6C1.
[Not 100% sure if my explanation is correct. Rectify if anywhere wrong]
Teleport your team to a given dimension. You need to execute another "DimensionWarpTeam"
event in the dimension to teleport either to another dimension or back to reality.
This event is used in R6D1 and R6D4.
Like the name said.
Note: What's the difference between "WardenIntel"
and "IntelMessage"
?
I'm not pretty sure...
"WardenIntel"
display intel message after "Delay"
timer ends.
"IntelMessage"
won't change [ZONE_4] into actual zone name, and is displayed after "SpawnDelay"
timer ends.
Stop all waves. For example, scout wave, reactor wave, uplink wave... everything.
What's "Custom SubObjective"? It's the "[PROGRESSION]" shown in the upper left of your hud, right below the objective description text.
What's the difference between "CustomSubObjectiveHeader"
and "CustomSubObjective"
? I don't know how to explain this. It is used in R6D3 Overload, which tells you to shutdown the Error alarm in Zone 292.
Note: unlike "WardenIntel"
, "CustomSubObjectiveHeader"
and "CustomSubObjective"
only accept reference to datablock. Do not use string.
Used in R6D1 to make the Main objective complete after the boss death.
Use "Layer"
to specify which layer to force win.
Used in "EventsOnActivate"
with "OnActivateOnSolveItem"
set to true
. Split up the event sequence to several parts and execute each on solving objective item.
Take R6C1, R6C2 Main, R6D4 as example.
GameData_TextDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_ItemDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_BoosterImplantConditionDataBlock_bin.json
GameData_BoosterImplantTemplateDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_RundownDataBlock_bin.json (filled)
The rundown datablock defines everything within the entire rundown, including levels, weapons, enemies, and more. Only one rundown can be loaded at a time, and it is chosen in the datablock.
No description provided.
Whether or not to use unlock requirements for this rundown.
Progression data to reach tier B of the rundown.
Progression data to reach tier C of the rundown.
Progression data to reach tier D of the rundown.
Progression data to reach tier E of the rundown.
Data for title, visuals, and description of the rundown.
The id for the vanity item datablock for this rundown.
Expedition data for all levels in tier A of this rundown.
Expedition data for all levels in tier B of this rundown.
Expedition data for all levels in tier C of this rundown.
Expedition data for all levels in tier D of this rundown.
Expedition data for all levels in tier E of this rundown.
Ammo settings are closely tied with cost of bullet settings and explained there.
equivalent for melee charge speed.
Dialog line fields seem mostly related to .
All aggressive enemies count towards cap. If the remaining allowed cost is lower than the minimum required cost of a group, the group cannot spawn and the wave pauses until enough points are available. The enemy type here is determined in .
Now that git is installed, reload VS Code and open the folder you want to start working with, such as a fresh MTFO dump (moved to a folder where mod managers won't mess with it), or your own rundown if you have one. The guide will use .
There may have been a lot of unfamiliar terms used just now. The git glossary is (google is also an option), but let's look at some fundamentals:
To find more information on how to use HarmonyX please reference their
I recommend you check out guides to RegEx and test online for mistakes and explanations, for example in .
Git is a powerful versioning tool not limited to VS Code. It is covered in its own guide .
can affect this.
can affect this.
Used together with Ammo values to several weapon ammo values. For example for a weapon with a cost of 10 and these (currently game default) ammo values:
Note that for sentries there's another multiplier in called ClassAmmoCostFactor.
- whole number from 0 to 4,294,967,295
- integer from -2,147,483,648 to 2,147,483,647
(aka float) - approximate real number of varying precision with values ranging from negative 3.402823e38 to positive 3.402823e38.
- data type with 2 values - "true" and "false".
- plain text.
are a special type, representing named values. Here's an example ():
The underlying enum type here is Int32, and it has 3 named values. Enums usually have this underlying type, start from 0, and move up in sequence, but there are exceptions. You can check out the page to see all enums.
There are special cases where the enum values are combined, e.g. AnimationControllers, type .
In JSON, these are all , starting and ending with curly braces { }
.
GameData_BoosterImplantEffectDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_FeedbackDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_CustomAssetShardDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearFlashlightPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_ArtifactDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_GearFrontPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_GameSetupDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_EnvironmentFeedbackDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearMeleeHeadPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearMeleePommelPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearReceiverPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
We will be compiling our plugin for release, meaning that there will be no debugging options set up
First Click Build > Configuration Manager in Visual Studio
Then in the Configuration Manager window, change Active solution configuration from "Debug" to "Release", then click close
Next Click Build > Build Solution
If you used the earlier project settings your plugin should now be created inside your r2modman profile
You should now be able to launch your modded game and see in the BepInEx log our logged message
This is a very basic summary of patching. See the harmony documentation for more details.
Typically, when making a plugin, developers will want to change how something in the base game behaves. Of course, in order to change the normal behavior we need to know what piece(s) of code in the base game determine that behavior so that we can target them with a patch. This means that part of the challenge of making a plugin is determining which method(s) to patch in the first place! If you're unsure how to go about determining which methods to target consider asking in our Discord Server.
The two basic kinds of patches are Prefix and Postfix patches. Both of these target some original method in the game's code in the same way through the [HarmonyPatch(...)]
attribute (though there are also more complicated ways to determine which method they target).
Postfix patches use the [HarmonyPostFix]
attribute. Postfix patches run after the original method. As with any patch this allows you to run any code you want at this point, but also importantly, this can let you alter the normal return value of that method.
Prefix patches use the [HarmonyPreFix]
attribute. Prefix patches run before the original method. They can alter the arguments of the original method before that method runs. They can also control whether or not to skip the original method completely.
Unfortunately, a more complicated kind of patch (Transpiler patches) which offers some unique benefits doesn't quite work with how GTFO is compiled.
Visual Studio Community allows you to navigate, edit and compile code.
Download Visual Studio here by click "Free Download" underneath the Community section
Run "VisualStudioSetup.exe" from your download folder
Continue through the process until you get to the "Installing" window with the "Workloads" tab
Under the "Desktop & Mobile" section select ".NET desktop development"
Under the "Gaming" section select "Game development with Unity"
Once you have your preferred components selected click "Install" on the bottom right
Wait for the heat death of the universe until your IDE is installed
Continue through the rest of the set up until a "Get started" window appears
Signing in to Visual Studio is optional
You should now be ready to create your C# class library project
The following Plugin class will allow BepInEx to load and handle your compiled assembly as a plugin
In Visual studio right click "Class1.cs" in the solution explorer and rename it to "Plugin.cs". Choose "Yes" to rename all references
If you do not have a Class1.cs you can create Plugin.cs instead by choosing Project > Add Class...
Copy and paste the following code into "Plugin.cs" replacing the entire contents of the file
If you do not see where to paste the code into, double click the Plugin.cs file in the solution explorer
Save your changes by selecting File > Save Plugin.cs
You should now be ready to compile your first plugin
We create a new class that inherits from the BepInEx BasePlugin class. We give this new class a special attribute (the BepInPlugin
thing above the class) which BepInEx uses to make this class into a full-fledged plugin. The arguments we supply to the attribute include:
The GUID: "NewbiePluginAuthor.MyFirstPlugin"
This is an identifier which should uniquely identify the plugin. Typically just author.pluginname
should suffice.
The plugin name: "MyFirstPlugin"
The plugin version: "1.0.0"
This uses semantic versioning.
Export unity asset files from GTFO
Download the latest AssetRipper_win_x64.zip
Extract the AssetRipper files and run AssetRipper.exe
Change the following settings
Check Skip StreamingAssets Folder
Choose File > Open Folder
Navigate to and select the following folder
~\Steam\steamapps\common\GTFO
After it loads the game content choose Export > Export all Files and select an output folder
You are now ready to import the asset files into a unity project
GameData_EnemyDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
This guide should cover level editing enough to get you started making your own levels
The Rundown currently out is Rundown 8, revision 34800. If you're reading afterwards, you may have to adjust. If we're lucky any future changes to level generation will be backwards compatible.
Be aware that game updates can break datablocks, and levels/rundowns usually suffer the most from this. Rundown developers often don't even port their rundowns after some updates.
There's now a page with finished blocks stored and updates noted located here.
If you get stuck or are unsure if some changes were made as updates passed, check it out.
As you probably already know, levels in GTFO are generated, and their data is stored in datablocks. In terms of difficulty, editing levels is between intermediate and one of the most advanced things in datablocks. This guide is not meant to teach all thing related to level editing, but it will hopefully give you enough to get you going in creating your own levels.
This goes without saying, but if you're looking to learn, I highly recommend editing and testing the blocks yourself as you read along.
Throughout the guide, I'll be posting datablocks or parts of them. If you move along, you won't have to copy-paste them, but you can verify they match using Diffchecker for example.
At this point you should be familiar with JSON, datablocks, and the following:
VS Code editor (technically not required but guide assumes you use it)
Recommended:
CConsole or some other modding toolkit
The guide is made of 5 parts:
Isolating a level
Editing rundown and level metadata
Adding and editing zones
Editing warden objective
Adding a secondary sector
The datablocks we work with in this guide:
Main:
RundownDataBlock
LevelLayoutDataBlock
WardenObjectiveDataBlock
Edited:
ExpeditionBalanceDataBlock
EnemyGroupDataBlock
EnemyPopulationDataBlock
FogSettingsDataBlock
SurvivalWaveSettingsDataBlock
ChainedPuzzleDataBlock
Touched:
LightSettingsDataBlock
ConsumableDistributionDataBlock
StaticSpawnDataBlock
BigPickupDistributionDataBlock
ComplexResourceSetDataBlock
ChainedPuzzleTypeDataBlock
TypeList (OriginalDataBlocks)
Datablock Editor (but actually just level layout editor)
Changing fields that have no impact on the game
Not all fields have functional use in-game. Some are used just to display something, like level descriptions for example. Now that we have isolated one level in our rundown, let's change some fields to distinguish it from R6B2.
First let's change the "name" fields of both rundown and level layout. As we know, this is one of the fields shared by all datablocks and must be unique in the file. It is never visible in-game without mods, but having good names helps with readability and lets us describe the block. This is especially relevant here since JSON has no comments (although there are ways).
The exact names are up to you. I can only try to give a few tips, whether to follow them is up to you:
Keep a consistent style of naming.
Describe what the block is concisely. A longer name is better than a shorter one that has no meaning.
Don't use abbreviations you might not remember.
Use practical names; no jokes.
You can mark unfinished blocks by adding TODO (and even what exactly is unfinished) in the name if you need to remind yourself.
If you're working on a block that has many entries and you're modifying only a few (e.g. archetype), you can think of a prefix to attach to all names so you know which ones you changed.
Here are the names I chose.
This is the only field we're changing that's usually invisible to the players.
Remember a lot of text fields are of the LocalizedText type, these can be both numbers and strings. If you want localization in your custom rundown you can use the LocalizedText type, in this guide we'll only use strings. You can always check the original text you're changing by looking for the ID in TextDataBlock.
Let's mark the fields we'll focus on:
In rundown datablock:
"Title" - the displayed title of the rundown;
"SurfaceDescription" - displayed when clicking the "intel" button (nobody reads this though);
"Prefix" - Mission prefix, in base game always used to specify tier. In old times the devs asked us to distinguish from base game rundowns (e.g. "act-1") but nowadays it doesn't matter;
"PublicName" - the name of the mission;
"ExpeditionDepth" - Drop cage target depth in expedition details;
"ExpeditionDescription" - text displayed under "://Intel_" in expedition details;
"RoleplayedWardenIntel" - text displayed under ":://Interrupted_Communications_" in expedition details and when dropping in.
In level layout:
"ZoneAliasStart" - used to calculate zone alias unless overriden. By default a zone's alias is this number + localindex offset.
There are more but this is enough for now.
Since this all has no impact on gameplay (except ZoneAliasStart but that has minimal impact), the values are up to you. Here's what I used:
The results:
There are more fields related to visual data in rundown and level layout but with this we have enough to make unique vanilla-style rundown & level information. We can move on to the 3rd step in the guide.
List of most commonly used datablocks
So you've learned the dark secrets of GTFO and now want to spread the madness? Well, this is the page for you!
So you want to contribute to our wiki! Great! Let's get that forbidden knowledge on here with this simple N step program.
Step 1: Follow this link to join as an editor of the wiki.
Step 2: Sign into GitBook using whatever account you'd like your edits to be credited to
Step 3: Click the big EDIT button in the top right hand corner of your screen.
Step 4: Edit the pages to your heart's content (but do split unrelated changes into multiple requests). If you would like to bring your edits to the next level, consider reading up on the markdown format GitBook uses.
Step 5: Submit your changes for review using the button in the lower right corner.
Step 6: Method 1 (preferred): join the modding discord and send a message in the wiki channel about your change request. Questions and suggestions will be discussed there.
Method 2: (if you don't want to join discord) The change request will be reviewed and you may receive comments with questions, suggestions, and discussion. They will have to be resolved before merging.
Step 7: When all discussion is resolved, the change request is merged.
Thanks for helping the community by spreading your knowledge!
GameData_ItemFPSSettingsDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_ComplexResourceSetDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_FlashlightSettingsDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_MeleeAnimationSetDataBlock_bin.json
Defines melee weapon animation sets alongside several timings related to them.
This datablock is referenced by a MeleeArchetypeDataBlock.
Time in seconds that attack must be held for the weapon to begin charging.
Time in seconds to reach full charge.
Time in seconds after starting a charge at which the weapon will automatically swing.
Time in seconds after starting a charge at which a sound effect plays and causes the reticle to blink red (to warn that the automatic attack will occur soon).
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
Animation for the light attack that swings from the right (e.g. the first swing).
Animation for the light attack that swings from the left (e.g. the second swing).
Animation that plays after hitting a light attack from the right.
Animation that plays after hitting a light attack from the left.
Animation for pushing. Note: Pushes cannot normally be consecutively done faster than 1.2s. However, attacks reset this cooldown and allow two consecutive pushes to be performed afterwards, separated by push's combo time.
Animation for charging an attack from the right.
Animation for charging an attack from the left.
Animation for the charge attack that swings from the right.
Animation for the charge attack that swings from the left.
Animation that plays after hitting a charge attack from the right.
Animation that plays after hitting a charge attack from the left.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_ExpeditionBalanceDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_WeaponDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_GearMeleeHandlePartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearMagPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_GearStockPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
No description provided.
GameData_GearToolDeliveryPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearPartAttachmentDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.
GameData_GearMeleeNeckPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
GameData_GearCategoryFilterDataBlock_bin.json
No description provided.
No description provided.
GameData_GearSightPartDataBlock_bin.json
No description provided.
No description provided.
No description provided.
No description provided.