Modding Monster Train – Patch to Allow for Adding Custom Units with Custom Syntheses

July 28, 2021

Modding with Harmony

Monster Train


Title:
My Monster Train Custom Unit Synthesis Patch

By:
Steven lilley (Me)


Github – Link

Description:
Link to my github page leading to my patch for adding custom units and syntheses to Monster Train.


Overview

Monster Train modding suffered from a lot of issues when the latest large DLC was made, the Last Divinity. Trainworks, a modding API created to help with modding Monster Train, was created before this DLC, and its addition broke many of its features.

One of the large additions from the DLC itself was unit synthesis, which let you break a character down into a unique upgrade for another character. This however was impossible to implement with Trainworks since it encountered new bugs because of unforeseen interactions with the new content. Originally, it would not even run if using new content, but a few updates to Trainworks at least made using them possible. However, unit syntheses continued to be an issue that did not work.

The existing custom units did not have any essences as they were created before the DLC. Trying to synthesize them into another unit would then create a softlock in the game. Being such a crucial aspect of the game, I investigated the error in an effort to remove it either on my own or through additional information to improve future iterations of Trainworks.

Issue and Monster Train’s UnitSynthesisMapping Class

CollectMappingData Method

The new DLC added a class within Monster Train’s base code named UnitSynthesisMapping which is responsible for creating the list of unit syntheses and holding that data for use in game. It has a method named CollectMappingData, which searches through the AllGameData for a list of all the CharacterData and all the CardUpgradeData in the game. It creates an initial list from every CharacterData in the game (to make sure every character has one, and only one upgrade).

While this appeared to be a perfect point to approach this issue, it was a bit indirect. This class was contained within a Unity Scriptable object, and most of the properties and methods contained within were private and very encapsulated. My best guess is that this Scriptable Object exists for the Monster Train team within the Unity Inspector and they actually call the CollectMappingData method with a button in the Inspector. This is because that method specifically has a Unity attribute called Context Menu which lets it be called from within the Inspector, outside of running the game. This threw me off for a while since I was trying to track when this method was called, and it turns out it never gets called naturally during the running of the game.

CollectMappingData Code from Monster Train

[ContextMenu("Collect Mapping Data")]
	private void CollectMappingData()
	{
		this.editorMappings.Clear();
		foreach (CardData cardData in this.allGameData.GetAllCardData())
		{
			if (!cardData.IsArchived && cardData.IsSpawnerCard() && !cardData.GetSpawnCharacterData().IsChampion())
			{
				this.editorMappings.Add(new UnitSynthesisMapping.MappingEntry(cardData.GetSpawnCharacterData(), null));
			}
		}
		this.editorMappings.Sort((UnitSynthesisMapping.MappingEntry a, UnitSynthesisMapping.MappingEntry b) => a.character.name.CompareTo(b.character.name));
		foreach (CardUpgradeData cardUpgradeData in this.allGameData.GetAllCardUpgradeData())
		{
			if (cardUpgradeData.IsUnitSynthesisUpgrade())
			{
				foreach (UnitSynthesisMapping.MappingEntry mappingEntry in this.editorMappings)
				{
					if (mappingEntry.character == cardUpgradeData.GetSourceSynthesisUnit())
					{
						mappingEntry.cardUpgrade = cardUpgradeData;
					}
				}
				cardUpgradeData.InternalSetLinkedPactDuplicateRarity(CollectableRarity.Rare);
			}
		}
	}

Calling Private Methods with Harmony and Reverse Patch

I covered this in a previous blog post, but it did turn out that this method worked well for accessing a calling a private method from within the base game’s code when I wanted to. Since I just wanted to forcibly call that private method, CollectMappingData, I did not need any other extra logic to run at the time. This makes the code for it very tight as follows:

[HarmonyPatch(typeof(UnitSynthesisMapping), "CollectMappingData", new Type[] { })]
    public class RecallingCollectMappingData
    {
        [HarmonyReversePatch]
        public static void MyTest(object instance)
        {
            // It's a stub so it has no initial content
        }
    }

The hardest part of implementing this was that it would work easiest with the single existing instance of the UnitSynthesisMapping class during the runtime of the game. We could instantiate our own instance to supply to this method, but without the other data it did not work at all. I was finally able to find a route to getting a reference to the actual existing instance through Trainworks actually.

Trainworks has a way to access the AllGameData class instance in the game, which gave me an entry point into a lot of data in the game. Through this, I was able to access the BalanceData class, which then gave me direct access to the existing UnitSynthesisMapping instance. This could then be passed into my Reverse Patch to call the method just as if it had been called from within that specific instance, which has all the access to the game’s data and existing lists if needed. This example path is shown below:

public static void FindUnitSynthesisMappingInstanceToStub()
        {
            // Gets a reference to AllGameData with Trainworks
            AllGameData testData = ProviderManager.SaveManager.GetAllGameData();
            CustomUnitSynthesisPatcher.Log("Got reference to AllGameData: " + testData.name);

            // Use AllGameData to get access to BalanceData
            BalanceData balanceData = testData.GetBalanceData();
            CustomUnitSynthesisPatcher.Log("Got reference to BalanceData: " + balanceData.name);

            // Use BalanceData to get access to the current instance of the UnitSynthesisMapping
            UnitSynthesisMapping mappingInstance = balanceData.SynthesisMapping;
            if (mappingInstance == null)
            {
                CustomUnitSynthesisPatcher.Log("Failed to find a mapping instance.");
            }
            else
            {
                CustomUnitSynthesisPatcher.Log("Able to find mapping instance: " + mappingInstance.GetID()); // Test to see if this is a different instance
            }

            // Calls CollectMappingData method
            RecallingCollectMappingData.MyTest(mappingInstance);
            CustomUnitSynthesisPatcher.Log("Called CollectMappingData.");
        }

Key String Tests for this Approach: CardEffectData and CardUpgradeData

Renaming the EffectStateName of the CardEffectData to Override Trainworks Default

It turns out two of the main hiccups I ran into with this approach were simply because of string checks not finding exactly what they wanted (or accidentally finding too many strings that were exactly the same). The first issue was in the CollectMappingData method’s first foreach loop, which includes in its if statement that cardData.IsSpawnerCard(). Somewhere along the lines it checks that the card has a CardEffectData specifically named “CardEffectSpawnMonster”. This was not being picked up initially however because Trainworks sets that property of your card to a very long string that also included “CardEffectSpawnMonster”, but all that information kept that part of the check from returning true. So simply making sure to rename that specific CardEffectData object’s EffectStateName to “CardEffectSpawnMonster” after setting EffectStateType (since this includes methods for creating its own EffectStateName) gets through this issue.

Setting the UpgradeTitleKey of your Synthesis CardUpgradeData to Override Trainworks Default

It just so happens that Trainworks default string options for some of these do not match the exact needs of this new Monster Train class (as they were made before these existed), and this was another case of that (although it made more sense). In the synthesis you build, if an UpgradeTitleKey is not set, a fairly empty default one is made for your CardUpgradeData. If you do not set this for several CardUpgradeData objects you make, then they will all have this SAME default UpgradeTitleKey. As a duplication check however, when adding a new CardUpgradeData to the overall list in the game, Trainworks checks by UpgradeTitleKey whether that name already exists or not, and if it does, it removes it from the list and adds the new one. As one would expect, if you have a bunch of objects with the same UpgradeTitleKey then, they all continually get replaced until only the last one remains (as it never gets replaced).

This is easy enough to solve by simply making sure to set a unique UpgradeTitleKey for each of your unit synthesis CardUpgardeDataBuilder objects. This is all that is required to make sure they do not keep deleting themselves in the current iteration of Trainworks (and is just a good practice in general).

via Blogger http://stevelilleyschool.blogspot.com/2021/07/modding-monster-train-patch-to-allow.html

Art Software Resources for Wacom One

July 20, 2021

Software Options

Digital Art


Title:
What’s the best software for a Wacom drawing tablet?

By:
Bruno Brasil – Drawing Tablet World


Drawing Tablet World – Link

Description:
Gives a list of softwares to use with Wacom tablets, with descriptions of what they offer.


Overview

I recently received a Wacom One tablet, and wanted to look into which art softwares work with it and work with it best. As far as I can tell it seems that most general digital art tools will translate decently to greatly when working with a tablet in this day and age, so it doesn’t seem limited in that aspect at all. Now I just need to figure out which one(s) make the most sense for what I want to use it for, and this resource seems like a decent starting point.

After this, I want to work it down to one or two softwares and start to round up some tutorials to get my footing with them. Most likely I will look to start with Adobe Photoshop since I have a little experience with it already and it’s always at least a decent option for almost any need. From there, I will most likely see what options those who make 2D game assets tend to use if possible, or which are just easier to pick up.

The list they provide is as follows:

  • Adobe Photoshop: (image editing, digital art, sketching)
  • Adobe Illustrator: (logo design, vector illustration)
  • Corel Painter: (digital painting, sketching)
  • Clip Studio Paint: (comics, illustration, 2D animation)
  • Autodesk SketchBook: (digital art, sketching)
  • Affinity Designer: (digital art, logo design, vector illustration)
  • Krita: (digital art, sketching, 2D animation)
  • GIMP: (image editing, digital art, sketching)
  • Inkscape: (logo design, vector illustration)
  • ArtRage: (digital art, sketching)
  • Corel Draw (Bonus): (logo design, vector illustration)

via Blogger http://stevelilleyschool.blogspot.com/2021/07/art-software-resources-for-wacom-one.html

Modding Monster Train – Harmony Reverse Patches

July 14, 2021

Modding with Harmony

Monster Train


Title:
Harmony ParDeike – Reverse Patch

By:
Harmony


Harmony – Documentation

Description:
Harmony documentation on patching with the Reverse Patch.


Overview

I was investigating ways to use Harmony to access private methods and fields from classes within the Unity target game itself, Monster Train in this case. One of the first direct options I came across was using reverse patches.

Basics of the Reverse Patch

“A reverse patch is a stub method in your own code that ‘becomes’ the original or part of the original method so you can use that yourself.”

    Typical use cases:

  • easily call private methods
  • no reflection, no delegate, native performance
  • you can cherry-pick part of the original method by using a transpiler
  • can give you the unmodified original
  • will freeze the implementation to the time you reverse patch

Implementation

After seeing its first use case being calling private methods, I thought this would be a perfect tool to use. As far as I have found, it appears to need a reference to an instance of the object that the private method belongs to. This has proved rather troublesome. While the setting up of the reverse patch has appeared to work and run, it is running into other issues that require further research.

The class I am trying to access is a Unity Scriptable Object, and has no easy way to directly access the existing instance (that I have found so far). I tried circumventing this by creating my own instance to work from, but that has its own issues. The new instance, if it is even being generated properly, is not intialized in any way and is missing integral references, such as to the AllGameData, which are also difficult to inject into the instance. This is again because it is a Unity Scriptable Object, so most fields are private but serialized, so they are very protected and only exposed in the Unity Inspector to make the necessary references I am missing in a newly generated instance.

via Blogger http://stevelilleyschool.blogspot.com/2021/07/modding-monster-train-harmony-reverse.html

Unity Shader Graph – Black Hole VFX – by Gabriel Prod.

July 7, 2021

Shader Graph

Unity


Title:
Unity VFX & Shader Graph – Black Hole Effect Tutorial

By:
Gabriel Aguiar Prod.


Youtube – Tutorial #1

Description:
Tutorial for a black hole VFX using Unity’s Shader Graph, particle systems, and Visual Effect graph.


Title:
{ How to CHANGE the SKYBOX in Unity } – HDR Textures in the description

By:

GameDevTraum


Youtube – Tutorial #2

Description:
Quick tutorial to use texture as Skybox in Unity.


General Notes

Fixing Errors

I immediately ran into an issue with the Scene Color node. The fix for this was going to the Main Camera and setting the “Opaque Texture” to ON (found under the Rendering section of the Camera). This made the shader and material match the scene color appropriately, giving that transparent look.

I found a generic space texture to use for my background to emulate the one used in the tutorial. I turned it into a Cube texture to create a Cubemap material to use as my Skybox material. That was what I used Tutorial #2 linked to above for.

I was still having an issue seeing the effects in the scene view (it was just remaining as a matte gray plane). I found that modifying the Opaque Texture flag in the UniversalRenderPipelineAsset (High-Quality for me and my current Unity version) resolved this issue, as described here:



Unity Questions – scene color node not working in shader graph

Multiply to Control Effect Distribution

They multiplied the noise with a round particle texture to create a round visual effect on the rectangular plane that contained all the interesting visual effects. The white parts of the texture contained the focus on the noise, while the black parts did not receive impact from the noise. The transition between the two then also creates a bit of a smoother transition from where the distortion occurs to the lack of distortion.


My Resulting Black Hole VFX from Following Tutorial #1


My Resulting Shader Graph for the Heat Distortion Following Tutorial #1


My Resulting Visual Effect Graph for the Floating Particles Following Tutorial #1

via Blogger http://stevelilleyschool.blogspot.com/2021/07/unity-shader-graph-black-hole-vfx-by.html

Modding Monster Train – Using Harmony to Track Methods and Log

July 1, 2021

Modding with Harmony

Monster Train


Title:
Getting Started with Trainworks Modding Tools

By:
KittenAqua


Github – Wiki

Description:
More detailed and in-depth documentation on properly setting up a C# project for modding Monster Train as well as examples for modding various content.


Title:
Harmony Patching Article

By:
Harmony


Harmony – Article

Description:
General documentation on Harmony.


Overview

As of the writing of this blog, the state of the Trainworks modding API with Monster Train after its DLC release is a bit rough. Creating custom cards was a core feature of Trainworks, but the DLC update has broken a lot of the original system. I have been investigating these issues to see if I can identify the problem(s) and potentially fix them. This has led me to learning a lot about using Harmony, a tool for modding Unity games, especially in a way to create logs to help me track bugs and problems when integrated with BepInEx.

Harmony Basics with Trainworks

The ‘Getting Started with Trainworks Modding Tools’ link above was very helpful in covering the basics of using Harmony with Monster Train. The most common methods used with these patches are ‘Postfix’ and ‘Prefix’.

Each patch is its own class, which contains methods performing the patching. It must also use attributes supplied by the Harmony API to indicate that it is a Harmony patch to be picked up when running the mod. This attributes for these cases generally contain information such as: the method name, the parameters of the method (if an overloaded method), and the class name containing said method.

Prefix and Postfix Arguments

The list of arguments covered by the ‘Getting Started…’ link above is:

  • Any arguments the original method took, with the same type and name
  • The instance of the class the method has been called on: ClassName __instance (two underscores)
  • Any fields of the instance, whether public or private. They should have the same name and type as the original, but with three underscores before: FieldType ___fieldName
  • The return value of the method: __result (two underscores)
  • A couple other things that aren’t used as often. Reference the official Harmony documentation for details.

The ‘ref’ keyword is used with the Postfix and Prefix methods often. This is because it is common that you will want to take in a variable from the existing methods and actively change the value of said argument.

Postfix

Postfix patches are used to perform actions after a method in the established project (Monster Train in this case) is called. This means it also has access to the result of said method to read from it or even alter it.

Prefix

Opposite of the Postfix, Prefix methods are those which are executed before the original method. As such, they are uniquely situated to prevent the original method from even executing if wanted (although this is something that is advised to do with care as it can easily break systems relying on it running).

From the official Harmony documentation, these are commonly used to:

  • access and edit the arguments of the original method
  • set the result of the original method
  • skip the original method and prefixes that alter its input/result
  • set custom state that can be recalled in the postfix

Example Logging Monster Train CardUpgradeData Setup Method

While investigating an issue with unit synthesis in the game, I encountered an error that led me to explore the Setup method in the CardUpgradeData class of Monster Train, as this appears to be a core part of the synthesis process. I decided to setup a Postfix method to return some information from this method when it was run (as well as to just help determine when this method was being run).

In this investigation I found that selecting existing units (which clearly have all the assets necessary) in the unit synthesis temple would call this method, along with a specialized unit synthesis upgrade. However, selecting a custom unit did not call this method at all. While this normally would make sense obviously since they do not have a unit synthesis upgrade made for them, the Trainworks tool is supposed to make a replacement synthesis upgrade in this case, which I thought may be picked up by this system.

Class for Logging

[HarmonyPatch(typeof(CardUpgradeState))]
    [HarmonyPatch("Setup")]
    [HarmonyPatch(new Type[] { typeof(CardUpgradeData), typeof(bool)})]
    public static class LogSynthesis
    {
        private static void Postfix(ref CardUpgradeState __instance)
        {
            //string cardUpgradeDataId = __instance.GetCardUpgradeDataId();
            string upgradeDescriptionKey = __instance.GetUpgradeDescriptionKey();
            string assetName = __instance.GetAssetName();

            if(upgradeDescriptionKey == null || assetName == null)
            {
                NyanCat.LogSomething("LogSynthesis hit a null string");
            }
            else
            {
                NyanCat.LogSomething(
                    "CardUpgradeState Setup method detected with data: \n" +
                    $"\tUpgrade Description Key: \n" +
                    $"\t\t{upgradeDescriptionKey}\n" +
                    $"\tAsset Name: \n" +
                    $"\t\t{assetName}");
            }
        }
    }

Fig. 1: Screen with Game, my Patch, and Log Console Output Example

via Blogger http://stevelilleyschool.blogspot.com/2021/07/modding-monster-train-using-harmony-to.html

Doppler Effect in Mario Kart – Game Audio – by Scruffy

July 1, 2021

Doppler Effect and Audio Controller

Game Audio


Title:
Mario Kart and the Doppler Effect

By:
Scruffy


Youtube – Information

Description:
Explanation of how Mario Kart creates the doppler effect and efficiently distributes audio to multiple players.


Overview

This video covers how Mario Kart Wii specifically uses the doppler effect, as well as just how some of their audio systems work in general. It is decent coverage of how to implement a basic doppler effect system into a game in general.

Fig. 1: Image from “Mario Kart and the Doppler Effect” Video Above by Scruffy

Setup of Doppler Effect System

The key is the relationship between sound frequency and relative velocity of objects. Their approach to measure this is just by measuring the distance from the audio source to the audio listener each frame, and if there is a difference, that is used for a relative velocity term. This relative velocity term is bound to some negative and positive scale (one direction meaning higher frequency and the other being lower frequency). The way this relative velocity maps to a difference in sound frequency can use any mathematical relationship to fit whatever feels best (i.e. linear, logarithmic, etc.).

They break this core down into three basic steps:

  1. Get distance between source and listener each frame
  2. Subtract from previous for rate of change
  3. Map rate of change to determine sound playback speed (to taste)

Expansion of the System and Efficiency

This explanation shows the direct relationship between an audio source and an audio listener, but games tend to have many audio sources. They show how immediatley this can at least be simplified by having some audio distance so the calculations only need to be performed on objects within a certain distance of the listener. The other big part of simplifying the system is just limiting which sources implement the doppler effect. Not every sound needs to use this, so it can be removed from many standard sources (i.e. the crowd in Mario Kart).

Split Screen Solution

This is fairly niche, but still interesting. With split screen, the audio of multiple listeners needs to come through a single audio output. Since they may experience different levels of the doppler effect for the same audio sources, they needed a solution to provide an experience that does not sound like a mess. Their approach was that each player only makes sound in their own camera (so one player is not listening to the other on the same screen), and when dealing with outside sources, only the player closest to the audio source is taken into account. The other player’s audio for that source is simply negated. This is a nice solution as the system already takes the distance between sources and listeners into account anyway.

via Blogger http://stevelilleyschool.blogspot.com/2021/07/doppler-effect-in-mario-kart-game-audio.html