User:Illuminatiswag/Sandbox: Difference between revisions

From Caves of Qud Wiki
Jump to navigation Jump to search
(version history...)
(preliminary harmony tutorial)
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{favilink|lush acid}}
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.


<span class="qud-image qud-tooltip"><span class="qud-image-link-image-container">[[File:mechanimist zealot.png|16px|link=Mechanimist zealot]]</span><span class="qud-image-link" style="color:#b1c9c3">[[Mechanimist zealot|<b><span style="color: #b1c9c3;">Mechanimist zealots</span></b>]]</span><span class="qud-tooltip-text delayed-tooltip">hi everyone!</span></span>
In this tutorial, we'll be making it possible to bring statues (like those created by a {{f|lithofex}})
to life with a {{f|nano-neuro animator}} or {{f|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.


== 203.33 ==
== Preliminaries ==
Released April 22, 2022
 
* The Joppa Zealot is now much louder.
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
* Improved the travel direction selection for the Joppa Zealot's particle effects.
the ObjectBlueprints file (Items.xml) for the {{f|nano-neuro animator}} to see how it animates an object.
* Description text for Wilderness Lore: Rivers and Lakes now correctly describes the skill.
We see it has the AnimateObject part, which we can look up later in the decompiled game code.
* Slynth no longer leave behind human corpses.
Next, we look at the blueprints (in Furniture.xml) for an object that can already be animated, the {{f|iron maiden}}.
* Kaleidoslugs in the Yd Freehold now belong to the Yd Freehold faction.
These are the relevant lines:
* You can no longer cross into [redacted] from outside the [redacted].
 
* Made some clarifications in the help description of the Strength attribute.
    <tag Name="AnimatedSkills" Value="Persuasion_Intimidate" />
* Containers that occupy multiple tiles will now be moved together, and break if forcefully separated.
    <tag Name="BodyType" Value="IronMaiden" />
* You will now be presented with a list of containers to open if several occupy the same tile.
    <tag Name="Animatable" />
* Cybernetic implants are now described as such when specified in baetyl demands and similar contexts.
 
* Pathfinding now more studiously avoids giant clams.
We also check the blueprint in the same file for "Random Stone Statue", which is what we want to animate.
* Fixed a bug that caused odd behavior from armor averaging when some of the body parts involved had 0 AV/DV and others didn't.
It's missing all the tags we see in the iron maiden, but has the part "RandomStatue", which we'll look up
* Fixed some cases where a lot of watervine or brinestalk would spawn in the same tile.
in the decompiled game code. We could just add them to the blueprint via normal
* Fixed a bug that prevented the Joppa Zealot's particle text effect from displaying.
* Fixed a bug that caused some liquids not to appear is valid cooking ingredients when they should have.
* Fixed a typo in the names of recipes that use cloning draught as an ingredient.
* Fixed some incorrect grammar in sultan tomb inscriptions.
* Fixed a typo in the Tongue & Cheek recipe effect description.
* Fixed a bug that caused the spreading of Klanq in the vicinity of the Six Day Stilt to only function within the cathedral.
* Fixed a bug that allowed you to loot destroyed containers with the Open action.
* Fixed a bug that caused the AI behavior of some movement and retreat related abilities to malfunction.
* Fixed a bug that caused furniture to rarely spawn stacked.
* Fixed a bug that increased world map lag.
== 203.32 ==
Released April 15, 2022
* Owned containers are shown as owned in the trade UI. Items in the owned container that belong to you are labeled to show that.
* The item listing when opening a container now shows which container you are opening.
* NPCs are now a bit less prone to attacking you if you damage objects owned by their faction, especially if the damage seems accidental.
* Abilities granted by items now have their cooldowns tracked separately from the equipped status of the item, and you no longer have to wait before unequipping an item with an ability on cool.
* You no longer automatically reload broken or rusted throwing weapons.
* The reload prompt now displays the current reload keybinding.
* Trolls no longer spawn troll foals on the world map, causing game crashes.
* Living things without brains can now be cloned using cloning draught rather than remaining in budding state indefinitely.
* Clones of animated objects now also indicate that they are animated in their display names.
* Animated doors no longer cause adjacent doors to open and close to match their own state.
* When you use Temporal Fugue in a body that is not your own and then try to talk to the clones, you no longer get the message "You make small talk with yourself" before entering conversation.
* The look display no longer forces capitalization on proper names that are not supposed to be capitalized.
* Fixed a bug that caused some merchant advertisements to not properly reveal the locations of their respective merchants when read.
* Fixed a bug that caused rare exceptions in handling liquid coatings.
* Fixed a bug that made custom visages fail to function if unimplanted and reimplanted.
* Fixed a bug that sometimes caused NPCs to equip items they weren't supposed to, such as Hand-E-Nukes.
* Fixed a bug that sometimes caused mine explosions to be displayed as coming from a *PooledObject*.
* Fixed a bug that sometimes caused the Hydropon to fail to build.
* Fixed a bug that caused zones to be empty if rebuilt after visiting a sky zone above.
* Fixed a bug that caused Svardym to croak in unspoken conversation text. ((e.g. water ritual reputation))
* Fixed a bug that caused hostile creatures to be present near the charred tree of Eskhind.
* Fixed a bug that caused the death message of being transmuted into a gem to not display.
* Fixed a bug that caused you to pray twice at statues created by the gaze of a lithofex.
* Fixed a bug that caused the stairs of the rust wells to be blocked by shale;
* Fixed a bug that caused some nearby items, minimap and message log settings to be improperly stored.
* Fixed a bug that allowed you to complete the quest "Weirdwire Conduit.. Eureka!" after collecting enough wire, whether it was still in your inventory or not.
* Fixed a bug that caused the effects of items such as torches and braziers to display over inventory icons if they lined up with their cell.
* Fixed a bug that allowed you to loot multiple sultan reliquaries if you interacted with them across zone borders.
* Fixed a harmless exception when aborting out of Spray-a-Brain usage from the stage of selecting a target.
* MORE ABOUT THI
== 200.41 ==
Released May 23, 2020 (beta branch)
*
* Tomb-related notes
* The Tomb-dwelling pangoloids knows as the mopango have officially moved in.
* Added or refined several types of mopango.
* Added a new quest: The Buried Watchers. Talk to Zothom in Ezra.
* Added a new quest: Fraying Favorites.
* Strange entities now (officially) roam the Tomb.
* Added a new legendary ooze, k-Goninon.
* Added the repulsive device.
* Added [redacted], [redacted], [redacted], and [redacted].
* We made some UI improvements to the Tomb-teleportation mechanic.
* Changed the ASCII floor glyph for teleportation-safe tiles.
* Added a new effect, tomb-tethered, to better message when creatures are safe from the teleportation effect of the Bell of Rest. Creatures are tomb-tethered when they both bear the Mark of Death and are occupying a tile that's teleportation-safe.
* Fixed a bug that caused you to be Tomb-teleported back to the beginning of the crematory from the supposedly safe Columbarium.
* Fixed a bug that caused exit teleporters in Lower and Upper Crypts to send you all the way back to the catacombs.
* Fixed a bug that caused issues at the recoming nook.
* General notes
* Added new loading status indicator art.
* The new stair tiles now look better when the 'Always highlight stairs' option is enabled.
* Added a new Modding option: 'Select enabled mods on new game'.
* "Press space" dialogs now close only when you press Escape, Enter, or Space.
* Goatfolk can now give directions in conversations.
* The overburdened status effect is now updated more reliably when your carrying capacity changes.
* The weight display in the trade UI is now more accurate.
* You no longer receive messages about merchants restocking if you're not in the same zone as they are.
* Objects without bodies can no longer be knocked prone.
* Save files now remember which mods are enabled and prompt you to re-enable those mods before loading.
* Fixed a bug that caused the onset of fungal infections to sometimes be 8-10 turns instead of 2-3 days.
* Fixed a bug that caused symbiotic fireflies to destroy themselves, and turn into "Objects", when equipped.
* In the classic UI option screen, slider and selection options that don't fit well in one line now occupy two lines.
* Exiting the world map now avoids placing you in cells where you would have to swim.
* [modding] Fixed a bug that caused GlobalConfig.json to be unmoddable.
* [modding] An objects can now set the ReplacementObject member of an ObjectCreatedEvent to return a replacement object instead of itself during object creation.
== 200.40 ==
Released May 15, 2020 (beta branch)
* Tomb-related notes:
* Added new tiles for Eater statues, Eater holograms, and urn duster.
* Crypt sitters and conservators no longer cause unintended trouble in dynamic encounters.
* General notes:
* New item mod: fitted with suspensors.
* Mafeo now has a pickaxe for sale.
* Excluded some more creatures from spawning as Mechanimist converts, including pulsed field magnets.
* Added descriptions for the following objects: rubble, burnished azzurum (formerly aquamarine), banana rancher, hammock, brick, Warden 1-FF, Mayor Haddas, and Yla Haj.
* Gave an ASCII makeover to the following objects: gravestones, unripe banana trees, Yla Haj, floor cushion, medium boulder, Zothom (also tweaked his tile colors), bookshelf, sign, power line, brick, woven basket, hammock, flux gauge, weathered wood, and sultan tomb murals.
* Powered exoskeletons can now have a variety of item mods.
* When you're under the effect of the gamma moth Mutating effect, random mutations you might acquire are now reseeded at some point during its duration.
* Sheba Hagadias no longer succumbs to natural behavior like going on a pilgrimage or urn dusting.
* Things without brains can no longer be dazed or stunned.
* Holograms can no longer be harvested or butchered.
* Small stones and large stones are no longer flammable.
* Fixed a bug that caused the Golgotha chutes to generate disconnected.
* Fixed a a bug that caused mouse movement to not work properly on the world map.
* Fixed a bug that caused missile weapons to hit non-blocking inanimate objects in their paths.
* Fixed a bug that caused merchants to spawn with double the inventory they're meant to.
* Fixed a bug that immobilized aquatic creatures.
* Fixed a bug that caused worn items effects to not go away when you removed the item.
* Fixed a bug that sometimes caused Barathrum's study to generate with a down staircase.
* [debug] Space no longer accepts the 'Would you like to die?' prompt.
== 200.35 ==
Released May 8, 2020
*
* Tomb-related notes:
* Expanded the Grand Vestibule to multiple maps.
* Changed the Omonporch entry zone to a new map: Court of the Sultans.
* Made various tweaks and enhancements to the Arcade to the Twin Gates.
* Made various tweaks and enhancements to the Columbarium.
* Made the Ezra and Arcade murals more authentic.
* Added a bell tolling sound effect to the Bell of Rest.
* Made Sixshrew a real merchant.
* Gave dialog to Yla Haj and Warden 1-FF.
* Tweaked Mayor Haddas's dialog.
* Gave better equipment and wares to Yla Haj.
* Gave several of the Tomb zones better names.
* Gave sky maps more sensible names.
* Added a cistern ruin to the east of the Arcade.
* Added more graffiti to the robbers' cut.
* Made some architectural changes to the Tomb outer walls and the Spindle chamber.
* Added a new tile for rubble.
* Added some more Eater statue and hologram tiles.
* General notes:
* Faction leaders now have their prowess more accurately judged and displayed in their look descriptions.
* Autowalk, autoexplore, and rest are now interrupted when your companions engage in combat, take damage, or die, as long as they're visible or audible.
* We changed the option "Maximum autoexplore squares/sec" to "Maximum automove squares/sec" and made it apply to all forms of automove.
* Metal items now retain less liquid, and fur items retain more.
* Quartzfur hats now message themselves as being made of quartz rather than glass.
* Autocollect can now be disabled on empty waterskins.
* Spiders no longer attack allied creatures that are stuck.
* Player-controlled spiders no longer autoattack stuck creatures.
* Destroying or teleporting a web now frees anything stuck in it.
* When an object is phased multiple times, damage from being forced into phase by reality stabilization now occurs only when the last effect is terminated.
* Changed the message when you try to remove an unremovable cybernetic implant.
* Items that affect the chance of salvaging multiple bits, like advanced toolkits, are no longer used on items that can only ever disassemble into one bit.
* The random-buy mutation choices should now be more stable for a given seed.
* If you're out of 2+ point mutations to buy, you're now presented with 1-point mutations.
* The look and target picker "Locked" status toggle is now remembered across games and reloads.
* In the classic UI, the cooking menu now reutrns to the selected option instead of the top of the list when you choose a recipe to cook then decide go Back.
* Dromad trader psychic thralls or domesticated slaves no longer generate map notes.
* Geomagnetic discs can no longer spawn with elemental damage mods.
* Made some improvements to geomagnetic disc pathing.
* Fixed an improper item ownership warning when you tried to examine or disassemble items in the inventories of certain creatures you were dominating.
* Fixed a bug that caused Flurry to not work on inanimate objects, which in turn caused NPCs with flurry to stand there inert instead of attacking inanimate objects they were angry at.
* Fixed some bugs with color rendering and formatting in books.
* Fixed a bug with liquid-fueled power consumption, such as in gyrocopter backpacks.
* Fixed some performance issues with normality gas and norm cores.
* [debug] Made wish string matching more accurate.
* [debug] The "Do you really want to die?" promp no longer accepts space or enter as input.
* [modding] The map editor now supports Ctrl-C to copy a selected region, which turns your brush into a rectangle the size of that region. Ctrl-click pastes the copied region, and right-click deletes the region bounded by the selected rectangle.
* [modding] The map editor now properly renders most walls, water tiles, and fences.
* [modding] The map editor now has tile previews in the blueprint selector.
== 200.29 ==
Released May 2, 2020
*
* Tomb-related notes:
* We added polish to the architecture of the Tomb.
* Added a new exterior caryatid wall and extended it up and down through the Z-dimension as appropriate.
* Added support pillars beneath the Tomb.
* Added structural ribs.
* Swapped the locations of the Life and Death Gates.
* Redesigned the Grand Vestibule.
* Added a lot more graffiti to the robbers' cut.
* Cleaned up the maps around the Liminal Way.
* Moved the up staircases in the Folk Catacombs.
* Replaced the Lace Stairways to the crematory with access corridors.
* General notes:
* The display format of some zone names has changed.
* Inscribed funerary urns no longer report being empty.
* You're no longer asked to drink from full urns as a quest step in dynamic quests.
* Automove and rest are now interrupted by decarbonizer beams that are detectable to you by sight, sound, or smell.
* When you're about to stop flying and you're at risk of taking fall damage (ie, at least one stratum above ground), you're now given an appropriate warning message.
* Fixed a bug with acid gas that caused the game to hang.
* Fixed a bug that caused items to retain more liquid than intended.
* [modding] Added a new API, StatShifter, to Parts and Effects that helps track stat shifts. See https://cavesofqud.gamepedia.com/Modding:StatShifter.
* [debug] Added a new debug option: "Show debug text for stat shifts".
* [debug] Added a new wish, "showstatshifts", that displays a list of items and effects shifting your stats.
* [modding] You can now select regions in the map editor by holding shift and dragging the select box over tiles. Ctrl-a selects the entire map.
* [modding] Redesigned the "selected cells" region of the map editor. You can now delete, bulk replace, and set ownership on objects in the selected region.
== April 24, 2020 (beta branch) ==* Tomb-related notes:
* We added more polish to the crematory.
* Redesigned the columbarium.
* Added ASCII art to the look screen for urns.
* Urn dusters now walk around dusting urns in the columbarium.
* Fixed some bugs with the interaction of various crematory objects.
* Added a visual effect for when objects are pushed by industrial fans.
* Added descriptions for the following objects: conveyor drive unit, conveyor belt, rubber curtains, bones, ashes, industrial fan, miasmatic ash, machine wall, metal door, plastic tree, statue of Eater, full-spectrum bright sconce, elevator shaft, empty urn, marble walkway, sunflower, and scrapable deposit box.
* Edited the ASCII glyphs for the entire crematory.
* [Redacted]
* General notes:
* You can now use water to clean your stained equipment via inventory actions on either your water containers or your stained equipment.
* You can now use the Walk keybind from the Look menu to autowalk to the tile you're looking at.
* Offhand attacks work again.
* Changed the glyph and tile colors of non-mayor quest givers in villages from dark green to bright yellow. Accordingly, we also changed Mehmet's color to bright yellow.
* Girshlings you encounter outside of Red Rock no longer advance 'What's Eating the Watervine?'.
* The water ritual now consumes a dram of liquid only when you initiate it instead of consuming one for every action you take during it.
* Force fields you can pass through no longer interrupt automove regardless of their source.
* Psychic hunters are no longer incorrectly interdicted on maps without reality stabilization.
* Cybernetic implants that occupy an equipment slot are no longer shown twice in the implantee's description.
* Temporary items can no longer be used as tinkering components.
* Improved the grammar in "you pass by" messages.
* Fixed a bug that caused Rebuke Robot to fail way more often than it should.
* Fixed a bug that caused take-all to not work in the classic UI looting menu when the cursor was on a category rather than an item.
* Fixed a bug that caused the 'Tweet this character build' button to not work.
* [debug]Added a new wish, "factionencounter:<faction>", that generates a special faction encounter in your current zone. For example, wishing for "factionencounter:Templar" generates a Templar faction encounter. You can override the tier and level values for the zone, both of which affect the creatures generated for the faction encounter, like so: "factionencounter:<faction>:<tier>" and "factionencounter:<faction>:<tier>:<level>".
* [modding] The ConversationScript part now supports a Color field that's used to specify a color or shader to be applied to the creature's conversation text.
* [modding] Self-closing <mutation /> tags in the XML no longer skip the next mutation when they're loaded.
* [modding] The new blueprints loader no longer crashes when given an <intproperty> with no Value (it assumes 0 instead).
* [modding] Added Transform > Flip Vertical to the map editor.
* [*][modding] Added File > Reload Blueprints to the map editor. This lets you reload ObjectBlueprints.xml without quitting and reopening the editor.
== 200.22 ==
Released April 17th, 2020 (beta branch)
* Note: this update will break save compatibility. If you'd like to continue a save from a previous patch, you can switch back to that patch's branch. From Steam, right-click Caves of Qud > Properties > Betas > choose a branch. Last week's branch is 200.21.
* Tomb-related notes
* Added a new gas: miasmatic ash.
* Grave moss now burns off into miasmatic ash.
* We made changes and added some polish to the crematory.
* Added industrial fans.
* Funerary urns now have names and inscriptions.
* Machine presses with only one piston now slam their target away instead of crushing it.
* The support pillars in the catacombs now come up through the crematory.
* Tweaked the layout of the conveyor belts and flame vents to be slightly more ordered.
* Added new wall tiles and changed the colors of some existing tiles.
* Replaced the existing machine room with a new one.
* Fixed a bug with the ASCII rendering of machine press.
* General notes
* Fixed a bunch of text coloring bugs introduced last patch. There'll likely be some more, though.
* We made some tweaks to liquid and liquid pool behavior.
* We renormalized the volumes of all the puddles, pools, and rivers in the game. Swim-depth pools (over 2000 drams) are now much more rare.
* Added a new status effect: wading. You now wade through pools of size 200-2000 drams. Wading gives -20 movespeed and interferes with some forms of movement, including sprinting.
* Entering a swim-depth pool from land now displays a warning message and requires you to confirm movement.
* Swim-depth pools now interrupt autowalk unless you are already swimming, flying, or out of phase with the pool.
* Some common liquid covering descriptions are now less verbose.
* Salt now only stains items when encountered in its pure form.
* Made the container preference for liquid collection more consistent. The order is: containers designated for autocollection of the liquid, then devices that use the liquid, then containers that already contain pure liquid of that type, then empty containers.
* You can now collect liquids into slotted liquid-fueled energy cells.
* Collecting a liquid now reports on which containers the liquid was collected into.
* Small spheres of negative weight now add to your weight when they're broken or otherwise inoperative.
* Psionic weapons with slotted cells now leave the cells behind when they evanesce.
* Psionic weapons can no longer be modded with tinkering.
* The [redacted] in Joppa now shows up in the alt display.
* Wraith-knights now despawn if their phylacteries are destroyed for any reason.
* Temporary items are now worthless, can no longer be cooked with, and can no longer have their bits harvested.
* Temporal fugue clones now leave behind any items they had acquired after being conjured.
* Eater's nectar injectors belonging to temporal fugue clones now affect only temporal fugue clones.
* We made some tweaks to the looting menu in the classic UI.
* The weights of takeable items are now displayed alongside the items themselves.
* The menu now has an adaptive width to avoid cutting off item names.
* Page up and page down now work more reliably.
* Encountering the following objects now interrupts autowalk: deep shafts, identified mines, aloes, spacetime rifts, and spacetime vortices.
* Artifacts with proper names are no longer treated as having proper names while unidentified.
* On the classic trade screen, we switched 'interact with the selected item' from the Tab key to the Space key. Tab now selects and deselects all items on the active column of the trade screen, as an analog to its take-all function in looting menus.
* Ice frogs no longer sit in immobile confusion until approached.
* Miner bots no longer generate mines based on modded grenades.
* NPCs are now less interested in using random items from their inventories as improvised weapons.
* Animating an object with Spray-a-Brain no longer causes every object of that type to be recategorized as a Creature.
* Items generated as quest items no longer cause every item of that type to be recategorized as a Quest Item.
* Answering no to using an unpowered advanced toolkit while autoexploring now interrupts the autoexplore, preventing message loops.
* Fixed some issues with Page Up and Page Down failing to scroll correctly on the classic inventory screen with very large inventories.
* Fixed a bug that caused the 'most recently played character' to net initialize properly.
* [debug] Added a new wish, "reveal settlements", that reveals all dynamically generated settlements on the world map.
* [modding] Fixed a bug that caused modded versions of Options.xml to fail to be initialized.
* [modding] Implemented Myopia as an AdjustVisibilityRadiusEvent, allowing equipment and parts to adjust the visibility

Latest revision as of 05:18, 26 September 2023

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.