Custom Triggers - brandonandzeus/Trainworks2 GitHub Wiki

Custom Triggers

Writing a Custom Trigger is the most difficult exercise. The Monster Train Codebase offers no support here, and you will need to implement your hooks to get your trigger to work with Harmony Patches in most cases. Sometimes if your trigger is simple enough, it can be expressed in terms of other GameData types. For example, if you had a trigger that only activates when an imp is summoned on the same floor. That can be done without a Patch using concepts presented in earlier pages.

Imp Counter.

This is a trigger I previously made for a mod. This trigger activates when an imp is damaged, allowing another unit to do something in return.

Here's the code. It is not included in the Template, but if you want to see it in action, see this mod. If you want to see the usage of the trigger. That is here

    // This is just here to hold constants.
    public static class OnOtherMonsterHit
    {
        public static CharacterTriggerData.Trigger MonsterTrigger = (new CharacterTrigger("Trigger_OnOtherMonsterHit")).GetEnum();
        public static CharacterTriggerData.Trigger ImpTrigger = (new CharacterTrigger("Trigger_OnOtherImpHit")).GetEnum();
    }

    // Method in Character State that actually reduces the HP of a unit.
    [HarmonyPatch(typeof(CharacterState), nameof(CharacterState.ApplyDamage))]
    class OnOtherMonsterHitPatch
    {
        // Just to track HP/Armor before and after ApplyDamage is called.
        private struct CharacterHpState
        {
            public int hp;
            public int armor;
        }

        private static CharacterHpState hpState = new CharacterHpState();
        public static List<CharacterState> outCharacters = new List<CharacterState>();

        public static void Prefix(CharacterState __instance)
        {
            // cache the hp/armor of the unit.
            hpState.hp = __instance.GetHP();
            hpState.armor = __instance.GetStatusEffectStacks(VanillaStatusEffectIDs.Armor);
        }

        public static IEnumerator Postfix(IEnumerator enumerator, CharacterState __instance, CombatManager ___combatManager)
        {
            // An actual use of the yield return here. The method is already called and the enumerator is filled with data.
            // Reyield the data to the caller. Otherwise things will break.
            while (enumerator.MoveNext())
                yield return enumerator.Current;

            if (__instance == null)
                yield break;

            if (__instance.GetTeamType() == Team.Type.Heroes)
                yield break;

            var lastAttackerCharacter = __instance.GetLastAttackerCharacter();
            if (lastAttackerCharacter == null || lastAttackerCharacter.IsDead || lastAttackerCharacter.IsDestroyed)
                yield break;

            // Test if the character was hit. If the HP or Armor changes then it should trigger OnHit
            if (__instance.GetHP() == hpState.hp && __instance.GetStatusEffectStacks(VanillaStatusEffectIDs.Armor) == hpState.armor)
            {
                yield break;
            }

            outCharacters.Clear();
            RoomState room = __instance.GetSpawnPoint().GetRoomOwner();
            room.AddCharactersToList(outCharacters, Team.Type.Monsters);

            foreach (CharacterState character in outCharacters)
            {
                if (character == __instance)
                {
                    continue;
                }
                // Again how to actually queue a trigger.
                yield return ___combatManager.QueueAndRunTrigger(character, OnOtherMonsterHit.MonsterTrigger, fireTriggersData: new CharacterState.FireTriggersData
                {
                    // This hack is used to get the LastAttackerCharacter in CardEffects.
                    // Set TargetMode.LastAttackedCharacter, unintuitive, but that's how the code for overrideTargetCharacter was written.
                    overrideTargetCharacter = __instance.GetLastAttackerCharacter(),
                });
                if (__instance.GetHasSubtype(SubtypeManager.GetSubtypeData(VanillaSubtypeIDs.Imp)))
                {
                    yield return ___combatManager.QueueAndRunTrigger(character, OnOtherMonsterHit.ImpTrigger, fireTriggersData: new CharacterState.FireTriggersData
                    {
                        overrideTargetCharacter = __instance.GetLastAttackerCharacter(),
                    });
                }
            }
        }
    }