Skip to content

Adding Relics

Alchyr edited this page Apr 17, 2024 · 27 revisions

Before working on relics, I would recommend making sure you understand the action queue.

If you've already read the tutorial on cards, this will likely seem very similar.


Registering Your Relics

The process of making relics has two steps.

Step 1: Make the relic.

Step 2: Register the relic.

AutoAdd is a feature of BaseMod that allows you to avoid manually registering every relic you make.

Setting up AutoAdd for relics

First, in your main mod file, add EditRelicsSubscriber to the subscribers at the top, and then implement the receiveEditRelics method.

public class MyMod implements
        EditRelicsSubscriber, //up at the top
        EditStringsSubscriber,
    @Override
    public void receiveEditRelics() { //somewhere in the class
        
    }

AutoAdd for relics is a bit more complicated than AutoAdd for cards. All cards are normally registered with the same method, but relics are registered differently depending on their type. Specifically, character-specific relics have to be registered differently from general relics. This code utilizes information stored in the BaseRelic class to register the relic appropriately.

    @Override
    public void receiveEditRelics() { //somewhere in the class
        new AutoAdd(modID) //Loads files from this mod
            .packageFilter(BaseRelic.class) //In the same package as this class
            .any(BaseRelic.class, (info, relic) -> { //Run this code for any classes that extend this class
                if (relic.pool != null)
                    BaseMod.addRelicToCustomPool(relic, relic.pool); //Register a custom character specific relic
                else
                    BaseMod.addRelic(relic, relic.relicType); //Register a shared or base game character specific relic

                //If the class is annotated with @AutoAdd.Seen, it will be marked as seen, making it visible in the relic library.
                //If you want all your relics to be visible by default, just remove this if statement.
                if (info.seen) 
                    UnlockTracker.markRelicAsSeen(relic.relicId);
            });
    }

You may need to use Alt+Enter to import some classes. Any relics you make should now be added automatically, as long as they're in the same package as BaseRelic. This includes sub-packages. If you want the details of how AutoAdd works, check the BaseMod wiki page for documentation. This code is made to work with how BasicMod is setup and will require changes if you are not using BasicMod.

If you make relics that don't extend the BaseRelic class, they will not be registered by this AutoAdd. You can register them manually with BaseMod.addRelic/addRelicToCustomPool(new YourRelic(), pool) or set up AutoAdd differently.

Making Relics

You should make your first relic from scratch to understand all the parts, but after that feel free to copy and paste it and just change what you need.

The first step is to make a new class in the relics package.

image

You could also organize your relics using packages, such as having a package for common, uncommon, and rare relics.

All relics in the game extend from AbstractRelic, the class used as the base for every relic. The BaseRelic class is an abstract class with some small features to make it easier to create relics. The main point is that it stores information that the previously set up AutoAdd can use to register the relic with BaseMod correctly.

public class MyRelic extends BaseRelic {

}

Relic Properties

First, you'll be defining a few properties that all relics need.

public class MyRelic extends BaseRelic {
    private static final String NAME = "MyRelic"; //The name will be used for determining the image file as well as the ID.
    public static final String ID = makeID(NAME); //This adds the mod's prefix to the relic ID, resulting in modID:MyRelic
    private static final RelicTier RARITY = RelicTier.COMMON; //The relic's rarity.
    private static final LandingSound SOUND = LandingSound.CLINK; //The sound played when the relic is clicked.
}

Every relic has to have a unique ID. By adding a prefix based on the mod id to your relic's name, it greatly reduces the likelihood of issues with other mods having relics with the same ID. Since the ID is used in various places, such as to define the player's starting relic, having the ID as a public constant is useful.

I recommend always having the name and ID defined as constants, as they're useful to reference elsewhere, but for RARITY and SOUND it's fine to just use them directly in the constructor instead of defining them as constants. They're defined here to make it easier to explain what they're for.

RARITY is the relic's rarity:

RelicTier.STARTER is for starting relics. COMMON, UNCOMMON, and RARE are self-explanatory. SHOP relics can only be obtained from the third relic slot in a shop, which is always a SHOP relic. BOSS is for relics found in the boss chest. SPECIAL is for relics that are only obtained from events/cannot be normally obtained.

SOUND is the sound played when the relic is picked up/clicked. The names somewhat describe the sound, so just use whichever is most fitting for your relic.

Constructor

BaseRelic has two different constructors. One is for general relics, and the other is for character specific relics.

public class MyRelic extends BaseRelic {
    private static final String NAME = "MyRelic"; //The name will be used for determining the image file as well as the ID.
    public static final String ID = makeID(NAME); //This adds the mod's prefix to the relic ID, resulting in modID:MyRelic
    private static final RelicTier RARITY = RelicTier.COMMON; //The relic's rarity.
    private static final LandingSound SOUND = LandingSound.CLINK; //The sound played when the relic is clicked.

    public MyRelic() {
        super(ID, NAME, MyCharacter.Meta.CARD_COLOR, RARITY, SOUND);
    }
}

This example is using the constructor for a character-specific relic. The third parameter is the card color of the character the relic is for. For an Ironclad only relic, it would be AbstractCard.CardColor.RED. If you aren't making a relic for a specific character, just omit that parameter. In that case, it would look like:

super(ID, NAME, RARITY, SOUND);

BaseRelic will use NAME to determine the relic's image files. We'll go over setting up the relic's images at the end.

How Relics Work

Right now, you have a relic that does nothing. To make it do things, you need to override methods defined in AbstractRelic known as hooks. These methods are all linked to specific events during gameplay, such as the start of combat, taking damage, or entering a new room.

The easiest way to know what method to override is to look at a base game example that triggers at the time you want. If you wanted a relic that does something at the end of combat, you could look at Ironclad's Burning Blood to find that it overrides onVictory. If you can't think of an example, you can see all of the methods by viewing the AbstractCard class with a decompiler (mentioned near the start of this).

Special Cases

Occasionally, you might want to make a relic that doesn't have any corresponding hook method for you to override. The original game also has relics like these, where it was something specific enough that making a hook was inconvenient/unnecessary. For example, Unceasing Top has no code in the relic itself. All the code takes place in various places where cards are removed from the player's hand.

For relics like these, you can first see if StSLib or BaseMod's subscribers provide the hook you need. If they don't, you'll need to do some patching to change the game's code to do what you want.

Adding Some Functionality

For this example relic, we'll make a relic that gives the player 10 Strength whenever they play a card.

To figure out what hook to use, you could look at a relic like Ink Bottle which counts cards played. Many other relics use the same hook, such as Pen Nib and Mummified Hand.

public class MyRelic extends BaseRelic {
    private static final String NAME = "MyRelic"; //The name will be used for determining the image file as well as the ID.
    public static final String ID = makeID(NAME); //This adds the mod's prefix to the relic ID, resulting in modID:MyRelic
    private static final RelicTier RARITY = RelicTier.COMMON; //The relic's rarity.
    private static final LandingSound SOUND = LandingSound.CLINK; //The sound played when the relic is clicked.

    private static final int STRENGTH = 10; //For convenience of changing it later and clearly knowing what the number means instead of just having a 10 sitting around in the code.

    public MyRelic() {
        super(ID, NAME, MyCharacter.Meta.CARD_COLOR, RARITY, SOUND);
    }


    @Override
    public void onUseCard(AbstractCard targetCard, UseCardAction useCardAction) {
        super.onUseCard(targetCard, useCardAction);
    }
    // Take advantage of autocomplete!
    // If you type "public onUse" IntelliJ should already have the method in the suggestions. 
    // Use the up/down arrows to select it and press enter to automatically create this whole chunk.
    // This autocomplete is also a good way to see all the hooks/look for the right hook by name, by just typing "publi"
}

To see how to give the player Strength, you can look at quite a few options, like Vajra, Girya, or Inflame. You'll need to use an ApplyPowerAction to give the player Strength.

    @Override
    public void onUseCard(AbstractCard targetCard, UseCardAction useCardAction) {
        addToBot(new ApplyPowerAction(AbstractDungeon.player, AbstractDungeon.player, new StrengthPower(AbstractDungeon.player, STRENGTH)));
    }

This will cause the relic to give the player the specified amount of Strength whenever they play a card.

Text

For relics, you must add their text to the localization file at localization/eng/RelicStrings.json or the game will crash, unlike with cards where you'll just get a missing title/description. For this example, the relic's name is MyRelic so the localization would look like this.

  "${modID}:MyRelic": {
    "NAME": "My Relic",
    "FLAVOR": "I followed a tutorial!",
    "DESCRIPTIONS": [
      "Whenever you play a card, gain #b",
      " #yStrength."
    ]
  }

You could just use Whenever you play a card, gain #b10 #yStrength. in the localization file, but that would make it more inconvenient to change the value later. Instead, you'll create the final description by overriding the getUpdatedDescription method in your relic.

    @Override
    public String getUpdatedDescription() {
        return DESCRIPTIONS[0] + STRENGTH + DESCRIPTIONS[1];
    }

Slay the Spire tends to construct relic descriptions using string concatenation like this, but you can also use the String.format method. In this case, you would use %d in the relic's description text for where the number should go.

    "DESCRIPTIONS": [
      "Whenever you play a card, gain #b%d #yStrength."
    ]

Then in getUpdatedDescription, you would use String.format.

    @Override
    public String getUpdatedDescription() {
        return String.format(DESCRIPTIONS[0], STRENGTH);
    }

You should now be able to test the relic, though it will have a placeholder image. A full explanation of how relic text should be formatted will be at the end.

image

Adding an Image

A relic has up to 3 images, but only one of them is required. The outline image is strongly recommended, especially for character-specific relics. Without them the relics tend to look a bit mismatched stylistically.

The example filenames here are based on a relic with private static final String NAME = "MyRelic";.

First is the base relic image. This is a 128x128 image, but only the center 64x64 area should be used. You can find an example template in the images/relics folder of the resources. The image for your relic should be named MyRelic.png.

Next is the large relic image. This is just a 256x256 (twice the size) version of the base relic image. You can make this one first, and then scale it down to create the base relic image. This one should use the same name, but it should go in the relics/large folder.

Last of all is the outline. This should be a 128x128 image very similar to the base relic image, but entirely white and slightly larger. This is rendered underneath the relic in a color based on what character the relic is for. This image goes in the relics folder and should have the same name as the relic image, but with Outline added. For this example, it would be MyRelicOutline.png.

The large relic image and outline are optional. If the large relic image is excluded, the smaller one will be scaled up instead when necessary, which will result in a blurry image. Not having an outline will just result in the relic not having an outline.


Relic Descriptions

For wording choices, use base game examples where possible.

The first letters of keywords are capitalized. The word ALL is fully capitalized in most cases, such as referring to ALL enemies, or ALL cards in your deck (Apotheosis).

All keywords must have #y placed in front of them. Keywords from your mod must also be prefixed with your mod ID.

Numbers are colored blue by putting #b in front of them. Numbers are shown numerically (0123456789), not by name.

For energy icons in text, use [E].

You can use [#rrggbb]red/green/blue[] hex color codes to color text whatever color you want. Be aware that coloring text this way will prevent keywords from being detected.

You can use NL to add a new line, but this is usually not necessary for relic descriptions.

For a starter relic, try to keep the description short. If it's too long, it might not fit on the character select screen.