Custom Relics - brandonandzeus/Trainworks2 GitHub Wiki

Custom Relics

Relics are actually very similar to cards and especially characters, so if you haven't yet, it'd be a grand idea to gander over that tutorial first. Once you complete this tutorial, you should be able to handle many more types of Builders. Most of the Data types are simply subclasses of RelicData. Collectable Relics, Mutators, Enhancers, and Covenants are all just Relics internally.

Wimp-cicle

Download the relic art here.

Wimp-cicle, like its namesake, generates a card at the start of your turn. This time it's not an imp but a Train Steward. The first thing we need to do is create a card pool.

CardPool cardPool = new CardPoolBuilder {
    CardPoolID = "Wimp-ciclePool",
    CardIDs = {VanillaCardIDs.TrainSteward}
}.Build();

Card pools are a bit weird by Monster Train standards. You have some, like the MegaPool, that have tons and tons of cards in them. Then you have some, like the StingOnlyPool, that have exactly one. As it turns out, there actually is a TrainStewardOnly pool as well, but unlike CardData, CharacterData, etc., card pools aren't stored in a global list, so we can't easily grab it. Since this one's small, it's more straightforward to just make it ourselves.

Relics, like cards, characters, and any other form of content, are made using a builder.

new CollectableRelicDataBuilder
{
    CollectableRelicID = TestPlugin.GUID + "_WimpcicleRelic",
    Name = "Wimp-cicle",
    Description = "At the start of your turn, add a Train Steward to your hand",
    RelicPoolIDs = { VanillaRelicPoolIDs.MegaRelicPool },
    IconPath = "assets/wimpcicle.png",
    EffectBuilders =
    {
        new RelicEffectDataBuilder
        {
            RelicEffectClassType = typeof(RelicEffectAddBattleCardToHand),
            ParamInt = 1,
            ParamCardPool = cardPool,
            ParamTrigger = CharacterTriggerData.Trigger.PreCombat
        }
    },
    Rarity = CollectableRarity.Common
}.BuildAndRegister();
  • CollectableRelicID: Must be unique.
  • Name: Whatever you want. As with the others, this will be the same across all languages.
  • Description: Same as it was everywhere else.
  • RelicPoolIDs: Like cards, relics go into pools. If you're making a regular relic, stuff it in the MegaRelicPool. It's the one the game uses for relic choices on the map and in the shop.
  • IconPath: Location of the relic art relative to your plugin's directory.
  • EffectBuilders: This is a list of RelicEffectDataBuilders. They're pretty much the same as regular CardEffectDataBuilders, but they use their own effect types.
  • RelicEffectClassType: We're just gonna follow Imp-cicle's lead here. In fact, aside from the card pool this whole effect will be identical to Imp-cicle. These should be a class type that inherits from RelicEffectBase.
  • ParamInt: Varies by relic effect type. For RelicEffectAddBattleCardToHand, this is the number of cards to add.
  • ParamCardPool: The card pool to choose cards from. Since our card pool only has Train Steward in it, it'll always choose Train Steward. If we made a pool of, for example, all the starter cards, it'd choose one of those at random instead.
  • ParamTrigger: When the relic effect fires. In this case, it's "PreCombat", which is actually the start of your turn.
  • Rarity: This is actually kind of a terrible relic, so let's make everyone's day worse by having it be common.

You could set a clan ID to restrict the relic to that clan if you wanted to. Leaving it off (as we did here) makes it clanless.

How do we test a relic? If you have Harmony set up, you can use a Harmony patch very similar to AddCardToStartingDeckPatch. Here's the code:

[HarmonyPatch(typeof(SaveManager), nameof(SaveManager.SetupRun))]
class AddRelicAtStartOfRunPatch
{
    static void Postfix(ref SaveManager __instance)
    {
        __instance.AddRelic(CustomCollectableRelicManager.GetRelicDataByID(Wimpcicle.ID));
    }
}

This will add the relic at the start of a new run, making it very easy to test.

EmberRefunder

The next relic is a relic with an EffectCondition. EffectConditions restrict when the RelicEffect triggers. Think Shadow Box and the requirement to play 20 Morsels to trigger the effect. So here's a relic with an Effect Condition.

This relic gives you 1 additional ember as long as you have 4 train stewards in the deck, people throw away train stewards often so we want to show them some love with this relic.

Here's the code.

new CollectableRelicDataBuilder
{
    CollectableRelicID = TestPlugin.GUID + "_EmberRefunderRelic",
    Name = "Ember Refunder",
    Description = "Gain 1 ember as long as you have at least 4 train stewards in your deck.",
    RelicPoolIDs = { VanillaRelicPoolIDs.MegaRelicPool },
    IconPath = "assets/ember.png",
    ClanID = Clan.ID,
    EffectBuilders =
    {
        new RelicEffectDataBuilder
        {
            RelicEffectClassType = typeof(RelicEffectModifyEnergy),
            ParamInt = 1,
            ParamSourceTeam = Team.Type.Monsters,
            EffectConditionBuilders =
            {
                new RelicEffectConditionBuilder
                {
                    ParamTrackedValue = CardStatistics.TrackedValueType.SubtypeInDeck,
                    ParamCardType = CardStatistics.CardTypeTarget.Monster,
                    ParamSubtype = VanillaSubtypeIDs.Steward,
                    ParamTrackTriggerCount = false,
                    AllowMultipleTriggersPerDuration = false,
                    ParamInt = 4,
                    ParamComparator = RelicEffectCondition.Comparator.Equal | RelicEffectCondition.Comparator.GreaterThan,
                }
            }
        }
    },
    Rarity = CollectableRarity.Common
}.BuildAndRegister();
  • RelicEffectClassType: RelicEffectModifyEnergy is used. Without a condition builder, this effect always gives you one extra ember per turn.
  • ParamTrackedValue: This accesses CardStatistics for a stat about our deck or battle. You may remember this way back in Play Other Cards from the Custom Spell Tutorial. Here we want to query for a subtype in the deck.
  • ParamCardType: This specifies that we are searching for Monster cards with subtypes in the deck.
  • ParamSubtype: Here is the subtype we are looking for.
  • ParamTrackTriggerCount:
  • AllowMultipleTriggersPerDuration:
  • ParamInt: This, along with ParamTrackedValue, ParamCardType, and ParamSubtype tells us we are looking for 4 monster cards of subtype Steward in deck.
  • ParamComparator: Don't forget to set this. This tells us what we want to do with ParamInt and the value fetched for ParamTrackedValue. Here we want to test if ParamTrackedValue is greater than or equal to ParamInt. In this case, we want 4 or more train stewards in the deck.

That's all there is to it. Now add it to your run, and try modifying ParamInt to see if you can get the effect not to trigger.

Next: Custom Enhancers