Using Reflection

What is Reflection?

Reflection is a feature built into C# that allows the code to identify and modify other peaces of code without referencing it at build time. We can use it to modify classes, access private variables and a lot more.

Quick Reference

Ensure you have imported the class, if you see an error, you can hover over the error and it will suggest what to import. To use reflection, we need using System.Reflection; at the top of our class definition file.

Getting a reference to an instance of a type

This will depend on the inheritance hierarchy of the type you want to reference. The unity engine uses a Type called MonoBehaviour, all AzzaMods mods are based on the MonoBehaviour type. It is very simple to get a reference to instances of MonoBehaviour classes.

Find the first active instance of a MonoBehaviour type:

// Find the first active instance of PlayerStatus in Muck:
PlayerStatus playerStatus = FindObjectOfType<PlayerStatus>();

// Ensure we actually found an instance
if(playerStatus != null)
{
    // do something with the class
}

Be aware that this method AMY return a null result, so it's important that we write defensive code that tests and validates that an instance of a class was returned to avoid any errors.

This will not find instances that have been disabled.

This method is part of the GameObject type, and can also be called with GameObject.FindObjectOfType. The GameObject part is typically non nessessary as AzzaMods are an instance of MonoBehaviour.

Loop over all active instances of a MonoBehaviour type:

// Loop over all PlayerMovement instances in Muck
foreach (PlayerMovement playerMovement in FindObjectsOfType<PlayerMovement>())
{
    // Super pedantic mode
    if(playerMovement == null) continue;

    // do something with the instance of the class
}

The FindObjectsOfType method will most likely return non-null instances of a class, but it never hurts to write defensive code, it's one line extra and could save you an error.

This block will find all the instances that are currently active in a scene. If the instance becomes inactive then this function will NOT return the instance.

This method is part of the GameObject type, and can also be called with GameObject.FindObjectsOfType. The GameObject part is typically non nessessary as AzzaMods are an instance of MonoBehaviour.

Loop over all instances of a MonoBehaviour type including inactive:

// Loop over all PlayerMovement instances in Muck
foreach (PlayerMovement playerMovement in Resources.FindObjectsOfTypeAll<PlayerMovement>())
{
    // Super pedantic mode
    if (playerMovement == null) continue;

    // do something with the instance of the class
}

This will find all instances of a type including ones that have been disabled in any given scene.

Finding instances of a non-MonoBehaviour type via Singleton Coding Pattern:

This is a LOT harder. You should pay attension to the Singleton coding pattern as it will typically come into play a lot. A lot of programmers use the Singleton coding pattern which typically appears as a static variable in a class called instance.

If there is a static field or property called instance then you can typically reference it directly:

// Get a reference to the ItemManager instance in Muck
ItemManager itemManager = ItemManager.Instance;

The item manager uses the singleton coding pattern and defines only a single instance of the class.

Another common way of implementing the singleton behavior is by using a Singleton class, we can access instances of a class as follows:

// Get a reference to the AttackManager in Clone Drone in the Danager Zone:
AttackManager attackManager = Singleton<AttackManager>.Instance;

Working With Methods

A method is a peace of code that is defined as part of a class. A method can be either static (you don't need an instance of the class) or it can be non-static (instance based) and requires an instance of a class to invoke it.

Get a specific method from a specific class when no overloads exist:

MethodInfo methodDamage = typeof(PlayerStatus).GetMethod("Damage", Patching.AnyMethod);

This finds the type PlayerStatus and then gets the Damage method.

Adding the Patching.AnyMethod flag will tell the reflection API that ANY method that matches this will be fine.

This will throw an exception if there are multiple overloads for the Damage method.

Get a specific method from a specific class when overloads exist:

Using dnSpy we can see the signature of the method is as follows:

public void Damage(int newHp, int damageType = 0, bool ignoreProtection = false)
{
    // method definition
}

We need to tell the reflection API what arguments to expect and the name of the method.

The following code specifies the signature of the method int, int, bool so the reflection API knows which method we want.

MethodInfo methodDamage = typeof(PlayerStatus).GetMethod("Damage", new System.Type[] { typeof(int), typeof(int), typeof(bool) });

Invoking a method:

Let's connect the first two concepts together and invoke a method:

// Grab the private unlock item method
MethodInfo methodUnlockItemSoft = typeof(UiEvents).GetMethod("UnlockItemSoft", Patching.AnyMethod);

// We are going to use a try-catch here as we aren't validating the the ItemManager.Instance actually exists
// In Muck, due to poor coding, you can't check if the instance is null, as that check will often say the instance is null, but then still work
// It's easier to just use a try catch
try
{
    // Loop over every item in the entire game
    foreach (KeyValuePair<int, InventoryItem> pair in ItemManager.Instance.allItems)
    {
        // Unlock stations, these methods are public so we can just invoke them directly, no need for reflection
        UiEvents.Instance.CheckProcessedItem(pair.Value.id);
        UiEvents.Instance.StationUnlock(pair.Value.id);

        // Invoke the private soft unlock method
        methodUnlockItemSoft.Invoke(UiEvents.Instance, new object[] { pair.Value.id });
    }
}
catch
{
    // do nothing
}

Firstly, we get a reference to the method we are going to use. It's not fantastic to have to look the reference up each time, it was done like this to keep the code simple. This reference is never going to change, so, you can slighyly improve efficency by defining that as a static field on your class:

private static MethodInfo methodUnlockItemSoft = null;

You could then move the section of the code that grabs the method into your OnModLoaded() method.

Up next, we use a try-catch, firstly the method invocation could throw an error, but secondly, the way the code was written in Muck, it's not possible to check if the instance is null.

We proceed to loop over every item that is defined in teh allItems array which we access via the ItemManager.Instance.

We call the public unlock methods UiEvents.Instance.CheckProcessedItem and UiEvents.Instance.StationUnlock.

We also need to call the UnlockItemSoft method but that is private -- that's where the reflection comes into play.

We use the reference stored in methodUnlockItemSoft and call the invoke method. The invoke method takes two arguments: - An instance to run it on - This is going to be our instance of teh UiEvents we want to run it on, in our case UiEvents.Instance, if the method was a static method (no instance required) then we could put null instance - An object array of arguments - The array needs to exactly match the signature of the method, in our case, it needs the id of the item to unlock, in the form of an int, so, we pass that value in

If all goes well, this code block should unlock all items in Muck -- This is the code used in the Essentials mod to unlock all items and stations.

Working With Fields

A field is variable of any type that is declared directly in a class or struct. Do not confuse this with a property which has a getter and setter.

Get a specific field:

FieldInfo fieldMaxRunSpeed = typeof(PlayerMovement).GetField("maxRunSpeed", Patching.AnyMethod);

This finds the type PlayerMovement and then gets the maxRunSpeed field from it.

Adding the Patching.AnyMethod flag will tell the reflection API that ANY field, static, private, public, ANYTHING is fine.

In most cases there would only be one field of each given name, so there is no need to be super specific here.

Getting a value from a field:

Let's put some concepts together and log each player's maxRunSpeed variable:

// Grab the maxRunSpeed field from the PlayerMovement class
FieldInfo fieldMaxRunSpeed = typeof(PlayerMovement).GetField("maxRunSpeed", Patching.AnyMethod);

// Loop over every active instance of the PlayerMovement class
foreach (PlayerMovement playerMovement in FindObjectsOfType<PlayerMovement>())
{
    // Defensive code to ensure the instance is valid
    if(playerMovement == null) continue;

    // Grab the maxRunSpeed from this instance of the class, and cast it to a float
    float maxRunSpeed = (float)fieldMaxRunSpeed.GetValue(playerMovement);

    // Log it
    Log.Info("The run speed is " + maxRunSpeed);

    // This may fail if you have another Log imported, in those cases, you can always use AzzaMods.Log.Info();
}

Firstly, we start by getting a reference to the field of interest. A field won't change during runtime, so you can use a similar approach as mentioned above and store the field into a static field on your mod to improve efficeny, and run the lookup when the mod loads.

We loop over every instance of the PlayerMovement class and then we get the run speed of that instance of the class.

We then call the AzzaMods logging method to send a log to the AzzaMods launcher.

Changing a field's value:

Let's create a more concrete example of a mod, this mod will change the player's run speed

// Some imports
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using AzzaMods;
using UnityEngine;

public class MuckChangeRunSpeed : MonoBehaviour
{
    // Define some stuff for us
    private static string optionMaxRunSpeed = "Max Run Speed";
    private static float maxRunSpeed = 13f;
    private static FieldInfo fieldMaxRunSpeed = null;

    // When the mod is loaded
    void OnModLoaded()
    {
        // Lookup our methods and store them
        fieldMaxRunSpeed = typeof(PlayerMovement).GetField("maxRunSpeed", Patching.AnyMethod);

        // Tell the Mod Launcher about our Max Run Speed optio=n
        Options.RegisterFloat(optionMaxRunSpeed, maxRunSpeed);
        Options.SetDescription(optionMaxRunSpeed, "The max run speed you can achieve. The default is 13.");
        Options.AddPersistence(optionMaxRunSpeed);

        // Start a coroutine, this will run once per second
        // We use this just in case the game overrides the run speed at some point, or if the player sets the option while in the menu
        // We could use the void Update() instead, but, we don't need to run code nearly that often
        StartCoroutine(SlowUpdate());
    }

    // Run once every second or so
    private IEnumerator SlowUpdate()
    {
        // Loop forever
        while(true)
        {
            // Wait a second, allow other code to resume in the mean time
            // This will prevent the entire game from locking up
            yield return new WaitForSeconds(1f);

            // Apply the run speed
            ApplyMaxRunSpeed();
        }
    }

    // When they change an option
    void OnOptionChanged(string optionName)
    {
        // Was the option the run speed option?
        if (optionName == optionMaxRunSpeed)
        {
            // Store the new value of the run speed
            maxRunSpeed = Options.GetFloat(optionName);

            // Apply the new value instantly
            ApplyMaxRunSpeed();
        }
    }

    // Apply the cached runspeed value
    private static void ApplyMaxRunSpeed()
    {
        // Loop over every player in the game right now
        foreach (PlayerMovement plyMovement in FindObjectsOfType<PlayerMovement>())
        {
            // Update the run speed for this specific player
            fieldMaxRunSpeed.SetValue(plyMovement, maxRunSpeed);
        }
    }
}

There's a lot to unpack here. Let's look at each component individually and explain what they do and why we do it.

// Some imports
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using AzzaMods;
using UnityEngine;

We start with imports, these imports give us access to reflection, and the AzzaMods library.

public class MuckChangeRunSpeed : MonoBehaviour
{

We define our mod which is based on the MonoBehaviour class, this gives us a lot of power, and essentially gives us access to the standard unity engine functionality as a result.

    // Define some stuff for us
    private static string optionMaxRunSpeed = "Max Run Speed";
    private static float maxRunSpeed = 13f;
    private static FieldInfo fieldMaxRunSpeed = null;

We define some static variables, there only really needs to be a single copy of these, so we make the variables static.

Defining the option as a static string makes it super easy to reference the variable in the code below that, and allows us to easily rename the option later on if we want to. We also store a reference to the field we are going to set, and the current run speed.

    // When the mod is loaded
    void OnModLoaded()
    {
        // Lookup our methods and store them
        fieldMaxRunSpeed = typeof(PlayerMovement).GetField("maxRunSpeed", Patching.AnyMethod);

        // Tell the Mod Launcher about our Max Run Speed optio=n
        Options.RegisterFloat(optionMaxRunSpeed, maxRunSpeed);
        Options.SetDescription(optionMaxRunSpeed, "The max run speed you can achieve. The default is 13.");
        Options.AddPersistence(optionMaxRunSpeed);

        // Start a coroutine, this will run once per second
        // We use this just in case the game overrides the run speed at some point, or if the player sets the option while in the menu
        // We could use the void Update() instead, but, we don't need to run code nearly that often
        StartCoroutine(SlowUpdate());
    }

This block is run each time the mod is loaded. We firstly look up the maxRunSpeed field.

We register the option, telling the launcher we want to define a float variable, set a description for it, and add persistance so it won't disappear when the game is closed.

We then start a coroutine, we decided to use a coroutine to ensure the max run speed is constantly applied / updated, at a frequency of once per second. There is no need to do it every single frame.

// Run once every second or so
private IEnumerator SlowUpdate()
{
    // Loop forever
    while(true)
    {
        // Wait a second, allow other code to resume in the mean time
        // This will prevent the entire game from locking up
        yield return new WaitForSeconds(1f);

        // Apply the run speed
        ApplyMaxRunSpeed();
    }
}

We have our coroutine definition, the coroutine will loop forever (until the mod is disabled or the game is closed). We start by waiting an entire second, the yield return new WaitForSeconds(1f); is non-blocking which allows the rest of the game to process while this function sleeps.

After one second passes, the function will resume, and we will call the ApplyMaxRunSpeed() function which will do the heavy lifting for us.

// When they change an option
void OnOptionChanged(string optionName)
{
    // Was the option the run speed option?
    if (optionName == optionMaxRunSpeed)
    {
        // Store the new value of the run speed
        maxRunSpeed = Options.GetFloat(optionName);

        // Apply the new value instantly
        ApplyMaxRunSpeed();
    }
}

We next add a handler for when options are changed, in this case, we check if it was the option we are interested in, we update our local cache of the variable, then try to apply the change straight away.

For reference, we don't have to cache the variable like this, we can call the Options.GetFloat(optionMaxRunSpeed) at any point and it will work fine.

// Apply the cached runspeed value
private static void ApplyMaxRunSpeed()
{
    // Loop over every player in the game right now
    foreach (PlayerMovement plyMovement in FindObjectsOfType<PlayerMovement>())
    {
        // Update the run speed for this specific player
        fieldMaxRunSpeed.SetValue(plyMovement, maxRunSpeed);
    }
}

Finally, we have our function that actually applies the changes and sets the max run speed of the player. It loops over every instance of the player and then applies the new value.

Using HarmonyLib Traverse

A fork of Harmony comes with the AzzaMods launcher by default which will allow you to use most functionality available in HarmonyLib. Let's give it a go.

Add a reference to HarmonyLib as follows:

using HarmonyLib;

Getting and setting a value using traverse:

Let's re-write our ApplyMaxRunSpeed() function to use Traverse.

// Apply the cached runspeed value
private static void ApplyMaxRunSpeed()
{
    // Loop over every player in the game right now
    foreach (PlayerMovement plyMovement in FindObjectsOfType<PlayerMovement>())
    {
        // Create a traverse for this player movement instance
        Traverse traverse = new Traverse(plyMovement);

        // Grab a reference to the original value -- just to demonstrate how
        float oldValue = traverse.Field<float>("maxRunSpeed").Value;

        // Update the run speed for this specific player
        traverse.Field<float>("maxRunSpeed").Value = maxRunSpeed;
    }
}