Script Extensions
Introduction
When making mods, it's very common that you want to extend the functionality of a class or struct without replacing the script file entirely, since doing so would mean that multiple mods wouldn't be able to work together since only one version of the script file would be loaded at a time. It would also mean that mods that rely on the base game data wouldn't get any script updates or fixes when the base game updates.
extend Keyword
The solution to this is theextend keyword. By extending classes, structs and enums, mods can add or replace functionality in a class without needing to copy the whole thing. When you extend an object, anything that you declare will be added to the end of the existing object, allowing you to add new methods, variables, and even interfaces, all without touching the original files.
Example:
// This could be defined anywhere, even in the base game
class MyClass
{
public:
enum MyEnum
{
Value1, // 0
Value2, // 1
Value3 = 10 // 10
}
void MyMethod() { Console.Log("Called Original MyMethod!"); }
}
// The extensions added by your mod
extend class MyClass
{
public:
// Add some elements to the end of MyEnum,
// continuing the sequence from the initial declaration.
extend enum MyEnum
{
ExtValue1, // 11
ExtValue2, // 12
};
// Replace the original 'MyMethod' with a new one
replace MyMethod() { Console.Log("Called Replaced MyMethod!"); }
// Add a new method to MyClass
void AddedMethod() { Console.Log("This method was added in an extend!"); }
// Add a new variable to MyClass
int SomeValue = 5;
}
// [...]
void Main()
{
MyClass myClass = new MyClass;
// Prints "Called Replaced MyMethod!"
myClass.MyMethod();
// Prints "This method was added in an extend!"
myClass.AddedMethod();
// myClass.SomeValue will now be 7
myClass.SomeValue += 2;
// Prints "Number of elements in MyEnum: 5", since we added 2 new elements
Console.Log($"Number of elements in MyEnum: {countof(MyClass.MyEnum)}");
}
replace Keyword
There is also the replace keyword, which allows you to outright replace an existing class, struct, enum or method. However this should be used with care, as there's a chance another mod that the player wants to use relies on the original structure of the object you replaced.
Note that replace completely removes all existing object, method and variable declarations, and if you wish to have them in your replacement, you'll have to re-add them yourself.
Example:
// This could be defined anywhere, even in the base game
class MyClass
{
public:
void MyMethod() { Console.Log("This will never be called"); }
void RemovedMethod() { }
}
// The replacement added by your mod
replace class MyClass
{
public:
void MyMethod() { Console.Log("This is the replacement!"); }
}
// [...]
void Main()
{
MyClass myClass = new MyClass;
// Will print "This is the replacement!"
myClass.MyMethod();
// Will throw a compiler error, as the replacement class
// doesn't contain RemovedMethod
myClass.RemovedMethod();
}
replace for that Object will be the version that ends up in the final Assembly. While this is a deterministic order for now (In order of the files in IncludeScripts.mdf), this may change in the future, so try and avoid multiple replacements if you can!Replacing an Object within an existing Object
If you want to replace, say, an enum within an existing class, but you don't want to replace the class, you can extend the class and then replace the enum within it. This also works for classes and structs.
Example:
class MyClass
{
public:
enum ReplaceMe
{
Value1,
Value2,
Value3
};
}
extend class MyClass
{
public:
replace enum ReplaceMe
{
ReplacementValue1,
ReplacementValue2,
}
// 'ReplaceMe' now only contains the two elements above
}
Method Replacements
Methods cannot use extend, but they can usereplace, however it works slightly differently than it does with objects.
Rather than completely erasing the original method, the original can still be called from your replaced method by using the call_replace_base method, which takes the same arguments and returns the same type as the original method. This is useful if you want to extend the functionality of an existing method, or only call it under certain circumstances.
Note that you can only replace methods when extending an Object.
Example:
class MyClass
{
public:
void ToReplace(int value)
{
Console.Log($"Called the original with value: {value}");
}
}
extend class MyClass
{
public:
bool CallOriginal = false;
replace void ToReplace(int value)
{
if (CallOriginal)
{
call_replace_base(value);
}
else
{
Console.Log($"Called the replacement with value: {value}");
}
}
}
// [...]
void Main()
{
MyClass myClass = new MyClass;
// Prints "Called the replacement with value: 5"
myClass.ToReplace(5);
// Prints "Called the original with value: 32"
myClass.CallOriginal = true;
myClass.ToReplace(32);
}
Limitations
- You cannot
replaceanything that has been externally defined in the engine. - You cannot
replacenamespaces. - You cannot use
extendto add variables to structs that have been externally defined in the engine, since the engine expects these structs to be of a specific size. - If the original class is
static, the new class must ALSO bestatic. This goes for bothextendandreplace. replaced methods must match the signature of the original, includingstaticandvirtualmodifiers.