Overwriting and Modifying Existing Content
It is possible to overwrite or modify content from the base game or from other mods. This is not recommended as opposed to creating your own content that extends it, because mods that overwrite the same thing will likely be incompatible with each other. However, it is the most practical way to achieve certain functionality.
Use a Library Mod
Some other modders have already created mods that assist with the process of overwriting or modifying content. They can save you a lot of time, especially if you want to modify many different assets.
ContentLib
ContentLib by Nog and Robb allows creating and modifying content via writing JSON files. It also offers a Blueprint and C++ API to programmatically generate, modify, or parse content at runtime. Additionally, it offers a limited form of CDO modification (described below) via JSON files.
Documentation, including a step-by-step walkthrough of some common tasks, can be found here.
TweakIt
TweakIt was a mod by Feyko that allowed you to write Lua scripts to programmatically modify game content. The mod has been discontinued, but its source code is available on GitHub and its documentation is available for historical purposes.
Use Class Default Object (CDO) Manipulation
If you still want to overwrite content without using a library, the correct mechanism for doing so is Class Default Object manipulation, sometimes referred to as a CDO Edit.
What is a Class Default Object?
A Class Default Object (CDO) is a template, like a metal cookie cutter used to stamp cookies out of a sheet of dough. When a new instance of the class is created, it copies the CDO’s values as the initial values of its properties. By changing properties on the CDO, you can influence the initial state of all instances of that class created after the edit.
Modifying a CDO
To perform a CDO edit, first obtain the class default object of the class you’re trying to override then use setter functions or directly modify values of the relevant properties.
When performing CDO manipulation, you must keep a reference to the CDO in memory to ensure that Unreal does not garbage collect your patched object (and its descendants) and later load it unmodified from disk, undoing your change. To do this, you need to reference the CDO in a property. More on this in the language-specific sections below.
|
Be mindful when modifying class default objects as you can break other mod and vanilla content without leaving a trace. For safety and troubleshooting reasons, SML’s helper function logs a warning when you access a class not owned by your mod. |
Timing
CDO edits must be performed at the correct time during the game lifecycle depending on what data they need and what content they are trying to modify. A CDO edit performed at an inopportune time can appear to work but result in unusual edge case behavior.
Keep in mind that when modifying properties on a CDO, you’re not changing anything on existing object instances - you’re only changing the template. Depending on what kind of content you are modifying and when in the game lifecycle your code performs the edit, some instances may have already been created using the old CDO values. For example, buildings already placed in the save file load in earlier than some mod code executes.
Your code may also need to fetch all existing instances of the class and modify their values too to ensure they match your new desired value. Performing your CDO modifications earlier in the mod loading process can sometimes remove the need for this extra step.
For example, if you want control over if a CDO is performed via a Session Setting, you must perform the edit after that data is available, such as in a Game World Module. But that’s after buildings that exist in the save file have already been created, so you may need to fetch and modify those instances too.
Understanding the Limits of CDO Edits
It is important to understand that Class Default Object modification is not capable of accomplishing everything.
CDO modification can only influence behaviors and properties that already exist an object - it can’t define new behaviors.
For example, a CDO edit can add a fluid to Space Elevator phase costs (because fluids are a type of item internally), but the Space Elevator doesn’t have any pipe ports, so it can’t be submitted! You can’t add a pipe port to the Space Elevator with CDO edits alone because there are more things going on under the hood than just the data field available on CDOs.
Also, the way some features are implemented behind the scenes could mean that modifying the class default object could have no effects whatsoever, as the values you changed could be overwritten by something else.
Via Blueprint
In Blueprint, you can use the SML Get Class Default Object node to get the class default object.
Afterwards, you can use the class' existing setters to modify fields.
You might need to use an Access Transformer
to make the field that you intend to change accessible from blueprints.
To keep a reference to the CDO, create a property of type Object in the blueprint where you also change the CDO.
Set the property’s value to be the CDO before manipulating the object.
If you are modifying multiple CDOs, you can make the property an array of objects instead, and add each CDO to the array.
Check out ExampleMod’s SubInstance_ExampleMod_SchematicCdoEdit for a code example.
Via C++
Here’s an example from Kyrium’s KBFL for adding additional allowed classes to the Pressurizer and its extractor nodes.
void UKBFLResourceNodeDescriptor_ResourceWell::AfterSpawning()
{
if(const TSubclassOf<AFGBuildableFrackingActivator> BPBuildableFrackingActivator = LoadClass<AFGBuildableFrackingActivator>(NULL, TEXT("/Game/FactoryGame/Buildable/Factory/FrackingSmasher/Build_FrackingSmasher.Build_FrackingSmasher_C")))
{
AFGBuildableFrackingActivator* FrackingActivatorDefault = BPBuildableFrackingActivator.GetDefaultObject();
frackingActivatorCDO = FrackingActivatorDefault;
FrackingActivatorDefault->mAllowedResources.AddUnique(mResourceClass);
}
if(const TSubclassOf<AFGBuildableFrackingExtractor> BPBuildableFrackingExtractor = LoadClass<AFGBuildableFrackingExtractor>(NULL, TEXT("/Game/FactoryGame/Buildable/Factory/FrackingExtractor/Build_FrackingExtractor.Build_FrackingExtractor_C")))
{
AFGBuildableFrackingExtractor* FrackingExtractorDefault = BPBuildableFrackingExtractor.GetDefaultObject();
frackingExtractorCDO = FrackingExtractorDefault;
FrackingExtractorDefault->mAllowedResources.AddUnique(mResourceClass);
}
}
In this example, the CDOs are held externally in the frackingActivatorCDO and frackingExtractorCDO properties.
To properly retain the CDOs, create a property of type UObject* somewhere in your mod that will be persistent,
such as a Game Instance Module.
Make sure to mark the property as UPROPERTY or UE will not know about the property.
If you are modifying multiple CDOs, you can make the property a TArray<UObject*> instead and add each CDO to the array.
Extending Existing Content
If you need to add new behavior to existing content, it may be easier to extend it by making a subclass of it, then define the new behaviors in your subclass.
This works well in tandem with the Placeholder System - even though we don’t have the implementation details of the real version, by extending its placeholder, we will still be able to use those features at runtime.
Hooking
If a CDO modification isn’t working and extending the existing content is not an option, hooking existing functions on the content may allow for the modifications you want.
Hooking is a more complex topic covered on separate pages for Blueprint Hooking and Native C++ Hooking.