User:Illuminatiswag/Sandbox
Harmony is etc etc. This tutorial will assume a general familiarity with XML modding and C# scripting, as Harmony should be a last resort used only where those cannot be applied. I'm not including decompiled C# code directly in this tutorial, even in snippets; please look these parts and functions up in your own copy so you can follow along.
In this tutorial, we'll be making it possible to bring statues (like those created by a lithofex) to life with a nano-neuro animator or Spray-a-Brain. Most walls and furniture can be animated, but statues are an exception. Stone statues of creatures are dynamically generated at runtime, whereas the specifications for animatable furniture are determined by their blueprints, so it's not trivial to fit them into the existing system. We can't do it simply by adding new XML data to specify, or new code as with normal C# scripting. We'll need to use Harmony to patch existing methods.
Preliminaries
The first step here isn't to write a patch; it's to figure out where a patch needs to go. First, we look at the ObjectBlueprints file (Items.xml) for the nano-neuro animator to see how it animates an object. We see it has the AnimateObject part, which we can look up later in the decompiled game code. Next, we look at the blueprints (in Furniture.xml) for an object that can already be animated, the iron maiden. These are the relevant lines:
<tag Name="AnimatedSkills" Value="Persuasion_Intimidate" /> <tag Name="BodyType" Value="IronMaiden" /> <tag Name="Animatable" />
We also check the blueprint in the same file for "Random Stone Statue", which is what we want to animate. It's missing all the tags we see in the iron maiden, but has the part "RandomStatue", which we'll look up in the decompiled game code. We could just add them to the blueprint via normal XML modding, and this would make the statue animatable. But we run into the question of what BodyType to assign to our random statue. We don't know whether it'll be a statue of a salthopper, a saw-hander, or Saad Amus the Sky-Bear. There's no single correct anatomy to assign. So we'll have to do it on the fly, at runtime, which means using C# rather than XML.
Next, we check the decompiled game code, starting with the AnimateObject part. The important pieces here are the CanAnimate method, which checks if a GameObject frankenObject can be animated, and the Animate method, which takes frankenObject and a few less-relevant parameters and brings frankenObject to life. CanAnimate checks if frankenObject has either the blueprint tag or the string property "Animatable" (more on this later), while Animate goes through a long process of assigning frankenObject all the parts it needs to become a real live creature, including assigning the anatomy based on the blueprint tag BodyType (or Humanoid by default).
We also check the decompiled code for RandomStatue. Here, the important bit is the method SetCreature, which modifies the object's properties (tile, description, etc.) to correspond with the creature it depicts. This is where we need to set up the information which AnimateObject.Animate will use to turn this statue into an appropriate creature. Since we need to modify an existing method, we'll need to use Harmony.
Now we begin the actual modding. Begin by creating a mod folder with the usual basic files in it. Next, add a C# file. The name doesn't really matter, but we'll call it AnimateStatue.cs. Put a namespace declaration in it and import Harmony, like so:
using HarmonyLib; namespace AnimateStatue.HarmonyPatches { //TODO }
Next, we need to add a Harmony patch to RandomStatue.SetCreature to assign the proper BodyType. This is a simple modification, and it doesn't impact any of the other lines in the method, so we don't have to worry about the order in which it happens. This makes it a perfect candidate for a Harmony postfix patch.
Postfixes
A postfix patch modifies a function by adding some code which will always be run after the original function finishes. This code can have access to the parameters used to call the function, the value returned by the function, and (in the case of non-static methods) the instance whose method is being called. Even if another mod adds a postfix patch to the same function as us, we're guaranteed that our postfix code will be run. We're not guaranteed that it'll run *correctly*, but this patch is simple and discrete enough that it'd be hard for another mod to break it accidentally.
We start our patch with a declaration of what function we're modifying, and some more imports we'll be using:
//...earlier stuff using XRL.World; using XRL.World.Parts; namespace AnimateStatue.HarmonyPatches { [HarmonyPatch(typeof(RandomStatue), nameof(RandomStatue.SetCreature))] class RandomStatuePatch { //TODO } }
This states that our patch, RandomStatuePatch, will modify the class RandomStatue's method SetCreature. We can then fill in the class definition with our actual postfix function:
//...earlier stuff class RandomStatuePatch { [HarmonyPostfix] static void Postfix(RandomStatue __instance, GameObject creatureObject) { //TODO } }
The HarmonyPostfix line tells Harmony that we're defining a postfix. The parameters of the Postfix function
declare what information we want access to. Here, we're using the special variable __instance
to get access to the RandomStatue part whose SetCreature method we're calling, like the keyword "this"
if we were writing a method rather than patching one. We're also taking `creatureObject`, the sole parameter
of the setCreature method. The name `creatureObject` is not arbitrary - if we're taking parameters
from the original function, we have to name them to match. If the developers renamed SetCreature's parameter
from `creatureObject` to `beingObject`, we would have to change the parameter here to `beingObject`.
Now that we understand the class and function signatures for our patch, let's add the substance of it:
//...earlier stuff static void Postfix(RandomStatue __instance, GameObject creatureObject) { __instance.ParentObject.SetStringProperty("Animatable", "Yes"); //this may be unnecessary __instance.ParentObject.SetStringProperty("BodyType", creatureObject.Body.Anatomy); }
Here, __instance is the RandomStatue part. __instance.ParentObject is therefore the actual Random Stone Statue game object we're dealing with, as that object has the RandomStatue part. We're setting the statue's Animatable string property to "Yes", and setting the BodyType string property to the anatomy of the creatureObject. (The former is technically unnecessary as we could instead put Animatable in a blueprint tag, but we're doing the patch here anyway. Feel free to delete it and instead do some XML if you prefer.). Since BodyType is set according to the creature the statue depicts, we should be able to animate it with the correct body type. Load up the game, enable the mod, spawn in some random statue (or lithofexes), and try it for yourself! Use the swap wish to check the creature's anatomy.
Unfortunately, as you'll have discovered if you tried it out, all statues are still humanoid. We're going to need more patching.
The problem here is the split between blueprint tags and object properties. These are two very similar ways of assigning random bits of data to objects. Tags and properties have a name and a value. Some code will check only whether a name is present, while other code will check the value for a given name and do something with it. The difference is where they're defined. Blueprint tags are set directly on object blueprints, like <tag Name="Animatable"/> or <tag Name="BodyType" Value="IronMaiden"/>. Object properties, on the other hand, are set at runtime, such as by our calls to SetStringProperty. In many cases, the two are effectively interchangeable, as game objects will often use methods like GetPropertyOrTag(name), which checks for a property with that name and, if there's none, checks the blueprint for a tag with that name. For instance, the AnimateObject.CanAnimate method checks if the object to be animated has Animatable as either a tag or a property.
The key issue is that the AnimateObject.Animate method sets the anatomy of the newly animated object by a call to GetTag("BodyType"),
which exclusively checks tags, not properties. But we can't set BodyType on the blueprint,
so we have to set it to check properties as well.
This might be possible to do with another postfix, simply overriding the anatomy. In fact, that would probably
be the safest route. Try doing it yourself as an exercise.
There is, however, a certain elegance to the idea of just tweaking this one method call instead of adding a whole new one. An odd sort of elegance, to be fair, since we'll have to drag ourselves down into the mud to actually achieve it. But if we really do want to just modify this one function call, in the middle of the method, without touching anything else or duplicating the call, we'll have found ourselves a use case for a Harmony transpiler.
Transpilers
Compared to standard C# scripting, Harmony patches are powerful but difficult and fragile, and should be used only as a last resort. Transpilers have the same relationship to the rest of Harmony as Harmony itself does to that standard scripting. Rather than hooking on to a function at the beginning or end to work with its arguments and values, transpilers directly manipulate the sequence of code instructions which make up the function. Here be dragons.
These code instructions are not in C#. Instead, they're in the Common Intermediate Language (CIL or IL), a bytecode instruction set developed by Microsoft as a compilation target for C# (and other high-level languages). Each instruction in IL is a single, small operation on a stack-based virtual machine. For instance, calling a function with three parameters, a single line of C#, requires four instructions in IL: three instructions to put the parameters on the stack, plus an instruction to actually call the function.
A detailed introduction to IL is beyond the scope of this tutorial, but the decompiler ILSpy has an option to decompile the code in "IL with C#" format, which will include the IL instructions annotated with their corresponding C# lines. This, in my opinion, is the most useful format for understanding the IL. In this case, we can easily find the C# line with the GetTag call, which will look roughly like this:
IL_0126: callvirt instance string XRL.World.GameObject::GetTag(string, string)
We want to change this to call GetTagOrStringProperty. We'll need to add some more imports, and a new patch class for AnimateObject.Animate:
//...earlier stuff using System.Reflection; using System.Reflection.Emit; using System.Collections.Generic; namespace AnimateStatue.HarmonyPatches { //...earlier stuff [HarmonyPatch(typeof(AnimateObject), nameof(AnimateObject.Animate))] class AnimateObjectPatch { //TODO } }
The Reflection imports will allow us to work with instructions more directly. Our transpiler will need to be annotated as such, and must be a function from a sequence of code instructions to a sequence of code instructions. Since we're working so abstractly here, we can't use normal collections, so we need to import System.Collections.Generic for the IEnumerable sequence type. Here's what the function signature will look like:
//...earlier stuff class AnimateObjectPatch { [HarmonyTranspiler] static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //TODO } }
We want this to find the code instruction that calls GetTag and swap it to call GetTagOrStringProperty. There are a couple calls to GetTag in this function, but luckily the one we care about is the first one, so we can just modify the first call to GetTag that we find. We yield rather than simply returning, because we're returning an IEnumerable sequence which will automatically collect the values we return:
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { var found = false; foreach (var instruction in instructions) { if (!found && false) //TODO should check if the instruction calls GetTag { yield return null; //TODO should be the code instruction but for GetTagOrStringProperty instead found = true; } else { yield return instruction; } } }
Now we need to be able to examine the instructions. For a full rundown on how to do this, see [Harmony documentation here]. Essentially, we get the information of the GameObject methods GetTag and GetPropertyOrTag (using System.Reflection), which we can then use to check and construct CodeInstructions (a class defined by Harmony to make it easier to work with IL in C#). CodeInstructions come with a handy Calls method, which takes a MethodInfo and checks whether the instruction is a call to the method with that info. This solves our first TODO. For the second TODO, we create a new CodeInstruction with the operation code Callvirt (from System.Reflection.Emit) which calls the method specified by another MethodInfo, in this case the MethodInfo m_getTagOrProp which refers to GameObject.GetPropertyOrTag.
//...earlier stuff class AnimateObjectPatch { static MethodInfo m_getTagOrProp = typeof(GameObject).GetMethod("GetPropertyOrTag"); static MethodInfo m_getTag = typeof(GameObject).GetMethod("GetTag"); [HarmonyTranspiler] static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //...earlier stuff if (!found && instruction.Calls(m_getTag)) { yield return new CodeInstruction(OpCodes.Callvirt, m_getTagOrProp); found = true; } //...earlier stuff } }
In summary, our transpiler patch takes in the IL instructions for AnimateObject.Animate and passes them through unchanged until it finds an instruction that's a call to GetTag. It replaces that instruction with a call to GetPropertyOrTag, then passes all the remaining IL instructions through unchanged. We can clearly see how fiddly and fragile this is. If the call to GetTag is replaced with a call to something else, it will replace the next call to GetTag instead. If a new GetTag call is added before the BodyType one, it will replace that one instead and leave BodyType to exclusively pull from blueprint tags.
Even so, this is almost a best-case scenario for a transpiler. We don't have to worry about branching or labels, we only have to replace a single instruction, and the replacement does essentially the same thing but more permissive, so it's unlikely to break too badly. If you take a crack at transpiling on your own, try to keep your patches likewise as minimal as possible - nothing good awaits in the deep swamps of assembly metaprogramming.