Custom Monster Cards - KittenAqua/TrainworksModdingTools GitHub Wiki

These wiki pages are deprecated and are outdated. Please find the newer tutorials here

Custom Monster Cards

For many of you, this is probably the exciting part. In fact, it may be so exciting that you skipped right over the previous tutorial about spell cards. Don't do that. Monster cards are actually just spell cards that summon monsters, and I'm not going to repeat things that were already covered there, so go back and read it over.

Once you've done that, you can move onto the next section where we'll discuss our first monster card, a true classic.

Blue-Eyes White Dragon

Download the card art here, then download the monster art here. They're both static images. Animated characters are much more complicated, so they're covered in their own dedicated tutorial.

The first thing we're going to do is create a new subtype. It's very easy. For later convenience I'm going to put it in its own class.

class SubtypeDragon
{
    public static readonly string Key = TestPlugin.GUID + "_Subtype_Dragon";

    public static void BuildAndRegister()
    {
        CustomCharacterManager.RegisterSubtype(Key, "Dragon");
    }
}

One line of actual code. Nice and easy. Now let's make the card to go with it!

new CardDataBuilder
{
    CardID = TestPlugin.GUID + "_BlueEyesCard",
    Name = "Blue-Eyes White Dragon",
    Cost = 3,
    CardType = CardType.Monster,
    Rarity = CollectableRarity.Rare,
    TargetsRoom = true,
    Targetless = false,
    AssetPath = "assets/blueeyes.png",
    ClanID = VanillaClanIDs.Stygian,
    CardPoolIDs = new List<string> { VanillaCardPoolIDs.StygianBanner, VanillaCardPoolIDs.UnitsAllBanner },
    EffectBuilders = new List<CardEffectDataBuilder>
    {
        new CardEffectDataBuilder
        {
            EffectStateName = "CardEffectSpawnMonster",
            TargetMode = TargetMode.DropTargetCharacter,
            ParamCharacterDataBuilder = new CharacterDataBuilder
            {
                CharacterID = TestPlugin.GUID + "_BlueEyesCharacter",
                Name = "Blue-Eyes White Dragon",
                Size = 5,
                Health = 2500,
                AttackDamage = 3000,
                AssetPath = "assets/blueeyes_character.png",
                SubtypeKeys = new List<string> { SubtypeDragon.Key }
            }
        }
    }
}.BuildAndRegister();

Much of what's here is either the same as it was for spell cards or fairly self-explanatory. Let's go over the parts that changed.

  • CardType: If you want this to be a monster card, you have to set this to CardType.Monster. Don't forget!
  • CardPoolIDs: Unlike spell cards which are usually just stuffed into the MegaPool, monsters go into banner pools. Since this is a Stygian card (for now), we'll put it in the Stygian pool. We'll also put it in the UnitsAllBanner pool, which holds all banner units in the game.
  • EffectStateName: Notice how this is CardEffectSpawnMonster. All monster cards have this effect in common.
  • TargetMode: Again, all monster cards will use DropTargetCharacter.
  • ParamCharacterDataBuilder: This is the actual character data that the monster card will summon.
  • CharacterID: As with CardID, this must be unique.
  • Name: As with the card, setting the Name field will set it for all languages. If you want your mod to support localization, you'll want to use NameKey, which is covered in the custom localization tutorial.
  • Size: The capacity you need available in the room you want to summon the monster in.
  • Health: Its health stat.
  • AttackDamage: Its attack stat.
  • AssetPath: As with cards, this should be the path relative to your plugin dll. This will be the monster's art (which is distinct from the card's art, which we used up above). Setting it this way will only use a static image; for animated character art, see the dedicated tutorial.
  • SubtypeKeys: A list of keys corresponding to subtypes. This is where we use the Dragon subtype key we created higher up. If you wanted to use a vanilla subtype, All of the subtypes are documented in Trainworks.Constants.VanillaSubtypeIDs or you can find them in AssetStudio.

Add the card to your starting deck and hop ingame to ensure that it works as expected. Congratulations! You've made your first monster card. It's agonizingly boring, so let's make another one with a bit more spice to it.

Dragon Costume

Ever-efficient, we're going to reuse the subtype we made in the previous step by creating another Dragon card. Download the card art here and the character art here.

In the previous example, we created our character directly in the CardDataBuilder. As you continue to make more complicated characters, you'll realize this becomes incredibly hard to read when adding many effects. So for this example (and all future ones), we'll make the character first, then the card.

This character is a dragon costume. When it takes a hit, it puts on a new costume (graphical effect not included), gaining a damage shield.

var dragonCostumeCharacter = new CharacterDataBuilder
{
    CharacterID = TestPlugin.GUID + "_DragonCostumeCharacter",
    Name = "Dragon Costume",
    Size = 5,
    Health = 50,
    AttackDamage = 5,
    AssetPath = "assets/dragoncostume_character.png",
    SubtypeKeys = new List<string> { SubtypeDragon.Key },
    TriggerBuilders = new List<CharacterTriggerDataBuilder>
    {
        new CharacterTriggerDataBuilder
        {
            Trigger = CharacterTriggerData.Trigger.OnHit,
            Description = "Gain <nobr><b>Damage Shield</b> <b>{[effect0.status0.power]}</b></nobr>",
            EffectBuilders = new List<CardEffectDataBuilder>
            {
                new CardEffectDataBuilder
                {
                    EffectStateName = "CardEffectAddStatusEffect",
                    TargetMode = TargetMode.Self,
                    TargetTeamType = Team.Type.Monsters,
                    ParamStatusEffects = new StatusEffectStackData[]
                    {
                        new StatusEffectStackData
                        {
                            count = 1,
                            statusId = VanillaStatusEffectIDs.DamageShield
                        }
                    }
                }
            }
        }
    }
};
  • TriggerBuilders: How you give your characters effects. Composed of CharacterTriggerDataBuilders.
  • CharacterTriggerDataBuilder: A trigger and its corresponding effect.
  • Trigger: The character trigger to use. Of particular note: triggers will generate their own text. This example is a Revenge card, but we did not put Revenge in the description; the game will do it for us.
  • Description: Trigger builders have their own description field as well. This is appended after the trigger name. Altogether this will create the text "Revenge: Gain Damage Shield 1".
  • EffectBuilders: The effects that fire when the trigger is triggered. We've already seen how this works; it's the same here as it was everywhere else.

It may surprise you to hear that with monster cards, the monster data is the complicated part. The card builder is simple and will look pretty much the same for all monsters within a clan. For completeness' sake, here it is:

new CardDataBuilder
{
    CardID = TestPlugin.GUID + "_DragonCostumeCard",
    Name = "Dragon Costume",
    Cost = 2,
    CardType = CardType.Monster,
    Rarity = CollectableRarity.Common,
    TargetsRoom = true,
    Targetless = false,
    AssetPath = "assets/dragoncostume.png",
    ClanID = VanillaClanIDs.Stygian,
    CardPoolIDs = new List<string> { VanillaCardPoolIDs.StygianBanner, VanillaCardPoolIDs.UnitsAllBanner },
    EffectBuilders = new List<CardEffectDataBuilder>
    {
        new CardEffectDataBuilder
        {
            EffectStateName = "CardEffectSpawnMonster",
            TargetMode = TargetMode.DropTargetCharacter,
            ParamCharacterDataBuilder = dragonCostumeCharacter
        }
    }
}.BuildAndRegister();

Apple Morsel

This time we'll be adding a card of an existing type: a new morsel. Grab the card art here and the character art here.

Apple Morsel is a morsel in the same tier as Rubble Morsel. It's not very good and it'll show up a lot. Actually, playing with it will grossly skew your morsel pool toward bad cards, so don't try any serious runs. Its effect: it's so disgusting (because it's not a rock) that the eater takes 5 damage and gains Rage 3.

As with the previous example, we'll start with the character data.

var appleMorselCharacter = new CharacterDataBuilder
{
    CharacterID = TestPlugin.GUID + "_AppleMorselCharacter",
    Name = "Apple Morsel",
    Size = 1,
    Health = 1,
    AttackDamage = 0,
    AssetPath = "assets/apple_morsel_character.png",
    SubtypeKeys = new List<string> { "SubtypesData_Snack" },
    PriorityDraw = false,
    TriggerBuilders = new List<CharacterTriggerDataBuilder>
    {
        new CharacterTriggerDataBuilder
        {
            Trigger = CharacterTriggerData.Trigger.OnEaten,
            Description = "Eater takes {[effect1.power]} damage and gains <nobr><b>Rage</b> <b>{[effect0.status0.power]}</b></nobr>",
            EffectBuilders = new List<CardEffectDataBuilder>
            {
                new CardEffectDataBuilder
                {
                    EffectStateName = "CardEffectAddStatusEffect",
                    TargetMode = TargetMode.LastFeederCharacter,
                    TargetTeamType = Team.Type.Monsters,
                    ParamStatusEffects = new StatusEffectStackData[]
                    {
                        new StatusEffectStackData
                        {
                            statusId = VanillaStatusEffectIDs.Rage,
                            count = 3
                        }
                    }
                },
                new CardEffectDataBuilder
                {
                    EffectStateName = "CardEffectDamage",
                    TargetMode = TargetMode.LastFeederCharacter,
                    TargetTeamType = Team.Type.Monsters,
                    ParamInt = 5
                }
            }
        }
    }
};
  • SubtypeKeys: This is the morsel subtype key. I found it in AssetStudio.
  • PriorityDraw: This defaults to true, which is what you'd want it to be for a banner unit since that's how the game knows to put it in one of your first few hands. Morsels aren't a priority draw, so we set it to false here.
  • Trigger: For morsels, it's OnEaten. This is required to make the Morsel be edible.
  • Description: We're using more than one effect in the description this time. effect0 is the effect at index 0 and effect1 is the effect at index 1 in the effects list.
  • EffectBuilders: The first effect here adds Rage 3 to the eater, and the second deals 5 damage.

Despite this character being completely different from the last two, there's not a whole lot to say. The differences are few. You can see here how even simple building blocks can create varied designs. Let's move on to the CardDataBuilder:

new CardDataBuilder
{
    CardID = TestPlugin.GUID + "_AppleMorselCard",
    Name = "Apple Morsel",
    Cost = 0,
    CardType = CardType.Monster,
    Rarity = CollectableRarity.Common,
    TargetsRoom = true,
    Targetless = false,
    AssetPath = "assets/applemorsel.png",
    ClanID = VanillaClanIDs.Umbra,
    CardPoolIDs = new List<string>
    {
        VanillaCardPoolIDs.MorselPool, VanillaCardPoolIDs.MorselPool,
        VanillaCardPoolIDs.MorselPool, VanillaCardPoolIDs.MorselPool,
        VanillaCardPoolIDs.MorselPool, VanillaCardPoolIDs.MorselPool,
        VanillaCardPoolIDs.MorselPool, VanillaCardPoolIDs.MorselPool,
        VanillaCardPoolIDs.MorselPool, VanillaCardPoolIDs.MorselPool,
        VanillaCardPoolIDs.MorselPoolStarter, VanillaCardPoolIDs.MorselPoolStarter,
        VanillaCardPoolIDs.MorselPoolStarter, VanillaCardPoolIDs.MorselPoolStarter
    },
    EffectBuilders = new List<CardEffectDataBuilder>
    {
        new CardEffectDataBuilder
        {
            EffectStateName = "CardEffectSpawnMonster",
            TargetMode = TargetMode.DropTargetCharacter,
            ParamCharacterDataBuilder = appleMorselCharacter
        }
    }
}.BuildAndRegister();

The big difference you'll notice here is in CardPoolIDs. Why, there are tons of them! This is how the game makes certain morsels more common than others. Every card in the card pool has the same chance of being rolled. How did the devs get around this? By adding the same card multiple times! In the main morsel pool, Rubble Morsel shows up ten times. In the starter card morsel pool it shows up four times. Apple Morsel copies the ratio. Notably, this makes other morsel cards much less likely to show up.

Next: Custom Relics

⚠️ **GitHub.com Fallback** ⚠️