Custom Card Traits - brandonandzeus/Trainworks2 GitHub Wiki

Custom Card Traits

Now we do a deep dive into CardTraits. To define a Card Trait, it is a modifier to a card, or it can be a passive effect added to a card. You can modify how a Card is playable. See CardTraitTreasure (Offering) and CardTraitCorruptRestricted (Extract). You can modify what happens to a card after it played CardTraitExhaustState (Consume) and CardTraitRetainState (Holdover). And you can modify the effects of a card with scaling effects see CardTraitScalingXXX.

In this tutorial, you guessed it, we will make a new CardTrait.

Trinity

The CardTrait we will be making is called Trinity if you've played One Step from Eden; it's a similar concept except for its per card. To explain the effect as how we will implement this tutorial when a particular card is played for the 3rd (or any multiple thereof) time in this battle, it will do an additional 50 damage and cost 0 ember.

When making a new CardTrait, here are the steps:

  1. Create a new class inheriting from CardTraitState.
  2. Override the methods you care about. At the very least, override GetCardText. This is how Traits add themselves to the Cards text.
  3. If you need to generate a Tooltip, then your CardTraitState needs to be whitelisted for that. This is done through Reflection.

I won't be getting into all of the overridable methods in CardTraitState. It should be pretty obvious what each method does. If not then analysing where the method is used should grant more insight.

So let's dive into it. Here's CardTraitTrinity. Comments and noteworthy things are inline.

    public class CardTraitTrinity : CardTraitState
    {
        public override IEnumerator OnCardPlayed(CardState cardState, CardManager cardManager, RoomManager roomManager)
        {
            // If the Trinity effect was played, reset ParamInt
            // otherwise increment ParamInt
            if (IsTrinity())
                SetParamInt(0);
            else
                SetParamInt(GetParamInt() + 1);

            // Required for IEnumerator methods if you don't call another IEnumerator method
            yield break;
        }

        // Method that can overrides the amount of damage done.
        public override int OnApplyingDamage(ApplyingDamageParameters damageParams)
        {
            // If the trinity effect is active, increase the damage by 50.
            if (IsTrinity())
                damageParams.damage += 50;
            return damageParams.damage;
        }

        // Method that can modify the cost of a card.
        public override int GetModifiedCost(int cost, CardState thisCard, CardStatistics cardStats, MonsterManager monsterManager)
        {
            // Make it cost 0 if the Trinity effect is active
            if (IsTrinity())
            {
                return 0;
            }
            return cost;
        }

        // Helper to determine when the Trinity Effect is active.
        public bool IsTrinity()
        {
            return GetParamInt() == 2;
        }

        // This method is how CardTraits add themselves to the card.
        public override string GetCardText()
        {
            // If you don't intend on providing localization. You can just return the text.
            return string.Format("<b>Trinity {0}</b>", GetParamInt() + 1);

            // Highly recommended to create a LocalizationKey for it. 
            // Warning: There is a potential for this to be called before your mod is set up, (Specifically when the SaveGame data is loaded).
            // This means you will need to handle a null result from LocalizeTraitKey.
            //var text = LocalizeTraitKey("CardTraitTrinity_CardText")
            //if (text == null)
            //  return string.Empty;
            //return string.Format(text, GetParamInt());
        }

        // This method provides a TooltipTile. If you intend on showing a Tooltip, the CardTraitClass Name needs to be whitelisted for it.
        // See: TestPlugin.SetupTraits
        public override string GetCardTooltipTitle()
        {
            return "Trinity";
            // Localization version
            //return LocalizeTraitKey("CardTraitTrinity_TooltipTitle");
        }

        // This method provides the TooltipText. Again if you intend on showing a Tooltip, the CardTraitClass Name needs to be whitelisted for it.
        // See: TestPlugin.SetupTraits
        public override string GetCardTooltipText()
        {
            int times = 2 - GetParamInt();
            if (!IsTrinity())
                return string.Format("When this card is played {0} more times, the card will cost 0 ember and have an additional 50 Magic Power", times);
            else
                return string.Format("This card costs 0 ember and has an additional 50 Magic Power");
            // return string.Format("CardTraitTrinity_TooltipText".Localize(), GetParamInt());
        }

        // This method provides the TooltipId. It is used to prevent the generated tooltip from being displayed multiple times.
        public override string GetCardTooltipId()
        {
            return "CardTraitTrinity";
        }

        // Much like CarTraits can add themselves to the Card's text, it can also append text to the CardEffect.
        // Here, we use this to indicate when Trinity is active to display that the card will have 50 magic power.
        public override string GetCurrentEffectText(CardStatistics cardStatistics, SaveManager saveManager, RelicManager relicManager)
        {
            if (IsTrinity())
                return "<b>+50 Magic Power</b>";
            else
                return string.Empty;
        }
    }

Another noteworthy thing is GetParamInt. If you recall from the previous tutorial and really many of the custom cards we did. The CardTraitData class is used to initialize the CardTraitState class. Anything we set in CardTraitData will be available to us here.

Lastly, since we want to display a cool tooltip for the CardTrait, we need to whitelist it. this is done with a simple two-liner. You can toss this in your plugin's initialize method.

var traits = (List<string>)AccessTools.Field(typeof(TooltipContainer), "TraitsSupportedInTooltips").GetValue(null);
traits.Add(nameof(CardTraitTrinity));

A sample Trinity Card

This card is called Trinity Burst and will feature the Trinity CardTrait. For easier testing, we will also give the card Holdover and Intrinsic.

var damage = new CardEffectDataBuilder
{
    EffectStateType = typeof(CardEffectDamage),
    ParamInt = 1,
    TargetTeamType = Team.Type.Heroes,
    TargetMode = TargetMode.RandomInRoom,
};

new CardDataBuilder
{
    CardID = TestPlugin.GUID + "_TrinityBurst",
    Name = "Trinity Burst",
    Description = "Deal [effect0.power] damage to a random unit three times",
    Cost = 1,
    Rarity = CollectableRarity.Common,
    TargetsRoom = true,
    Targetless = true,
    ClanID = Clan.ID,
    AssetPath = "assets/trinityburst.png",
    CardPoolIDs = { VanillaCardPoolIDs.MegaPool },
    EffectBuilders =
    {
        damage, damage, damage
    },
    TraitBuilders =
    {
        new CardTraitDataBuilder
        {
            TraitStateType = typeof(CardTraitTrinity),
            // You can automatically activate the Trinity version by setting this to 2.
            //ParamInt = 2,
        },
        new CardTraitDataBuilder
        {
            TraitStateType = typeof(CardTraitIntrinsicState),
        },
        new CardTraitDataBuilder
        {
            TraitStateType = typeof(CardTraitRetain),
        }
    }
}.BuildAndRegister();

As the comment states, if you set ParamInt to 2 here, then that will start off the card with the Trinity effect.

That's all there is to it. CardTraits are powerful and reusable and can do things normal CardEffects can not do. And speaking of...

Next: Custom Card Effects

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