Skip to content

Adding Potions

Alchyr edited this page Sep 9, 2024 · 17 revisions

Registering Your Potions

The process of making potions has two steps.

Step 1: Make the potion.

Step 2: Register the potion.

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

Setting up AutoAdd for potions

Potions do not have any specific subscriber you need to add to your main mod file to register them in. Instead, they're simply registered in receivePostInitialize, which you should already have in the main mod file.

For organization, we'll make a separate static method to register all the potions in, and just call that method in receivePostInitialize.

    @Override
    public void receivePostInitialize() {
        registerPotions();
        
        //... the rest of the method
    }

    public static void registerPotions() {

    }

You can find a bit more explanation of AutoAdd in the pages for adding cards and relics. This code utilizes information stored in the BasePotion class to register the potion appropriately.

    public static void registerPotions() {
        new AutoAdd(modID) //Loads files from this mod
                .packageFilter(BasePotion.class) //In the same package as this class
                .any(BasePotion.class, (info, potion) -> { //Run this code for any classes that extend this class
                    //These three null parameters are colors.
                    //If they're not null, they'll overwrite whatever color is set in the potions themselves.
                    //This is an old feature added before having potions determine their own color was possible.
                    BaseMod.addPotion(potion.getClass(), null, null, null, potion.ID, potion.playerClass);
                    //playerClass will make a potion character-specific. By default, it's null and will do nothing.
                });
    }

You may need to use Alt+Enter to import some classes. Any potions you make should now be added automatically, as long as they're in the same package as BasePotion. 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 BasePotion and will not work if you are not using this abstract class.

If you make potions that don't extend the BasePotion class, they will not be registered by this AutoAdd. You can register them manually with BaseMod.addPotion or set up AutoAdd differently.

The BasePotion class has a lot of code to simplify the potion creation process. If looking at other potions as examples, look primarily at their constructors and use methods.

Making Potions

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

image

You should probably organize your potions using packages, such as having a package for common, uncommon, and rare potions.

public class MyPotion extends BasePotion {

}

First, you'll need to define the potion's ID.

public class MyPotion extends BasePotion {
    public static final String ID = makeID("MyPotion");

}

Every potion has to have a unique ID. You can also use something like makeID(MyPotion.class.getSimpleName()). This will have the same result, but if you make a copy of the class it will be changed automatically.

BasePotion has two different constructors. In one of them you set the colors of the potion yourself, and in the other you just choose one of the "preset" colorings defined by the base game. In this example, we'll be using the one where you set the colors yourself. First, define the colors you'll be using.

public class MyPotion extends BasePotion {
    public static final String ID = makeID(MyPotion.class.getSimpleName());

    private static final Color LIQUID_COLOR = CardHelper.getColor(255, 0, 255);
    private static final Color HYBRID_COLOR = CardHelper.getColor(255, 0, 255);
    private static final Color SPOTS_COLOR = CardHelper.getColor(255, 0, 255);

CardHelper.getColor is a helper method defined by the base game to create a color based on red/green/blue values from 0 to 255. You can define these colors in any way you want, whether using a preexisting color (eg. Color.GRAY.cpy();) or using one of the constructors of the Color class (new Color().

It's important to note that which of these colors you need depends on the shape of the potion. Not all shapes support all three colors. In fact, if you provide a color that isn't supported, an error will occur and your potion will just turn into a Fire Potion. In these cases, you should be able to find an error message in the log explaining the problem, such as this:

image

In this example, the potion has a spots color, but the shape used doesn't support it. To fix this, you would just use null for SPOTS_COLOR.

    private static final Color LIQUID_COLOR = CardHelper.getColor(255, 0, 255);
    private static final Color HYBRID_COLOR = CardHelper.getColor(255, 0, 255);
    private static final Color SPOTS_COLOR = null;

Constructor

    public MyPotion() {
        super(ID, 5, PotionRarity.COMMON, PotionSize.MOON, LIQUID_COLOR, HYBRID_COLOR, SPOTS_COLOR);
    }

The first parameter is just the ID.

The second parameter is the potion's potency, the number that appears in its description and is affected by relics like Sacred Bark. If your potion doesn't have a number like this just use 0.

Third is the rarity.

Fourth is the shape. This determines the shape of the bottle and which of the color variables you can use. For a custom appearance, just use any of them and continue on until the end of this page.

Lastly, is the color. Here you'll either give it three colors you've chosen, or a PotionColor, like PotionColor.FIRE.

If you want the potion to be only for a specific character, add

    public MyPotion() {
        super(ID, 5, PotionRarity.COMMON, PotionSize.MOON, LIQUID_COLOR, HYBRID_COLOR, SPOTS_COLOR);
        playerClass = AbstractPlayer.PlayerClass.IRONCLAD;
    }

to the constructor. This would make this potion exclusive to the Ironclad (note that this is set up through the AutoAdd checking this field; normally you have to set the character when registering the potion instead).

If your potion is going to be thrown at an enemy, set isThrown = true; and targetRequired = true;. If it's just thrown but doesn't need a target (eg. Explosive Potion) just set isThrown to true.

Making the Potion Work

Right now, your potion class should look something like this:

image

Click on the class definition (the line with the error) and hit Alt+Enter. This should give you the option to "Implement methods".

image

Choose it. In the window appears, just leave it as-is and click Ok.

image

This should create two empty methods inside the class, getDescription and use.

Text

The text for potions is loaded from PotionStrings.json, and accessed using the DESCRIPTIONS array. You can use this method, defining the text in parts:

  "${modID}:MyPotion": {
    "NAME": "Some Kind of Potion",
    "DESCRIPTIONS": [
      "Gain #b",
      " #yStrength."
    ]
  }

And then put the description together in the getDescription method. #b is used to color the number that will be added there blue. (Updated 9/9/2024 - if the DESCRIPTIONS field does not exist use potionStrings.DESCRIPTIONS.)

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

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

  "${modID}:MyPotion": {
    "NAME": "Some Kind of Potion",
    "DESCRIPTIONS": [
      "Gain #b%d #yStrength."
    ]
  }

Then in getDescription, you would use String.format.

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

Functionality

In the use method, you queue actions to do whatever you want the potion to do. In this case, gaining Strength. Since this potion doesn't target an enemy, target is unused.

    @Override
    public void use(AbstractCreature target) {
        if (AbstractDungeon.getCurrRoom().phase == AbstractRoom.RoomPhase.COMBAT) {
            addToBot(new ApplyPowerAction(AbstractDungeon.player, AbstractDungeon.player, new StrengthPower(AbstractDungeon.player, potency), potency));
        }
    }

The in-combat check if (AbstractDungeon.getCurrRoom().phase == AbstractRoom.RoomPhase.COMBAT) may not be necessary.

Additional Tooltips

Potions do not generate keyword tooltips on their own. If you need them (or any other additional tooltip), override the addAdditionalTips method and add the tooltips to the "tips" ArrayList.

    @Override
    public void addAdditionalTips() {
        //Adding a tooltip for Strength
        this.tips.add(new PowerTip(TipHelper.capitalize(GameDictionary.STRENGTH.NAMES[0]), GameDictionary.keywords.get(GameDictionary.STRENGTH.NAMES[0])));
    }

When Slay the Spire starts and loads its localization, it stores most of its keyword text in the GameDictionary class. If you want to get some text from one of your custom keywords, this is how I recommend doing it for BasicMod.

Custom Appearance

Utilize the setContainerImg/setOutlineImg/setLiquidImg/setHybridImg/setSpotsImg methods, along with TextureLoader.getTexture. Use methods like imagePath (defined in the main mod file) to create the path to the image in your resource files.

Images are rendered in the order outlineImg, liquidImg, hybridImg, spotsImg, containerImg. liquidImg, hybridImg, and spotsImg are rendered in the provided colors, if those colors are not null. containerImg will appear the same as the original image. These images are expected to be 64x64.

To use a fixed image, you can pass null for hybridColor and spotsColor, use a blank image for setLiquidImg (you can use TextureLoader.getTexture("images/blank.png") for a blank image built into Slay the Spire), and then use setContainerImg to the desired image. You should also setOutlineImg to an outline of that image.

Additional Details

Sacred Bark

If your potion uses potency, Sacred Bark will work with no additional changes. If your potion does not use potency, you can manually check for Sacred Bark if you want or just don't if doubling the effect doesn't make sense.