-
Notifications
You must be signed in to change notification settings - Fork 5
Custom Artifacts
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.
{
"$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"
}
]
}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.
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 beMegaRelicPool -
rarity- Controls the rarity. This should either becommonoruncommon. 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.
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_formatsThis 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 parametersCardCountDescriptionModifieruses, the meaning depends on which description modifier is used.
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.
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.
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"
}-
namelike with CardEffects we specify the code that gets executed that implements the RelicEffect. Here we specify the RelicEffect classRelicEffectAddTempUpgrade -
source_team- This is just liketarget_teamwith 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 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 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.
-
Start a run with Hellhorned preferably Shardtail Queen
-
Give yourself the artifact either through the GUI or by using the console (command:
artifact ShiningHorn) -
Check the description of the artifact. Depending on your start it should include the exact number of imps in your deck.
-
Draw an imp during battle. It should have the Green Endless text.
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.
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"
}
]
}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.
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_durationAllow 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
monsterand subtypeSubtypesData_TrainSteward. This is the left hand side of the comparison. -
param_comparatoris ["greater than", "equal"] which means greater than OR equal -
param_intis 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.
-
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.