BUTR Documentation

Disabling Parts of External Functions

Introduction

Sometimes you need to use a game function, but with some parts disabled. For example, Clan.OnCompanionAdded not only adds a hero to the clan companion list, but also adds him to the hero list.

public void OnCompanionAdded(Hero companion)
{
  _companionsCache.Add(companion);
  OnHeroAdded(companion); // Function that adds the hero to the hero list
}

But what if we don’t want this? One way you could just copy the function and get the maintenance burden of it.
Another way would be to use a Harmony reverse patch. You can get a copy of the function and manually manipulate the IL code to disable the OnHeroAdded call.
We suggest a higher level approach, based on using scopes and IDisposable.

Scoped Disabling Technique

using allows us to create a scope. While this scope is active, we use a harmony patch that disables the execution of the unwanted function.

using (new AddCompanionActionHandler())
{
    // Within this scope any call to `OnHeroAdded` will be skipped.
    oldLeader.Clan.OnCompanionAdded(oldLeader);
}

The Harmony patch

public class AddCompanionActionHandler : IDisposable
{
    public AddCompanionActionHandler() => AddCompanionActionPatch.SkipChange = true;
    public void Dispose() => AddCompanionActionPatch.SkipChange = false;
}

internal class AddCompanionActionPatch
{
    internal static bool SkipChange = false;

    public static bool Enable(Harmony harmony)
    {
        return true &
            harmony.TryPatch(
                original: AccessTools2.Method(typeof(Clan), "OnHeroAdded"),
                prefix: AccessTools2.Method(typeof(AddCompanionActionPatch), nameof(Prefix)));
    }

    private static bool Prefix() => !SkipChange;
}
Last updated on 25 Nov 2022
Published on 25 Nov 2022
 Edit on GitHub