Modding:Adding Code at Startup

This page is about modding. See the modding overview for an abstract on modding.
For best results, it's recommended to have read the following topics before this one:

Overview

There are a variety of ways to run code before a new game starts or an existing save is loaded.

Code Pattern When the Code Runs Additional Notes
Mod-Sensitive Cache
  • When the main menu first loads
  • When the list of active mods changes
Not specific to a particular game and won't be called again if the player loads a different save. Code runs only once when the main menu loads during executable startup and is not called again unless the player modifies their approved/active mods while the game is running. In that case, the new mod configuration is "hotloaded" and mod-sensitive cache code is called again.
Blueprint Preload
  • When the main menu first loads
  • When the list of active mods changes
Runs at the same time as Mod-Sensitive Cache code, but has a different load framework, as described below.
Game-Based Cache
  • Immediately after the New Game option is selected
  • When an existing save is loaded
Called every time the game context changes (i.e. new game or load game). Code is called before the player object exists and before world generation, so this option is not suitable if you need to manipulate the player.
PlayerMutator
  • After player is created for a New Game
Code runs only once when a New Game starts, after the player object is first created. Is never called again. Useful if you want your code to run only once per save file. For example, you might add a custom part to the player that holds your code. For more information about this method, refer to Modding:Adding Code to the Player.
AfterGameLoaded Hook
  • After a save game is loaded and the Player object exists
Code runs immediately after a save game is loaded, and when the Player object exists. Useful if your code needs to modify the player object after a save game load (similar to PlayerMutator for new games). For more information about this method, refer to Modding:Adding Code to the Player.
Harmony Injection
  • At any time in the code flow
This is the most flexible method by far, because you can inject your code anywhere in the code flow. However, it is also the most difficult and technical of the options. Most mods do not need this and it is not recommended for modders who are new to C# scripting. For more information about this method, refer to Modding:Harmony.

Mod-Sensitive Cache

Tag any C# class with the [HasModSensitiveStaticCache] attribute to indicate that the class includes mod-sensitive code that the game should run at initial game startup, as well as any time that the list of active mods changes.

Timing

Mod-sensitive cache code is invoked by the XRL.ModManager.ResetModSensitiveStaticCaches() method, which is called at the following times:

  • When the player's active scripting mods are compiled on startup.
  • During a hotload, which occurs any time the active mod configuration changes.
  • When the "reload" wish is used.

Implementation

The mod-sensitive cache is implemented in a similar fashion to the game-sensitive cache (see section below), but there is no special handling for a Reset() method and there are a few other subtle differences.

Classes with [HasModSensitiveStaticCache] attribute are treated as follows (in this order):

  • If the class has any static fields with the [ModSensitiveStaticCache] attribute, those fields are (re)initialized to their default value (for value types) or set to null (for object types). Note that the null object type default behavior is opposite from the default behavior of GameBasedStaticCache. To force an object field to be populated with a newly created instance via Activator.CreateInstance, specify [ModSensitiveStaticCache(true)].
  • If the class has any static methods with the [ModSensitiveCacheInit] attribute, the game invokes those methods.

Example

[HasModSensitiveStaticCache]
public static class Initialiser
{
    [ModSensitiveStaticCache]
    public static int Counter; // Reset to default int value at game startup and whenever mod configuration changes

    [ModSensitiveStaticCache(true)]
    public static List<SolidColor> Colors = new List<SolidColor>(); // Reset to a new empty List<SolidColor> at game startup and whenever mod configuration changes

    [ModSensitiveCacheInit]
    public static void MyModCacheResetCode()
    {
        // Called at game startup and whenever mod configuration changes
    }
}

Blueprint Preload

Tag any class that inherits XRL.World.IPart with the [WantLoadBlueprint] attribute to indicate that the game should pre-load all ObjectBlueprints that include this part.

Blueprint preload is a process completed during game startup (and when mod configuration changes). During this process, the game creates an instance of all ObjectBlueprints that include an IPart which has requested preload. This can serve a number of purposes. For example, the base game's TinkerItem part requests blueprint preload in order to create a tinker recipe for each item with a TinkerItem part. In older versions of Caves of Qud, all ObjectBlueprints were preloaded at game startup. Now, this framework has been adjusted to only preload blueprints that include a part which specifically requests the preload behavior.

Timing

Blueprint preload is invoked by the XRL.World.GameObjectFactory.CallLoadBlueprint() method and occurs immediately after mod-sensitive cache code is processed. Refer to the Mod-Sensitive Cache > Timing section above for more detail.

Implementation

During the blueprint preload, the game goes through all objects defined in ObjectBlueprints.xml. If it finds an object that includes a part whose class has the [WantLoadBlueprint] attribute, the game creates a temporary GameObject based on that blueprint, and then calls GameObject.LoadBlueprint() on the object. This results in the following behavior:

  • The GameObject calls IPart.LoadBlueprint() on each of its parts. A custom part may override this function to perform custom behavior during blueprint preload. This function is not called during normal gameplay, so it's a good place to put one-time initialization code for the part (However, keep in mind that this function might still be called multiple times during the blueprint preload process, if the part is present on more than one object).
  • The class constructor is called for each IPart on the object. This will also be true whenever an object is created during normal gameplay, so it's generally not recommended to put blueprint preload logic into the class constructor.

Example

Here's an example straight from the game code (XRL.World.Parts.TinkerItem)

namespace XRL.World.Parts
{
    [WantLoadBlueprint]
    //...
    public class TinkerItem : IPart
    {
        //...
        public override void LoadBlueprint()
        {
            if (this.CanBuild && string.IsNullOrEmpty(this.SubstituteBlueprint) && !this.ParentObject.HasTag("BaseObject"))
            {
                TinkerData tinkerData = new TinkerData();
                tinkerData.Blueprint = this.ParentObject.Blueprint;
                tinkerData.Cost = this.Bits;
                tinkerData.Tier = this.BuildTier;
                tinkerData.Type = "Build";
                tinkerData.Category = this.ParentObject.GetTag("TinkerCategory", "none");
                tinkerData.Ingredient = this.Ingredient;
                tinkerData.DisplayName = this.ParentObject.pRender.DisplayName;
                TinkerData.TinkerRecipes.Add(tinkerData);
            }
        }
        //...
    }
}

Game-Based Cache

Tag any C# class with the [HasGameBasedStaticCache] attribute to indicate that the class includes game-sensitive code that the game should run each time a new game starts or a save game is loaded.

Timing

Game-based cache code is invoked by the XRL.Core.XRLCore.ResetGameBasedStaticCaches() method, which is called at the following times:

  • Immediately after selecting "New Game" option from main menu (before the player object exists or world generation occurs)
  • Immediately after loading a saved game (including scenarios such as a reload due to Precognition)

Implementation

Classes with [HasGameBasedStaticCache] attribute are treated as follows (in this order):

  • If the class has any static fields with the [GameBasedStaticCache] attribute, those fields are (re)initialized to their default value (for value types) or set to a new instance via Activator.CreateInstance (for object types). Object types can specify [GameBasedStaticCache(CreateInstance = false)] to force the field to be set to null instead of creating a new instance.
  • If the class has a static Reset() method, the game invokes that method.
  • If the class has any other static methods with the [GameBasedCacheInit] attribute, the game invokes those methods.

Example

[HasGameBasedStaticCache]
public static class Initialiser
{
    [GameBasedStaticCache]
    public static int Counter; // Reset to default int value whenever a new game is started or a save is loaded

    public static int OtherCounter; // Value is NOT automatically reset (though you could reset it in your Reset() method)

    public static void Reset()
    {
        // Called whenever a new game is started or a save is loaded - no attribute tag is needed
    }

    [GameBasedCacheInit]
    public static void AdditionalSetup()
    {
        // Called after Reset()
    }
}

Pre-Game Cache

The pre-game cache is an additional load point that is similar to the game-based cache. The overall order in which cache code runs is as follows:

  1. Mod-sensitive cache code
  2. Pre-game cache code (including Blueprint preload)
  3. Game-based cache code

To use the pre-game cache, add either the [HasGameBasedStaticCache] attribute or the [HasModSensitiveStaticCache] attribute to your class (it works with both). Then, add the [PreGameCacheInit] attribute to one or more methods in your class. The pre-game cache method is invoked after mod-sensitive cache code runs, and before game-sensitive cache code runs.

Note that pre-game cache methods are run before every new game or loaded game, even if they are inside a class with only the [HasModSensitiveStaticCache] attribute. So be particularly careful if you're implementing a pre-game cache method that you only want to run after the mod loadout changes.