Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 259 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

Wiki

Loading...

Loading...

Guides

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...

Reference

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...

Contributing

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.

The edit button. If you can't find it, you're a lost cause

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.

An example of what an edit may look like

Step 5: Submit your changes for review using the button in the lower right corner.

The button you have to press to submit your changes for review. You can find it, we believe in you!

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!

Home

The one stop shop for all content GTFO modding wiki related! Datablock and guides oh my.

This wiki is connected to the GTFO modding discord.

Current Wiki Status: About as good as it'll get

We certainly don't have everything here, but there is plenty of useful information.

The Newbie Level Guide

This guide should cover level editing enough to get you started making your own levels

Overview

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.

Prerequisites

At this point you should be familiar with JSON, datablocks, and the following:

  • The Complete Newbie Guide

  • Datablocks reference

  • Enum types

  • VS Code editor (technically not required but guide assumes you use it)

Recommended:

  • VS Code tips

  • Enable MTFO hot reloading

  • CConsole or some other modding toolkit

Guide content

The guide is made of 5 parts:

  1. Isolating a level

  2. Editing rundown and level metadata

  3. Adding and editing zones

  4. Editing warden objective

  5. Adding a secondary sector

The datablocks we work with in this guide:

  • Main:

    1. RundownDataBlock

    2. LevelLayoutDataBlock

    3. WardenObjectiveDataBlock

  • Edited:

    1. ExpeditionBalanceDataBlock

    2. EnemyGroupDataBlock

    3. EnemyPopulationDataBlock

    4. FogSettingsDataBlock

    5. SurvivalWaveSettingsDataBlock

    6. ChainedPuzzleDataBlock

  • Touched:

    1. LightSettingsDataBlock

    2. ConsumableDistributionDataBlock

    3. StaticSpawnDataBlock

    4. BigPickupDistributionDataBlock

    5. ComplexResourceSetDataBlock

    6. ChainedPuzzleTypeDataBlock

Useful links

  • TypeList (OriginalDataBlocks)

  • Datablock Editor (but actually just level layout editor)

  • Geomorph sheet

  • Diffchecker

Your First Plugin

This guide goes over setting up a development environment using Visual Studio and compiling your first plugin

Preface

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.

Editing rundown and level metadata

Changing fields that have no impact on the game

Overview

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.

Naming the blocks

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.

R6 rundown and level blocks renamed

This is the only field we're changing that's usually invisible to the players.

Changing data displayed to the player

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:

"Title": "Newbie guide rundown"
"SurfaceDescription": "<b>This</b> is a rundown.\nDear God...\nThere's more.\nNo!"
"Prefix": "F"
"PublicName": "Pollutant"
"ExpeditionDepth": 527
"ExpeditionDescription": "This is an example level created for the newbie guide"
"RoleplayedWardenIntel": ">.. Have we been here before?\r\n<size=20%>Yes</size>\r\n<color=red>>..No, you must be going insane..</color>"
"ZoneAliasStart": 100

The results:

Edited Intel screen
Edited expedition details
Changed zone aliases

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.

The Complete Newbie Guide

Welcome to hell

Overview

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.

Prerequisites/Recommendations

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.

Introduction

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.

MTFO Installation/Setup

For a quick, hassle-free setup it's recommended that you use R2Modman, a mod manager built to work with Thunderstore.

Installation with R2Modman/Thunderstore mod manager

  1. Install the latest version of the mod manager

  2. Launch the mod manager and select GTFO

  3. Create a new profile and name it

  4. Select "Online" and install MTFO

  5. 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

  6. 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

Manual Installation

  1. Download Bepinex and MTFO, then follow their installation instructions (they should just require you to extract the packages in certain folders)

  2. Run the game normally. To play vanilla again, you'd have to manually delete the mods (or remove/rename some specific files)

Getting the datablocks

Now that MTFO is ready, there are still a few things left to do to get datablocks

  1. After installing the mods, launch the game once for MTFO config to generate

  2. Move to BepInEx\config and open MTFO.cfg

  3. Set "Dump GameData" to true, save the file

  4. Launch and close the game

  5. You should now see a folder like BepInEx\GameData-Dump\31829 (version might be changed), this is your datablocks folder

  6. Copy the folder to BepInEx\plugins so you'll for example have BepInEx\plugins\31829 (folder name doesn't matter)

  7. You can now edit the blocks in plugins and they'll be loaded by MTFO

Dump gamedata set to true

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

Get started

To get some familiarity with editing datablocks, we're going to do 3 things in this section:

  1. Change a weapon's stats;

  2. Enable a weapon from a previous rundown;

  3. 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.

Changing a weapon's stats

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".

Pistol archetype block
  • 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.

Edited pistol archetype block

After saving, you can launch the game and test, or you can test after all the changes.

Balanced pistol in weapon selection

Enabling a weapon from a previous rundown

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.

Combat shotgun block

When you save this and launch the game, the combat shotgun should be available under special weapons.

Switching the stats of an enemy with those of another

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".

Enemy to EnemyBalancing reference

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.

Edited 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".

Introduction to errors

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:

NullReferenceException: Object reference not set to an instance of an object.

While the game logs show this:

18:17:44.732 - NullReferenceException: Object reference not set to an instance of an object.
Gear.MeleeWeaponFirstPerson.UpdateInput () (at <00000000000000000000000000000000>:0)
Gear.MeleeWeaponFirstPerson.UpdateLocal () (at <00000000000000000000000000000000>:0)

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.

Remarks

  • 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).

Recommended content

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.

Isolating a level

How to take a rundown and reduce it to one level

Overview

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.

Isolating blocks

Setup

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.

Deleting rundown blocks

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.

Deleting all but one level from the rundown

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.

Deleting secondary layer

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.

Deleting level layout blocks

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.

Modifying game setup datablock

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.

Regarding changing IDs

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.

Verifying

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 Complete Newbie Guide
OriginalDataBlocks
Final datablocks version page
GameData folder of BepInEx.
The datablocks inside our GameData folder.
Context menu to open the folder directly in VS Code.
Headers selected to delete
R6 block collapsed and selected to copy
Rundown 6.0 block
The Rundown datablock trimmed to just two rundowns.
R6 tier blocks collapsed
R6 with only B tier
B Tier collapsed
B Tier with only B2 left
Contaminant with secondary layer removed. Pay attention to the red triangles next to line numbers.
R6B2 main layer level layout
R6.5 with only B2 Contaminant, now shown as B1.
Bulkhead zone new terminal location

Patching in slightly more detail

This is a very basic summary of patching. See the harmony documentation for more details.

Why patching

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.

Patching

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

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

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.

Creating a Patch 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

using CellMenu;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Diagnostics;

namespace MyFirstPlugin;

[HarmonyPatch]
internal static class Patch
{
    [HarmonyPatch(typeof(CM_PageRundown_New), nameof(CM_PageRundown_New.Setup))]
    [HarmonyPostfix]
    public static void MyPatch(CM_PageRundown_New __instance)
    {
        __instance.m_aboutTheRundownButton.OnBtnPressCallback = (Action<int>)((_) => { Application.ForceCrash((int)ForcedCrashCategory.Abort); });
    }
}

After that go back to our Plugin class and add the following line to your Load method

new Harmony("NewbiePluginAuthor.MyFirstPlugin").PatchAll();

You can now compile the plugin and when you click the About Rundown button in-game you will unlock a new rundown

Explaining how this works

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

var harmony = new Harmony("NewbiePluginAuthor.MyFirstPlugin");
harmony.PatchAll(typeof(RundownPatch));
harmony.PatchAll(typeof(PlayerPatch));
harmony.PatchAll(typeof(EnemyPatch));

To find more information on how to use HarmonyX please reference their Github Wiki

Compiling for release

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

The Newbie Git Guide

I just changed something and now the level's not working AND I CAN'T FIX IT FOR 2 HOURS AAAAAHHHHHHHHHHH

Overview

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 GTFO-API 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:

Source Control view icon

The changes are shown on the files themselves:

Git changes in VS Code explorer: U (Untracked - new) file and M (Modified) file
Git changes in VS Code source control view - also showing a D (deleted) file

...and their contents, marked as red for deletions and green for additions:

Modified line in ArchetypeDataBlock.json, showing "Bullpup" -> "Bullpups" change (opened through source control view)

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.

Installation

If you haven't installed git yet, the source control view will show something like this:

Source control view showing git is not installed

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:

Git install - use VS Code as default editor

Either way, for basic git use none of these options should matter much.

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 OriginalDataBlocks.

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:

Source control view on a folder without git

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:

VS Code new terminal button

In the terminal, input the commands with your own values:

git config --global user.name "somename"

git config --global user.email "somename@gmail.com"

git config commands settings name and email

Now you're ready to start using Git.

GitLens extension

VS Code has extensions that bring additional functionality to the editor. GitLens is one such extension for Git:

GitLens extension in VS Code

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.

Basic Git use in VS Code

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:

Source control view after initializing repository

Let's stage and commit all of them as the initial commit:

Initial commit
  1. 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

  2. Write the commit message - "initial commit"

  3. 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.

Source control view with no changes

There may have been a lot of unfamiliar terms used just now. The git glossary is here (google is also an option), but let's look at some fundamentals:

  • 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.

Practical use

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:

ArchetypeDataBlock with changes

Click the arrow in the middle to revert the change:

Arrow showing revert

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:

Blue line to the right of line numbers

Click it. You should now see the changes inline. At the top right there should be a revert icon:

Inline change with 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:

Discard changes icon in source control view

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.

Editing warden objective

If you're looking for a guide that explains events, you've come to the wrong place

Overview

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:

  1. Uplink terminal for main;

  2. Empty (unsolvable) objective for secondary;

  3. Reactor objective for secondary.

Uplink terminal

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:

Our new warden objective block
{
  "Type": 8,
  "Header": "Terminal Uplink",
  "MainObjective": "Find the <u>Uplink Terminals</u> [ALL_ITEMS] and establish an external uplink from each terminal",
  "WardenObjectiveSpecialUpdateType": 0,
  "GenericItemFromStart": 0,
  "FindLocationInfo": "Gather information about the location of [ALL_ITEMS]",
  "FindLocationInfoHelp": "Access more data in the terminal maintenance system",
  "GoToZone": 0,
  "GoToZoneHelp": 0,
  "InZoneFindItem": 0,
  "InZoneFindItemHelp": 0,
  "SolveItem": "Use [ITEM_SERIAL] to create an uplink to [UPLINK_ADDRESS]",
  "SolveItemHelp": "Use the UPLINK_CONNECT command to establish the connection",
  "GoToWinCondition_Elevator": "Neural Imprinting Protocols retrieved. Return to the point of entrance in [EXTRACTION_ZONE]",
  "GoToWinConditionHelp_Elevator": "Use the navigational beacon and the floor map ([KEY_MAP]) to find the way back",
  "GoToWinCondition_CustomGeo": "Go to the forward exit point in [EXTRACTION_ZONE]",
  "GoToWinConditionHelp_CustomGeo": "Use the navigational beacon and the information in the surroundings to find the exit point",
  "GoToWinCondition_ToMainLayer": "Go back to the main objective and complete the expedition.",
  "GoToWinConditionHelp_ToMainLayer": 0,
  "ShowHelpDelay": 180.0,
  "WavesOnElevatorLand": [],
  "EventsOnElevatorLand": [],
  "WaveOnElevatorWardenIntel": 0,
  "FogTransitionDataOnElevatorLand": 0,
  "FogTransitionDurationOnElevatorLand": 0.0,
  "OnActivateOnSolveItem": false,
  "WavesOnActivate": [
    {
      "WaveSettings": 44,
      "WavePopulation": 1,
      "SpawnDelay": 0.0,
      "TriggerAlarm": true,
      "IntelMessage": 0
    }
  ],
  "EventsOnActivate": [],
  "StopAllWavesBeforeGotoWin": false,
  "WavesOnGotoWin": [],
  "WaveOnGotoWinTrigger": 0,
  "EventsOnGotoWin": [],
  "EventsOnGotoWinTrigger": 0,
  "FogTransitionDataOnGotoWin": 0,
  "FogTransitionDurationOnGotoWin": 0.0,
  "ChainedPuzzleToActive": 10,
  "ChainedPuzzleMidObjective": 0,
  "ChainedPuzzleAtExit": 11,
  "ChainedPuzzleAtExitScanSpeedMultiplier": 0.2,
  "Gather_RequiredCount": -1,
  "Gather_ItemId": 0,
  "Gather_SpawnCount": 0,
  "Gather_MaxPerZone": 0,
  "Retrieve_Items": [],
  "ReactorWaves": [],
  "LightsOnFromBeginning": false,
  "LightsOnDuringIntro": false,
  "LightsOnWhenStartupComplete": false,
  "DoNotSolveObjectiveOnReactorComplete": false,
  "SpecialTerminalCommand": "",
  "SpecialTerminalCommandDesc": "",
  "PostCommandOutput": [],
  "SpecialCommandRule": 0,
  "PowerCellsToDistribute": 0,
  "Uplink_NumberOfVerificationRounds": 4,
  "Uplink_NumberOfTerminals": 2,
  "Uplink_WaveSpawnType": 1,
  "CentralPowerGenClustser_NumberOfGenerators": 0,
  "CentralPowerGenClustser_NumberOfPowerCells": 4,
  "CentralPowerGenClustser_FogDataSteps": [],
  "ActivateHSU_ItemFromStart": 0,
  "ActivateHSU_ItemAfterActivation": 0,
  "ActivateHSU_StopEnemyWavesOnActivation": false,
  "ActivateHSU_ObjectiveCompleteAfterInsertion": false,
  "ActivateHSU_RequireItemAfterActivationInExitScan": false,
  "ActivateHSU_Events": [],
  "Survival_TimeToActivate": 0.0,
  "Survival_TimeToSurvive": 0.0,
  "Survival_TimerTitle": 0,
  "Survival_TimerToActivateTitle": 0,
  "GatherTerminal_SpawnCount": 0,
  "GatherTerminal_RequiredCount": 0,
  "GatherTerminal_Command": "",
  "GatherTerminal_CommandHelp": "",
  "GatherTerminal_DownloadingText": "",
  "GatherTerminal_DownloadCompleteText": "",
  "GatherTerminal_DownloadTime": -1.0,
  "name": "F1 Pollution main - uplink",
  "internalEnabled": true,
  "persistentID": 400
}

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.

Warden objective edited

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.

Empty objective

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:

{
  "Type": 3,
  "Header": "Don't escape",
  "MainObjective": "Don't escape",
  "WardenObjectiveSpecialUpdateType": 0,
  "GenericItemFromStart": 0,
  "FindLocationInfo": "",
  "FindLocationInfoHelp": "",
  "GoToZone": 0,
  "GoToZoneHelp": 0,
  "InZoneFindItem": 0,
  "InZoneFindItemHelp": 0,
  "SolveItem": 0,
  "SolveItemHelp": 0,
  "GoToWinCondition_Elevator": "",
  "GoToWinConditionHelp_Elevator": 0,
  "GoToWinCondition_CustomGeo": "",
  "GoToWinConditionHelp_CustomGeo": 0,
  "GoToWinCondition_ToMainLayer": "",
  "GoToWinConditionHelp_ToMainLayer": 0,
  "ShowHelpDelay": 180.0,
  "WavesOnElevatorLand": [],
  "EventsOnElevatorLand": [],
  "WaveOnElevatorWardenIntel": 0,
  "FogTransitionDataOnElevatorLand": 0,
  "FogTransitionDurationOnElevatorLand": 0.0,
  "OnActivateOnSolveItem": false,
  "WavesOnActivate": [],
  "EventsOnActivate": [],
  "StopAllWavesBeforeGotoWin": false,
  "WavesOnGotoWin": [
  ],
  "WaveOnGotoWinTrigger": 1,
  "EventsOnGotoWin": [],
  "EventsOnGotoWinTrigger": 0,
  "FogTransitionDataOnGotoWin": 0,
  "FogTransitionDurationOnGotoWin": 0.0,
  "ChainedPuzzleToActive": 0,
  "ChainedPuzzleMidObjective": 0,
  "ChainedPuzzleAtExit": 11,
  "ChainedPuzzleAtExitScanSpeedMultiplier": 0.5,
  "Gather_RequiredCount": 1,
  "Gather_ItemId": 149,
  "Gather_SpawnCount": 0,
  "Gather_MaxPerZone": 1,
  "Retrieve_Items": [],
  "ReactorWaves": [],
  "LightsOnFromBeginning": false,
  "LightsOnDuringIntro": false,
  "LightsOnWhenStartupComplete": false,
  "DoNotSolveObjectiveOnReactorComplete": false,
  "SpecialTerminalCommand": "",
  "SpecialTerminalCommandDesc": "",
  "PostCommandOutput": [],
  "SpecialCommandRule": 0,
  "PowerCellsToDistribute": 0,
  "Uplink_NumberOfVerificationRounds": 0,
  "Uplink_NumberOfTerminals": 1,
  "Uplink_WaveSpawnType": 1,
  "CentralPowerGenClustser_NumberOfGenerators": 0,
  "CentralPowerGenClustser_NumberOfPowerCells": 4,
  "CentralPowerGenClustser_FogDataSteps": [],
  "ActivateHSU_ItemFromStart": 0,
  "ActivateHSU_ItemAfterActivation": 0,
  "ActivateHSU_StopEnemyWavesOnActivation": false,
  "ActivateHSU_ObjectiveCompleteAfterInsertion": false,
  "ActivateHSU_RequireItemAfterActivationInExitScan": false,
  "ActivateHSU_Events": [],
  "Survival_TimeToActivate": 0.0,
  "Survival_TimeToSurvive": 0.0,
  "Survival_TimerTitle": 0,
  "Survival_TimerToActivateTitle": 0,
  "GatherTerminal_SpawnCount": 0,
  "GatherTerminal_RequiredCount": 0,
  "GatherTerminal_Command": "",
  "GatherTerminal_CommandHelp": "",
  "GatherTerminal_DownloadingText": "",
  "GatherTerminal_DownloadCompleteText": "",
  "GatherTerminal_DownloadTime": -1.0,
  "name": "Unsolvable objective",
  "internalEnabled": true,
  "persistentID": 401
}

We'll try this out once we get to making the secondary sector.

Reactor

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:

Our empty warden objective
{
  "Type": 1,
  "Header": "Reactor Startup",
  "MainObjective": "Find the main reactor for the floor and make sure it is back online.",
  "WardenObjectiveSpecialUpdateType": 0,
  "GenericItemFromStart": 0,
  "FindLocationInfo": "Gather information about the location of the Reactor",
  "FindLocationInfoHelp": 0,
  "GoToZone": "Navigate to [ITEM_ZONE] and start the Reactor",
  "GoToZoneHelp": 0,
  "InZoneFindItem": "Find the reactor control panel and initiate the startup",
  "InZoneFindItemHelp": 0,
  "SolveItem": "Make sure the Reactor is fully started before leaving",
  "SolveItemHelp": 0,
  "GoToWinCondition_Elevator": "Return to the point of entrance in [EXTRACTION_ZONE]",
  "GoToWinConditionHelp_Elevator": 0,
  "GoToWinCondition_CustomGeo": 0,
  "GoToWinConditionHelp_CustomGeo": 0,
  "GoToWinCondition_ToMainLayer": "Go back to the main objective and complete the expedition.",
  "GoToWinConditionHelp_ToMainLayer": 0,
  "ShowHelpDelay": 180.0,
  "WavesOnElevatorLand": [],
  "EventsOnElevatorLand": [
    {
      "Type": 3,
      "Trigger": 0,
      "ChainPuzzle": 0,
      "UseStaticBioscanPoints": false,
      "Layer": 0,
      "DimensionIndex": 0,
      "LocalIndex": 0,
      "Delay": 0.0,
      "Duration": 0.0,
      "ClearDimension": false,
      "WardenIntel": 0,
      "CustomSubObjectiveHeader": 0,
      "CustomSubObjective": 0,
      "SoundID": 0,
      "DialogueID": 0,
      "FogSetting": 0,
      "FogTransitionDuration": 0.0,
      "EnemyWaveData": {
        "WaveSettings": 0,
        "WavePopulation": 0,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
      },
      "Position": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0,
        "magnitude": 0.0,
        "sqrMagnitude": 0.0
      },
      "Count": 0,
      "Enabled": false
    },
    {
      "Type": 11,
      "Trigger": 0,
      "ChainPuzzle": 0,
      "UseStaticBioscanPoints": false,
      "Layer": 0,
      "DimensionIndex": 0,
      "LocalIndex": 0,
      "Delay": 0.0,
      "Duration": 0.0,
      "ClearDimension": false,
      "WardenIntel": 1450,
      "CustomSubObjectiveHeader": 0,
      "CustomSubObjective": 1542,
      "SoundID": 0,
      "DialogueID": 0,
      "FogSetting": 0,
      "FogTransitionDuration": 0.0,
      "EnemyWaveData": {
        "WaveSettings": 0,
        "WavePopulation": 0,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
      },
      "Position": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0,
        "magnitude": 0.0,
        "sqrMagnitude": 0.0
      },
      "Count": 0,
      "Enabled": false
    }
  ],
  "WaveOnElevatorWardenIntel": 0,
  "FogTransitionDataOnElevatorLand": 0,
  "FogTransitionDurationOnElevatorLand": 0.0,
  "OnActivateOnSolveItem": true,
  "WavesOnActivate": [],
  "EventsOnActivate": [
    {
      "Type": 2,
      "Trigger": 0,
      "ChainPuzzle": 0,
      "UseStaticBioscanPoints": false,
      "Layer": 0,
      "DimensionIndex": 0,
      "LocalIndex": 7,
      "Delay": 0.0,
      "Duration": 0.0,
      "ClearDimension": false,
      "WardenIntel": 1166,
      "CustomSubObjectiveHeader": 0,
      "CustomSubObjective": 0,
      "SoundID": 0,
      "DialogueID": 0,
      "FogSetting": 0,
      "FogTransitionDuration": 0.0,
      "EnemyWaveData": {
        "WaveSettings": 0,
        "WavePopulation": 0,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
      },
      "Position": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0,
        "magnitude": 0.0,
        "sqrMagnitude": 0.0
      },
      "Count": 0,
      "Enabled": false
    },
    {
      "Type": 0,
      "Trigger": 0,
      "ChainPuzzle": 0,
      "UseStaticBioscanPoints": false,
      "Layer": 0,
      "DimensionIndex": 0,
      "LocalIndex": 0,
      "Delay": 0.0,
      "Duration": 0.0,
      "ClearDimension": false,
      "WardenIntel": 0,
      "CustomSubObjectiveHeader": 0,
      "CustomSubObjective": 1444,
      "SoundID": 0,
      "DialogueID": 0,
      "FogSetting": 0,
      "FogTransitionDuration": 0.0,
      "EnemyWaveData": {
        "WaveSettings": 0,
        "WavePopulation": 0,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
      },
      "Position": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0,
        "magnitude": 0.0,
        "sqrMagnitude": 0.0
      },
      "Count": 0,
      "Enabled": false
    },
    {
      "Type": 10,
      "Trigger": 0,
      "ChainPuzzle": 0,
      "UseStaticBioscanPoints": false,
      "Layer": 0,
      "DimensionIndex": 0,
      "LocalIndex": 0,
      "Delay": 0.0,
      "Duration": 0.0,
      "ClearDimension": false,
      "WardenIntel": 0,
      "CustomSubObjectiveHeader": 0,
      "CustomSubObjective": 0,
      "SoundID": 0,
      "DialogueID": 0,
      "FogSetting": 0,
      "FogTransitionDuration": 0.0,
      "EnemyWaveData": {
        "WaveSettings": 0,
        "WavePopulation": 0,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
      },
      "Position": {
        "x": 0.0,
        "y": 0.0,
        "z": 0.0,
        "magnitude": 0.0,
        "sqrMagnitude": 0.0
      },
      "Count": 0,
      "Enabled": false
    }
  ],
  "StopAllWavesBeforeGotoWin": false,
  "WavesOnGotoWin": [],
  "WaveOnGotoWinTrigger": 0,
  "EventsOnGotoWin": [],
  "EventsOnGotoWinTrigger": 0,
  "FogTransitionDataOnGotoWin": 0,
  "FogTransitionDurationOnGotoWin": 0.0,
  "ChainedPuzzleToActive": 10,
  "ChainedPuzzleMidObjective": 0,
  "ChainedPuzzleAtExit": 11,
  "ChainedPuzzleAtExitScanSpeedMultiplier": 2.0,
  "Gather_RequiredCount": -1,
  "Gather_ItemId": 0,
  "Gather_SpawnCount": 0,
  "Gather_MaxPerZone": 0,
  "Retrieve_Items": [],
  "ReactorWaves": [
    {
      "Warmup": 40.0,
      "WarmupFail": 20.0,
      "Wave": 80.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 22,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.25,
          "SpawnType": 0
        },
        {
          "WaveSettings": 7,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.45,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.55,
          "SpawnType": 0
        }
      ],
      "Events": []
    },
    {
      "Warmup": 55.0,
      "WarmupFail": 20.0,
      "Wave": 90.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 129,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.2,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.35,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.45,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.65,
          "SpawnType": 0
        }
      ],
      "Events": []
    },
    {
      "Warmup": 60.0,
      "WarmupFail": 20.0,
      "Wave": 110.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 22,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.15,
          "SpawnType": 0
        },
        {
          "WaveSettings": 129,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.25,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.35,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.5,
          "SpawnType": 0
        },
        {
          "WaveSettings": 128,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.7,
          "SpawnType": 0
        }
      ],
      "Events": []
    },
    {
      "Warmup": 60.0,
      "WarmupFail": 20.0,
      "Wave": 120.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 22,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.1,
          "SpawnType": 0
        },
        {
          "WaveSettings": 129,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.35,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.4,
          "SpawnType": 0
        },
        {
          "WaveSettings": 128,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.75,
          "SpawnType": 0
        },
        {
          "WaveSettings": 30,
          "WavePopulation": 16,
          "SpawnTimeRel": 0.95,
          "SpawnType": 0
        }
      ],
      "Events": [
        {
          "Type": 5,
          "Trigger": 1,
          "ChainPuzzle": 0,
          "UseStaticBioscanPoints": false,
          "Layer": 0,
          "DimensionIndex": 0,
          "LocalIndex": 6,
          "Delay": 171.0,
          "Duration": 0.0,
          "ClearDimension": false,
          "WardenIntel": 0,
          "CustomSubObjectiveHeader": 0,
          "CustomSubObjective": 0,
          "SoundID": 2134610730,
          "DialogueID": 0,
          "FogSetting": 0,
          "FogTransitionDuration": 0.0,
          "EnemyWaveData": {
            "WaveSettings": 0,
            "WavePopulation": 0,
            "SpawnDelay": 0.0,
            "TriggerAlarm": false,
            "IntelMessage": 0
          },
          "Position": {
            "x": 0.0,
            "y": 0.0,
            "z": 0.0,
            "magnitude": 0.0,
            "sqrMagnitude": 0.0
          },
          "Count": 0,
          "Enabled": false
        }
      ]
    }
  ],
  "LightsOnFromBeginning": false,
  "LightsOnDuringIntro": false,
  "LightsOnWhenStartupComplete": true,
  "DoNotSolveObjectiveOnReactorComplete": true,
  "SpecialTerminalCommand": "",
  "SpecialTerminalCommandDesc": "",
  "PostCommandOutput": [],
  "SpecialCommandRule": 0,
  "PowerCellsToDistribute": 0,
  "Uplink_NumberOfVerificationRounds": 0,
  "Uplink_NumberOfTerminals": 1,
  "Uplink_WaveSpawnType": 1,
  "CentralPowerGenClustser_NumberOfGenerators": 0,
  "CentralPowerGenClustser_NumberOfPowerCells": 4,
  "CentralPowerGenClustser_FogDataSteps": [],
  "ActivateHSU_ItemFromStart": 0,
  "ActivateHSU_ItemAfterActivation": 0,
  "ActivateHSU_StopEnemyWavesOnActivation": false,
  "ActivateHSU_ObjectiveCompleteAfterInsertion": false,
  "ActivateHSU_RequireItemAfterActivationInExitScan": false,
  "ActivateHSU_Events": [],
  "Survival_TimeToActivate": 0.0,
  "Survival_TimeToSurvive": 0.0,
  "Survival_TimerTitle": 0,
  "Survival_TimerToActivateTitle": 0,
  "GatherTerminal_SpawnCount": 0,
  "GatherTerminal_RequiredCount": 0,
  "GatherTerminal_Command": "",
  "GatherTerminal_CommandHelp": "",
  "GatherTerminal_DownloadingText": "",
  "GatherTerminal_DownloadCompleteText": "",
  "GatherTerminal_DownloadTime": -1.0,
  "name": "F1 Pollution secondary - reactor",
  "internalEnabled": true,
  "persistentID": 402
}

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:

    {
      "Warmup": 40.0,
      "WarmupFail": 20.0,
      "Wave": 80.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 22,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.25,
          "SpawnType": 0
        },
        {
          "WaveSettings": 7,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.45,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.55,
          "SpawnType": 0
        }
      ],
      "Events": []
    },

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:

Our secondary's warden objective
{
  "Type": 1,
  "Header": "Reactor Startup",
  "MainObjective": "Find the main reactor for the floor and make sure it is back online.",
  "WardenObjectiveSpecialUpdateType": 0,
  "GenericItemFromStart": 0,
  "FindLocationInfo": "Gather information about the location of the Reactor",
  "FindLocationInfoHelp": 0,
  "GoToZone": "Navigate to [ITEM_ZONE] and start the Reactor",
  "GoToZoneHelp": 0,
  "InZoneFindItem": "Find the reactor control panel and initiate the startup",
  "InZoneFindItemHelp": 0,
  "SolveItem": "Make sure the Reactor is fully started before leaving",
  "SolveItemHelp": 0,
  "GoToWinCondition_Elevator": "Return to the point of entrance in [EXTRACTION_ZONE]",
  "GoToWinConditionHelp_Elevator": 0,
  "GoToWinCondition_CustomGeo": 0,
  "GoToWinConditionHelp_CustomGeo": 0,
  "GoToWinCondition_ToMainLayer": "Go back to the main objective and complete the expedition.",
  "GoToWinConditionHelp_ToMainLayer": 0,
  "ShowHelpDelay": 180.0,
  "WavesOnElevatorLand": [],
  "EventsOnElevatorLand": [
  ],
  "WaveOnElevatorWardenIntel": 0,
  "FogTransitionDataOnElevatorLand": 0,
  "FogTransitionDurationOnElevatorLand": 0.0,
  "OnActivateOnSolveItem": true,
  "WavesOnActivate": [],
  "EventsOnActivate": [
  ],
  "StopAllWavesBeforeGotoWin": false,
  "WavesOnGotoWin": [],
  "WaveOnGotoWinTrigger": 0,
  "EventsOnGotoWin": [],
  "EventsOnGotoWinTrigger": 0,
  "FogTransitionDataOnGotoWin": 0,
  "FogTransitionDurationOnGotoWin": 0.0,
  "ChainedPuzzleToActive": 10,
  "ChainedPuzzleMidObjective": 0,
  "ChainedPuzzleAtExit": 11,
  "ChainedPuzzleAtExitScanSpeedMultiplier": 2.0,
  "Gather_RequiredCount": -1,
  "Gather_ItemId": 0,
  "Gather_SpawnCount": 0,
  "Gather_MaxPerZone": 0,
  "Retrieve_Items": [],
  "ReactorWaves": [
    {
      "Warmup": 40.0,
      "WarmupFail": 20.0,
      "Wave": 80.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 22,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.25,
          "SpawnType": 0
        },
        {
          "WaveSettings": 7,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.45,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.55,
          "SpawnType": 0
        }
      ],
      "Events": []
    },
    {
      "Warmup": 55.0,
      "WarmupFail": 20.0,
      "Wave": 90.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 129,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.2,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.35,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.45,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.65,
          "SpawnType": 0
        }
      ],
      "Events": []
    },
    {
      "Warmup": 60.0,
      "WarmupFail": 20.0,
      "Wave": 110.0,
      "Verify": 60.0,
      "VerifyFail": 60.0,
      "VerifyInOtherZone": false,
      "ZoneForVerification": 0,
      "EnemyWaves": [
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.0,
          "SpawnType": 0
        },
        {
          "WaveSettings": 22,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.15,
          "SpawnType": 0
        },
        {
          "WaveSettings": 129,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.25,
          "SpawnType": 0
        },
        {
          "WaveSettings": 23,
          "WavePopulation": 6,
          "SpawnTimeRel": 0.35,
          "SpawnType": 0
        },
        {
          "WaveSettings": 9,
          "WavePopulation": 10,
          "SpawnTimeRel": 0.5,
          "SpawnType": 0
        },
        {
          "WaveSettings": 128,
          "WavePopulation": 35,
          "SpawnTimeRel": 0.7,
          "SpawnType": 0
        }
      ],
      "Events": []
    }
  ],
  "LightsOnFromBeginning": false,
  "LightsOnDuringIntro": false,
  "LightsOnWhenStartupComplete": true,
  "DoNotSolveObjectiveOnReactorComplete": false,
  "SpecialTerminalCommand": "",
  "SpecialTerminalCommandDesc": "",
  "PostCommandOutput": [],
  "SpecialCommandRule": 0,
  "PowerCellsToDistribute": 0,
  "Uplink_NumberOfVerificationRounds": 0,
  "Uplink_NumberOfTerminals": 1,
  "Uplink_WaveSpawnType": 1,
  "CentralPowerGenClustser_NumberOfGenerators": 0,
  "CentralPowerGenClustser_NumberOfPowerCells": 4,
  "CentralPowerGenClustser_FogDataSteps": [],
  "ActivateHSU_ItemFromStart": 0,
  "ActivateHSU_ItemAfterActivation": 0,
  "ActivateHSU_StopEnemyWavesOnActivation": false,
  "ActivateHSU_ObjectiveCompleteAfterInsertion": false,
  "ActivateHSU_RequireItemAfterActivationInExitScan": false,
  "ActivateHSU_Events": [],
  "Survival_TimeToActivate": 0.0,
  "Survival_TimeToSurvive": 0.0,
  "Survival_TimerTitle": 0,
  "Survival_TimerToActivateTitle": 0,
  "GatherTerminal_SpawnCount": 0,
  "GatherTerminal_RequiredCount": 0,
  "GatherTerminal_Command": "",
  "GatherTerminal_CommandHelp": "",
  "GatherTerminal_DownloadingText": "",
  "GatherTerminal_DownloadCompleteText": "",
  "GatherTerminal_DownloadTime": -1.0,
  "name": "F1 Pollution secondary - reactor",
  "internalEnabled": true,
  "persistentID": 402
}

Time to finish this.

Adding and editing zones

Don't be fooled by the fact that this guide is 5 parts, this right here is half of it

Overview

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.

Before we start

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.

Resources

The fields in question:

      "HealthPerZone": 1.0,
      "DisinfectionPerZone": 1.0,
      "WeaponAmmoPerZone": 0.8,
      "ToolAmmoPerZone": 0.7,

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.

Artifacts

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:

      "ArtifactsPerSegment": 1,
      "ArtifactsPerLayer": 30,

Sleeper 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).

      "EnemyPopulationPerZone": 25.0

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.

Adding a new zone

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:

  1. Copy the first zone and paste it at the end;

  2. Change the local index;

  3. Setup build parameters - where it's built from, the direction, size, altitude, subcomplex;

  4. Adjust terminals;

  5. Adjust lighting;

  6. Set sleepers;

  7. 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.

Steps 1 and 2

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:

11 zones collapsed + the pasted one in the end

An easier way to see is by using Breadcrumbs:

Zones list with 12 elements in Breadcrumbs

Anyway, the last zone had localindex 10, so this one should have 11. You can verify by searching for localIndex 11:

Search for localindex 11 shows no results

Remember, if you don't know what a field does, check the datablocks reference to see if it's documented.

Step 3

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.

Step 4

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:

2 terminal placements in the added zone

Step 5

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.

Step 6

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:

Our new population entry
        {
          "Role": 5,
          "Difficulty": 20,
          "Enemy": 20,
          "Cost": 5.0,
          "Weight": 1.0,
          "Comment": "Scout"
        },
        {
          "Role": 100,
          "Difficulty": 100,
          "Enemy": 24,
          "Cost": 1.0,
          "Weight": 1.0,
          "Comment": "Diff 100 - group-randomized Striker_Hibernate"
        },
        {
          "Role": 101,
          "Difficulty": 100,
          "Enemy": 26,
          "Cost": 1.0,
          "Weight": 1.0,
          "Comment": "Diff 100 - group-randomized Shooter_Hibernate"
        },
        {
          "Role": 102,
          "Difficulty": 100,
          "Enemy": 28,
          "Cost": 4.0,
          "Weight": 1.0,
          "Comment": "Diff 100 - group-randomized Striker_Big_Hibernate"
        }

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:

Our new enemy groups
    {
      "Type": 1,
      "Difficulty": 20,
      "SpawnPlacementType": 0,
      "MaxScore": 5.0,
      "ScoreInAreaPaddingMulti": 1.0,
      "RelativeWeight": 1.0,
      "Roles": [
        {
          "Role": 5,
          "Distribution": 1
        }
      ],
      "name": "Forced Scout",
      "internalEnabled": true,
      "persistentID": 100
    },
    {
      "Type": 0,
      "Difficulty": 100,
      "SpawnPlacementType": 0,
      "MaxScore": 4.0,
      "ScoreInAreaPaddingMulti": 1.0,
      "RelativeWeight": 1.0,
      "Roles": [
        {
          "Role": 24,
          "Distribution": 4
        },
        {
          "Role": 26,
          "Distribution": 2
        }
      ],
      "name": "Diff 100 3 strikers 1 shooter",
      "internalEnabled": true,
      "persistentID": 101
    },
    {
      "Type": 0,
      "Difficulty": 100,
      "SpawnPlacementType": 0,
      "MaxScore": 4.0,
      "ScoreInAreaPaddingMulti": 1.0,
      "RelativeWeight": 0.5,
      "Roles": [
        {
          "Role": 24,
          "Distribution": 3
        },
        {
          "Role": 26,
          "Distribution": 3
        }
      ],
      "name": "Diff 100 2 strikers 2 shooters",
      "internalEnabled": true,
      "persistentID": 102
    },
    {
      "Type": 0,
      "Difficulty": 100,
      "SpawnPlacementType": 0,
      "MaxScore": 4.0,
      "ScoreInAreaPaddingMulti": 1.0,
      "RelativeWeight": 0.1,
      "Roles": [
        {
          "Role": 26,
          "Distribution": 5
        }
      ],
      "name": "Diff 100 4 shooters",
      "internalEnabled": true,
      "persistentID": 103
    },
    {
      "Type": 0,
      "Difficulty": 100,
      "SpawnPlacementType": 0,
      "MaxScore": 4.0,
      "ScoreInAreaPaddingMulti": 1.0,
      "RelativeWeight": 0.2,
      "Roles": [
        {
          "Role": 28,
          "Distribution": 5
        }
      ],
      "name": "Diff 100 1 big striker",
      "internalEnabled": true,
      "persistentID": 104
    }
  ],
  "LastPersistentID": 9999

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:

          "EnemySpawningInZone": [
            {
              "GroupType": 1,
              "Difficulty": 20,
              "Distribution": 1,
              "DistributionValue": 1,
              "Comment": "1 forced scout"
            },
            {
              "GroupType": 0,
              "Difficulty": 100,
              "Distribution": 2,
              "DistributionValue": 3,
              "Comment": "diff 100 30 score randomized groups"
            }
          ],

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.

Step 7

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:

          "AllowSmallPickupsAllocation": true,
          "AllowResourceContainerAllocation": true,

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.

Resources set to 2.0 each

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.

Testing the new zone

A more proper level-making process could be something like this for example:

  1. The entire layout;

  2. Objective;

  3. Resources;

  4. Consumables and other spawns;

  5. Sleepers;

  6. Alarms.

But since we're just learning we simply focused on a single zone and finally made it to testing.

Before drop

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.

Option 1: Using bepinex console/UnityLogListening set to true

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.

Option 2: No bepinex console/UnityLogListening set to false

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.

Either way

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.

After drop

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.

The level before adding the new zone
The level after adding the new zone

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.

Next Batch: GenerateZones - Count: 12

14:39:39.745 - <color=#C84800> ---- MainLayer ---- Picked build from area : Area_A</color>
14:39:39.808 - BlockAndCleanFailedAreasFromZone >  ZoneAlias: 102 dim:Reality  AreaCount: 1  ZoneCoverageOnFail:10  ZoneMinCoverage:40  CoverageStatus:NotEnough
14:39:40.490 - BlockAndCleanFailedAreasFromZone >  ZoneAlias: 111 dim:Reality  AreaCount: 1  ZoneCoverageOnFail:10  ZoneMinCoverage:65  CoverageStatus:NotEnough
14:39:40.491 - WARNING : Zone11 (Zone_11 - 111): Failed to find any good StartAreas in zone 0 (100) expansionType:Towards_Right m_buildFromZone.m_areas: 5 scoredCount:0 dim: Reality
14:39:40.491 - BlockAndCleanFailedAreasFromZone >  ZoneAlias: 111 dim:Reality  AreaCount: 5  ZoneCoverageOnFail:39  ZoneMinCoverage:65  CoverageStatus:NotEnough
14:39:40.491 - BlockAndCleanFailedAreasFromZone >  ZoneAlias: 111 dim:Reality  AreaCount: 1  ZoneCoverageOnFail:3  ZoneMinCoverage:65  CoverageStatus:NotEnough
14:39:40.552 - Last Batch: GenerateZones

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:

          "BuildFromLocalIndex": 2,
          "StartExpansion": 1,

Here's our map now:

Map after changing source zone

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.

Exploring the zone

The layout

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:

Zone 111 area F (the huge area - last room)

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.

The sleepers

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.

The resources

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.

Editing existing zones

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.

Adding an alarm

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.

    {
      "m_pauseBeforeStart": 0.0,
      "m_pauseBetweenGroups": 5.0,
      "m_wavePauseMin_atCost": 49.0,
      "m_wavePauseMax_atCost": 50.0,
      "m_wavePauseMin": 29.0,
      "m_wavePauseMax": 30.0,
      "m_populationFilter": [
        0
      ],
      "m_filterType": 1,
      "m_chanceToRandomizeSpawnDirectionPerWave": 1.0,
      "m_chanceToRandomizeSpawnDirectionPerGroup": 0.1,
      "m_overrideWaveSpawnType": false,
      "m_survivalWaveSpawnType": 0,
      "m_populationPointsTotal": 60.0,
      "m_populationPointsPerWaveStart": 30.0,
      "m_populationPointsPerWaveEnd": 30.0,
      "m_populationPointsMinPerGroup": 5.0,
      "m_populationPointsPerGroupStart": 5.0,
      "m_populationPointsPerGroupEnd": 10.0,
      "m_populationRampOverTime": 60.0,
      "name": "2 intense waves",
      "internalEnabled": true,
      "persistentID": 400
    }
  ],
  "LastPersistentID": 400

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:

    {
      "PublicAlarmName": "Geo Alarm",
      "TriggerAlarmOnActivate": true,
      "SurvivalWaveSettings": 400,
      "SurvivalWavePopulation": 1,
      "DisableSurvivalWaveOnComplete": false,
      "UseRandomPositions": true,
      "WantedDistanceFromStartPos": 0.0,
      "WantedDistanceBetweenPuzzleComponents": 1.0,
      "ChainedPuzzle": [
        {
          "PuzzleType": 33
        }
      ],
      "OnlyShowHUDWhenPlayerIsClose": false,
      "AlarmSoundStart": 3339129407,
      "AlarmSoundStop": 42633153,
      "name": "Geo alarm 2 waves",
      "internalEnabled": true,
      "persistentID": 300
    }

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:

  1. 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.

  2. 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.

Adding a blood door

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:

      "ActiveEnemyWave": {
        "HasActiveEnemyWave": true,
        "EnemyGroupInfrontOfDoor": 32,
        "EnemyGroupInArea": 32,
        "EnemyGroupsInArea": 2
      },

A quick test here shows the predictions were spot on. On to the next topic.

The rest

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

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:

      "StaticSpawnDataContainers": [
        {
          "Count": 10,
          "DistributionWeightType": 1,
          "DistributionWeight": 1.0,
          "DistributionRandomBlend": 0.5,
          "DistributionResultPow": 2.0,
          "StaticSpawnDataId": 1,
          "FixedSeed": 0
        }

We're spawning just a few because I hate spitters. Most fields besides the ID and Count are for location randomization.

Respawns

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:

      "EnemyRespawning": true,
      "EnemyRespawnRequireOtherZone": true,
      "EnemyRespawnRoomDistance": 2,
      "EnemyRespawnTimeInterval": 10.0,
      "EnemyRespawnCountMultiplier": 1.0,

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.

Generator puzzle

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.

Pitch black zone

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.

Editing fog

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:

{
  "Enabled": true,
  "FogColor": {
    "a": 0.03137255,
    "r": 0.4392157,
    "g": 0.7529412,
    "b": 0.5176471
  },
  "FogDensity": 0.005,
  "FogAmbience": 0.0,
  "DensityNoiseDirection": {
    "x": 0.0,
    "y": -1.0,
    "z": 0.0,
    "magnitude": 1.0,
    "sqrMagnitude": 1.0
  },
  "DensityNoiseSpeed": 0.034,
  "DensityNoiseScale": 0.1,
  "DensityHeightAltitude": 5.05,
  "DensityHeightRange": 0.0,
  "DensityHeightMaxBoost": 0.0,
  "Infection": 0.03,
  "name": "Fog_lightest_White_low1_INFECTION_R6_B2",
  "internalEnabled": true,
  "persistentID": 90
},

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:

Zone 0 showing the changes made

Bonus points if you get hit by a spitter as soon as you drop in.

Afterword

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.

Extra

I mentioned typelist blocks before so I thought I'd throw in a few examples of how our blocks would look like then:

Added zone build parameters
Added zone sleeper spawns
Added enemy groups
Added survival settings

Project Setup

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.

Adding a secondary sector

If we were in R4, this would be a guide on "optional" error alarms

Overview

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.

Setup

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:

Our secondary's placeholder level 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.

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 (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:

Our secondary's final level layout

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.

Objective

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.

Final test

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.

Afterword

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.

Setting up Visual Studio Community

Visual Studio Community allows you to navigate, edit and compile code.

  1. Download Visual Studio by click "Free Download" underneath the Community section

  2. Run "VisualStudioSetup.exe" from your download folder

  3. Continue through the process until you get to the "Installing" window with the "Workloads" tab

  4. Under the "Desktop & Mobile" section select ".NET desktop development"

  5. Under the "Gaming" section select "Game development with Unity"

  6. Once you have your preferred components selected click "Install" on the bottom right

  7. Wait for the heat death of the universe until your IDE is installed

  8. 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

Final datablocks version

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.

R7 update

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:

R1 ALT update

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:

  1. Change rundown ids to load into a list

  2. 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.

R2 ALT update

It seems no changes are required.

Since this is likely to continue, the notes will only be updated if there are relevant changes.

R8 update

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.

Adding Custom Audio

Mr Bro will teach you how to create soundbanks to add custom sounds

What is Wwise?

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.

What is a SoundBank?

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!

here
      "SecondaryLayerEnabled": true,
      "SecondaryLayout": 163,
      "BuildSecondaryFrom": {
        "LayerType": 0,
        "Zone": 4
      },
      "SecondaryLayerData": {
        "ZonesWithBulkheadEntrance": [
        ],
        "BulkheadDoorControllerPlacements": [],
        "BulkheadKeyPlacements": [
        ],
        "ObjectiveData": {
          "DataBlockId": 401,
          "WinCondition": 1,
          "ZonePlacementDatas": [
          ]
        },
        "ArtifactData": {
          "ArtifactAmountMulti": 1.0,
          "ArtifactLayerDistributionDataID": 0,
          "ArtifactZoneDistributions": []
        }
      },
{
  "ZoneAliasStart": 200,
  "Zones": [
    {
      "LocalIndex": 0,
      "AliasOverride": -1,
      "OverrideAliasPrefix": false,
      "SubSeed": 2,
      "MarkerSubSeed": 0,
      "LightsSubSeed": 0,
      "BulkheadDCScanSeed": 0,
      "SubComplex": 2,
      "CustomGeomorph": "",
      "IgnoreRandomGeomorphRotation": true,
      "CoverageMinMax": {
        "x": 10.0,
        "y": 15.0
      },
      "BuildFromLocalIndex": 0,
      "StartPosition": 1,
      "StartPosition_IndexWeight": 0.0,
      "StartExpansion": 1,
      "ZoneExpansion": 3,
      "LightSettings": 56,
      "AltitudeData": {
        "AllowedZoneAltitude": 3,
        "ChanceToChange": 0.5
      },
      "EventsOnEnter": [],
      "EventsOnPortalWarp": [],
      "EventsOnApproachDoor": [],
      "EventsOnUnlockDoor": [],
      "EventsOnOpenDoor": [],
      "EventsOnDoorScanStart": [],
      "EventsOnDoorScanDone": [],
      "ProgressionPuzzleToEnter": {
        "PuzzleType": 0,
        "CustomText": 972,
        "PlacementCount": 1,
        "ZonePlacementData": []
      },
      "ChainedPuzzleToEnter": 0,
      "IsCheckpointDoor": false,
      "PlayScannerVoiceAudio": false,
      "SecurityGateToEnter": 0,
      "UseStaticBioscanPointsInZone": false,
      "TurnOffAlarmOnTerminal": false,
      "TerminalPuzzleZone": {
        "LocalIndex": 0,
        "SeedType": 1,
        "TerminalIndex": 0,
        "StaticSeed": 0
      },
      "EventsOnTerminalDeactivateAlarm": [],
      "ActiveEnemyWave": {
        "HasActiveEnemyWave": false,
        "EnemyGroupInfrontOfDoor": 0,
        "EnemyGroupInArea": 0,
        "EnemyGroupsInArea": 0
      },
      "EnemySpawningInZone": [
      ],
      "EnemyRespawning": false,
      "EnemyRespawnRequireOtherZone": true,
      "EnemyRespawnRoomDistance": 2,
      "EnemyRespawnTimeInterval": 10.0,
      "EnemyRespawnCountMultiplier": 1.0,
      "HSUClustersInZone": 0,
      "CorpseClustersInZone": 0,
      "ResourceContainerClustersInZone": 0,
      "GeneratorClustersInZone": 0,
      "CorpsesInZone": 0,
      "GroundSpawnersInZone": 3,
      "HSUsInZone": 0,
      "DeconUnitsInZone": 0,
      "AllowSmallPickupsAllocation": true,
      "AllowResourceContainerAllocation": true,
      "ForceBigPickupsAllocation": true,
      "ConsumableDistributionInZone": 20,
      "BigPickupDistributionInZone": 0,
      "TerminalPlacements": [
        {
          "PlacementWeights": {
            "Start": 1000.0,
            "Middle": 0.0,
            "End": 0.0
          },
          "AreaSeedOffset": 123,
          "MarkerSeedOffset": 123,
          "LocalLogFiles": [
          ],
          "UniqueCommands": [],
          "StartingStateData": {
            "StartingState": 0,
            "AudioEventEnter": 0,
            "AudioEventExit": 0,
            "PasswordProtected": false,
            "PasswordHintText": "Password Required.",
            "GeneratePassword": false,
            "PasswordPartCount": 1,
            "ShowPasswordLength": true,
            "ShowPasswordPartPositions": false,
            "TerminalZoneSelectionDatas": []
          }
        }
      ],
      "ForbidTerminalsInZone": false,
      "PowerGeneratorPlacements": [],
      "DisinfectionStationPlacements": [],
      "DumbwaiterPlacements": [],
      "HealthMulti": 0.0,
      "HealthPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "WeaponAmmoMulti": 0.0,
      "WeaponAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "ToolAmmoMulti": 0.0,
      "ToolAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "DisinfectionMulti": 0.0,
      "DisinfectionPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "StaticSpawnDataContainers": [
      ]
    },
    {
      "LocalIndex": 1,
      "AliasOverride": -1,
      "OverrideAliasPrefix": false,
      "SubSeed": 2,
      "MarkerSubSeed": 0,
      "LightsSubSeed": 0,
      "BulkheadDCScanSeed": 0,
      "SubComplex": 2,
      "CustomGeomorph": "Assets/AssetPrefabs/Complex/Mining/Geomorphs/Refinery/geo_64x64_mining_refinery_I_HA_05.prefab",
      "IgnoreRandomGeomorphRotation": true,
      "CoverageMinMax": {
        "x": 30.0,
        "y": 30.0
      },
      "BuildFromLocalIndex": 0,
      "StartPosition": 3,
      "StartPosition_IndexWeight": 0.0,
      "StartExpansion": 1,
      "ZoneExpansion": 3,
      "LightSettings": 56,
      "AltitudeData": {
        "AllowedZoneAltitude": 3,
        "ChanceToChange": 0.5
      },
      "EventsOnEnter": [],
      "EventsOnPortalWarp": [],
      "EventsOnApproachDoor": [],
      "EventsOnUnlockDoor": [],
      "EventsOnOpenDoor": [],
      "EventsOnDoorScanStart": [],
      "EventsOnDoorScanDone": [],
      "ProgressionPuzzleToEnter": {
        "PuzzleType": 0,
        "CustomText": 972,
        "PlacementCount": 1,
        "ZonePlacementData": []
      },
      "ChainedPuzzleToEnter": 0,
      "IsCheckpointDoor": false,
      "PlayScannerVoiceAudio": false,
      "SecurityGateToEnter": 0,
      "UseStaticBioscanPointsInZone": false,
      "TurnOffAlarmOnTerminal": false,
      "TerminalPuzzleZone": {
        "LocalIndex": 0,
        "SeedType": 1,
        "TerminalIndex": 0,
        "StaticSeed": 0
      },
      "EventsOnTerminalDeactivateAlarm": [],
      "ActiveEnemyWave": {
        "HasActiveEnemyWave": false,
        "EnemyGroupInfrontOfDoor": 0,
        "EnemyGroupInArea": 0,
        "EnemyGroupsInArea": 0
      },
      "EnemySpawningInZone": [
      ],
      "EnemyRespawning": false,
      "EnemyRespawnRequireOtherZone": true,
      "EnemyRespawnRoomDistance": 2,
      "EnemyRespawnTimeInterval": 10.0,
      "EnemyRespawnCountMultiplier": 1.0,
      "HSUClustersInZone": 0,
      "CorpseClustersInZone": 0,
      "ResourceContainerClustersInZone": 0,
      "GeneratorClustersInZone": 0,
      "CorpsesInZone": 0,
      "GroundSpawnersInZone": 3,
      "HSUsInZone": 0,
      "DeconUnitsInZone": 0,
      "AllowSmallPickupsAllocation": true,
      "AllowResourceContainerAllocation": true,
      "ForceBigPickupsAllocation": true,
      "ConsumableDistributionInZone": 20,
      "BigPickupDistributionInZone": 0,
      "TerminalPlacements": [
        {
          "PlacementWeights": {
            "Start": 1000.0,
            "Middle": 0.0,
            "End": 0.0
          },
          "AreaSeedOffset": 123,
          "MarkerSeedOffset": 123,
          "LocalLogFiles": [
          ],
          "UniqueCommands": [],
          "StartingStateData": {
            "StartingState": 0,
            "AudioEventEnter": 0,
            "AudioEventExit": 0,
            "PasswordProtected": false,
            "PasswordHintText": "Password Required.",
            "GeneratePassword": false,
            "PasswordPartCount": 1,
            "ShowPasswordLength": true,
            "ShowPasswordPartPositions": false,
            "TerminalZoneSelectionDatas": []
          }
        }
      ],
      "ForbidTerminalsInZone": false,
      "PowerGeneratorPlacements": [],
      "DisinfectionStationPlacements": [],
      "DumbwaiterPlacements": [],
      "HealthMulti": 0.0,
      "HealthPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "WeaponAmmoMulti": 0.0,
      "WeaponAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "ToolAmmoMulti": 0.0,
      "ToolAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "DisinfectionMulti": 0.0,
      "DisinfectionPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "StaticSpawnDataContainers": [
      ]
    },
    {
      "LocalIndex": 2,
      "AliasOverride": -1,
      "OverrideAliasPrefix": false,
      "SubSeed": 2,
      "MarkerSubSeed": 0,
      "LightsSubSeed": 0,
      "BulkheadDCScanSeed": 0,
      "SubComplex": 2,
      "CustomGeomorph": "Assets/AssetPrefabs/Complex/Mining/Geomorphs/geo_64x64_mining_reactor_open_HA_01.prefab",
      "IgnoreRandomGeomorphRotation": true,
      "CoverageMinMax": {
        "x": 40.0,
        "y": 45.0
      },
      "BuildFromLocalIndex": 1,
      "StartPosition": 1,
      "StartPosition_IndexWeight": 0.0,
      "StartExpansion": 1,
      "ZoneExpansion": 3,
      "LightSettings": 56,
      "AltitudeData": {
        "AllowedZoneAltitude": 3,
        "ChanceToChange": 0.5
      },
      "EventsOnEnter": [],
      "EventsOnPortalWarp": [],
      "EventsOnApproachDoor": [],
      "EventsOnUnlockDoor": [],
      "EventsOnOpenDoor": [],
      "EventsOnDoorScanStart": [],
      "EventsOnDoorScanDone": [],
      "ProgressionPuzzleToEnter": {
        "PuzzleType": 0,
        "CustomText": 972,
        "PlacementCount": 1,
        "ZonePlacementData": []
      },
      "ChainedPuzzleToEnter": 0,
      "IsCheckpointDoor": false,
      "PlayScannerVoiceAudio": false,
      "SecurityGateToEnter": 0,
      "UseStaticBioscanPointsInZone": false,
      "TurnOffAlarmOnTerminal": false,
      "TerminalPuzzleZone": {
        "LocalIndex": 0,
        "SeedType": 1,
        "TerminalIndex": 0,
        "StaticSeed": 0
      },
      "EventsOnTerminalDeactivateAlarm": [],
      "ActiveEnemyWave": {
        "HasActiveEnemyWave": false,
        "EnemyGroupInfrontOfDoor": 0,
        "EnemyGroupInArea": 0,
        "EnemyGroupsInArea": 0
      },
      "EnemySpawningInZone": [
      ],
      "EnemyRespawning": false,
      "EnemyRespawnRequireOtherZone": true,
      "EnemyRespawnRoomDistance": 2,
      "EnemyRespawnTimeInterval": 10.0,
      "EnemyRespawnCountMultiplier": 1.0,
      "HSUClustersInZone": 0,
      "CorpseClustersInZone": 0,
      "ResourceContainerClustersInZone": 0,
      "GeneratorClustersInZone": 0,
      "CorpsesInZone": 0,
      "GroundSpawnersInZone": 3,
      "HSUsInZone": 0,
      "DeconUnitsInZone": 0,
      "AllowSmallPickupsAllocation": true,
      "AllowResourceContainerAllocation": true,
      "ForceBigPickupsAllocation": true,
      "ConsumableDistributionInZone": 20,
      "BigPickupDistributionInZone": 0,
      "TerminalPlacements": [
        {
          "PlacementWeights": {
            "Start": 1000.0,
            "Middle": 0.0,
            "End": 0.0
          },
          "AreaSeedOffset": 123,
          "MarkerSeedOffset": 123,
          "LocalLogFiles": [
          ],
          "UniqueCommands": [],
          "StartingStateData": {
            "StartingState": 0,
            "AudioEventEnter": 0,
            "AudioEventExit": 0,
            "PasswordProtected": false,
            "PasswordHintText": "Password Required.",
            "GeneratePassword": false,
            "PasswordPartCount": 1,
            "ShowPasswordLength": true,
            "ShowPasswordPartPositions": false,
            "TerminalZoneSelectionDatas": []
          }
        }
      ],
      "ForbidTerminalsInZone": false,
      "PowerGeneratorPlacements": [],
      "DisinfectionStationPlacements": [],
      "DumbwaiterPlacements": [],
      "HealthMulti": 0.0,
      "HealthPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "WeaponAmmoMulti": 0.0,
      "WeaponAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "ToolAmmoMulti": 0.0,
      "ToolAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "DisinfectionMulti": 0.0,
      "DisinfectionPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "StaticSpawnDataContainers": [
      ]
    }
  ],
  "name": "Newbie guide B1 secondary",
  "internalEnabled": true,
  "persistentID": 163
}
  "ZoneAliasStart": 200,
  "Zones": [
    {
      "LocalIndex": 0,
      "AliasOverride": -1,
      "OverrideAliasPrefix": false,
      "SubSeed": 2,
      "MarkerSubSeed": 0,
      "LightsSubSeed": 0,
      "BulkheadDCScanSeed": 0,
      "SubComplex": 2,
      "CustomGeomorph": "",
      "CoverageMinMax": {
        "x": 40.0,
        "y": 45.0
      },
      "BuildFromLocalIndex": 0,
      "StartPosition": 1,
      "StartPosition_IndexWeight": 0.0,
      "StartExpansion": 1,
      "ZoneExpansion": 3,
      "LightSettings": 56,
      "AltitudeData": {
        "AllowedZoneAltitude": 3,
        "ChanceToChange": 0.5
      },
      "EventsOnEnter": [],
      "EventsOnPortalWarp": [],
      "EventsOnApproachDoor": [],
      "EventsOnUnlockDoor": [],
      "EventsOnOpenDoor": [],
      "EventsOnDoorScanStart": [],
      "EventsOnDoorScanDone": [],
      "ProgressionPuzzleToEnter": {
        "PuzzleType": 0,
        "CustomText": 972,
        "PlacementCount": 1,
        "ZonePlacementData": []
      },
      "ChainedPuzzleToEnter": 0,
      "IsCheckpointDoor": false,
      "PlayScannerVoiceAudio": true,
      "SecurityGateToEnter": 0,
      "UseStaticBioscanPointsInZone": false,
      "TurnOffAlarmOnTerminal": false,
      "TerminalPuzzleZone": {
        "LocalIndex": 0,
        "SeedType": 1,
        "TerminalIndex": 0,
        "StaticSeed": 0
      },
      "EventsOnTerminalDeactivateAlarm": [],
      "ActiveEnemyWave": {
        "HasActiveEnemyWave": false,
        "EnemyGroupInfrontOfDoor": 0,
        "EnemyGroupInArea": 0,
        "EnemyGroupsInArea": 0
      },
      "EnemySpawningInZone": [
      ],
      "EnemyRespawning": false,
      "EnemyRespawnRequireOtherZone": true,
      "EnemyRespawnRoomDistance": 2,
      "EnemyRespawnTimeInterval": 10.0,
      "EnemyRespawnCountMultiplier": 1.0,
      "HSUClustersInZone": 0,
      "CorpseClustersInZone": 0,
      "ResourceContainerClustersInZone": 0,
      "GeneratorClustersInZone": 0,
      "CorpsesInZone": 0,
      "GroundSpawnersInZone": 3,
      "HSUsInZone": 0,
      "DeconUnitsInZone": 0,
      "AllowSmallPickupsAllocation": true,
      "AllowResourceContainerAllocation": true,
      "ForceBigPickupsAllocation": true,
      "ConsumableDistributionInZone": 20,
      "BigPickupDistributionInZone": 0,
      "TerminalPlacements": [
        {
          "PlacementWeights": {
            "Start": 1000.0,
            "Middle": 0.0,
            "End": 0.0
          },
          "AreaSeedOffset": 123,
          "MarkerSeedOffset": 123,
          "LocalLogFiles": [
          ],
          "UniqueCommands": [],
          "StartingStateData": {
            "StartingState": 0,
            "AudioEventEnter": 0,
            "AudioEventExit": 0,
            "PasswordProtected": false,
            "PasswordHintText": "Password Required.",
            "GeneratePassword": false,
            "PasswordPartCount": 1,
            "ShowPasswordLength": true,
            "ShowPasswordPartPositions": false,
            "TerminalZoneSelectionDatas": []
          }
        }
      ],
      "ForbidTerminalsInZone": false,
      "PowerGeneratorPlacements": [],
      "DisinfectionStationPlacements": [],
      "HealthMulti": 0.0,
      "HealthPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "WeaponAmmoMulti": 0.0,
      "WeaponAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "ToolAmmoMulti": 0.0,
      "ToolAmmoPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "DisinfectionMulti": 0.0,
      "DisinfectionPlacement": {
        "Start": 0.0,
        "Middle": 0.0,
        "End": 0.0
      },
      "StaticSpawnDataContainers": [
      ]
    }
  ],
  "name": "Newbie guide B1 secondary",
  "internalEnabled": true,
  "persistentID": 163
}
Geomorph sheet
Bulkhead dc for secondary layer
Breadcrumbs showing custom geos
Map with bridge and reactor
View from bridge zone to initial zone of secondary layer

Audio files

How to setup foobar2000 with the vgmstream component to play and extract the audio files from GTFO

Setup and playback

  1. Download and install foobar2000

  2. Download and open vgmstream foobar component

  3. Open foobar2000 and choose File > Add Folder

  4. Find and add the following folder: ~\Steam\steamapps\common\GTFO\GTFO_Data\StreamingAssets\GeneratedSoundBanks\Windows

  5. Open the SoundbanksInfo.xml in a text editor to see what audio files the game uses

  6. In foobar2000 search for the file id with Ctrl + F

  7. Double click the audio to listen to it

Converting files

  1. In foobar2000 right click a track

  2. Choose Convert > Quick Convert

  3. Choose an output setting and click Convert

  4. Select an output path and save

Modded GTFO on Linux

basically like it's done on windows

Steam, GTFO, R2modman

  • 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 https://github.com/ebkr/r2modmanPlus/releases

  • 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)

Fixing Unity errors after importing

How to fix the numerous errors when importing asset files from GTFO

Please for the love of god expand this section

Datamining

How to retrieve sounds, assets, models, etc. from the game files

This section is still in development

Import asset files to Unity

Import GTFO asset files into Unity

Unity version 2019.4.21f1 is required to create a project for GTFO

  1. Open Unity Hub and in the Projects tab click the drop-down button next to Open

  2. Select Add project from disk and navigate to your exported assets folder

  3. Choose the "ExportedProject" folder and ensure the editor version is correct

  4. Open the project in Unity Hub and wait for Half-Life 3

  5. Enjoy the numerous errors

GameSetup
Updated map in R7

Using SoundBanks in GTFO

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:

(Profile)/
└──BepInEx/
    └──Assets/
        └──SoundBank/
           └──(your soundbanks)

(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

Creating a Sound Event

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)

Creating a SoundBank

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.

Adding Sounds into Wwise

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)

Introduction to Errors

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.

Overview

Originally covered in The Complete Newbie Guide, 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 tech support channel in the modding discord.

Important distinctions

  • 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.

Viewing errors

When launching the game with mods enabled, you'll see the BepInEx console as a separate window.

BepInEx console 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:

NullReferenceException: Object reference not set to an instance of an object.

While the game logs show this:

18:17:44.732 - NullReferenceException: Object reference not set to an instance of an object.
Gear.MeleeWeaponFirstPerson.UpdateInput () (at <00000000000000000000000000000000>:0)
Gear.MeleeWeaponFirstPerson.UpdateLocal () (at <00000000000000000000000000000000>:0)

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.

Export asset files from GTFO

Export unity asset files from GTFO

  1. Download the latest

  2. Extract the AssetRipper files and run AssetRipper.exe

  3. Change the following settings

    • Check Skip StreamingAssets Folder

  4. Choose File > Open Folder

  5. Navigate to and select the following folder ~\Steam\steamapps\common\GTFO

  6. 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

Datablocks

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.

Prerequisites

To work with datablocks, you should be familiar with JSON.

Datablocks Storage

Datablocks and their change history is kept on .

Common Fields

All datablocks share 3 main fields, each of them has special meaning.

persistentID - UInt32

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.

internalEnabled - Boolean

Whether this block is enabled. Disabled blocks are ignored by the game.

name - String

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.

Additional Fields

Headers

This field is only used by devs in their editor.

LastPersistentID - UInt32

The last persistentID found in datablocks. Serves no purpose but can clutter the logs if not set correctly.

Types

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:

  • - 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.

Enumerator/Enum

are a special type, representing named values. Here's an example ():

  • None - 0

  • Force_One - 1

  • Rel_Value - 2

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.

In JSON, enums can be specified both as underlying type and as plain text.

There are special cases where the enum values are combined, e.g. AnimationControllers, type .

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.

Nested type/Class/Object

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.

In JSON, these are all , starting and ending with curly braces { }.

Every datablock file defines one object containing several fields, including a list of objects - Blocks.

List/Array and Dictionary

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.

Empty values

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.

Additional notes

Vector

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.

Time fields

Fields related to time/length/duration are specified in seconds unless stated otherwise.

Creating Custom Weapons

Most information below this point has been reproduced from , with updates, additions, and clarifications.

In most, if not all cases, information from the actual DataBlock pages on this wiki is marginally more up-to-date than whatever is reproduced in this guide - please refer to the DataBlock pages if there are discrepancies.

In GTFO, weapons are composed of a large number of DataBlocks which provide stats, determine behavior, recoil data, and configure their appearance.

Before going any further, it’s important that you understand what main DataBlocks contain weapon data, and what data are in each.

  • : Creates an inventory object that players can select in the lobby and then use in-game by defining a collection of 3d objects (weapon parts) and associating them with data from a entry, as well as some other DataBlock entries.

  • : Groups together 4 entries and 1 entry.

  • : Defines weapon statistics and associates a entry with them, in addition to referencing other metadata.

  • : Defines what inventory slot a piece of equipment uses, controls its HUD elements, and defines what type of Item it is.

Note: There will be some DataBlock information reproduced in this guide. For clarity, inline comments have been added. Again, please refer to the actual datablock pages for more detail.

Archetype

At the heart of every weapon in GTFO is the , which determines a gun’s statistics and metadata. Like most DataBlocks, there are many fields and not all of them are always relevant.

This is a very terse guide to this DataBlock - please see the actual page for more details.

Note: Any new weapon Archetype you create needs a unique PersistentID for its data. Keep track of this ID as you’ll be using it in the GearCategory DataBlock.

GearCategory

Perhaps the most baffling DataBlock involved in custom weapons and gear is the GearCategory DataBlock. The GearCategory system is designed to group together 4 archetype DataBlock entries into one category and assigns them an entry in the Item Datablock, which is used to assign the weapons behavior on the HUD and a slot in the inventory to fit into. Let’s take a look at an example.

Note: Any new GearCategory you create needs a unique PersistentID for its data. Keep track of this PersistentID so you can reference it in the PlayerOfflineGearDataBlock.

GearCategoryDataBlock: PartAlignPriority

Sometimes, multiple parts in a GearJSON will share the same aligns as each other, which will result in one of the parts defaulting to have that align. In order to control which parts get the align in cases like this, we need to edit the PartAlignPriority in the GearCategoryDataBlock. The GearCategory which is linked to your weapon via the ‘Category’ component type will determine which GearCategory you need to modify for this. Let’s look at the Magnum’s part align priority to see how it works:

"AlignType": This corresponds to the Enum that is used by Aligns in the GearDataBlocks.

"PartPrio": A list of gear component types, ordered by priority. This determines which part to assign this align to.

So, this set of align priorities means that align types 7 and 8 (LeftHand and RightHand) are both going to prioritize part 12. From this setup, we can guess that this is probably necessary so that both hands align on the grip.

PlayerOfflineGear

In GTFO, almost every piece of equipment is assembled programmatically to get more mileage out of the limited assets the game shipped with.

Weapons, tools, and even some consumables are put together using this system. For the sake of simplicity, we will refer to these assembled items as ‘Gear’. Every assembled Gear item in GTFO is set up in the PlayerOfflineGearDataBlock via a string called GearJSON.

GearJSON formatting

To start, let’s break down the GearJSON string for the Pistol:

Note: Line breaks have been added into this string for the explanation, but in your DataBlock the entire string must be on a single line with no linebreaks.

"{\"Ver\":1,\"Name\":\"Shelling S49\", This line assigns the GearJSON a ‘descriptive name’, which is displayed in your inventory and in the lobby screen.

\"Packet\":{\"Comps\":{\"Length\":16, This line tells the game how many gear components your part contains. If this number doesn’t match the actual number of gear components, the game won’t read the GearJSON string correctly.

\"a\":{\"c\":2,\"v\":8}, This line defines a gear component to add to our GearJSON, which is broken up into 3 values:

  1. \"a\" The index, in alphabetical order, of this gear component. Notice in our example that every component starts with a unique letter identifier.

  2. \"c\":2 The component type, such as FrontPart, ReceiverPart, or StockPart. In this component, type is ‘2’ which corresponds to GearCategory. If you attempt to use the same component type on multiple components, only one of them will load.

  3. "V\":8 The component value. This tells the game which component to load for a given type. In this component, since the type was GearCategory, this will load the GearCategory with persistentID 8.

The ending section of the GearJSON string, starting with \"MatTrans\", is not used by the game.

You can have as many gear components as you want in a GearJSON, provided you use correct syntax to define them. Because of the complexity and obscurity of GearJSON strings, writing one from scratch is discouraged. Instead, duplicating and modifying an existing GearJSON from the base game is a simpler method that will save you time (and sanity).

Gear Component Types

Note: Unused/Unusable component types are omitted.

ID
Component Type
Description

Note: Even though the developers typically only used appropriate parts, you can actually use any of these parts on any piece of gear.

Gear Datablocks: General

The gear DataBlocks are used to assign behavior, animations, models, and aligns to gear parts, for use in GearJSON. The most simple, and arguably the most important part of these DataBlocks is ‘General’, which tells the game what model and children to load, and what hand animations to use.

"Model": The path of the part prefab to load

"Children": A list of child prefabs to load

"GearCategoryFilter": 0, Unused.

"LeftHandGripAnim": The animation clip to play for the player’s arms on layer 5

"RightHandGripAnim": The animation clip to play for the player’s arms on layer 6

"AssetBundle": 20, The asset bundle to load the prefabs from.

"BundleShard": 3 The asset shard to load the prefabs from.

Generally, when copying prefabs into this DataBlock, it’s important to make sure you use the same AssetBundle and BundleShard as the prefab you copied.

Gear Datablocks: Aligns

Gear parts are attached to each other using aligns, which are empty Game Objects placed onto gear parts which aligned parts are parented to. In order to change the usage of these aligns, you need to edit, or make a copy of the gear DataBlock entry for the desired part. Let’s look at the aligns of Front_UZI_1:

"AlignType": 0, The Type of the align, which determines which part attaches to this

"AlignName": "Receiver_M" The name of the game object which this align attaches parts to. Unfortunately, we can’t add or modify these game objects in this DataBlock, but we can tell the game what parts to place onto these aligns.

Gear Datablocks: Align Types

ID
Align Type
Description

Gear Datablocks: FireSequence and ReloadSequence

To handle animations, the many Gear DataBlocks contain lists of WeaponAnimSequenceItems, which provide timing, behavior, and a string to reference the animation. Let’s look at the Short Shotgun reload sequence:

"TriggerTime": 0.0, The delay, from the start of the sequence, to play the clip.

"Type": 5, The type, which determines the behavior of the animation.

"StringData": "shotgun_reload_1_shell_in" The string of the animation clip.

Note: This formatting and behavior is consistent between reload and firing sequence.

Gear Datablocks: Animation Types

ID
Animation Type
Description

GearPartCustomization: Modifying gear part transforms

By default, it is possible to use whatever weapon or tool parts you want on any gun, and to choose what aligns those parts snap to. However, it is not possible to adjust the position, scale, or rotation of parts. To get around this limitation, you can use the , which allows you to modify the location, rotation, and scale of parts and get far more mileage out of them. Once the GearPartCustomization plugin is installed, it'll create a new file in your custom folder titled GearPartTransform.json.

Let’s take a look at the composition of this file.

Each entry in the Parts list is an object with the following properties:

Each entry in the Children list is an object with the following properties:

This might seem daunting - you'll potentially be editing a very large amount of data, but thanks to , this process is heavily simplified and relatively easy.

When you first boot up the game with , you’ll see something like this.

For now, press F7 to close the Unity Explorer menu. Pick a level, pick the gear you'd like to customize, and start the game. Once you've loaded into the level and are in control, open the Unity Explorer menu by pressing F7. Go to the Object Explorer window and and select Object Search. Set the scope of the search to UnityObject, the class filter to Gear.GearPartHolder, and click Search.

In our search results, you’ll find the GearPartHolder objects for everything in your character's current inventory. To continue, click on the GearPartHolder of the gear you want to customize. In this example, let's modify the Hanaway PSB.

With the GearPartHolder open, you’ll be able to see all of the properties and fields of the GearPartHolder. You can also click Inspect GameObject to view the GameObject itself, where you can move, rotate, and scale the gun to make it easier to view your change while editing - for example, you can move it to the center of the screen so you can see it better. While there are many of fields here, very few of them are useful. Let’s inspect GearPartHolder.FrontPart.

You can now see the FrontPart’s global position, local position, rotation, and scale, as well as its attached child objects. Don't use the sliders to adjust these values - at the scale of this game, even minute adjustments to the sliders can move parts a tremendous distance.

Note: To change where the gun is, don't change the global position, change the local position.

When you change the values for this weapon part, you can see that the weapon part is changed in-game as well. Once you’re happy with how it looks, you can input these values into your entry in the GearPartTransform.json.

ItemFPSSettings - First person weapon positioning

To modify the position of weapons in the first person camera, you need to either assign an existing ItemFPSSetting entry (There are plenty of good ones in the base game) or write a custom one. Let’s look at an entry in the :

Best Practices and Notes

PersistentIDs: Before you make a new weapon, it's wise to come up with an unused PersistentID and use that for everything associated with your weapon. You can reference the TextDataBlock for ideas on how large PersistentIDs can get, and be sure to search through all existing files to make sure you don't have any collisions.

Enabling MTFO Hot Reload

Hot reloading can let you test your changes without having to restart your game, it's great!

Enabling MTFO Hot Reload

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.

VS Code Tips

Editor features that make working with datablocks easier

Saving files

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)

Opening folders from context menu

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).

Syntax errors

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.

Split editor

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.

Collapse blocks

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.

Navigating files

Go to start/end of block

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

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.

Go to line

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.

Go to file

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.

Search

This one might seem obvious but there can be features you're not aware of.

Searching in selection

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.

Searching across files

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 search

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".

I recommend you check out guides to RegEx and test online for mistakes and explanations, for example in .

RegEx replace

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.

Creating temporary files

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

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.

Git

Git is a powerful versioning tool not limited to VS Code. It is covered in its own guide .

AssetRipper_win_x64.zip
GitHub
SurvivalWaveSettings
UInt32
Int32
Single
Boolean
String
Enums
eEnemyZoneDistribution
enum types
EnemyMovementDataBlock
AnimatorControllerHandleName
objects
Example dictionary
Vectors fields that aren't supposed to be there
"PublicName": //Weapon inventory name, PersistentID or String
"Description": //Weapon lobby description, PersistentID or String
"FireMode": //How the weapon shoots:
            //0 - Semiauto (one shot per trigger pull)
            //1 - Burst (multiple sequential shots per trigger pull)
            //2 - Auto (continuous fire as long as trigger is held)
            //3 - SemiBurst (unused but functional, see ArchtypeDataBlock for details)
"RecoilDataID": //Weapon recoil settings, references a PersistentID in RecoilDatablock
"DamageBoosterEffect": //Determines what boosters affect this weapon.
                       //See "Effect" field in BoosterImplantEffectDataBlock
"Damage": //Weapon base damage
"DamageFalloff": { // see ArchetypeDataBlock for more details
    "x": //Falloff begins
    "y": //Falloff ends
},
"StaggerDamageMulti": //Multiplier to get weapon's stagger damage
"PrecisionDamageMulti": //Multiplier for shots that hit an enemy's weakpoint
"DefaultClipSize": //Number of shots in a magazine
"DefaultReloadTime": //Time in seconds to reload
"CostOfBullet": //Total ammo capacity, see ArchetypeDataBlock for more details
"ShotDelay": //Time in seconds between each shot
"PiercingBullets": //Whether this weapon's projectiles pierce though enemies
"PiercingDamageCountLimit": //See ArchetypeDataBlock for more details 
"HipFireSpread": //Size of the accuracy cone when hipfiring, not degrees
"AimSpread": //Size of the accuracy cone when aiming, not degrees
"EquipTransitionTime": //Time in seconds to equip the weapon
"EquipSequence": //Animation sequence to play when equipping the weapon
                 //(see later section on AnimationSequences for more details) 
"AimTransitionTime": //Time in seconds to aim down sights
"AimSequence": //Animation sequence to play when aiming the weapon
               //(see later section on AnimationSequences for more details)
"BurstDelay": //Delay in seconds between bursts
"BurstShotCount": //Number of shots to fire in a burst
"ShotgunBulletCount": //Number of pellets to fire if the weapon is a shotgun
"ShotgunConeSize": //Radius of the shotgun’s cone (Integer)
"ShotgunBulletSpread": //Spread between each pellet (Integer)
"SpecialChargetupTime": //Time it takes to charge up before firing this weapon
"SpecialCooldownTime": //Time it takes to cool down after firing this weapon
"SpecialSemiBurstCountTimeout": //Unused or broken
"Sentry_StartFireDelay": //Time before a sentry starts firing
"Sentry_RotationSpeed": //Sentry rotation speed
"Sentry_DetectionMaxRange": //Maximum distance a sentry can target enemies at
"Sentry_DetectionMaxAngle": //Angle of the sentry's sweep in degrees
"Sentry_FireTowardsTargetInsteadOfForward": //see ArchetypeDataBlock for details
"Sentry_LongRangeThreshold": //Unused?
"Sentry_ShortRangeThreshold": //Unused?
"Sentry_LegacyEnemyDetection": //Use the new or old sentrygun targeting behavior
"Sentry_FireTagOnly": //Only shoot at biotracker tagged enemies, True/False
"Sentry_PrioTag": //Prioritize biotracked enemies, True/False
"Sentry_StartFireDelayTagMulti": //Multipliers when firing at biotracked enemies,
"Sentry_RotationSpeedTagMulti":  //see ArchetypeDataBlock for more details
"Sentry_DamageTagMulti":         //...
"Sentry_StaggerDamageTagMulti":  //...
"Sentry_CostOfBulletTagMulti":   //...
"Sentry_ShotDelayTagMulti":      //...
"name": //The weapon’s internal name in the files, unused
"internalEnabled": //Is weapon enabled in-game
"persistentID": //Weapon’s ID for reference in other DataBlocks
"PublicName": "", //In-game name for other gear, not used by weapons
"Description": "", //Lobby description for other gear, not used by weapons
"BaseItem": 108, //PersistentID in ItemDataBlock of this weapon's base item
"HUDIcon": "", //Unused
"FPSArmPoseName": "", //Unused
"ThirdPersonFullbodyMovement": 0, //Animation set to use, see GearCategoryDataBlock
"SemiArchetype": 1,         //0th archetype in this category
"BurstArchetype": 3,	    //1st archetype in this category
"AutoArchetype": 5,	    //2nd archetype in this category
"SemiBurstArchetype": 16,   //3rd archetype in this category
/*
These refer to the PersistentIDs of gun behavior entries in ArchetypeDataBlock.
You only need the correct PersistentID for the firemode your gun actually uses.
Weapon firemode is selected in its PlayerOfflineGearDataBlock GearJSON.
*/
"PartAlignPriority": [], //Determines how parts with conflicting aligns behave.
"name": "Rifle", //Internal name, unused
"internalEnabled": true, //Whether or not this category is enabled ingame
"persistentID": 1 //Category ID for reference in other DataBlocks
"PartAlignPriority": [
{
    "AlignType": 7,
    "PartPrio": [
    12
    ]
},
{
    "AlignType": 8,
    "PartPrio": [
    12
    ]
}
],
"GearJSON":
"{\"Ver\":1,\"Name\":\"Shelling S49\",
\"Packet\":{\"Comps\":{\"Length\":16,
\"a\":{\"c\":2,\"v\":8},
\"b\":{\"c\":3,\"v\":108},
\"c\":{\"c\":4,\"v\":8},
\"d\":{\"c\":5,\"v\":1},
\"e\":{\"c\":6,\"v\":1},
\"f\":{\"c\":7,\"v\":1},
\"g\":{\"c\":8,\"v\":16},
\"h\":{\"c\":9,\"v\":15},
\"i\":{\"c\":10,\"v\":27},
\"j\":{\"c\":11,\"v\":27},
\"k\":{\"c\":12,\"v\":38},
\"l\":{\"c\":16,\"v\":1001},
\"m\":{\"c\":19,\"v\":1002},
\"n\":{\"c\":23,\"v\":8},
\"o\":{\"c\":25,\"v\":3}},
\"MatTrans\":{\"tDecalA\":{\"position\":{\"x\":-0.098,\"y\":-0.07},\"scale\":0.05},
\"tDecalB\":{\"position\":{\"x\":-0.098,\"y\":-0.07},\"scale\":0.04},
\"tPattern\":{\"position\":{\"x\":-0.09,\"y\":-0.03},\"angle\":-90.0,\"scale\":0.1}},
\"publicName\":{\"data\":\"Shelling S49\"}}}",

1

FireMode

0-3, determines which FireMode PersistentID to use from the GearCategory

2

Category

PersistentID of the GearCategoryDataBlock to load

3

BaseItem

PersistentID of the ItemDataBlock entry to load.

4

ItemFPSSettings

PersistentID of the ItemFPSSettings entry to load.

5

AudioSetting

PersistentID of the WeaponAudio entry to load

6

MuzzleFlash

PersistentID of the WeaponMuzzleFlash entry to load

7

ShellCasing

PersistentID of the WeaponShellCasing entry to load

12

FrontPart

The front, barrel portion of a weapon

16

ReceiverPart

The receiver of a weapon

19

StockPart

The grip or stock of a weapon

21

SightPart

The sight of a weapon; some front parts have this function built in

23

MagPart

The magazine of a weapon. Not all parts support this

25

FlashlightPart

The flashlight of a weapon

27

ToolMainPart

The base part which the rest of the tool parts align to

30

ToolGripPart

The grip or stock of a tool; this is interchangeable with StockPart

33

ToolDeliveryPart

Typically the business end of a tool

37

ToolPayloadPart

Varies widely between tools

40

ToolTargetingPart

The ‘targeting module’ placed on some tools

42

ToolScreenPart

The display screen, only functional if the gear uses the correct BaseItem

44

MeleeHeadPart

The head of a melee weapon

46

MeleeNeckPart

The part that connects the head to the handle of a melee weapon

48

MeleeHandlePart

The handle of a a melee weapon

50

MeleePommelPart

The base of the handle of a melee weapon

"General": {
    "Model": "Assets/AssetPrefabs/Items/Gear/Parts/Fronts/Front_Short_Shotgun_2.prefab",
    "Children": [],
    "GearCategoryFilter": 0,
    "LeftHandGripAnim": "Short_Shotgun_2_Player_Idle",
    "RightHandGripAnim": "Short_Shotgun_2_Player_Idle",
    "AssetBundle": 20,
    "BundleShard": 3
},
"Aligns": [
    {
        "AlignType": 0,
        "AlignName": "Receiver_M"
    },
    {
        "AlignType": 1,
        "AlignName": "Front_SE"
    },
    {
        "AlignType": 2,
        "AlignName": "Front_Mag"
    },
    {
        "AlignType": 5,
        "AlignName": "Front_Flash"
    },
    {
        "AlignType": 7,
        "AlignName": "LeftHand"
    },
    {
        "AlignType": 6,
        "AlignName": "Sight_Align"
    },
    {
        "AlignType": 4,
        "AlignName": "Receiver_Sight"
    }
],

0

Muzzle

Weapon’s muzzle flash

1

ShellEject

Weapon’s ejection port

2

Magazine

Corresponds to the MagPart gear component

4

Sight

Corresponds to the SightPart gear component

5

Flashlight

Corresponds to the FlashlightPart gear component

6

SightLook

The aim camera if no sight part is present

7

LeftHand

The player’s left hand

8

RightHand

The player’s right hand

9

Receiver

Corresponds to the ReceiverPart gear component

10

Front

Corresponds to the FrontPart gear component

12

ToolGrip

Corresponds to the ToolGripPart gear component

13

ToolDelivery

Corresponds to the ToolDeliveryPart gear component

14

ToolPayload

Corresponds to the ToolPayloadPart gear component

15

ToolTargeting

Corresponds to the ToolTargetingPart gear component

16

ToolScreen

Corresponds to the ToolScreenPart gear component

17

ToolDetection

Unknown

18

ToolScanning

Unknown

20

MeleeHead

Corresponds to the MeleeHeadPart gear component

40

RotationPivot

Presumably used by sentry guns to position the contained weapon parts

41

GroundPlacement

Presumably used to position the base of a sentry gun

"ReloadSequence": [
    {
        "TriggerTime": 0.0,
        "Type": 0,
        "StringData": "ShotgunReload_bump"
    },
    {
        "TriggerTime": 0.0,
        "Type": 5,
        "StringData": "carbine_reload_3_foley"
    },
    {
        "TriggerTime": 0.95,
        "Type": 5,
        "StringData": "shotgun_reload_1_shell_in"
    },
    {
        "TriggerTime": 1.0,
        "Type": 6
    },
    {
        "TriggerTime": 1.5,
        "Type": 5,
        "StringData": "shotgun_reload_2_cock"
    },
    {
        "TriggerTime": 2.0,
        "Type": 7
    }
],

0

WeaponMovementAnim

Animate the first person weapon holder

1

FrontPartAnim

Animate the weapon’s front part

2

LeftHandAnim

Animate the FPS arms (animation layer 5)

3

LeftHandMagAnim

Animate the FPS arms using the magazine’s animation. (No animation clip input required)

4

ReceiverAnim

Animate the weapon’s receiver part

5

Sound

Play a sound event using the animation string as a sound event ID

6

DoUpdateAmmo

Refill the weapon’s magazine. (No animation clip input required)

7

Empty

Do nothing. Typically used at the end of a sequence. (No animation clip input required)

8

StockAnim

Animate the weapon’s stock part

9

RightHandAnim

Animate the FPS arms (animation layer 6)

    "OfflineID": //PersistentID of the PlayerOfflineGear entry to customize
    "Name": //The name of the customization settings. (For organization, no effect)
    "InternalEnabled": //Whether or not to load this customization setting
    "Parts": //The list of parts to customize on this gear item.
    "PartHolderObject": //Property within the GearPartHolder to modify
    "PartType": //Component Type to modify. (Only used if PartHolderObject is unset)
    "Enabled": //Enable or disable this Part’s GameObject (True/False)
    "PartTransform": //Modify Part's position, rotation, and scale
    "Children": /* The list of children on this part to modify.
    Many gear parts contain children for additional models, aligns, animations, etc.
    If you don’t want to customize this Part's children, leave this list empty. */`
    "ChildName": //The name of the child object to modify.
    "Enabled": //Enable or disable this child's GameObject (True/False)
    "PartTransform": //Modify child's position, rotation, and scale.
    "Children": /* Any Children of this object that we want to modify.
    Many parts have nested children multiple levels deep,
    so you need to modify the children of children to access those. */
"localPosHip": //Position of the weapon while hipfiring
"localRotHip":	//Angle of the weapon while hipfiring
"localPosRelaxed": //Position of the weapon while in the idle animation
"localRotRelaxed": //Rotation of the weapon while in the idle animation
"localPosZoom": //Position of the weapon when aiming
"localRotZoom": //Rotation of the weapon when aiming
"bodyOffsetLocal": //Root position; other positions are offset from this
"bodyRotationOffsetLocal": //Root rotation; other rotations offset from this
"ItemCameraFOVDefault": //Default FOV of the Item Camera (renders only weapons/tools)
"ItemCameraFOVZoom": //Aimed FOV of the Item Camera (renders only weapons/tools)
"LookCameraFOVZoom": //Aimed FOV of the Player Camera (renders only the world)
"canAim": //Can this weapon enter the Aiming state, True/False
"onlyStartAimOnPressed": //Unused
"canRelax": //Can this weapon can play an idle animation?
"customDelayUntilRelax": //Time until this weapon plays an idle animation
"allowRotToAimPos": //Can weapon rotate when moving to the aimed position
"rotToAimPosMinDis": //Unused
"transitionToAim": //Speed of aim transition, 0 = quick, 1 = slow
"idleAnimData": //Animation used when player is stationary (ItemMovementAnimation)
"walkAnimData": //Animation used when player is walking (ItemMovementAnimation)
"runAnimData": //Animation used when player is stationary (ItemMovementAnimation)
"DofDefault": //The depth of field settings (camera blur) for hipfiring
"DofAim": //The depth of field settings (camera blur) when aiming
"name": //Internal name of these settings, unused
"internalEnabled": //Is this group of settings enabled in-game
"persistentID": //Unique ID of this ItemFPSSettings group
Mccad00's "How to make custom weapons for GTFO" guide
PlayerOfflineGearDataBlock
GearCategoryDataBlock
GearCategoryDataBlock
ArchetypeDataBlock
ItemDataBlock
ArchetypeDataBlock
RecoilDataBlock
ItemDataBlock
ArchetypeDataBlock
ArchetypeDataBlock
GearPartCustomization plugin
Unity Explorer
Unity Explorer
ItemFPSSettingsDataBlock
Fortunately, you only really have to understand the green-highlighted entries in this chart.
An exploded view of a gun, illustrating the various parts used to create it
The R5-era Rundown screen, with Unity Explorer open
Searching for GearPartHolder objects
Inspecting the Hanaway PSB's parts
Inspecting the Front_Rifle_2 GameObject
Accessing MTFO's config.
The EnableHotReload setting in MTFO's config.
The Reload Game Data button.
regex101
here
Open with code in context menu
Open with code checkboxes during installation
File with syntax errors
Problems view
Split editor view with 2 files open
A rundown block collapsed
Breadcrumbs showing the cursor is in CustomGeomorphs
Breadcrumbs showing that the cursor is in CustomGeomorphs in Complex_Tech block
Line number menu
Top menu with enemybalancing file selected
tierd with 7 matches
tierd with find in selection
Searching for "hunter" in the enums folder
RegEx search pers.+13
RegEx search & replace for all names
Group names with OBSOLETE_ prefix added
New file created by double-clicking
Comment field in enemy population
big striker comment
VS Code saying comments are not allowed in JSON
Change language mode in command menu
Configure file association option
Json with comments selection

ChainedPuzzle

GameData_ChainedPuzzleDataBlock_bin.json (filled)

Defines Chained Puzzle settings (e.g. Security Door alarm).

Fields

PublicAlarmName - String

The Alarm name. For example, Class S Surge Alarm

TriggerAlarmOnActivate - Boolean

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.

SurvivalWaveSettings - UInt32 (SurvivalWaveSettingsDataBlock)

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.

SurvivalWavePopulation - UInt32 (SurvivalWavePopulationDataBlock)

Determine what type(s) of enemy would spawn.

SurvivalWaveAreaDistance - Int32

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.

DisableSurvivalWaveOnComplete - Boolean

Specify whether to stop the wave after Chained Puzzle Completion. Typically set to false for ://ERROR! alarm.

UseRandomPositions - Boolean

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.

WantedDistanceFromStartPos - Single

As explained by the field name :)

WantedDistanceBetweenPuzzleComponents - Single

Also as explained by the field name :)

ChainedPuzzle - List ChainedPuzzleComponent (nested type)

Determines the count and types of scans.

OnlyShowHUDWhenPlayerIsClose - Boolean

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.

AlarmSoundStart - UInt32

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.

AlarmSoundStop - UInt32

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.

Archetype

GameData_ArchetypeDataBlock_bin.json (filled)

Defines weapon stats and metadata, including sentries.

This datablock is not referenced directly, but rather selected from PlayerOfflineGearDataBlock component firemode paired with specified component gear category from GearCategoryDataBlock. Sentry archetypes are hardcoded.

Fields

PublicName - LocalizedText (nested type)

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 PlayerOfflineGearDataBlock.

Description - LocalizedText (nested type)

Weapon description displayed in lobby.

FireMode - eWeaponFireMode (enum)

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.

RecoilDataID - UInt32 (RecoilDataBlock)

Recoil data ID of this weapon.

DamageBoosterEffect - AgentModifier (enum)

Which booster types should affect this weapon.

Seems functional but currently unused.

Damage - Single

Base damage of this weapon.

DamageFalloff - Vector2

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.

StaggerDamageMulti - Single

Stagger damage multiplier for base damage.

PrecisionDamageMulti - Single

Precision damage multiplier for base damage. This multiplier is only applied when a weakspot is hit (back damage doesn't count).

DefaultClipSize - Int32

Default mag size.

GearMagPartDataBlock can affect this.

DefaultReloadTime - Single

Time in seconds for the reload animation to complete. Note that reload can be completed before the animation is done.

GearMagPartDataBlock can affect this.

CostOfBullet - Single

Used together with PlayerDataBlock Ammo values to several weapon ammo values. For example for a weapon with a cost of 10 and these (currently game default) ammo values:

  "AmmoStandardInitial": 300,
  "AmmoStandardInitialOnDropin": 200,
  "AmmoStandardMaxCap": 460,
  "AmmoStandardResourcePackMaxCap": 450,

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)

Note that for sentries there's another multiplier in ItemDataBlock called ClassAmmoCostFactor.

ShotDelay - Single

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.

ShellCasingSize - Single

Shell casing size multiplier.

ShellCasingSpeedRange - Vector2

Shell casing ejection speed range.

PiercingBullets - Boolean

Whether this weapon can pierce.

Pierce is currently broken for the Shotgun sentries. It functions as intended for others.

PiercingDamageCountLimit - Int32

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.

HipFireSpread - Single

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.

AimSpread - Single

ADS random spread.

EquipTransitionTime - Single

How long equip animation takes. Also affects when it can be fired after selecting.

EquipSequence - List WeaponAnimSequenceItem (nested type)

Animation sequence to play when equipping a weapon.

AimTransitionTime - Single

How long ADS animation takes.

AimSequence - List WeaponAnimSequenceItem (nested type)

Animation sequence to play when aiming with a weapon.

BurstDelay - Single

For burst weapons, delay between separate bursts.

BurstShotCount - Int32

For burst weapons, how many shots a single burst fires.

ShotgunBulletCount - Int32

For shotguns, pellet count in a shot.

ShotgunConeSize - Int32

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.

ShotgunBulletSpread - Int32

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.

SpecialChargetupTime - Single

How long it takes for the weapon to charge before firing.

SpecialCooldownTime - Single

Delay before a weapon can charge up again when it stops firing.

Currently broken for auto weapons.

SpecialSemiBurstCountTimeout - Single

Time for semiburst weapon shot count to reset.

Sentry_StartFireDelay - Single

Delay before a sentry starts firing when detecting an enemy.

Sentry_RotationSpeed - Single

Sentry rotation speed.

Sentry_DetectionMaxRange - Single

Sentry detection max range.

Currently broken (sort of) - the noise range the sentry causes around itself directly depends on this value.

Sentry_DetectionMaxAngle - Single

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.

Sentry_FireTowardsTargetInsteadOfForward - Boolean

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.

Sentry_LongRangeThreshold - Single

Distance for long range boosters.

Sentry_ShortRangeThreshold - Single

Distance for short range boosters.

Sentry_LegacyEnemyDetection - Boolean

Seems to determine how sentries should target enemies.

Practical difference is unknown.

Sentry_FireTagOnly - Boolean

Should sentry only target biotracker-tagged enemies.

Sentry_PrioTag - Boolean

Should sentry prioritize targeting biotracker-tagged enemies.

Sentry_StartFireDelayTagMulti - Single

Start fire delay multiplier when targeting biotracker-tagged enemies.

Sentry_RotationSpeedTagMulti - Single

Rotation speed multiplier when targeting biotracker-tagged enemies.

Sentry_DamageTagMulti - Single

Damage multiplier when targeting biotracker-tagged enemies.

Sentry_StaggerDamageTagMulti - Single

Stagger damage multiplier when targeting biotracker-tagged enemies.

Sentry_CostOfBulletTagMulti - Single

CostOfBullet multiplier when targeting biotracker-tagged enemies.

Sentry_ShotDelayTagMulti - Single

Shot delay multiplier when targeting biotracker-tagged enemies.

name - String

Internal name string for this DataBlock entry.

Not used in-game.

External Guides

If there's something useful for modding but not stored on the wiki, we'll reference it here.

Geomorph sheet

Partly an explanation on how level generation works, mostly a list of all geomorphs and what they look like.

Weapon guide

Explains most things about how weapons work and how to make them - for archival purposes.

This has been transcribed and updated, as the Creating Custom Weapons guide.

Datablock (level layout) editor

A way to make level layout blocks online instead of working with JSON.

Unity setup

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.

OriginalDataBlocks (TypeList)

Stores all datablocks and their type information as well as enums. Can be used to view all changes to datablocks starting from mid-R4.

Main

List of most commonly used datablocks

ConsumableDistribution

GameData_ConsumableDistributionDataBlock_bin.json

No description provided.


Fields

SpawnsPerZone - Int32

No description provided.

ChanceToSpawnInResourceContainer - Single

No description provided.

SpawnData - List ConsumableSpawnData (nested type)

No description provided.

Writing a Plugin class

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

using BepInEx;
using BepInEx.Unity.IL2CPP;

namespace MyFirstPlugin;

[BepInPlugin("NewbiePluginAuthor.MyFirstPlugin", "MyFirstPlugin", "1.0.0")]
public class Plugin : BasePlugin
{
    public override void Load()
    {
        // Plugin startup logic
        Log.LogInfo("MyFirstPlugin is loaded!");
    }
}

Save your changes by selecting File > Save Plugin.cs

You should now be ready to compile your first plugin

Explaining how this work

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.

Dimension

GameData_DimensionDataBlock_bin.json

No description provided.


Fields

DimensionData - DimensionData (nested type)

No description provided.

BigPickupDistribution

GameData_BigPickupDistributionDataBlock_bin.json

No description provided.


Fields

SpawnsPerZone - Int32

No description provided.

SpawnData - List BigPickupSpawnData (nested type)

No description provided.

ComplexResourceSet

GameData_ComplexResourceSetDataBlock_bin.json

No description provided.


Fields

ComplexType - Complex (enum)

No description provided.

PrimareSubComplexUsed - SubComplex (enum)

No description provided.

BundleName - AssetBundleName (enum)

No description provided.

LevelGenConfig - LevelGenConfig (nested type)

No description provided.

GeomorphTiles_1x1 - List ResourceData (nested type)

No description provided.

GeomorphTiles_2x1 - List ResourceData (nested type)

No description provided.

GeomorphTiles_2x2 - List ResourceData (nested type)

No description provided.

RandomizeGeomorphOrder - Boolean

No description provided.

SmallWeakGates - List ResourceData (nested type)

No description provided.

SmallSecurityGates - List ResourceData (nested type)

No description provided.

SmallApexGates - List ResourceData (nested type)

No description provided.

SmallBulkheadGates - List ResourceData (nested type)

No description provided.

SmallMainPathBulkheadGates - List ResourceData (nested type)

No description provided.

SmallWallCaps - List ResourceData (nested type)

No description provided.

SmallDestroyedCaps - List ResourceData (nested type)

No description provided.

SmallWallAndDestroyedCaps - List ResourceData (nested type)

No description provided.

MediumWeakGates - List ResourceData (nested type)

No description provided.

MediumSecurityGates - List ResourceData (nested type)

No description provided.

MediumApexGates - List ResourceData (nested type)

No description provided.

MediumBulkheadGates - List ResourceData (nested type)

No description provided.

MediumMainPathBulkheadGates - List ResourceData (nested type)

No description provided.

MediumWallCaps - List ResourceData (nested type)

No description provided.

MediumDestroyedCaps - List ResourceData (nested type)

No description provided.

MediumWallAndDestroyedCaps - List ResourceData (nested type)

No description provided.

LargeWeakGates - List ResourceData (nested type)

No description provided.

LargeSecurityGates - List ResourceData (nested type)

No description provided.

LargeApexGates - List ResourceData (nested type)

No description provided.

LargeBulkheadGates - List ResourceData (nested type)

No description provided.

LargeMainPathBulkheadGates - List ResourceData (nested type)

No description provided.

LargeWallCaps - List ResourceData (nested type)

No description provided.

LargeDestroyedCaps - List ResourceData (nested type)

No description provided.

LargeWallAndDestroyedCaps - List ResourceData (nested type)

No description provided.

StraightPlugsNoGates - List ResourceData (nested type)

No description provided.

StraightPlugsWithGates - List ResourceData (nested type)

No description provided.

SingleDropPlugsNoGates - List ResourceData (nested type)

No description provided.

SingleDropPlugsWithGates - List ResourceData (nested type)

No description provided.

DoubleDropPlugsNoGates - List ResourceData (nested type)

No description provided.

DoubleDropPlugsWithGates - List ResourceData (nested type)

No description provided.

PlugCaps - List ResourceData (nested type)

No description provided.

ElevatorShafts_1x1 - List ResourceData (nested type)

No description provided.

CustomGeomorphs_Exit_1x1 - List ResourceData (nested type)

No description provided.

CustomGeomorphs_Objective_1x1 - List ResourceData (nested type)

No description provided.

CustomGeomorphs_Challenge_1x1 - List ResourceData (nested type)

No description provided.

Ladders_4m - List ResourceData (nested type)

No description provided.

Ladders_2m - List ResourceData (nested type)

No description provided.

Ladders_1m - List ResourceData (nested type)

No description provided.

Ladders_05m - List ResourceData (nested type)

No description provided.

Ladders_Bottom - List ResourceData (nested type)

No description provided.

Ladders_Top - List ResourceData (nested type)

No description provided.

EnemyGroup

GameData_EnemyGroupDataBlock_bin.json (filled)

This datablock is used for setting enemy spawns with a focus on randomization.

Spawning enemies is explained in EnemySpawningData.

EnemyGroup blocks 37 and 38 are used by birther and birther boss respectively. This can only be changed with mods.

Fields

Type - eEnemyGroupType (enum)

Type match of this group.

There are special types, explained in EnemySpawningData.

Difficulty - eEnemyRoleDifficulty (enum)

Difficulty match of this group.

SpawnPlacementType - eSpawnPlacementType (enum)

Defines where enemies are placed in the selected area.

MaxScore - Single

The score (or population points/cost) of this group.

ScoreInAreaPaddingMulti - Single

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.

RelativeWeight - Single

Relative weight of this group. Affects how likely it is to be selected when several are matched.

Roles - List EnemyGroupCompositionData (nested type)

List of roles in this group. Each role will spawn one type of enemy when this group is selected.

Creating a C# class library project

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

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>

		<!--
		Use the following property to set your preferred r2modman profile
		-->
		<Profile>Default</Profile>

		<TargetFramework>net6.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<DebugType>None</DebugType>
		<AssemblyName>$(SolutionName)</AssemblyName>
		<RootNamespace>$(SolutionName)</RootNamespace>
		<BepInEx>$(AppData)\r2modmanPlus-local\GTFO\profiles\$(Profile)\BepInEx</BepInEx>
		<BuildDirectory>$(BepInEx)\plugins\$(SolutionName)\</BuildDirectory>
		<OutputPath>$(BuildDirectory)</OutputPath>	
		<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
	</PropertyGroup>
	<Target Name="PostBuild" BeforeTargets="PostBuildEvent">
		<Delete Files="$(OutputPath)$(AssemblyName).deps.json" />
	</Target>
	<ItemGroup>
		<Reference Include="$(BepInEx)\core\0Harmony.dll" Private="false" />
		<Reference Include="$(BepInEx)\core\BepInEx.Core.dll" Private="false" />
		<Reference Include="$(BepInEx)\core\BepInEx.Unity.IL2CPP.dll" Private="false" />
		<Reference Include="$(BepInEx)\core\Il2CppInterop.Common.dll" Private="false" />
		<Reference Include="$(BepInEx)\core\Il2CppInterop.Runtime.dll" Private="false" />
		<Reference Include="$(BepInEx)\interop\*.dll" Private="false" />
		<Reference Remove="$(BepInEx)\interop\netstandard.dll" />
	</ItemGroup>
</Project>

Finally save the changes to the csproj file

You should now be ready to create your Plugin class

EnemyBalancing

GameData_EnemyBalancingDataBlock_bin.json

No description provided.


Fields

Health - HealthData (nested type)

No description provided.

GlueTolerance - Single

No description provided.

GlueFadeOutTime - Single

No description provided.

CanBePushed - Boolean

No description provided.

ForbidTwitchHit - Boolean

No description provided.

AllowDamgeBonusFromBehind - Boolean

No description provided.

UseTentacleTunnelCheck - Boolean

No description provided.

UseVisibilityRaycastDuringTentacleAttack - Boolean

No description provided.

TentacleAttackDamageRadiusIfNoTunnelCheck - Single

No description provided.

TentacleAttackDamage - Single

No description provided.

MeleeAttackDamage - Single

No description provided.

MeleeAttackDamageCheckRadius - Single

No description provided.

TagTime - Single

No description provided.

EnemyCollisionRadius - Single

No description provided.

EnemyCollisionPlayerMovementReduction - Single

No description provided.

EnemyCollisionMinimumMoveSpeedModifier - Single

No description provided.

GearCategory

GameData_GearCategoryDataBlock_bin.json (filled)

This datablock collects several different DataBlock entries and types of metadata and integrates them into a single entry that can be referenced in the PlayerOfflineGearDatablock:

  • An ItemDataBlock entry (BaseItem, defining what kind of item something is)

  • 4 ArchetypeDataBlock fire modes (for projectile weapons)

  • A Melee attack mode (also from ArchetypeDataBlock, for Melee weapons)

  • A priority list for Part alignments, used when a weapon is assembled in PlayerOfflineGearDataBlock

Fields

PublicName - LocalizedText (nested type)

In-game name, not used for weapons.

Description - LocalizedText (nested type)

Lobby description, not used by weapons.

BaseItem - UInt32 (ItemDataBlock)

PersistentID of an entry in ItemDataBlock.

HUDIcon - String

No description provided.

IconRotationOffset - Single

No description provided.

IconZoomOffset - Single

No description provided.

FPSArmPoseName - String

No description provided.

ThirdPersonFullbodyMovement - eFullbodyPlayerMovementSet (enum)

Third-person animation set this item uses.

SemiArchetype - UInt32 (ArchetypeDataBlock)

A reference to an ArchetypeDataBlock entry, 0th field in fire mode selection.

Does not determine a weapon's actual fire/usage mode, that determination is made in a weapon's PlayerOfflineGearDataBlock GearJSON.

BurstArchetype - UInt32 (ArchetypeDataBlock)

A reference to an ArchetypeDataBlock entry, 1st field in fire mode selection.

Does not determine a weapon's actual fire/usage mode, that determination is made in a weapon's PlayerOfflineGearDataBlock GearJSON.

AutoArchetype - UInt32 (ArchetypeDataBlock)

A reference to an ArchetypeDataBlock entry, 2nd field in fire mode selection.

Does not determine a weapon's actual fire/usage mode, that determination is made in a weapon's PlayerOfflineGearDataBlock GearJSON.

SemiBurstArchetype - UInt32 (ArchetypeDataBlock)

A reference to an ArchetypeDataBlock entry, 3rd field in fire mode selection.

Does not determine a weapon's actual fire/usage mode, that determination is made in a weapon's PlayerOfflineGearDataBlock GearJSON.

MeleeArchetype - UInt32 (MeleeArchetypeDataBlock)

No description provided.

PartAlignPriority - List GearPartAlignPriority (nested type)

Determines how parts with conflicting aligns behave.

name - String

Internal name string for this DataBlock entry.

Not used in-game.

EnemyBehavior

GameData_EnemyBehaviorDataBlock_bin.json

No description provided.


Fields

MeleeAttackDistance - MinMaxValue (nested type)

No description provided.

RangedAttackDistance - MinMaxValue (nested type)

No description provided.

StrafeDistance - MinMaxValue (nested type)

No description provided.

HowOftenToStrafe - MinMaxValue (nested type)

No description provided.

DistanceToStrafeWithoutLOS - MinMaxValue (nested type)

No description provided.

DistanceToStrafeInTargetsLOS - MinMaxValue (nested type)

No description provided.

DistanceToStrafeWithoutTargetsLOS - MinMaxValue (nested type)

No description provided.

MoveCloserDelayAfterAttack - MinMaxValue (nested type)

No description provided.

MoveTowardsTargetDuration - MinMaxValue (nested type)

No description provided.

ClosestTargetDistance - MinMaxValue (nested type)

No description provided.

StartWalkDuration - MinMaxValue (nested type)

No description provided.

StartWalkTimeoutUntilNext - MinMaxValue (nested type)

No description provided.

ScreamStateDelay - MinMaxValue (nested type)

No description provided.

RushTargetDistance - Single

No description provided.

ChanceToPropagateOnDetection - Single

No description provided.

ChanceToPropagateOnDeath - Single

No description provided.

ChanceToPropagateToMastermind - Single

No description provided.

PropagationDistance - Single

No description provided.

DisableTargetPreferenceWeight - Boolean

No description provided.

IgnoreBotsTargeting - Boolean

No description provided.

IsFlyer - Boolean

No description provided.

Flyer_HibernateRemap - AgentMode (enum)

No description provided.

Flyer_PathingNodeMargin - Int32

No description provided.

PathingTargetOffsetY - MinMaxValue (nested type)

No description provided.

PathingTargetDistance - MinMaxValue (nested type)

No description provided.

StrafeVerticalDistance - MinMaxValue (nested type)

No description provided.

Flyer_Acceleration - Single

No description provided.

Flyer_Deceleration - Single

No description provided.

Flyer_SpeedMultipler - Single

No description provided.

Flyer_AttackChargeDuration - MinMaxValue (nested type)

No description provided.

Flyer_AttackDuration - MinMaxValue (nested type)

No description provided.

Flyer_BackToMovementAfterAttack - MinMaxValue (nested type)

No description provided.

Flyer_LightDamageForce - Single

No description provided.

Flyer_HeavyDamageForce - Single

No description provided.

Flyer_RecoilForce - MinMaxValue (nested type)

No description provided.

Flyer_RecoilTorque - MinMaxValue (nested type)

No description provided.

Flyer_RollThreshold - Single

No description provided.

Flyer_YawThreshold - Single

No description provided.

Flyer_PitchThreshold - Single

No description provided.

Flyer_DeathDealy - MinMaxValue (nested type)

No description provided.

Flyer_BossSpawnWaveID - UInt32 (SurvivalWavePopulationDataBlock)

No description provided.

Flyer_BossEnemyID - UInt32 (EnemyDataBlock)

No description provided.

Flyer_SpawnOutOfBossStateDuration - MinMaxValue (nested type)

No description provided.

Flyer_EstimatedDistanceFromBoss - MinMaxValue (nested type)

No description provided.

EnemyPopulation

GameData_EnemyPopulationDataBlock_bin.json (filled)

This datablock is used to select enemies by EnemyGroupDataBlock through filters.

While technically you can have several population blocks, usually only the first block is used both in-game and by modders.

Spawning enemies is explained in EnemySpawningData.

Fields

RoleDatas - List EnemyRoleData (nested type)

List of enemy role entries.

Gear

GameData_GearDataBlock_bin.json

No description provided.


Fields

officialForRelease - Boolean

No description provided.

publicName - String

No description provided.

baseItemID - UInt32

No description provided.

weaponPersonalityID - UInt32

No description provided.

showAmmoInGUI - Boolean

No description provided.

archetypesAllowed - List GearArchetypeData (nested type)

No description provided.

ClassAmmoCostFactor - Single

No description provided.

PublicGearInfo - String

No description provided.

dropPeriodRange - List GearDropPeriodData (nested type)

No description provided.

structualModsAllowedGrip - List ItemPartData (nested type)

No description provided.

structualModsAllowedStock - List ItemPartData (nested type)

No description provided.

structualModsAllowedBarrel - List ItemPartData (nested type)

No description provided.

structualModsAllowedReceiver - List ItemPartData (nested type)

No description provided.

visualModsAllowed - List ItemPartData (nested type)

No description provided.

toolsAllowed - List ItemPartData (nested type)

No description provided.

sightsAllowed - List ItemPartData (nested type)

No description provided.

gripsAllowed - List ItemPartData (nested type)

No description provided.

muzzlesAllowed - List ItemPartData (nested type)

No description provided.

magazinesAllowed - List ItemPartData (nested type)

No description provided.

allowEmptyStructualModSlot - Boolean

No description provided.

allowEmptyVisualModSlot - Boolean

No description provided.

allowEmptyToolSlot - Boolean

No description provided.

allowEmptySightSlot - Boolean

No description provided.

allowEmptyGripSlot - Boolean

No description provided.

allowEmptyMuzzleSlot - Boolean

No description provided.

allowEmptyMagazineSlot - Boolean

No description provided.

EnemyDetection

GameData_EnemyDetectionDataBlock_bin.json

No description provided.


Fields

weaponDetectionDistanceMin - Single

No description provided.

weaponDetectionDistanceMax - Single

No description provided.

detectionNodeDistanceMax - Single

No description provided.

movementDetectionDistance - Single

No description provided.

detectionBuildupSpeed - Single

No description provided.

detectionCooldownSpeed - Single

No description provided.

EnemySFX

GameData_EnemySFXDataBlock_bin.json

No description provided.


Fields

SFX_ID_walk - UInt32

No description provided.

SFX_ID_run - UInt32

No description provided.

SFX_ID_climbLadder - UInt32

No description provided.

SFX_ID_heartbeatPulse - UInt32

No description provided.

SFX_ID_hibernateIdle - UInt32

No description provided.

hibernateIdleInterval - Vector2

No description provided.

SFX_ID_hibernateWakeUp - UInt32

No description provided.

SFX_ID_hibernateDie - UInt32

No description provided.

SFX_ID_hibernateDetectionStart - UInt32

No description provided.

SFX_ID_scream - UInt32

No description provided.

SFX_ID_stuckInGlue - UInt32

No description provided.

SFX_ID_releaseFromGlue - UInt32

No description provided.

SFX_ID_attackWindUp - UInt32

No description provided.

SFX_ID_attackWindUp_NotLocalTarget - UInt32

No description provided.

SFX_ID_attackFire - UInt32

No description provided.

SFX_ID_hurtSmall - UInt32

No description provided.

SFX_ID_hurtBig - UInt32

No description provided.

SFX_ID_die - UInt32

No description provided.

SFX_ID_JumpStart - UInt32

No description provided.

SFX_ID_JumpInAir - UInt32

No description provided.

SFX_ID_JumpLand - UInt32

No description provided.

SFX_ID_BigTentacleStart - UInt32

No description provided.

SFX_ID_BigTentacleTipLoop - UInt32

No description provided.

SFX_ID_BigTentacleEnd - UInt32

No description provided.

ExpeditionBalance

GameData_ExpeditionBalanceDataBlock_bin.json

No description provided.


Fields

HealthPerZone - Single

No description provided.

DisinfectionPerZone - Single

No description provided.

WeaponAmmoPerZone - Single

No description provided.

ToolAmmoPerZone - Single

No description provided.

CommodityValuePerZone - Single

No description provided.

ChanceToPutCommodityInResourceContainer - Single

No description provided.

ChanceToPutArtifactInResourceContainer - Single

No description provided.

ChanceToSpawnCommodityLargePack - Single

No description provided.

ChanceToSpawnCommodityMediumPack - Single

No description provided.

ChanceToReUseResourceContainer - Single

No description provided.

MaxPacksPerResourceContainer - Int32

No description provided.

EmptyWeakResourceContainersPerZone - Single

No description provided.

EmptySecureResourceContainersPerZone - Single

No description provided.

LootPerZone - Single

No description provided.

AirPerZone - Single

No description provided.

AirPerZoneInNoAir - Single

No description provided.

TerminalsPerZone - Single

No description provided.

TentacleTraps - (nested type)

No description provided.

ParasiteNests - (nested type)

No description provided.

EnemyPatrolGroupsPerZone - Int32

No description provided.

StaticEnemiesMaxPerZone - Int32

No description provided.

StaticEnemiesMaxSmallArea - Int32

No description provided.

StaticEnemiesMaxMediumArea - Int32

No description provided.

StaticEnemiesMaxLargeArea - Int32

No description provided.

StaticEnemiesMaxHugeArea - Int32

No description provided.

EnemyPopulationPerZone - Single

No description provided.

VoxelCoverageAreaMultiplier - Single

No description provided.

VoxelCoverageAreaScoringRandomMultiplier - Single

No description provided.

ArtifactsPerSegment - Int32

No description provided.

ArtifactsPerLayer - Int32

No description provided.

WeakDoor4x4Health - Single

No description provided.

WeakDoor8x4Health - Single

No description provided.

WeakDoorChanceLockWeightNoLock - Single

No description provided.

WeakDoorChanceLockWeightMeleeLock - Single

No description provided.

WeakDoorChanceLockWeightHackableLock - Single

No description provided.

WeakDoorUnlockedChanceForOpen - Single

No description provided.

WeakDoorOpenChanceForWallRemoverUsed - Single

No description provided.

WeakDoorLockHealth - Single

No description provided.

WeakResourceContainerWithPackChanceForLocked - Single

No description provided.

ResourcePackSizes - List Single

No description provided.

GlueVolumeToDoorHealthConversion - Single

No description provided.

GlueVolumeForDoorGlueMaxState - Single

No description provided.

Item

GameData_ItemDataBlock_bin.json

No description provided.


Fields

publicName - String

No description provided.

LocalizedName - (nested type)

No description provided.

terminalItemShortName - String

No description provided.

terminalItemLongName - String

No description provided.

addSerialNumberToName - Boolean

No description provided.

registerInTerminalSystem - Boolean

No description provided.

DimensionWarpType - (enum)

No description provided.

Shard - (enum)

No description provided.

inventorySlot - (enum)

No description provided.

FPSSettings - UInt32 ()

No description provided.

crosshair - (enum)

No description provided.

HUDIcon - String

No description provided.

ShowCrosshairWhenAiming - Boolean

No description provided.

GUIShowAmmoClip - Boolean

No description provided.

GUIShowAmmoPack - Boolean

No description provided.

GUIShowAmmoTotalRel - Boolean

No description provided.

GUIShowAmmoInfinite - Boolean

No description provided.

canMoveQuick - Boolean

No description provided.

BlockToolAmmoRefill - Boolean

No description provided.

ClassAmmoCostFactor - Single

No description provided.

ConsumableAmmoMin - Int32

No description provided.

ConsumableAmmoMax - Int32

No description provided.

audioEventEquip - String

No description provided.

FirstPersonPrefabs - List String

No description provided.

ThirdPersonPrefabs - List String

No description provided.

PickupPrefabs - List String

No description provided.

InstancePrefabs - List String

No description provided.

EquipTransitionTime - Single

No description provided.

AimTransitionTime - Single

No description provided.

FPSArmsAnim - String

No description provided.

ThirdPersonFullbodyMovementSet - (enum)

No description provided.

LeftHandGripAlign - String

No description provided.

LeftHandGripAnim - String

No description provided.

RightHandGripAlign - String

No description provided.

RightHandGripAnim - String

No description provided.

SightLookAlign - String

No description provided.

MuzzleAlign - String

No description provided.

BackpackAlign - String

No description provided.

Enemy

GameData_EnemyDataBlock_bin.json

No description provided.


Fields

EnemyType - (enum)

No description provided.

AssetBundle - (enum)

No description provided.

BundleShard - (enum)

No description provided.

BasePrefabs - List String

No description provided.

ModelDatas - (nested type)

No description provided.

DetectionDataId - UInt32 ()

No description provided.

BehaviorDataId - UInt32 ()

No description provided.

MovementDataId - UInt32 ()

No description provided.

BalancingDataId - UInt32 ()

No description provided.

SFXDataId - UInt32 ()

No description provided.

ArenaDimensions - List UInt32 ()

No description provided.

LinkedSlaveModels - (nested type)

No description provided.

InternalMaterial - (enum)

No description provided.

isCoccoon - Boolean

No description provided.

EnemySpottedDialogId - UInt32 ()

No description provided.

AI_Abilities - (nested type)

No description provided.

ItemFPSSettings

GameData_ItemFPSSettingsDataBlock_bin.json (filled)

Determines Item location and camera settings for the first-person view.

Note: GTFO uses 2 separate cameras in the First-Person view - the Look Camera renders the world, and the Item Camera renders the item held in the player's hands. These two cameras usually have differing FOVs in both the aimed and unaimed state of a weapon/tool.


Fields

localPosHip - Vector3

Position of weapon when hipfiring

localRotHip - Vector3

Rotation of weapon when hipfiring

SwayAmount - Single

No description provided.

crouchTiltAngle - Single

Angle the weapon tilts when player is crouched

localPosRelaxed - Vector3

Position of weapon when idle animation is playing

localRotRelaxed - Vector3

Rotation of weapon when idle animation is playing

localPosZoom - Vector3

Position of weapon when aiming

localRotZoom - Vector3

Rotation of weapon when aiming

bodyOffsetLocal - Vector3

Root position of weapon

bodyRotationOffsetLocal - Vector3

Root rotation of weapon

ItemCameraFOVDefault - Int32

FOV of camera that renders the weapon or tool

ItemCameraFOVZoom - Int32

FOV of camera that renders the weapon or tool, when aiming

LookCameraFOVZoom - Int32

FOV of camera that renders the world

canAim - Boolean

Can this weapon be aimed?

onlyStartAimOnPressed - Boolean

Identical across all base game Items (set to false), probably unused

canRelax - Boolean

Can this weapon play an idle animation?

customDelayUntilRelax - Single

Time until this weapon plays an idle animation

allowRotToAimPos - Boolean

Is the weapon allowed to rotate when moving to the aimed position?

rotToAimPosMinDis - Single

Identical across all base game Items (set to 1.0), probably unused

transitionToAim - (enum)

Speed of aim transition animation

RecoilAnimation - UInt32 ()

Animation used when the weapon is recoiling

IdleAnimation - UInt32 ()

Animation used when the player is stationary

WalkAnimation - UInt32 ()

Animation used when the player is walking

RunAnimation - UInt32 ()

Animation used when the player is sprinting

JumpAnimation - UInt32 ()

Animation used when the player is jumping

LandAnimation - UInt32 ()

Animation used when the player lands from a jump

ChargeCancelAnimation - UInt32 ()

Animation used when the weapon is charged and the charge is aborted before completion

DofDefault - (nested type)

Default Depth of Field settings

DofAim - (nested type)

Depth of Field settings when weapon is aiming

EnemyMovement

GameData_EnemyMovementDataBlock_bin.json

No description provided.


Fields

ForceDisableAnimator - Boolean

No description provided.

AnimationControllers - (enum)

No description provided.

MoveSlowerWhenInPlayerRoom - Boolean

No description provided.

MoveSlowerWithinTargetDistance - Single

No description provided.

GlobalAnimSpeedMulti - Single

No description provided.

PathMoveAnimSpeedMulti - Single

No description provided.

RotationLerp - Single

No description provided.

RotationLerpWhenNotMoving - Single

No description provided.

BlendIntoAttackAnim - Single

No description provided.

BlendIntoScreamAnim - Single

No description provided.

AllowMovmentBlendOutOfPathMove - Boolean

No description provided.

PathMoveAnimDampTime - Single

No description provided.

AllowClimbDownLadders - Boolean

No description provided.

LocomotionPathMove - (enum)

No description provided.

LocomotionHitReact - (enum)

No description provided.

LocomotionDead - (enum)

No description provided.

LocomotionShooterAttack - (enum)

No description provided.

LocomotionScream - (enum)

No description provided.

FogSettings

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.

Fields

Enabled - Boolean

Unused.

FogColor - Color

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:

FogDensity - Single

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

FogAmbience - Single

Unused.

DensityNoiseDirection - Vector3

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.

DensityNoiseSpeed - Single

Controls how fast noise particles float / how uneven the tide pattern of the fog plane is.

DensityNoiseScale - Single

Seems to be the size of the noise particles. Higher values can make them look visibly separated, kind of like dust clouds.

DensityHeightAltitude - Single

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

DensityHeightRange - Single

Distance above lowest point for fog height, used in calculating the highest point for fog height.

DensityHeightMaxBoost - Single

This is the actual field for controlling the density of (non-inversed) fog. The larger, the more occluding.

Infection - Single

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

StaticEnemyData
StaticEnemyData
LocalizedText
ItemWarpType
AssetBundleShard
InventorySlot
ItemFPSSettingsDataBlock
Crosshair
eFullbodyPlayerMovementSet
eEnemyType
AssetBundleName
AssetBundleShard
List ModelData
EnemyDetectionDataBlock
EnemyBehaviorDataBlock
EnemyMovementDataBlock
EnemyBalancingDataBlock
EnemySFXDataBlock
DimensionDataBlock
List LinkedSlaveModelData
MaterialType
PlayerDialogDataBlock
List AbilityData
eFPISTransitionTime
ItemMovementAnimationDataBlock
ItemMovementAnimationDataBlock
ItemMovementAnimationDataBlock
ItemMovementAnimationDataBlock
ItemMovementAnimationDataBlock
ItemMovementAnimationDataBlock
ItemMovementAnimationDataBlock
DOFSettingsData
DOFSettingsData
AnimatorControllerHandleName
ES_StateEnum
ES_StateEnum
ES_StateEnum
ES_StateEnum
ES_StateEnum
Extremely dense red fog, looking from above fog
Extremely dense red fog, looking from below fog

FlashlightSettings

GameData_FlashlightSettingsDataBlock_bin.json

No description provided.


Fields

range - Single

No description provided.

angle - Single

No description provided.

intensity - Single

No description provided.

cookie - String

No description provided.

color - Color

No description provided.

startupShard - AssetBundleShard (enum)

No description provided.

MeleeAnimationSet

GameData_MeleeAnimationSetDataBlock_bin.json

Defines melee weapon animation sets alongside several timings related to them.

This datablock is referenced by a MeleeArchetypeDataBlock.


Fields

HoldToChargeTime - Single

Time in seconds that attack must be held for the weapon to begin charging.

MaxDamageChargeTime - Single

Time in seconds to reach full charge.

AutoAttackTime - Single

Time in seconds after starting a charge at which the weapon will automatically swing.

AutoAttackWarningTime - Single

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).

FPIdleAnim - AnimHash (nested type)

No description provided.

FPSettleAnim - AnimHash (nested type)

No description provided.

FPWalkAnim - AnimHash (nested type)

No description provided.

FPRunAnim - AnimHash (nested type)

No description provided.

FPJumpAnim - AnimHash (nested type)

No description provided.

FPLandAnim - AnimHash (nested type)

No description provided.

FPChargeCancelAnimRight - AnimHash (nested type)

No description provided.

FPChargeCancelAnimLeft - AnimHash (nested type)

No description provided.

FPAttackMissRight - MeleeAttackData (nested type)

Animation for the light attack that swings from the right (e.g. the first swing).

FPAttackMissLeft - MeleeAttackData (nested type)

Animation for the light attack that swings from the left (e.g. the second swing).

FPAttackHitRight - MeleeAttackData (nested type)

Animation that plays after hitting a light attack from the right.

FPAttackHitLeft - MeleeAttackData (nested type)

Animation that plays after hitting a light attack from the left.

FPAttackPush - MeleeAttackData (nested type)

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.

FPAttackChargeUpRight - MeleeAttackData (nested type)

Animation for charging an attack from the right.

FPAttackChargeUpLeft - MeleeAttackData (nested type)

Animation for charging an attack from the left.

FPAttackChargeUpReleaseRight - MeleeAttackData (nested type)

Animation for the charge attack that swings from the right.

FPAttackChargeUpReleaseLeft - MeleeAttackData (nested type)

Animation for the charge attack that swings from the left.

FPAttackChargeUpHitRight - MeleeAttackData (nested type)

Animation that plays after hitting a charge attack from the right.

FPAttackChargeUpHitLeft - MeleeAttackData (nested type)

Animation that plays after hitting a charge attack from the left.

TPAnimHashIdle - AnimHash (nested type)

No description provided.

TPAnimHashIdleCrouch - AnimHash (nested type)

No description provided.

TPAnimHashAttackLeft - AnimHash (nested type)

No description provided.

TPAnimHashAttackLeftCrouch - AnimHash (nested type)

No description provided.

TPAnimHashAttackLeftCharge - AnimHash (nested type)

No description provided.

TPAnimHashAttackLeftChargeCrouch - AnimHash (nested type)

No description provided.

TPAnimHashAttackLeftRelease - AnimHash (nested type)

No description provided.

TPAnimHashAttackLeftReleaseCrouch - AnimHash (nested type)

No description provided.

LightSettings

GameData_LightSettingsDataBlock_bin.json

No description provided.


Fields

LightCategorySettings - List LightCategorySetting (nested type)

No description provided.

Player

GameData_PlayerDataBlock_bin.json (filled)

Defines player stats.

While this is a datablock, currently its only reference is hardcoded ID 1.

Fields

health - Single

The max health of a player.

healthRegenStartDelayAfterDamage - Single

How long it takes for health to start regenerating after taking damage.

healthRegenRelMax - Single

Relative max hp to regenerate.

E.g. 0.5 from 100 base would be 50.

healthRegenDelay - Single

Delay between health regeneration updates.

healthRegenPerSecond - Single

How much health is regenerated per second.

friendlyFireMulti - Single

Friendly fire damage multiplier.

Currently broken for tools.

fallDamageMinHeight - Single

Minimum height a player has to fall to take fall damage.

fallDamageMaxHeight - Single

Falling height where a player takes maximum fall damage.

fallDamageMin - Single

Minimum fall damage a player can take.

fallDamageMax - Single

Maximum fall damage a player can take.

Anywhere between min and max, fall damage scales linearly.

nanoswarmShieldDamageMultiplierCurve - AnimationCurve

Unknown, most likely related to the unreleased nanoswarm tool and likely to change.

nanoswarmShieldResistanceCurve - AnimationCurve

Unknown, most likely related to the unreleased nanoswarm tool and likely to change.

nanoswarmNegativeResistanceCurve - AnimationCurve

Unknown, most likely related to the unreleased nanoswarm tool and likely to change.

battery - Int32

Seems unused.

smallBatteryConsumtionPerSec - Single

Seems unused.

mediumBatteryConsumtionPerSec - Single

Seems unused.

largeBatteryConsumtionPerSec - Single

Seems unused.

walkMoveSpeed - Single

Player move speed while walking.

runMoveSpeed - Single

Player move speed when running/sprinting.

airMoveSpeed - Single

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.

crouchMoveSpeed - Single

Player move speed while crouching.

ladderMoveSpeed - Single

Player move speed when going up and down ladders.

walkFootstepLength - Single

Only seems to have an impact on when player footstep audio is played.

runFootstepLength - Single

Only seems to have an impact on when player footstep audio is played.

crouchFootstepLength - Single

Only seems to have an impact on when player footstep audio is played.

throttleSmoothAcc - Single

Multiplier for smooth acceleration. At low values players will have trouble speeding up.

throttleSmoothStop - Single

Multiplier for smooth stopping. At low values players will have trouble stopping in place (can still reverse direction normally depending on acc value).

throttleSmoothVertical - Single

Seems to have no effect.

jumpVelInitial - Single

Initial vertical velocity gained from a jump.

jumpGravityMulDefault - Single

Gravity mults affect how fast falling accelerates (rising decelerates).

Gravity mult when rising and holding the jump button.

jumpGravityMulButtonReleased - Single

Gravity mult when rising and not holding the jump button.

jumpGravityMulAfterPeak - Single

Gravity mult after jump peak.

jumpGravityMulFalling - Single

Gravity mult when falling without jumping.

jumpVerticalVelocityMax - Single

Maximum vertical movement speed.

camPosDefault - Vector3

Default player camera position offset from player's feet.

camPosCrouch - Vector3

Player camera position offset while crouched.

camFovRunDif - Single

Added fov while player is running.

DofDefault - DOFSettingsData (nested type)

Default depth of field settings.

DofInElevator - DOFSettingsData (nested type)

Depth of field settings in elevator.

DofInTerminal - DOFSettingsData (nested type)

Depth of field settings in terminal.

ChromaticAbberationIntensityMax - Single

Seems unused.

mouselookAimScaleMinMax - Vector2

Mouselook settings are probably something you shouldn't mess with.

Seems to affect aim sensitivity when in sights.

mouselookAimScaleFovRef - Vector2

Seems to affect aim sensitivity when in sights.

FPSArmsOffset - Vector3

FPS settings are probably something you shouldn't mess with.

FPS model setting.

FPSBodyOffset - Vector3

FPS model setting.

AdditionalFPSBodyOffsetWhenRunning - Vector3

FPS model setting.

FPSBodyOffsetWhenNotFollowingCamera - Vector3

FPS model setting.

FPSBodyScale - Vector3

FPS model setting.

FPSBodyMoveNeckToFollowCamera - Boolean

FPS model setting.

FPSBodyWantedDisToNeck - Single

FPS model setting.

GearMaxSightHeightDiff - Single

Affects maximum possible height above default of sights.

AmmoStandardInitial - Int32

Ammo settings are closely tied with Archetype cost of bullet settings and explained there.

These settings are not literal bullet counts but rather a base value for calculations.

Initial standard weapon ammo when starting the level.

AmmoStandardInitialOnDropin - Int32

Seems unused.

AmmoStandardMaxCap - Int32

Max reserve ammo for standard weapons.

AmmoStandardResourcePackMaxCap - Int32

Affects how much ammo you get from a refill pack.

AmmoSpecialInitial - Int32

Initial special weapon ammo when starting the level.

AmmoSpecialInitialOnDropin - Int32

Seems unused.

AmmoSpecialMaxCap - Int32

Max reserve ammo for special weapons.

AmmoSpecialResourcePackMaxCap - Int32

Affects how much ammo you get from a refill pack.

AmmoClassInitial - Int32

Initial tool ammo.

AmmoClassInitialOnDropin - Int32

Seems unused.

AmmoClassMaxCap - Int32

Max tool ammo.

AmmoClassResourcePackMaxCap - Int32

Affects how much ammo you get from a refill pack.

There is an additional multiplier for sentries.

implantSmallTriggerDelay - Single

Seems unused.

implantBigTriggerDelay - Single

Seems unused.

itemAnimWeight - Single

The following item settings seem to be either unused or have no impact worth editing for modders, therefore they have no descriptions.

itemAnimAxisWeight - Vector3

No description provided.

itemLookatAimWeight - Single

No description provided.

itemLookSwayWeight - Single

No description provided.

itemLookSwayPosImpulseScale - Vector3

No description provided.

itemLookSwayPosStiffness - Single

No description provided.

itemLookSwayPosDamping - Single

No description provided.

itemLookSwayRotImpulseScale - Vector3

No description provided.

itemLookSwayRotXMulti - Vector2

No description provided.

itemLookSwayRotStiffness - Single

No description provided.

itemLookSwayRotDamping - Single

No description provided.

itemLookSwayWeightAiming - Single

No description provided.

itemLookSwayPosImpulseScaleAiming - Vector3

No description provided.

itemLookSwayRotImpulseScaleAiming - Vector3

No description provided.

itemAnimWeightAiming - Single

No description provided.

itemRecoilWeight - Single

No description provided.

itemFootstepWeight - Single

No description provided.

itemFootstepWeightAiming - Single

No description provided.

itemFootstepDelay - Single

No description provided.

itemFootstepImpulseScale - Vector3

No description provided.

itemFootstepStiffness - Single

No description provided.

itemFootstepDamping - Single

No description provided.

breathingEnabled - Boolean

Determines whether audible breathing from stamina and infection is enabled.

breathingStaminaEnabled - Boolean

Determines whether audible breathing from stamina is enabled.

breathingScaredEnabled - Boolean

Seems unused or unknown.

breathingDebugEnabled - Boolean

When enabled, prints some additional debug logs regarding breathing.

breathingVolume - Single

Base audible breathing sound volume.

breathingHealthLowLimit - Single

Health limit for switching to low health breathing sound.

StaminaTimeBeforeResting - Single

Time without spending stamina before player starts resting.

StaminaRegenRestingInCombat - Single

Stamina regeneration rate when resting in combat.

StaminaRegenNotRestingInCombat - Single

Stamina regeneration rate when not resting in combat.

StaminaRegenRestingOutOfCombat - Single

Stamina regeneration rate when resting out of combat.

StaminaRegenNotRestingOutOfCombat - Single

Stamina regeneration rate when not resting out of combat.

StaminaEnableAffectMoveSpeed - Boolean

Whether stamina can affect movement speed.

StaminaHighMovespeedModifier - Single

Movement speed multiplier when stamina is high.

StaminaLowMovespeedModifier - Single

Movement speed multiplier when stamina is low.

StaminaMoveSpeedCurveExponent - Single

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.

StaminaEnableAffectMeleeSpeed - Boolean

Whether stamina should affect melee charge speed.

StaminaHighMeleeSpeedModifier - Single

Melee charge speed multiplier when stamina is high.

StaminaLowMeleeSpeedModifier - Single

Melee charge speed multiplier when stamina is low.

StaminaMeleeSpeedCurveExponent - Single

StaminaMoveSpeedCurveExponent equivalent for melee charge speed.

StaminaExhaustedAudioThreshold - Single

Sfx Audio threshold for stamina exhausted loop.

StaminaWasTiredAudioThreshold - Single

Threshold for setting an audio switch for playing was tired breathing audio later.

StaminaRestedAudioThreshold - Single

Stamina threshold for playing rested audio after player was tired.

StaminaUsageAffectedByDrama - Boolean

Effectively decides whether InCombat values will be used during combat.

StaminaMinimumCapWhenNotInCombat - Single

Minimum allowed stamina when not in combat.

StaminaMaximumCapWhenInCombat - Single

Maximum allowed stamina when in combat.

StaminaMaximumCapWhenInCombatFallRate - Single

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.

StaminaJumpCost - ActionCost

Jump stamina action cost.

StaminaSneakCost - ActionCost

Crouching movement stamina action cost.

StaminaWalkCost - ActionCost

Walking stamina action cost.

StaminaRunCost - ActionCost

Running/sprinting stamina action cost.

StaminaCrouchEnterCost - ActionCost

Crouch enter stamina action cost.

defaultDialogLineDelay - Single

Delay between separate dialog lines.

Dialog line fields seem mostly related to PlayerDialogDataBlock.

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.

shortDialogCooldown - Single

How long a short dialog is blocked after playing.

mediumDialogCooldown - Single

How long a medium dialog is blocked after playing.

longDialogCooldown - Single

How long a long dialog is blocked after playing.

radioEnabledDefaultDistance - Single

No description provided.

radioQualityLowestAtDistance - Single

No description provided.

lowTensionMaxLimit - Single

No description provided.

mediumTensionMaxLimit - Single

No description provided.

dialogEnabledInSpectator - Boolean

No description provided.

radioQualityInSpectator - Single

No description provided.

radioDistortionInSpectator - Single

No description provided.

noAirTimeToEmpty - Single

No description provided.

noAirDamageRel - Single

No description provided.

noAirDamageDelay - Single

No description provided.

Regarding ActionCost

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.

baseStaminaCostInCombat - Single

Stamina cost of this action in combat.

baseStaminaCostOutOfCombat - Single

Stamina cost of this action out of combat.

resetRestingTimerInCombat - Boolean

Whether this action should reset resting timer in combat.

resetRestingTimerOutOfCombat - Boolean

Whether this action should reset resting timer out of combat.

PlayerOfflineGear

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.

Fields

Type - eOfflineGearType (enum)

The only known use and difference here is "RundownSpecificInventory" which simply changes the text displayed in lobby.

GearJSON - String

This innocent little string here contains an absurd amount of information in an even more absurd format (it's JSON embedded into JSON). It's most likely this way because it's sent over the network to all other players, so the devs didn't make it more readable in a DataBlock.

This JSON and its meaning (including various related DataBlocks) is covered in the Creating Custom Weapons guide.

MeleeArchetype

GameData_MeleeArchetypeDataBlock_bin.json

Defines melee weapon stats and metadata.

This datablock is not referenced directly, but instead specified by the GearCategoryDataBlock given to a melee weapon.


Fields

PublicName - String

Datablock name. Unused elsewhere.

NoiseLevel - DamageNoiseLevel (enum)

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).

ChargedAttackDamage - Single

Damage dealt by fully charged attacks. Partial charge interpolates the damage from light to fully charged by the charge progress cubed.

LightAttackDamage - Single

Damage dealt by light attacks.

LightStaggerMulti - Single

Stagger damage multiplier for light attacks.

ChargedStaggerMulti - Single

Stagger damage multiplier for fully charged attacks. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.

LightPrecisionMulti - Single

Precision damage multiplier for light attacks.

ChargedPrecisionMulti - Single

Precision damage multiplier for fully charged attacks. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.

LightEnvironmentMulti - Single

Environment damage multiplier for light attacks (i.e. vs. locks and doors).

ChargedEnvironmentMulti - Single

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.

LightBackstabberMulti - Single

Backstab damage multiplier for light attacks. Stacks multiplicatively with the default backstab bonus. Does not apply to enemies without the default backstab bonus.

ChargedBackstabberMulti - Single

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.

LightSleeperMulti - Single

Damage multiplier to sleeping enemies for light attacks. Does not apply to Scouts.

ChargedSleeperMulti - Single

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.

SkipLimbDestruction - Boolean

Prevents the weapon from breaking enemy limbs.

CameraDamageRayLength - Single

Distance in meters that the weapon hits what is directly in view. Does not affect the model hitbox.

AttackSphereRadius - Single

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.

PushDamageSphereRadius - Single

Distance in meters around the player that pushes can affect targets in.

CanHitMultipleEnemies - Boolean

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.

EvaluateHoldBeforeAttack - Boolean

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.

PlayImpactEffect - Boolean

Should cause the impact particle effect when hitting terrain.

AllowRunningWhenCharging - Boolean

Allows the user to sprint while charging. If false, will stop and prevent sprinting while charging.

PlayerRunSpeedMultiWhileCharging - Single

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.

MeleeAnimationSet - UInt32 (MeleeAnimationSetDataBlock)

Melee animation set ID of this weapon.

MeleeSFXSet - UInt32 (MeleeSFXDataBlock)

Melee SFX set ID of this weapon.

ChargedAttackStaminaCost - ActionCost

Stamina cost for fully charged attacks. Partial charge interpolates the multiplier from light to fully charged by the charge progress cubed.

LightAttackStaminaCost - ActionCost

Stamina cost for light attacks.

PushStaminaCost - ActionCost

Stamina cost for pushes.

SurvivalWaveSettings

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).

Fields

PersistentID value range is changed from default to 1-255

All time-related settings are specified in seconds.

m_pauseBeforeStart - Single

Delay before waves start spawning after alarm start.

m_pauseBetweenGroups - Single

Delay between enemy groups.

m_wavePauseMin_atCost - Single

Minimum score boundary for pauses between waves.

m_wavePauseMax_atCost - Single

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.

m_wavePauseMin - Single

Delay between waves at or below minimum score boundary.

m_wavePauseMax - Single

Delay between waves at maximum score boundary.

m_populationFilter - List eEnemyType (enum)

List of enemy types in filter.

m_filterType - eEnemyFilterType (enum)

Whether to spawn only, or spawn all but the types included in population filter.

m_chanceToRandomizeSpawnDirectionPerWave - Single

Chance for spawn direction to change between waves.

m_chanceToRandomizeSpawnDirectionPerGroup - Single

Change for spawn direction to change between groups.

m_overrideWaveSpawnType - Boolean

Whether to override spawn type set in code.

m_survivalWaveSpawnType - SurvivalWaveSpawnType (enum)

The spawn type when override is set to true.

m_populationPointsTotal - Single

The total population points for waves. The alarm automatically stops if this runs out. -1 is infinite.

m_populationPointsPerWaveStart - Single

Population points for a wave at start ramp.

m_populationPointsPerWaveEnd - Single

Population points for a wave at end ramp.

m_populationPointsMinPerGroup - Single

Minimum required cost for a group to spawn. This setting is related to the soft cap of enemies.

m_populationPointsPerGroupStart - Single

Population points for a group at start ramp.

m_populationPointsPerGroupEnd - Single

Population points for a group at end ramp.

m_populationRampOverTime - Single

Lerp over time for start-end population point settings.

Regarding population points and soft cap

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.

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 EnemyDataBlock.

The enemy type for wave population point cost is determined by wave settings.

LevelLayout

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.

Fields

ZoneAliasStart - Int32

The number of the first zone as seen in-game. The rest are derived from this and LocalIndex.

Zones - List ExpeditionZoneData (nested type)

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).

Recoil

GameData_RecoilDataBlock_bin.json

No description provided.


Fields

power - MinMaxValue (nested type)

No description provided.

spring - Single

No description provided.

dampening - Single

No description provided.

hipFireCrosshairSizeDefault - Single

No description provided.

hipFireCrosshairRecoilPop - Single

No description provided.

hipFireCrosshairSizeMax - Single

No description provided.

horizontalScale - MinMaxValue (nested type)

No description provided.

verticalScale - MinMaxValue (nested type)

No description provided.

directionalSimilarity - Single

No description provided.

worldToViewSpaceBlendHorizontal - Single

No description provided.

worldToViewSpaceBlendVertical - Single

No description provided.

recoilPosImpulse - Vector3

No description provided.

recoilPosShift - Vector3

No description provided.

recoilPosShiftWeight - Single

No description provided.

recoilPosStiffness - Single

No description provided.

recoilPosDamping - Single

No description provided.

recoilPosImpulseWeight - Single

No description provided.

recoilCameraPosWeight - Single

No description provided.

recoilAimingWeight - Single

No description provided.

recoilRotImpulse - Vector3

No description provided.

recoilRotStiffness - Single

No description provided.

recoilRotDamping - Single

No description provided.

recoilRotImpulseWeight - Single

No description provided.

recoilCameraRotWeight - Single

No description provided.

concussionIntensity - Single

No description provided.

concussionFrequency - Single

No description provided.

concussionDuration - Single

No description provided.

Artifact

GameData_ArtifactDataBlock_bin.json

No description provided.


Fields

PublicName - String

No description provided.

ArtifactCategory - ArtifactCategory (enum)

No description provided.

PrefabPath - String

No description provided.

PrefabPaths - List String

No description provided.

TagsID - UInt32 (ArtifactTagDataBlock)

No description provided.

SurvivalWavePopulation

GameData_SurvivalWavePopulationDataBlock_bin.json (filled)

This block is used for assigning eEnemyType types from SurvivalWaveSettingsDataBlock to actual enemies using PersistentID from EnemyDataBlock.

Fields

WaveRoleWeakling - UInt32 (EnemyDataBlock)

EnemyDataBlock PersistentID of eEnemyType Weakling for this population.

WaveRoleStandard - UInt32 (EnemyDataBlock)

EnemyDataBlock PersistentID of eEnemyType Standard for this population.

WaveRoleSpecial - UInt32 (EnemyDataBlock)

EnemyDataBlock PersistentID of eEnemyType Special for this population.

WaveRoleMiniBoss - UInt32 (EnemyDataBlock)

EnemyDataBlock PersistentID of eEnemyType MiniBoss for this population.

WaveRoleBoss - UInt32 (EnemyDataBlock)

EnemyDataBlock PersistentID of eEnemyType Boss for this population.

Note that Boss is currently broken in base game.

VanityItemsTemplate

GameData_VanityItemsTemplateDataBlock_bin.json

No description provided.


Fields

publicName - String

No description provided.

type - ClothesType (enum)

No description provided.

prefab - String

No description provided.

DropWeight - Single

No description provided.

icon - String

No description provided.

Atmosphere

GameData_AtmosphereDataBlock_bin.json

No description provided.


Fields

LightColor - Color

No description provided.

PlanetAlbedo - Color

No description provided.

Rarely Edited

List of datablocks that modders rarely edit

ArtifactDistribution

GameData_ArtifactDistributionDataBlock_bin.json

No description provided.


Fields

BasicWeight - Single

No description provided.

AdvancedWeight - Single

No description provided.

SpecializedWeight - Single

No description provided.

BoosterImplantCondition

GameData_BoosterImplantConditionDataBlock_bin.json

No description provided.


Fields

Condition - (enum)

No description provided.

PublicShortName - (nested type)

No description provided.

PublicName - (nested type)

No description provided.

Description - (nested type)

No description provided.

IconPath - String

No description provided.

Rundown

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.

Fields

NeverShowRundownTree - Boolean

No description provided.

UseTierUnlockRequirements - Boolean

Whether or not to use unlock requirements for this rundown.

ReqToReachTierB - (nested type)

Progression data to reach tier B of the rundown.

ReqToReachTierC - (nested type)

Progression data to reach tier C of the rundown.

ReqToReachTierD - (nested type)

Progression data to reach tier D of the rundown.

ReqToReachTierE - (nested type)

Progression data to reach tier E of the rundown.

StorytellingData - (nested type)

Data for title, visuals, and description of the rundown.

VanityItemLayerDropDataBlock - UInt32 ()

The id for the vanity item datablock for this rundown.

TierA - (nested type)

Expedition data for all levels in tier A of this rundown.

TierB - (nested type)

Expedition data for all levels in tier B of this rundown.

TierC - (nested type)

Expedition data for all levels in tier C of this rundown.

TierD - (nested type)

Expedition data for all levels in tier D of this rundown.

TierE - (nested type)

Expedition data for all levels in tier E of this rundown.

Text

GameData_TextDataBlock_bin.json

No description provided.


Fields

SkipLocalization - Boolean

No description provided.

MachineTranslation - Boolean

No description provided.

English - String

No description provided.

Description - String

No description provided.

CharacterMetaData - UInt32 ()

No description provided.

French - (nested type)

No description provided.

Italian - (nested type)

No description provided.

German - (nested type)

No description provided.

Spanish - (nested type)

No description provided.

Russian - (nested type)

No description provided.

Portuguese_Brazil - (nested type)

No description provided.

Polish - (nested type)

No description provided.

Japanese - (nested type)

No description provided.

Korean - (nested type)

No description provided.

Chinese_Traditional - (nested type)

No description provided.

Chinese_Simplified - (nested type)

No description provided.

ExportVersion - Int32

No description provided.

ImportVersion - Int32

No description provided.

BoosterCondition
LocalizedText
LocalizedText
LocalizedText
GameSetup
RundownTierProgressionData
RundownTierProgressionData
RundownTierProgressionData
RundownTierProgressionData
RundownStorytellingData
VanityItemsLayerDropsDataBlock
List ExpeditionInTierData
List ExpeditionInTierData
List ExpeditionInTierData
List ExpeditionInTierData
List ExpeditionInTierData
TextCharacterMetaDataBlock
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData
LanguageData

ArtifactTag

GameData_ArtifactTagDataBlock_bin.json

No description provided.


Fields

Tags - List ArtifactTags (enum)

No description provided.

EnvironmentFeedback

GameData_EnvironmentFeedbackDataBlock_bin.json

No description provided.


Fields

audioData - List FeedbackAudioCompData (nested type)

No description provided.

effectData - List FeedbackEffectCompData (nested type)

No description provided.

Weapon

GameData_WeaponDataBlock_bin.json

No description provided.


Fields

muzzleFlashFPS - String

No description provided.

muzzleFlash3RD - String

No description provided.

muzzleFlashFPSSpecial - String

No description provided.

muzzleFlash3RDSpecial - String

No description provided.

muzzleFeedback - String

No description provided.

shellType - ShellTypes (enum)

No description provided.

shellEjectFeedback - String

No description provided.

clipSize - Int32

No description provided.

hasRotatingCylinder - Boolean

No description provided.

reloadDuration - Single

No description provided.

EffectiveDamagePerSecond - String

No description provided.

recoilPosImpulse - Vector3

No description provided.

recoilPosShift - Vector3

No description provided.

recoilPosShiftWeight - Single

No description provided.

recoilPosStiffness - Single

No description provided.

recoilPosDamping - Single

No description provided.

recoilPosImpulseWeight - Single

No description provided.

recoilCameraPosWeight - Single

No description provided.

recoilRotImpulse - Vector3

No description provided.

recoilRotStiffness - Single

No description provided.

recoilRotDamping - Single

No description provided.

recoilRotImpulseWeight - Single

No description provided.

recoilCameraRotWeight - Single

No description provided.

recoilAimingWeight - Single

No description provided.

eventOnSemiFire2D - List String

No description provided.

eventOnBurstFire2D - List String

No description provided.

eventOnAutoFireStart2D - List String

No description provided.

eventOnAutoFireEnd2D - List String

No description provided.

eventOnSpecialSemiFire2D - List String

No description provided.

eventOnSpecialBurstFire2D - List String

No description provided.

eventOnSpecialAutoFireStart2D - List String

No description provided.

eventOnSpecialAutoFireEnd2D - List String

No description provided.

eventOnSpecialChargeup2D - List String

No description provided.

eventOnSpecialCooldown2D - List String

No description provided.

eventOnSpecialChargeupEnd2D - String

No description provided.

eventOnSpecialCooldownEnd2D - String

No description provided.

eventOnSemiFire3D - List String

No description provided.

eventOnBurstFire3D - List String

No description provided.

eventOnAutoFireStart3D - List String

No description provided.

eventOnAutoFireEnd3D - List String

No description provided.

eventOnSpecialSemiFire3D - List String

No description provided.

eventOnSpecialBurstFire3D - List String

No description provided.

eventOnSpecialAutoFireStart3D - List String

No description provided.

eventOnSpecialAutoFireEnd3D - List String

No description provided.

eventOnSpecialChargeup3D - List String

No description provided.

eventOnSpecialCooldown3D - List String

No description provided.

eventOnSpecialChargeupEnd3D - String

No description provided.

eventOnSpecialCooldownEnd3D - String

No description provided.

eventClick - String

No description provided.

eventReload - String

No description provided.

useSemiAudioForBurstShots - Boolean

No description provided.

BoosterImplantEffect

GameData_BoosterImplantEffectDataBlock_bin.json

No description provided.


Fields

Effect - AgentModifier (enum)

No description provided.

PublicShortName - LocalizedText (nested type)

No description provided.

PublicName - LocalizedText (nested type)

No description provided.

Description - LocalizedText (nested type)

No description provided.

DescriptionNegative - LocalizedText (nested type)

No description provided.

BoosterEffectCategory - BoosterEffectCategory (enum)

No description provided.

BoosterImplantTemplate

GameData_BoosterImplantTemplateDataBlock_bin.json

No description provided.


Fields

Deprecated - Boolean

No description provided.

PublicName - LocalizedText (nested type)

No description provided.

Description - LocalizedText (nested type)

No description provided.

DurationRange - Vector2

No description provided.

DropWeight - Single

No description provided.

Conditions - List UInt32 (BoosterImplantConditionDataBlock)

No description provided.

RandomConditions - List UInt32 (BoosterImplantConditionDataBlock)

No description provided.

Effects - List BoosterImplantEffectInstance (nested type)

No description provided.

RandomEffects - List List BoosterImplantEffectInstance (nested type)

No description provided.

ImplantCategory - BoosterImplantCategory (enum)

No description provided.

MainEffectType - BoosterEffectCategory (enum)

No description provided.

WardenObjective

GameData_WardenObjectiveDataBlock_bin.json (filled)

Fields

Type - eWardenObjectiveType (enum)

No description provided.

Header - LocalizedText (nested type)

No description provided.

MainObjective - LocalizedText (nested type)

No description provided.

WardenObjectiveSpecialUpdateType - eWardenObjectiveSpecialUpdateType (enum)

No description provided.

GenericItemFromStart - UInt32 (ItemDataBlock)

No description provided.

DoNotMarkPickupItemsAsWardenObjectives - Boolean

No description provided.

FindLocationInfo - LocalizedText (nested type)

No description provided.

FindLocationInfoHelp - LocalizedText (nested type)

No description provided.

GoToZone - LocalizedText (nested type)

No description provided.

GoToZoneHelp - LocalizedText (nested type)

No description provided.

InZoneFindItem - LocalizedText (nested type)

No description provided.

InZoneFindItemHelp - LocalizedText (nested type)

No description provided.

SolveItem - LocalizedText (nested type)

No description provided.

SolveItemHelp - LocalizedText (nested type)

No description provided.

GoToWinCondition_Elevator - LocalizedText (nested type)

No description provided.

GoToWinConditionHelp_Elevator - LocalizedText (nested type)

No description provided.

GoToWinCondition_CustomGeo - LocalizedText (nested type)

No description provided.

GoToWinConditionHelp_CustomGeo - LocalizedText (nested type)

No description provided.

GoToWinCondition_ToMainLayer - LocalizedText (nested type)

No description provided.

GoToWinConditionHelp_ToMainLayer - LocalizedText (nested type)

No description provided.

ShowHelpDelay - Single

No description provided.

WavesOnElevatorLand - List GenericEnemyWaveData (nested type)

No description provided.

EventsOnElevatorLand - List WardenObjectiveEventData (nested type)

No description provided.

WaveOnElevatorWardenIntel - LocalizedText (nested type)

No description provided.

FogTransitionDataOnElevatorLand - UInt32 (FogSettingsDataBlock)

No description provided.

FogTransitionDurationOnElevatorLand - Single

No description provided.

OnActivateOnSolveItem - Boolean

No description provided.

WavesOnActivate - List GenericEnemyWaveData (nested type)

No description provided.

EventsOnActivate - List WardenObjectiveEventData (nested type)

No description provided.

StopAllWavesBeforeGotoWin - Boolean

No description provided.

WavesOnGotoWin - List GenericEnemyWaveData (nested type)

No description provided.

WaveOnGotoWinTrigger - eRetrieveExitWaveTrigger (enum)

No description provided.

EventsOnGotoWin - List WardenObjectiveEventData (nested type)

No description provided.

EventsOnGotoWinTrigger - eRetrieveExitWaveTrigger (enum)

No description provided.

FogTransitionDataOnGotoWin - UInt32 (FogSettingsDataBlock)

No description provided.

FogTransitionDurationOnGotoWin - Single

No description provided.

ChainedPuzzleToActive - UInt32 (ChainedPuzzleDataBlock)

No description provided.

ChainedPuzzleMidObjective - UInt32 (ChainedPuzzleDataBlock)

No description provided.

ChainedPuzzleAtExit - UInt32 (ChainedPuzzleDataBlock)

No description provided.

ChainedPuzzleAtExitScanSpeedMultiplier - Single

No description provided.

Gather_RequiredCount - Int32

No description provided.

Gather_ItemId - UInt32 (ItemDataBlock)

No description provided.

Gather_SpawnCount - Int32

No description provided.

Gather_MaxPerZone - Int32

No description provided.

Retrieve_Items - List UInt32 (ItemDataBlock)

No description provided.

ReactorWaves - List ReactorWaveData (nested type)

No description provided.

LightsOnFromBeginning - Boolean

No description provided.

LightsOnDuringIntro - Boolean

No description provided.

LightsOnWhenStartupComplete - Boolean

No description provided.

DoNotSolveObjectiveOnReactorComplete - Boolean

No description provided.

SpecialTerminalCommand - String

No description provided.

SpecialTerminalCommandDesc - LocalizedText (nested type)

No description provided.

PostCommandOutput - List String

No description provided.

SpecialCommandRule - TERM_CommandRule (enum)

No description provided.

PowerCellsToDistribute - Int32

No description provided.

Uplink_NumberOfVerificationRounds - Int32

No description provided.

Uplink_NumberOfTerminals - Int32

No description provided.

Uplink_WaveSpawnType - SurvivalWaveSpawnType (enum)

No description provided.

CentralPowerGenClustser_NumberOfGenerators - Int32

No description provided.

CentralPowerGenClustser_NumberOfPowerCells - Int32

No description provided.

CentralPowerGenClustser_FogDataSteps - List GeneralFogDataStep (nested type)

No description provided.

ActivateHSU_ItemFromStart - UInt32 (ItemDataBlock)

No description provided.

ActivateHSU_ItemAfterActivation - UInt32 (ItemDataBlock)

No description provided.

ActivateHSU_BringItemInElevator - Boolean

No description provided.

ActivateHSU_MarkItemInElevatorAsWardenObjective - Boolean

No description provided.

ActivateHSU_StopEnemyWavesOnActivation - Boolean

No description provided.

ActivateHSU_ObjectiveCompleteAfterInsertion - Boolean

No description provided.

ActivateHSU_RequireItemAfterActivationInExitScan - Boolean

No description provided.

ActivateHSU_Events - List WardenObjectiveEventData (nested type)

No description provided.

Survival_TimeToActivate - Single

No description provided.

Survival_TimeToSurvive - Single

No description provided.

Survival_TimerTitle - LocalizedText (nested type)

No description provided.

Survival_TimerToActivateTitle - LocalizedText (nested type)

No description provided.

GatherTerminal_SpawnCount - Int32

No description provided.

GatherTerminal_RequiredCount - Int32

No description provided.

GatherTerminal_Command - String

No description provided.

GatherTerminal_CommandHelp - LocalizedText (nested type)

No description provided.

GatherTerminal_DownloadingText - LocalizedText (nested type)

No description provided.

GatherTerminal_DownloadCompleteText - LocalizedText (nested type)

No description provided.

GatherTerminal_DownloadTime - Single

No description provided.

TimedTerminalSequence_NumberOfRounds - Int32

No description provided.

TimedTerminalSequence_NumberOfTerminals - Int32

No description provided.

TimedTerminalSequence_TimePerRound - Single

No description provided.

TimedTerminalSequence_TimeForConfirmation - Single

No description provided.

TimedTerminalSequence_UseFilterForSourceTerminalPicking - Boolean

No description provided.

TimedTerminalSequence_SourceTerminalWorldEventObjectFilter - String

No description provided.

TimedTerminalSequence_EventsOnSequenceStart - List List WardenObjectiveEventData (nested type)

No description provided.

TimedTerminalSequence_EventsOnSequenceDone - List List WardenObjectiveEventData (nested type)

No description provided.

TimedTerminalSequence_EventsOnSequenceFail - List List WardenObjectiveEventData (nested type)

No description provided.

Additional docs

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.

General fields

// The following are common fields used by all objective types.
// Enum that determines what type of the objective is. 
// All objective types are listed above. 
"Type": 6,

// Probably a useless field :)
"Header": "Retrieve Essential Items", 

// Text that describes the objective. Here it’s simply a string, but for most WardenObjective in Rundown 6 it is reference to DB in TextDatablock. 
"MainObjective": "Find the reactor for the floor and make sure it is back online.", 

// Not quite sure… awaiting supplements :)
"WardenObjectiveSpecialUpdateType": 0,

// The following fields are all Text fields 
// that work analogously as "MainObjective".
"FindLocationInfo": 0, 
"FindLocationInfoHelp": 0, 
"GoToZone": 0,
"GoToZoneHelp": 0,
"InZoneFindItem": 0,
"InZoneFindItemHelp": 0,
"SolveItem": 0,
"SolveItemHelp": 0,

// The following Text fields will be displayed upon objective completion. 
// Fields ending with "_Elevator" and "_CustomGeo" are 
// typically  used by Main Objective. If you make the 
// extraction point in the elevator zone, the former ones 
// will be applied, otherwise (implying that you 
// are using forward exit) the latter ones.
// Fields ending with "_ToMainLayer" are typically 
// used by Secondary or Overload. 
// However, for the case of forward exit being set 
// in either Secondary or Overload, 
// I’m not pretty sure… awaiting supplements :)
"GoToWinCondition_Elevator": 0,
"GoToWinConditionHelp_Elevator": 0,
"GoToWinCondition_CustomGeo": 0,
"GoToWinConditionHelp_CustomGeo": 0,
"GoToWinCondition_ToMainLayer": 0,
"GoToWinConditionHelp_ToMainLayer": 0,
"ShowHelpDelay": 180.0,

Waves / Events / Fog / Chained Puzzle fields

These fields tells the objective manager to spawn enemy waves and execute events at given point.

On Elevator Land

Literally on elevator land (upon cage drop). The fields are all self-clarifying.

"WavesOnElevatorLand": [],
"WaveOnElevatorWardenIntel": 0,
"EventsOnElevatorLand": [],

// Firstly used in R4E1, which combos with Tank Error Alarm. 
// This 2 fields could be easily replaced 
// by a "SetFogSettings" Event in "EventsOnElevatorLand"
"FogTransitionDataOnElevatorLand": 0,
"FogTransitionDurationOnElevatorLand": 0.0, 

On Activate

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

// The following explanation is copy-pasted from @FlowAria's message in 
// channel "modding-general". I'll do some extension basing on this.
//
// OnActivateOnSolveItem tells ObjectiveManager to trigger EventsOnActivate 
// for each objective item "solved", this include:
// 1. Picking up Objective gather item (Once for each) 
// 2. Retrieve Item (Once for each, have some checkpoint bug)
// 3. Input gather terminal command (Could be triggered as you want per single terminal, buggy)
// 4. Plug powercell to objective generator (Generator Distribution / Generator Cluster)
// 5. Established Uplink (per each terminals)
// 6. Collected DNA from HSU
// 7. Fully finish generator cluster
// 8. Fully startup/shutdown reactor
// 9. Finished Special command objective terminal 
// 
// The followings are my opinions, feel free to correct them if they are wrong.
// -> Considering 4. and 7., you could find that "OnActivateOnSolveItem"
//    works awkwardly on Generator-Cluster. 
//    To my knowledge, Modulation C3 Secondary has taken this into good use, 
//    you may use that as an example.
// -> For 8., by setting the field below "DoNotSolveObjectiveOnReactorComplete",
//    to "false", it is possible that you set multiple Reactors all in the same 
//    layer of the level and trigger different sequences of events upon
//    finishing each startup/shutdown, and finally use 
//    a "ForceWin" event to complete the layer.
"OnActivateOnSolveItem": false,

// "WavesOnActivate" is only used by several objective types. 
// -> Terminal Uplink / Corrupted Uplink 
//    (spawn enemy wave on uplink establishment 
//     and end them on verifications complete)
// -> Special terminal commands 
//    (after the activation scan, spawn enemy waves at given points.)
// -> Survival 
//    (spawn enemy waves at given points.)
// -> Activate HSU 
//    (not sure)
"WavesOnActivate": [],

// If "OnActivateOnSolveItem" is set to "true", refer to the doc above.
// Otherwise, refer to the following.
// "EventsOnActivate" is only used by several objective types.
// -> Special terminal commands 
//    (after the activation scan, execute events at given points.)
// -> Survival 
//    (execute events at given points.)
"EventsOnActivate": [],

// R5B3 Main and R5C2 Overload are good examples for good 
// use of "WavesOnActivate" and "EventsOnActivate"

On Goto Win

By "Goto Win", it means objective completion.

// Stop all waves upon objective completion.
// This can be replaced by a "StopEnemyWave" event 
// in "EventsOnGotoWin"
"StopAllWavesBeforeGotoWin": false,

// For "WaveOnGotoWinTrigger" and "EventsOnGotoWinTrigger",
// they are both enum that acceot the following 2 values. 
// 0 - OnObjectiveCompleted,
// 1 - WhenExitScanMakesProgress
// Note: I'm not sure if "EventsOnGotoWinTrigger" works properly. 
//       Awaiting confirmation :) 
"WavesOnGotoWin": [],
"WaveOnGotoWinTrigger": 0,
"EventsOnGotoWin": [],
"EventsOnGotoWinTrigger": 0,

// Set Fog transition upon objective completion. 
// This can be replaced by a "SetFogSetting" event in "EventsOnGotoWin"
"FogTransitionDataOnGotoWin": 0,
"FogTransitionDurationOnGotoWin": 0.0,

Objective Chained Puzzle

// Used by: 
// -> HSU_CollectDNASample 
// -> Reactor Startup/Shutdown (to initiate their verification sequences)
// -> Special Terminal Command 
// -> Terminal Uplink (after CONNECT, before VERIFY)
// This chained puzzle is usually a Security Scan without alarm,
// but of course, you can make it an alarm if you want :)
"ChainedPuzzleToActive": 0, // typical value: 4, 10

// Used by CentralGeneratorCluster, ReactorShutdown and SpecialTerminalCommand
// This is the chained puzzle the team would be required to complete
// after plugging in all powercell / input shutdown verification code / input special terminal command 
"ChainedPuzzleMidObjective": 0,   

// Extraction scan. If the objective you are editing is 
// not a Main layer objective, and does not require retrieving Big Pickup Item(s)  
// to the extraction scan, feel free to left this field unchanged.
"ChainedPuzzleAtExit": 11, // typical value: 11.

// All vanilla levels use the same extraction scan puzzle.
// So changing the multiplier below could 
// change the extraction scan speed for different levels.
"ChainedPuzzleAtExitScanSpeedMultiplier": 1.0,

Objective-specific fields

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.

3 - Gather Small Item

Objective Type for R6C1 and R6D4.

// Gather Small Item
"Gather_RequiredCount": -1,
// reference to ItemDataBlock
"Gather_ItemId": 133, 
"Gather_SpawnCount": 0,
"Gather_MaxPerZone": 0,

6 - Retrieve Big Item

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"

// Retrieve Big Item 
"Retrieve_Items": [ 
  // references to ItemDatablock. 
  133,
  133
],

1/2 - Reactor Startup/Shutdown

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.

// -------------------------------------
// Reactor Startup/Shutdown

// Note that if it is Reactor Shutdown, you need to 
// leave at least 1 empty wave data in "ReactorWaves" to make the objective 
// work properly (otherwise there will be 
// no verification code for the shutdown)

// Probably the most complicated, long field. 
// Used by both Reactor Startup and Shutdown.
// For Reactor Shutdown, it's used only for verification code generation.
// This field serves mostly for Reactor Startup.
"ReactorWaves": [
// Array of wave information.
// For conciseness, the example here only contains 1 wave. 
// To create multiple waves, copy-pasted the first wave and edit 
// the fields as needed. 

{ // 1st reactor wave.
    "Warmup": 30.0,        // First Warmup time
    "WarmupFail": 30.0,    // Warmup time if last wave faild to verify
    "Wave": 120.0,         // Defense time
    "Verify": 285.0,       // First Verify time
    "VerifyFail": 60.0,    // Verify time if last wave faild to verify
    
    // Is the code put in other zone? 
    // If set to true, don't forget to spawn a terminal / terminals 
    // in the corresponding zone.
    // 
    // If set to false, the code will be given on the HUD.
    "VerifyInOtherZone": true,
    
    // Works only when "VerifyInOtherZone" is set to true.
    // Specifies the zone to put the code.
    // One of the terminals in the zone will be selected (randomly).
    "ZoneForVerification": 13,
    
    // Defense Wave.
    "EnemyWaves": [
        {
            "WaveSettings": 23,     // reference to SurvivalWaveSettings
                                    // Search the word "Reactor" in SurvivalWaveSettings,
                                    // there are already some wave settings written by 10cc,
                                    // take them into good use.
                                    
            "WavePopulation": 6,    // reference to SurvivalWavePopulation
            
            "SpawnTimeRel": 0.0,    // Spawn time relative.
                                    // The exact spawn time = SpawnTimeRel * Wave
                                       
            "SpawnType": 1        // Enums. 
                                  // 0 - ClosestToReactorNoPlayerBetween
                                  // 1 - InElevatorZone
                                  
                                  // Note: This spawn type can be overriden
                                  // by m_overrideWaveSpawnType in SurvivalWaveSettings.
                                  // This is how flyers are spawned in R6D1 reactor
        }
    ],
    "Events": [
       // For reactor wave events,
       // "Trigger" plays a role in "when to execute this event".
       // It is an enum that accepts the following values:
       // 0 - None
       // 1 - OnStart
       // 2 - OnMid
       // 3 - OnEnd
       // Use the below one as an example, 
       // and don't miss the comments after "Trigger"
        {
            "Trigger": 1, // Executed before wave defense
            "Type": 2,
            "Layer": 0,
            "LocalIndex": 13,
            "Delay": 5.0,
            "WardenIntel": 1369, //"Door to [ZONE_13] unlocked by startup sequence.",
        },
        {
            "Trigger": 2, // Executed after wave defense 
            "Type": 11,
            "Layer": 0,
            "Delay": 12.0,
            "WardenIntel": 1370, //"Auxiliary temrinal located in [ZONE_13]",
            "CustomSubObjectiveHeader": 0,
            "CustomSubObjective": 99999 // "Find the verification code in [ZONE_13]"
        },
        {  
            "Trigger": 3, // Executed after verification for this wave completed.   
            "Type": 11,
            "Layer": 0,
            "LocalIndex": 0,
            "Delay": 0.0,
            "WardenIntel": 0,
            "CustomSubObjectiveHeader": 0,
            "CustomSubObjective": 0
        }
        // Note: For Trigger OnEnd (3) reactor wave events, 
        // They are only executed on host side, which means:
        // It works fine. But, on client side, the Warden Intel
        // for the events won't be displayed. 
    ]
}, // First reactor wave ended
{
// the second reactor wave
},
{
// the third reactor wave
},
// etc...
],

// Determine if the Light is all on upon cage drop. 
// To my knowledge this field is broken in R6 
// - You need to add a “AllLightsOff” 
// in "EventsOnElevatorLand" to make it work as expected
"LightsOnFromBeginning": false, 

// Determine if the Light is all on during 
// when the current wave is in warm-up phase
"LightsOnDuringIntro": false,

// Determine if the Light is all on after startup complete
// when the current wave is in warm-up phase
"LightsOnWhenStartupComplete": false,

// If set to true, the Reactor Startup / Shutdown objective 
// will be deemed un-completed after Startup/Shutdown completed.
// You need to use a ForceObjectiveComplete event
// somewhere in the expedition.
// In Vanilla this field is only used in R6D1, 
// in which the ForceObjectiveComplete event 
// is executed upon boss death
// On the other hand, with "OnActivateOnSolveItem" set to true, 
// you can use "EventsOnActivate" in place of "EventsOnGotoWin"
// Probably you can use this field to create a expedition 
// in which you need to initiate multiple Startup/Shutdown sequences :)
"DoNotSolveObjectiveOnReactorComplete": true, 

5 - Special Terminal Command

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.

// Special Terminal Command
// Surpassed by terminal "UniqueCommands" 
// Awaiting supplements :)
"SpecialTerminalCommand": "",
"SpecialTerminalCommandDesc": "",
"PostCommandOutput": [],
"SpecialCommandRule": 0,

7 - Distribute Powercell

R6C2 Main Objective.

Note:

  1. 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.

  2. 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"

  3. 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.

// The number of powercell to distribute..
"PowerCellsToDistribute": 0,

8/13 - Terminal Uplink / Corrupted Uplink

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.

"Uplink_NumberOfVerificationRounds": 0,
"Uplink_NumberOfTerminals": 1,

// Enum that determine wave spawn type.
// Not sure how this field works, 
// To my knowledge: 
// 0 - In the nearby zone (like most sec-door alarm)
// 1 - In the current zone 
//     (which means the current zone should contain 
//      multiple rooms (areas) to make the enemies spawn as expected)
// 
// -> Not sure if the spawn type can be override 
//    by "m_overrideWaveSpawnType" in SurvivalWaveSettings
// Awaiting supplements :)
"Uplink_WaveSpawnType": 1,

9 - Central Generator Cluster

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.

// Central Generator Cluster 
"CentralPowerGenClustser_NumberOfGenerators": 4,
"CentralPowerGenClustser_NumberOfPowerCells": 4,
// You can change the fog upon plugging in each cell
// This field is not used in Rundown 6. 
// Take the following format as example.
"CentralPowerGenClustser_FogDataSteps": [
    { // Fog changes upon plugging in the 1st powercell
      "m_fogDataId": 80, // reference to FogSettingsDatablock
      "m_transitionToTime": 1.0
    },
    { // Fog changes upon plugging in the 2nd powercell
      // If you don't want the fog to change at this point, 
      // set "m_fogDataId" to the same as the one before. 
      "m_fogDataId": 80, 
      "m_transitionToTime": 1.0
    },
    { // Upon plugging in the 3rd powercell
      "m_fogDataId": 81, 
      "m_transitionToTime": 60.0
    },
    { // Upon plugging in the 4th powercell
      "m_fogDataId": 82, 
      "m_transitionToTime": 360.0
    },
    { // Fog changes upon completing the ChainedPuzzleMidObjective.
      // Awaitng supplement: I'm not sure what would happen 
      //                     if "ChainedPuzzleMidObjective" is 0.
      "m_fogDataId": 83, 
      "m_transitionToTime": 10.0
    }
],

10 - Activate Neonate_HSU / Datasphere. Unseal Hi-Sec Cargo Crate.

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"

// -------------------------------------
// Activate Neonate_HSU / Datasphere. Unseal Hi-Sec cargo crate.
// -> Neonate_HSU (which has 4 variants) and Datasphere are all Big pickup Item. 
// -> Vanilla levels for reference: R3A1, R3B2, R3D1, R4C1, R5B3

// Speaking of that you set Activate Neonate_HSU as you Main layer objective,
// -> "ActivateHSU_ItemFromStart" specifies the Big Pickup item that will be placed 
//    in the elevator cargo container upon cage drop,
// -> "ActivateHSU_ItemAfterActivation" specifies the Big Pickup item will
//    be replaced by another Big Pickup item after it's inserted.
// Both of them are references to ItemDatablock    
"ActivateHSU_ItemFromStart": 0,
"ActivateHSU_ItemAfterActivation": 0,
"ActivateHSU_StopEnemyWavesOnActivation": false,

// Works analogously as "DoNotSolveObjectiveOnReactorComplete".
"ActivateHSU_ObjectiveCompleteAfterInsertion": false,
"ActivateHSU_RequireItemAfterActivationInExitScan": false,
"ActivateHSU_Events": [],

11 - Survival

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".

// The time for the players to "get prepared"
"Survival_TimeToActivate": 0.0, 

// The "Timer"
"Survival_TimeToSurvive": 0.0, 

// The titles. You can use either a string or a reference to TextDatablock
"Survival_TimerTitle": "", 
"Survival_TimerToActivateTitle": "",

12 - Gather Terminal

// Gather Terminal (R6D2 Main), works analogously as Gather Small Item. 
"GatherTerminal_SpawnCount": 0, 
"GatherTerminal_RequiredCount": 0, 
"GatherTerminal_Command": "", 
"GatherTerminal_CommandHelp": "", 
"GatherTerminal_DownloadingText": "", 
"GatherTerminal_DownloadCompleteText": "", 
"GatherTerminal_DownloadTime": -1.0, 

How to write an Event?

First, let's take a look at an event copy-pasted from the vanilla datablock:

{
    "Trigger": 0,
    
    // set up chained puzzle for a certain command on a terminal
    // can only be used in "UniqueCommands" in LevelLayoutDatablock
    "ChainPuzzle": 0,   
    "UseStaticBioscanPoints": false,
    
    "Type": 0,
    "Layer": 0,
    "DimensionIndex": 0,
    "LocalIndex": 0,
    "Delay": 0.0,
    "Duration": 0.0,
    "ClearDimension": false,
    "WardenIntel": 0,
    "CustomSubObjectiveHeader": 0,
    "CustomSubObjective": 0,
    "SoundID": 0,
    "DialogueID": 0,
    "FogSetting": 0,
    "FogTransitionDuration": 0.0,
    "EnemyWaveData": {
        "WaveSettings": 0,
        "WavePopulation": 0,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
    }
}

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.

0 - None

Does nothing. You can use it to display Warden Intel.

{
    "Trigger": 0,
    "Type": 0,
    "LocalIndex": 0,
    "Delay": 0.0,
    "WardenIntel": 1345 // "Door to [ZONE_5] unlocked"
}

1 - OpenSecurityDoor

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".

{
    "Trigger": 0,
    "Type": 1,
    "Layer": 1,          // Specify layer in Reality (cannot be in dimension).  
    "DimensionIndex": 0, // Specify dimension if you are unlocking a door in dimension
    "LocalIndex": 2,     // Specify the door to unlock 
    "Delay": 0.0,
    "WardenIntel": 1345 // "Door to [ZONE_2] opened"
}

2 - UnlockSecurityDoor

Works similarly as OpenSecurityDoor.

{
    "Trigger": 0,
    "Type": 2,
    "Layer": 1,          // Specify layer in Reality (cannot be in dimension).  
    "DimensionIndex": 0, // Specify dimension if you are unlocking a door in dimension
    "LocalIndex": 2,     // Specify the door to unlock 
    "Delay": 0.0,
    "WardenIntel": 1345 // "Door to [ZONE_2] unlocked"
}

3 - AllLightsOff

Shutdown all lights in the current level.

{
    "Trigger": 0,
    "Type": 3,
    "Delay": 0.0,
    "WardenIntel": 1345 // "://SUPPROT SYSTEM OFFLINE"
}

4 - AllLightsOn

Turn on all lights in the current level.

{
    "Trigger": 0,
    "Type": 4,
    "Delay": 0.0,
    "WardenIntel": 1345 // "://SUPPROT SYSTEM RESTORED"
}

5 - PlaySound

Play certain sound in/from specific zone. You will need to collect the sound ID from vanilla data.

{
    "Trigger": 0,
    "Type": 5,
    "Layer": 1,
    "DimensionIndex": 0,
    "LocalIndex": 2,
    "Delay": 0.0,
    "WardenIntel": 1345, // "Birther Scream"
    "SoundID": 55555555, // Don't use this as your sound id :)
    "DialogueID": 0 // I'm not sure if this is also used. 
}

6 - SetFogSettings

Changed the fog for the entire level.

{
    "Trigger": 0,
    "Type": 6,
    "Delay": 0.0,
    "FogSetting": 87,
    "WardenIntel": 1345, // "Malfunction in air purification system."
    "FogTransitionDuration": 5400.0
}

7 - DimensionFlashTeam

[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.

{
    "Trigger": 0,
    "Type": 7,
    "DimensionIndex": 1,     // Dimension to teleport to
    "LocalIndex": 0,
    "Delay": 0.0,
    "Duration": 120.0,       // Teleport back after 120 seconds.
    "ClearDimension": true, // Removed all enemies in the dimension after teleporting back
    "WardenIntel": 1345
}

8 - DimensionWarpTeam

[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.

{
    "Trigger": 0,
    "Type": 8,
    "DimensionIndex": 1,     // Dimension to teleport to
    "LocalIndex": 0,
    "Delay": 0.0,
    "ClearDimension": true, // Removed all enemies in previous the dimension
    "WardenIntel": 1345
}

9 - SpawnEnemyWave

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.

{
    "Trigger": 0,
    "Type": 9,
    "Delay": 0.0,
    "WardenIntel": 0,
    "EnemyWaveData": {
        "WaveSettings": 23,
        "WavePopulation": 7,
        "SpawnDelay": 0.0,
        "TriggerAlarm": false,
        "IntelMessage": 0
    }
}

10 - StopEnemyWave

Stop all waves. For example, scout wave, reactor wave, uplink wave... everything.

{
    "Trigger": 0,
    "Type": 10,
    "Delay": 0.0,
    "WardenIntel": 0,
}

11 - UpdateCustomSubObjective

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.

{    
    "Trigger": 0,
    "Type": 11,
    "Layer": 0,
    "Delay": 0.0,
    "WardenIntel": 0,
    "CustomSubObjectiveHeader": 1344,
    "CustomSubObjective": 1345
}

12 - ForceCompleteObjective

Used in R6D1 to make the Main objective complete after the boss death.

Use "Layer" to specify which layer to force win.

{    
    "Trigger": 0,
    "Type": 12,
    "Layer": 1,
    "Delay": 0.0,
    "WardenIntel": 0
}

999 - EventBreak

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.

{    
    "Trigger": 0,
    "Type": 999
}

CustomAssetShard

GameData_CustomAssetShardDataBlock_bin.json

No description provided.


Fields

BundleName - AssetBundleName (enum)

No description provided.

Assets - List AssetData (nested type)

No description provided.

ChainedPuzzleType

GameData_ChainedPuzzleTypeDataBlock_bin.json

No description provided.


Fields

Prefab - String

No description provided.

GameSetup

GameData_GameSetupDataBlock_bin.json

No description provided.


Fields

RundownIdsToLoad - List UInt32 ()

No description provided.

StartupScreenToLoad - (enum)

No description provided.

GearMagPart

GameData_GearMagPartDataBlock_bin.json

No description provided.


Fields

ClipSizeMultiplier - Single

No description provided.

ReloadTimeMultiplier - Single

No description provided.

ReloadLeftHandGripAnim - String

No description provided.

DropSoundType - (enum)

No description provided.

General - (nested type)

No description provided.

Aligns - (nested type)

No description provided.

GearCategoryFilter

GameData_GearCategoryFilterDataBlock_bin.json

No description provided.


Fields

AllowedCategories - (nested type)

No description provided.

GearFrontPart

GameData_GearFrontPartDataBlock_bin.json

No description provided.


Fields

General - (nested type)

No description provided.

Aligns - (nested type)

No description provided.

FireSequence - (nested type)

No description provided.

ReloadSequence - (nested type)

No description provided.

Feedback

GameData_FeedbackDataBlock_bin.json

No description provided.


Fields

audioData - (nested type)

No description provided.

effectData - (nested type)

No description provided.

GearFlashlightPart

GameData_GearFlashlightPartDataBlock_bin.json

No description provided.


Fields

General - (nested type)

No description provided.

Aligns - (nested type)

No description provided.

RundownDataBlock
eStartupScreenKey
MagazineDropSoundType
GearPartGeneralData
List GearPartAlignData
List GearCategoryFilterData
GearPartGeneralData
List GearPartAlignData
List WeaponAnimSequenceItem
List WeaponAnimSequenceItem
List FeedbackAudioCompData
List FeedbackEffectCompData
GearPartGeneralData
List GearPartAlignData

GearMeleeHeadPart

GameData_GearMeleeHeadPartDataBlock_bin.json

No description provided.


Fields

General - GearPartGeneralData (nested type)

No description provided.

Aligns - List GearPartAlignData (nested type)

No description provided.

GearMeleeNeckPart

GameData_GearMeleeNeckPartDataBlock_bin.json

No description provided.


Fields

General - GearPartGeneralData (nested type)

No description provided.

Aligns - List GearPartAlignData (nested type)

No description provided.

Clouds

GameData_CloudsDataBlock_bin.json

No description provided.


Fields

CoverageTexture - String

No description provided.

CoverageSize - Single

No description provided.

Altitude - Single

No description provided.

Height - Single

No description provided.

Center - Single

No description provided.

Belly - Single

No description provided.

Crown - Single

No description provided.

Anvil - Single

No description provided.

Softness - Single

No description provided.

NoiseTexture - String

No description provided.

NoiseScale1 - Single

No description provided.

NoiseIntensity1 - Single

No description provided.

NoiseScale2 - Single

No description provided.

NoiseIntensity2 - Single

No description provided.

AmbientOcclusion - Single

No description provided.

startupShard - AssetBundleShard (enum)

No description provided.

GearStockPart

GameData_GearStockPartDataBlock_bin.json

No description provided.


Fields

General - GearPartGeneralData (nested type)

No description provided.

Aligns - List GearPartAlignData (nested type)

No description provided.

FireSequence - List WeaponAnimSequenceItem (nested type)

No description provided.

ReloadSequence - List WeaponAnimSequenceItem (nested type)

No description provided.

GearMeleePommelPart

GameData_GearMeleePommelPartDataBlock_bin.json

No description provided.


Fields

General - (nested type)

No description provided.

Aligns - (nested type)

No description provided.

GearPartAttachment

GameData_GearPartAttachmentDataBlock_bin.json

No description provided.


Fields

AttachToAlign - (enum)

No description provided.

General - (nested type)

No description provided.

Aligns - (nested type)

No description provided.

GearPartGeneralData
List GearPartAlignData
eGearPartAlign
GearPartGeneralData
List GearPartAlignData

GearMeleeHandlePart

GameData_GearMeleeHandlePartDataBlock_bin.json

No description provided.


Fields

General - GearPartGeneralData (nested type)

No description provided.

Aligns - List GearPartAlignData (nested type)

No description provided.

GearReceiverPart

GameData_GearReceiverPartDataBlock_bin.json

No description provided.


Fields

General - GearPartGeneralData (nested type)

No description provided.

Aligns - List GearPartAlignData (nested type)

No description provided.

FireSequence - List WeaponAnimSequenceItem (nested type)

No description provided.

GearSightPart

GameData_GearSightPartDataBlock_bin.json

No description provided.


Fields

General - GearPartGeneralData (nested type)

No description provided.

Aligns - List GearPartAlignData (nested type)

No description provided.

SightProperties - GearSightPartProperties (nested type)

No description provided.

GitHub - UntiIted/GTFO-NewbieLevelGuideGitHub
Logo
Audiokinetic | Download Wwise
DownloadAudacity ®
Logo
Logo