-
Notifications
You must be signed in to change notification settings - Fork 5
Custom Card Traits
In this tutorial we will discuss CardTraits in detail.
To define what a CardTrait is it is not strictly a Keyword. CardTraits are generic modifiers applied to a card to change the behaviour and properties of the card, they are very versatile in what you can do with them.
For instance here are a few notable player facing CardTraits and what they do
-
Spellchain (CardTraitCopyOnPlay) when you play the card you receive a copy of the card with a CardUpgrade applied.
-
Doublestack (CardTraitJuice2) that scales any status effects applied by any effect on the card its applied on by 2.
-
Extract (CardTraitCorruptRestricted) restricts you from playing a card if you don't have enough Charged Echoes on the floor.
Additionally, if you recall from Custom Spell Cards where we made a card with a scaling effect, we used a CardTrait to handle the scaling for us, so not all CardTraits have a player facing keyword associated with it.
The CardTrait we will be making is called Trinity if you've played One Step from Eden; it's a similar concept. The effect is when you play a specific set of cards 3 times on the third play there is a bonus effect depending on the card.
The difference is we will be doing a simplified version where each card has a tracker and the third time you play this card it costs 0 ember and has 50 more magic power.
To explain the effect as how we will implement this tutorial when a particular card with Trinity 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:
- Create a new class inheriting from CardTraitState.
- Override the methods you care about. At the very least, if you want a player facing keyword name, like other traits have, override GetCardText. This is how CardTraits add themselves to the cards' text. You should also override GetCardTooltipText which should return the tooltip text for the CardTrait.
Important
If your trait requires tooltips, then your CardTraitState needs to be whitelisted for that. The Conductor modding library has a function to help with this.
I won't be going into all of the overridable methods in CardTraitState. It should be straightforward 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 provided inline
public class CardTraitSelfTrinity : CardTraitState
{
// Required function to be implemented due to interface. You can document your Trait for other modders here.
// But that is not required.
// This code is never ran, its usage is for the developers of the game.
public override PropDescriptions CreateEditorInspectorDescriptions()
{
// param_int will specify the amount of extra damage.
return new PropDescriptions
{
[CardTraitFieldNames.ParamInt.GetFieldName()] = new PropDescription("Extra damage on third cast.")
};
}
// Method that can modify the amount of damage done. Return value is the new damage to deal.
public override int OnApplyingDamage(ApplyingDamageParameters damageParams, ICoreGameManagers coreGameManagers)
{
// If the trinity effect is active, increase the damage by ParamInt. This will be configured with the CardTraitData.
if (IsTrinity())
{
return damageParams.damage + GetParamInt();
}
return damageParams.damage;
}
// Method that can modify the cost of a card. Return value is the new cost of the card.
public override int GetModifiedCost(int cost, CardState thisCard, CardStatistics cardStats, MonsterManager? monsterManager)
{
if (IsTrinity())
{
return 0;
}
return cost;
}
// Helper to determine when the Trinity Effect is active.
public bool IsTrinity()
{
return GetTrinityCount() == 2;
}
public int GetTrinityCount()
{
// Traits have a reference to the card it has been applied to.
var associatedCard = GetCard();
if (associatedCard == null)
return 0;
// GetCurrentScenarioPlayCount gets the number of times the card was played in the current battle.
return associatedCard.GetCurrentScenarioPlayCount() % 3;
}
// This method is how CardTraits add themselves to the card.
public override string GetCardText()
{
// Handles getting the localization given a localization key string and formats it as a Card Trait keyword. Does not return null.
var text = LocalizeTraitKey("CardTraitTrinity_CardText");
var associatedCard = GetCard();
if (associatedCard == null)
return string.Empty;
return string.Format(text, GetTrinityCount() + 1);
}
// This method provides the TooltipText. Again if you intend on showing a Tooltip, the CardTrait class needs to be whitelisted for it.
public override string GetCardTooltipText()
{
int times = 2 - GetTrinityCount();
if (!IsTrinity())
return string.Format("CardTraitTrinity_TooltipText".Localize(), times, GetParamInt());
else
return string.Format("CardTraitTrinity_TrinityCharged_TooltipText".Localize(), GetParamInt());
}
// Much like CardTraits 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 make the card deal an additional 50 damage.
public override string GetCurrentEffectText(CardStatistics? cardStatistics, SaveManager? saveManager, RelicManager? relicManager)
{
if (IsTrinity())
return string.Format("CardTraitTrinity_TrinityCharged_CardText".Localize(), GetParamInt());
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 our CardTraitState subclass. Anything we set in CardTraitData will be available to us here. Here I wanted to use a configurable value to adjust the extra damage rather than hardcoding it to just increase the damage by 50.
You've noticed the calls to LocalizeTraitKey and string.Localize. These functions interface with the localization library and gets a translation for the given key. Upto this point, the localizations have been a black box, anytime you've named a card or provided a description to a card, internally the framework internally generates a unique localization key, associates it with the translations provided and then registers it with the LocalizationManager.
In this case, in the code we manually made localization keys:
-
CardTraitTrinity_CardText
-
CardTraitTrinity_TooltipText
-
CardTraitTrinity_TrinityCharged_TooltipText
-
CardTraitTrinity_TrinityCharged_CardText
Now we need to provide localizations for these given keys. We can do that with JSON and the framework will add translations for these keys for us
"localization_terms": [
{
"key": "CardTraitTrinity_CardText",
"texts": {
"english": "Trinity {0}"
}
},
{
"key": "CardTraitTrinity_TooltipText",
"texts": {
"english": "If this card is played {0} more times, the card will cost 0 ember and deal an additional {1} damage."
}
},
{
"key": "CardTraitTrinity_TrinityCharged_TooltipText",
"texts": {
"english": "This card costs 0 [ember] and will deal an additional {0} damage until it is played."
}
},
{
"key": "CardTraitTrinity_TrinityCharged_CardText",
"texts": {
"english": "<i>(+50 damage)</i>"
}
}
]Important
When creating manual localization keys, please make sure to include your ModGUID within it. Otherwise there is a chance for collisions if two modders have two keys with the same name.
Without the JSON when the code goes to translate a key that does not have a translation, we will get a localization error. The game will handle this and will replace an invalid localization with the text KEY>>localization_key<<. An example if CardTraitTrinity_CardText was missing it would display KEY>>CardTraitTrinity_CardText<< right at the top of the card with the trait.
Lastly, since we want to display a cool tooltip for the CardTrait, we need to whitelist it.
As mentioned Conductor has a nifty helper function that handles this for us. All we need to do is call this function once it in our Plugin's Initialize function.
Conductor.Utilities.SetupTraitTooltips();The function scans your classes for CardTraitState subclasses that override GetCardTooltipText and whitelists the type to be able to display tooltips.
CardTraits are a bit harder to test as you can't cheat one onto a card without first making an enhancer.
You can pick a starter card and edit it to put the Trait onto the card. For instance to put it on Firestarter.
"cards": [
{
"id": "StarterFireStarter",
"override": "replace",
"traits": ["@Trinity"]
}
],
"traits": [
{
"id": "Trinity",
"name": "@CardTraitTrinity",
"param_int": 50
}
],Once you have this you should put Keepstone and Seekstone on the card. And play the card over 3 turns. Before the third time you play it the text (+50 damage) should appear at the bottom of the card. Additionally the tooltips should change to reflect that it costs 0 ember and will do an additional 50 damage.
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