Skip to content
Brandon edited this page Jun 9, 2026 · 6 revisions

Shining Horn

Overview

We will create Shining Horn, an Artifact that gives Endless to every Imp unit in the player's deck.

The Artifact will also display the number of Imps currently in the player's deck. This helps the player determine whether Shining Horn is a useful reward before selecting it.

Shining Horn introduces several important relic concepts:

  • Creating an Artifact, known internally as a CollectableRelic.
  • RelicPools
  • Applying a temporary CardUpgrade to matching cards.
  • Adding dynamically generated text with a DescriptionModifier.

Full JSON Definition

{
  "$schema": "https://github.com/Monster-Train-2-Modding-Group/Trainworks-Reloaded/releases/latest/download/schema.json",
  "relics": [
    {
      "id": "ShiningHorn",
      "type": "collectable",
      "names": {
        "english": "Shining Horn"
      },
      "descriptions": {
        "english": "[imp] units get [endless]."
      },
      "lore_style": "herzal",
      "relic_effects": [ "@AddEndlessToImps" ],
      "icon": "@ShiningHornIcon",
      "icon_small": "@ShiningHornSmallIcon",
      "extensions": [
        {
          "collectable": {
            "class": "ClassHellhorned",
            "pools": [ "MegaRelicPool" ],
            "rarity": "uncommon",
            "description_modifiers": [
              {
                "name": "CardCountDescriptionModifier",
                "param_card_type": "monster",
                "text_formats": {
                  "english": "([value] [imp] units)"
                },
                "param_subtype": "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c"
              }
            ]
          }
        }
      ]
    }
  ],
  "relic_effects": [
    {
      "id": "AddEndlessToImps",
      "name": "RelicEffectAddTempUpgrade",
      "source_team": "monsters",
      "param_upgrade": "@EndlessForImps"
    }
  ],
  "upgrades": [
    {
      "id": "EndlessForImps",
      "is_unique": true,
      "status_effect_upgrades": [
        {
          "status": "endless",
          "count": 1
        }
      ],
      "filters": [ "@OnlyImps" ]
    }
  ],
  "upgrade_masks": [
    {
      "id": "OnlyImps",
      "card_type": "monster",
      "required_subtypes": [ "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c" ]
    }
  ],
  "sprites": [
    {
      "id": "ShiningHornIcon",
      "path": "textures/relic_art/ShiningHornIcon.png"
    },
    {
      "id": "ShiningHornSmallIcon",
      "path": "textures/relic_art/ShiningHornSmallIcon.png"
    }
  ]
}

The Artifact Definition

Relics come in several different types. The framework must know which type of relic it needs to create.

Shining Horn is a CollectableRelic, which is the type of relic presented to players as an Artifact:

    {
      "id": "ShiningHorn",
      "type": "collectable",
      "names": {
        "english": "Shining Horn"
      },
      "descriptions": {
        "english": "[imp] units get [endless]."
      },
      "lore_style": "herzal",
      "relic_effects": [ "@AddEndlessToImps" ],
      "icon": "@ShiningHornIcon",
      "icon_small": "@ShiningHornSmallIcon",
      "extensions": [
        {
          "collectable": {
            "class": "ClassHellhorned",
            "pools": [ "MegaRelicPool" ],
            "rarity": "uncommon",
            "description_modifiers": [
              {
                "name": "CardCountDescriptionModifier",
                "param_card_type": "monster",
                "text_formats": {
                  "english": "([value] [imp] units)"
                },
                "param_subtype": "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c"
              }
            ]
          }
        }
      ]
    }
  • type - This tells the framework to create a CollectableRelic.

  • relic_effects - This specifies the RelicEffects the relic has.

  • lore_style - This controls the style of the tooltips for the lore of this relic.

  • icon/icon_small - These are the icons for the Artifact. We need a main one and a smaller variant for the HUD.

  • extensions - This sections defines properties exclusive to CollectableRelics.

CollectableRelic Properties

Properties specific to CollectableRelics are placed inside a collectable extension:

      "extensions": [
        {
          "collectable": {
            "class": "ClassHellhorned",
            "pools": [ "MegaRelicPool" ],
            "rarity": "uncommon",
            "description_modifiers": [
              {
                "name": "CardCountDescriptionModifier",
                "param_card_type": "monster",
                "text_formats": {
                  "english": "([value] [imp] units)"
                },
                "param_subtype": "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c"
              }
            ]
          }
        }
      ]
  • class - This is the clan associated with the Artifact. Omit for a Clanless artifact.

  • pools - This is the list of RelicPools where to put this Artifact. This should always be MegaRelicPool

  • rarity - Controls the rarity. This should either be common or uncommon. This also affects the price in Merchant of Trinkets.

  • description_modifiers - This property is responsible for appending dynamically generated text to the end of the Relic's description.

Description Modifiers

As mentioned description modifiers allow an Artifact to append dynamically generated text to the end of the Artifact's description.

For this artifact it is only valuable when the player's deck contains Imps. To help the player evaluate the Artifact, we will append the current number of Imp units to its description.

Here is our description modifier:

            "description_modifiers": [
              {
                "name": "CardCountDescriptionModifier",
                "param_card_type": "monster",
                "text_formats": {
                  "english": "([value] [imp] units)"
                },
                "param_subtype": "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c"
              }
  • name - This is the name of the DescriptionModifier class. Like CardEffects, CardTraits, and RoomModifiers. This references code (a class) that is executed that implements the behavior.

  • text_formats This is the base format string provided in various languages. [value] is a placeholder that will be replaced with the value the description modifier computes.

    param_card_type / param_subtype - these are the parameters CardCountDescriptionModifier uses, the meaning depends on which description modifier is used.

CardCountDescriptionModifier

CardCountDescriptionModifier counts cards that satisfy the supplied criteria:

The first restriction requires the cards to be monster cards:

"param_card_type": "monster"

The second restriction requires the cards to have the Imp subtype:

"param_subtype": "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c"

Together, these properties cause the modifier to count only Imp unit cards in the player's deck. The count of imp cards will replace [value] in text_formats giving us our dynamically generated description to append to the Artifacts description.

Relic Effects

Artifacts use RelicEffects instead of the CardEffects used by cards and characters:

"relic_effects": [ "@AddEndlessToImps" ]

RelicEffects are much more powerful than CardEffects. Depending on the effect, they can operate both inside and outside combat and modify many aspects of a run.

For example, RelicEffects can modify:

  • Cards in the player's deck.
  • Enemy encounters.
  • Add triggers to both monsters and enemy units.
  • Card and Artifact rarities.
  • Merchant behavior.
  • Run-wide rules and values.

In addition, RelicEffects do not play in sequential order like CardEffects do. Think of them more like Card Triggers or CharacterTriggers. Each RelicEffect has an independent timing on when it triggers its effects.

The RelicEffect

Shining Horn references a RelicEffect that applies an upgrade to every matching Imp card.

The Artifact references the following RelicEffect:

    {
      "id": "AddEndlessToImps",
      "name": "RelicEffectAddTempUpgrade",
      "source_team": "monsters",
      "param_upgrade": "@EndlessForImps"
    }
  • name like with CardEffects we specify the code that gets executed that implements the RelicEffect. Here we specify the RelicEffect class RelicEffectAddTempUpgrade

  • source_team - This is just like target_team with CardEffects. The team that will be affected by the RelicEffect.

  • param_upgrade - This is a parameter sent to the RelicEffect. Its meaning depends on the RelicEffect that you are using.

RelicEffectAddTempUpgrade

RelicEffectAddTempUpgrade applies a CardUpgrade to matching cards:

"name": "RelicEffectAddTempUpgrade"

The upgrade is considered temporary because it is granted by the Artifact during the run rather than permanently added to card.

The CardUpgrade being applied is referenced by param_upgrade:

"param_upgrade": "@EndlessForImps"

The upgrade itself determines both what is added and which cards are eligible to receive it.

The Endless Upgrade

The CardUpgrade applied by the RelicEffect is defined as follows:

    {
      "id": "EndlessForImps",
      "is_unique": true,
      "status_effect_upgrades": [
        {
          "status": "endless",
          "count": 1
        }
      ],
      "filters": [ "@OnlyImps" ]
    }

For any upgrade to be used by RelicEffectAddTempUpgrade it is important that the is_unique flag on this upgrade is set to true. Otherwise, each time a clone of the card is acquired the RelicEffect will continuously stack the upgrade onto the card clone.

is_unique prevents that by only allowing 1 instance of this upgrade to be applied to the card.

The upgrade also has a CardUpgradeMask to restrict the upgrade only for Imp units as shown here:

    {
      "id": "OnlyImps",
      "card_type": "monster",
      "required_subtypes": [ "SubtypesData_Imp_0f9b989f-15b5-4b16-8378-5d8ed8691e7c" ]
    }

We restrict to only Monster cards, with the Imp subtype.

Testing

  1. Start a run with Hellhorned preferably Shardtail Queen

  2. Give yourself the artifact either through the GUI or by using the console (command: artifact ShiningHorn)

  3. Check the description of the artifact. Depending on your start it should include the exact number of imps in your deck.

  4. Draw an imp during battle. It should have the Green Endless text.

Ember Refunder

Overview

Next we will cover an important aspect of Relics. Sometimes we don't want to make a relic effect apply all the time during battle, but only when a specific condition is met. Here we will introduce RelicEffectConditions. A RelicEffectCondition can be associated with any RelicEffect to make it conditional. They work based off the TrackedValue system which we used previously to make a card with a dynamically scaling effect based on a value.

The artifact we will be creating is called EmberRefunder. As long as there are 4 Train Stewards in your deck the player gains 1 extra energy per turn. It is a clanless artifact.

Full JSON Definition

This is no different than Shining Horn. The only new thing is a new RelicEffect and the RelicEffectCondition attached to it

{
  "$schema": "https://github.com/Monster-Train-2-Modding-Group/Trainworks-Reloaded/releases/latest/download/schema.json",
  "relics": [
    {
      "id": "EmberRefunder",
      "type": "collectable",
      "names": {
        "english": "Ember Refunder"
      },
      "descriptions": {
        "english": "Gain [effect0.power] [ember] as long as you have at leeat 4 <b>Train Stewards</b> in your deck."
      },
      "lore_style": "herzal",
      "relic_effects": [ "@OneEmberForAllTrainStewards" ],
      "icon": "@EmberRefunderIcon",
      "icon_small": "@EmberRefunderSmallIcon",
      "extensions": [
        {
          "collectable": {
            "pools": [ "MegaRelicPool" ],
            "rarity": "common",
            "description_modifiers": [
              {
                "name": "CardCountDescriptionModifier",
                "param_card_type": "monster",
                "text_formats": {
                  "english": "([value] <b>Train Stewards</b>)"
                },
                "param_subtype": "SubtypesData_TrainSteward"
              }
            ]
          }
        }
      ]
    }
  ],
  "relic_effects": [
    {
      "id": "OneEmberForAllTrainStewards",
      "name": "RelicEffectModifyEnergy",
      "source_team": "monsters",
      "param_int": 1,
      "conditions": ["@MustHave4TrainStewards"]
    }
  ],
  "relic_effect_conditions": [
    {
      "id": "MustHave4TrainStewards",
      "param_tracked_value": "subtype_in_deck",
      "param_entry_duration": "this_turn",
      "param_card_type": "monster",
      "param_subtype": "SubtypesData_TrainSteward",
      "param_track_trigger_count": false,
      "allow_multiple_triggers_per_duration": false,
      "param_int": 4,
      "param_comparator": ["equal", "greater_than"]
    }
  ],
  "sprites": [
    {
      "id": "EmberRefunderIcon",
      "path": "textures/relic_art/EmberRefunderIcon.png"
    },
    {
      "id": "EmberRefunderSmallIcon",
      "path": "textures/relic_art/EmberRefunderSmallIcon.png"
    }
  ]
}

The Relic Effect

We introduce a new RelicEffect class RelicEffectModifyEnergy.

    {
      "id": "OneEmberForAllTrainStewards",
      "name": "RelicEffectModifyEnergy",
      "source_team": "monsters",
      "param_int": 1,
      "conditions": ["@MustHave4TrainStewards"]
    }

RelicEffectModifyEnergy, of course, changes the amount of ember granted every turn. The amount of ember to gain (or lose) is specified via param_int. Positive to increase ember, and negative values decrease ember.

We specify a RelicEffectCondition which is a referenced within the json which we will explain now.

Relic Effect Conditions

We define our RelicEffectCondition in its top level property relic_effect_conditions Here is the snippet:

    {
      "id": "MustHave4TrainStewards",
      "param_tracked_value": "subtype_in_deck",
      "param_entry_duration": "this_turn",
      "param_card_type": "monster",
      "param_subtype": "SubtypesData_TrainSteward",
      "param_track_trigger_count": false,
      "allow_multiple_triggers_per_duration": false,
      "param_int": 4,
      "param_comparator": ["equal", "greater_than"]
    }

These are all of the properties you can set with a RelicEffectCondition.

  • param_tracked_value - Which tracked stat to fetch. Determines the meaning of the rest of the associated params.

  • param_entry_duration - The duration of the stat to fetch.

  • param_card_type - The type of card.

  • param_subtype - The monster subtype.

  • param_track_trigger_count - From the game's code.

    Determines if we should use the total trigger count as the tracked value instead of using a value from CardStatistics. Use this to limit triggers to X per turn or per battle.

    Setting this will ignore the params above and instead the value being compared is the number of times the artifact's effect has triggered.

  • allow_multiple_triggers_per_duration

    Allow multiple triggers per duration that is defined as Entry Duration. This can be used to limit a relic from trigger more than X times per turn or per battle

  • param_int - Important parameter determining the right hand side of the comparision. The tracked value (or RelicEffect trigger count) is the left hand side of the comparison.

  • param_comparator - What comparison is being made. This is an array with values "greater than", "equal", or "less than".

So from this definition this RelicEffectCondition

  • Gets the number of cards in deck with type monster and subtype SubtypesData_TrainSteward. This is the left hand side of the comparison.

  • param_comparator is ["greater than", "equal"] which means greater than OR equal

  • param_int is 4 which is the right hand side ofthe comparison

So, the number of monster cards with subtype TrainSteward is greater than or equal 4.

If the condition evaluates to true the RelicEffect is allowed to fire.

Testing

  • Give yourself the artifact. If you are outside of battle it doesn't have an effect.

  • When in battle a little text icon with the "value computed / 4" will appear next to the artifact.

  • During the turn you should have 4 ember.

  • Purge a Train Steward (either by warping to a Purge node or Equipping Void Armament and deleting the Train Steward that way)

  • In the next battle you should only have 3 ember as normally.

Scroll of Ice

Overview

For the last artifact of this tutorial, we will be creating Scroll of Ice. The effect of this artifact is friendly units deal 4 damage twice to the front enemy unit on Incant.

This artifact shows how to silently add triggers to eligible characters.

Full JSON Definition

This is no different than Shining Horn. The only new thing is a new RelicEffect and the RelicEffectCondition attached to it

{
  "$schema": "https://github.com/Monster-Train-2-Modding-Group/Trainworks-Reloaded/releases/latest/download/schema.json",
  "relics": [
    {
      "id": "ScrollOfIce",
      "type": "collectable",
      "names": {
        "english": "Scroll Of Ice"
      },
      "descriptions": {
        "english": "Friendly units deal [effect0.power] damage twice to the front enemy unit on [incant]."
      },
      "lore_style": "herzal",
      "relic_effects": [ "@GrantIce" ],
      "icon": "@ScrollOfIceIcon",
      "icon_small": "@ScrollOfIceSmallIcon",
      "extensions": [
        {
          "collectable": {
            "pools": [ "MegaRelicPool" ],
            "rarity": "uncommon",
            "class": "ClassStygian"
          }
        }
      ]
    }
  ],
  "relic_effects": [
    {
      "id": "GrantIce",
      "name": "RelicEffectAddTrigger",
      "source_team": "monsters",
      "param_bool": true,
      "triggers": [ "@CastIceSpell" ],
      "param_int": 4
    }
  ],
  "character_triggers": [
    {
      "id": "CastIceSpell",
      "trigger": "card_spell_played",
      "hide_visual_and_ignore_silence": true,
      "effects": [
        "@IceDamageFront",
        "@IceDamageFront"
      ]
    }
  ],
  "effects": [
    {
      "id": "IceDamageFront",
      "name": "CardEffectDamage",
      "target_mode": "front_in_room",
      "target_team": "heroes",
      "param_int": 4
    }
  ],
  "sprites": [
    {
      "id": "ScrollOfIceIcon",
      "path": "textures/relic_art/ScrollOfIceIcon.png"
    },
    {
      "id": "ScrollOfIceSmallIcon",
      "path": "textures/relic_art/ScrollOfIceSmallIcon.png"
    }
  ]
}

The Relic Effect

Adding triggers to units is handled by RelicEffectAddTrigger. The difference from RelicEffectAddTempUpgrade is what is applied to the unit. The former adds just a new CharacterTrigger and the latter adds a CardUpgrade to the unit. RelicEffectAddTrigger has additional parameters to fine tune how the trigger is applied.

    {
      "id": "GrantIce",
      "name": "RelicEffectAddTrigger",
      "source_team": "monsters",
      "param_bool": true,
      "triggers": [ "@CastIceSpell" ],
      "param_int": 4
    }
  • source_team - The target team that will receive the trigger

  • param_bool - This boolean value, prevents anything from modifying the trigger count, so in this instance if you had Founding Seal and this artifact the characters don't suddenly deal 4 damage 4 times. By convention, we set this to true to prevent that to make it seem that the effects come from the artifact.

  • triggers - For RelicEffectAddTrigger this is the list of triggers that will be added to eligible units.

  • param_int - This parameter doesn't functionally do anything in terms of RelicEffectAddTrigger it is used for the substitution text in the Artifacts description. There is no available substitution text to grab a value from the trigger referenced above, so this is a cheat the game uses.

Additionally 2 parameters not listed here can be used to filter for eligibility.

  • param_subtype - List a subtype here to restrict to a specific subtype. Only units with the subtype will receive the new trigger.

  • param_characters - An additional list of characters to restrict for eligibility. Only the characters that match will be able to receive the new trigger.

If both are specified then only Characters listed with the specific subtype will be eligible.

The Trigger

    {
      "id": "CastIceSpell",
      "trigger": "card_spell_played",
      "hide_visual_and_ignore_silence": true,
      "effects": [
        "@IceDamageFront",
        "@IceDamageFront"
      ]
    }

Here we see a another instance where you'd want to set hide_visual_and_ignore_silence. We set this to true to make it seem like the Artifact is doing the damage.

This pattern makes it very easy to write Artifacts in terms of CharacterTriggers and CardEffects, like we did for Room cards.

Testing

  • Give yourself the artifact outside of battle.

  • When in battle spawn a few units, and play any spell card.

  • The front enemy unit should take 4 damage number of units * 2 times on the floor where you played the card.

Conclusion

We covered basic CollectableRelics. The information from this page can easily be applied to other Relic types like Mutators, Sins, PyreHearts, and Covenants. These are all Relics the only difference is that each type has an extension to allow you to set fields exclusive to those relic types.

We did not cover many of the RelicEffects, there are at least 300 of them. These are all documented (at least their parameters) in [Base Game RelicEffect Reference](Base-Game-Relic-Effect-Reference)

Clone this wiki locally