[JAVA] How to make a mod for Slay the Spire

I want to enjoy making mods for Slay the Spire

Recognize

That's why it is a course on how to make a mod for Slay the Spire.

BaseMod wiki in Git https://github.com/daviscook477/BaseMod/wiki/Getting-Started-(For-Modders)

Mod list on the wiki in Mod The Spire's Git https://github.com/kiooeht/ModTheSpire/wiki/List-of-Known-Mods

Introduction to Slay the Spire Mod Development https://qiita.com/kojim/items/0c7164c78a5a909b4478 https://qiita.com/kojim/items/1a97f200fc8e545cee13 https://qiita.com/kojim/items/f61d9f3553e2d045aa2e

You can do it by looking at the above. Thanks to my ancestors.

I will explain "Getting Started With Modding Slay The Spire" on the BaseMod wiki because it will not increase the number of people who make it. The beginning is the construction of the development environment, but if you have a Java development environment, please read it diagonally. In the first place, this article is for super beginners.

Development environment

First of all, you are told to create a folder. You can go anywhere, but please create a folder. MOD In a folder with the name I'm going to make a lot (such as my_mods). So, it is said that you can create a folder called "lib" in it, so create it. Put BaseMod.jar and ModTheSpire.jar there.

BaseMod.jar https://github.com/daviscook477/BaseMod/releases ModTheSpire.jar https://github.com/kiooeht/ModTheSpire/releases

BaseMod is a mod API, and Mod The Spire is required to run Slay the Spire with mods. Well both are needed. Yes. Oh, you can also enter from Steam Workshop. Let's put it in.

Then, in the same folder, find desktop-1.0.jar in the Slay the Spire folder and put it in it.

Now, regarding the item of "Environment Setup", since it is a development environment, please choose the one you like. It was written that it was easy, so I did it with Intel iJ, and I will only explain it after that. So first install Java SE Development Kit 8 and then InteliJ as well.

It says that you should create New Project after starting Intel iJ with. Select JDK1.8 and Maven Next, GroupID and ArtifactId should be the name of the mod you are about to create. The example is ExampleMod. Finally, finish so that the project file is created in the folder created earlier.

Well, then suddenly an important thing is written, but I think that pom.xml is directly under the project folder, so edit it.

Example Mod pom.xml https://gist.github.com/alexdriedger/fb74397086ee80073417f19d6305bb05

Here, the locations of the three jar files that you put in the lib folder and their versions are specified. Be careful if the directory structure is different from the example.

Also, I think it was necessary to partially edit the package from line 54 here. Please write it because it is the output destination after building.

When you finish editing the pom, you will see a pop-up saying "Maven projects need to be imported", so press Import Changes to reflect it. Then press Ctrl + Alt + Shift + S to bring up the Structure screen, select Modules, and there are 3 Mavens, so check it and Apply.

Now, if you double-click "Lifecycle> package" in the window opened by "View> Tool Windows> Maven Projects", you can build it and a .jar file will be created in the target folder.

Let's do our best until we make a suitable character

Color and card addition

The character class of Slay the Spire is called COLOR, Iron Clad is RED, Silent is GREEN, and Defect is BLUE. If you want to make a new character, you have to add this COLOR first (although it would be easier if you just put an additional card for these guys).

Custom Colors https://github.com/daviscook477/BaseMod/wiki/Custom-Colors

It's difficult to read and understand this much, so I'll explain it myself from here on. Let's go one by one. First, add colors and cards. I'm not sure if it was added only in color, so I can add cards at once.

Also, it's annoying to explain, so if you don't understand well, please see the following repository. It's a hassle to prepare images, so please start from here. https://github.com/levelnineteen/stsmod

I think that the structure is like this when the project is created, but the class (program part) is placed under java, and the text and images are placed under resource.

examle_mod +.idea +src -+main --+java --+resource

First, create a directory with the mod name under java, and a directory called patches under it, and create the following two classes.

examle_mod +.idea +src -+main --+java ---+example_mod ----+patches --+resource

AbstractCardEnum.java


package example_mod.patches; //I think it will enter here automatically

import com.evacipated.cardcrawl.modthespire.lib.SpireEnum;
import com.megacrit.cardcrawl.cards.AbstractCard;

public class AbstractCardEnum {

    @SpireEnum
    public static AbstractCard.CardColor EXAMPLE_COLOR; //Favorite variable name. I will use it forever.

}

LibraryTypeEnum.java


package example_mod.patches; //I think it will enter here automatically

import com.evacipated.cardcrawl.modthespire.lib.SpireEnum;
import com.megacrit.cardcrawl.helpers.CardLibrary;

public class LibraryTypeEnum {

    @SpireEnum
    public static CardLibrary.LibraryType EXAMPLE_COLOR; //Same as above. I will use it forever.

}

Now that the preparations are complete, create Main.java or MOD name.java class directly under java / example_mod so that it is easy to understand.

Main.java


import basemod.BaseMod;
import basemod.interfaces.*;
import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer;
import com.megacrit.cardcrawl.core.Settings;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.megacrit.cardcrawl.helpers.CardHelper;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.localization.CharacterStrings;

import example_mod.patches.*; //I'll read the patch I made earlier

@SpireInitializer
public class Main implements
        EditCardsSubscriber,   //Implement when adding a card
        EditStringsSubscriber,  //Implement when reading a language file
{
    //First, there are variables for basic color settings.
    private static final Color EXAMPLE_COLOR_BG = CardHelper.getColor(100.0f, 50.0f, 50.0f); //This is the background color of the bar when it appears in the card list selection.
    private static final String ATTACK_EXAMPLE         = "img/cards/bg_attack_512.png "; //This area will be explained later
    private static final String SKILL_EXAMPLE          = "img/cards/bg_skill_512.png "; //Please write the variable name for the time being
    private static final String POWER_EXAMPLE           = "img/cards/bg_power_512.png ";
    private static final String ENERGY_ORB_EXAMPLE         = "img/cards/orb_512.png ";
    private static final String ATTACK_PORT_EXAMPLE        = "img/cards/bg_attack_1024.png ";
    private static final String SKILL_PORT_EXAMPLE         = "img/cards/bg_skill_1024.png ";
    private static final String POWER_PORT_EXAMPLE         = "img/cards/bg_power_1024.png ";
    private static final String ENERGY_ORB_PORT_EXAMPLE = "img/cards/orb_1024.png ";
    private static final String ENERGY_ORB_CARD_EXAMPLE = "img/cards/orb_ui.png ";

    public Main(){
        BaseMod.subscribe(this);
        BaseMod.addColor(
                //It is a color addition part. Just enter the variables set above.
                AbstractCardEnum.EXAMPLE_COLOR    //color
                , EXAMPLE_COLOR_BG //bgColor
                , EXAMPLE_COLOR_BG//backColor
                , EXAMPLE_COLOR_BG//frameColor
                , EXAMPLE_COLOR_BG//frameOutlineColor
                , EXAMPLE_COLOR_BG//descBoxColor
                , EXAMPLE_COLOR_BG //trailVfColor
                , EXAMPLE_COLOR_BG//glowColor
                , ATTACK_EXAMPLE//attackBg
                , SKILL_EXAMPLE//skillBg
                , POWER_EXAMPLE//powerBG
                , ENERGY_ORB_EXAMPLE//energyOrb
                , ATTACK_PORT_EXAMPLE//attackBgPortrait
                , SKILL_PORT_EXAMPLE//skillBgPortrait
                , POWER_PORT_EXAMPLE//powerBgPortrait
                , ENERGY_ORB_PORT_EXAMPLE//energyOrbPortrait
                , ENERGY_ORB_CARD_EXAMPLE//CardEnergyOrb
        );
    }

    public static void initialize() {
        Main main = new Main();
    }

    @Override
    public void receiveEditStrings() {
        //Language files will be explained later.
        BaseMod.loadCustomStringsFile(CardStrings.class, "localization/cards-" + Settings.language + ".json");
    }

    @Override
    public void receiveEditCards() {
        //This is the card addition part. This will also be explained later.
        BaseMod.addCard(new example_mod.cards.TestAttack());
    }
}

I still have something to write. Because I have to write the contents of the essential card. Prepare a directory called cards directly under java / example_mod and put the following classes.

TestAttack.java


package example_mod.cards; //Well here should enter automatically

import basemod.abstracts.CustomCard;
import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import example_mod.patches.AbstractCardEnum; //Card color cannot be handled unless this is imported

public class TestAttack extends CustomCard {
    public static final String ID = "examplemod:TestAttack"; //Don't make a mistake because it is the reference ID of the language file.
    private static CardStrings cardStrings = CardCrawlGame.languagePack.getCardStrings(ID);
    public static final String NAME = cardStrings.NAME;
    public static final String DESCRIPTION = cardStrings.DESCRIPTION;
    public static final String IMG_PATH = "img/cards/card.png "; //It is a picture of the card. I will explain later.
    private static final int COST = 0; //Energy cost
    private static final int ATTACK_DMG = 4; //damage
    private static final int UPGRADE_PLUS_DMG = 3; //Damage when upgrading

    public TestAttack() {
        super(ID, NAME, IMG_PATH, COST, DESCRIPTION,
                CardType.ATTACK, //Card type
                AbstractCardEnum.EXAMPLE_COLOR, //The card color variable that I have been using since a while ago. Don't make a mistake
                CardRarity.COMMON,   //Card rarity
                CardTarget.ENEMY //Who is the target
        );
        this.damage=this.baseDamage = ATTACK_DMG;
    }

    @Override
    public void use(AbstractPlayer p, AbstractMonster m) {
        //Cause damage
        AbstractDungeon.actionManager.addToBottom(
                new com.megacrit.cardcrawl.actions.common.DamageAction(
                        m,
                        new DamageInfo(p, this.damage, this.damageTypeForTurn),
                        AbstractGameAction.AttackEffect.SLASH_DIAGONAL) //Screen effect
        );
    }

    @Override
    public AbstractCard makeCopy() {
        return new TestAttack();
    }

    //Processing at the time of card upgrade
    @Override
    public void upgrade() {
        if (!this.upgraded) {
            upgradeName();
            upgradeDamage(UPGRADE_PLUS_DMG);
        }
    }
}

Yes, thank you for your hard work. There are many things to do just by adding a new type of card. There are still.

examle_mod +.idea +src -+main --+java ---Main.java ---+example_mod ----+cards -----TestAttack.java ----+paches -----LibraryTypeEnum.java -----AbstractCardEnum.java --+resource

Now we need to add the image and language files to the resource. Create an img / cards directory and a localization directory under resource. The following files are required in img / cards. All are transparent png files.

bg_attack_512.png Background of the attack card. 512x512 bg_skill_512.png Skill card background. 512x512 bg_power_512.png Power card background. 512x512 You don't have to prepare the band under the card name. orb_512.png Energy orb. Since it can be placed on a 512x512 card, it must be drawn small in the upper left. bg_attack_1024.png This area should be expanded from 512 to 1024x1024. bg_skill_1024.png bg_power_1024.png orb_1024.png However, this guy must not enlarge 512 (the center point is different). Well, I think it's better to reduce each of these large ones. orb_ui.png Display when probably used in tooltips. 23x23 card.png Card design. Originally, it is easy to match with the card name (card class name). Well because it's a test. 250x190 card_p.png A large version of the design. You can enlarge the above. 500x380. Since it is used automatically, it is not specified in any class.

The following json files are required in localization.

cards-JPN.json


{
  "examplemod:TestAttack": { //Mod identifier:Write with the card name. Otherwise, I'll wear it.
  "NAME": "card name",
  "DESCRIPTION": "A description of the card.!d!If you write, the damage value will come out, but please check there"
   }
}

Well, it's made overseas, so please make cards-ENG.json and prepare it in English.

I forgot, but if there is no json below directly under resources, various things will be said when selecting a mod, so please put it in.

ModTheSpire.json

{
  "modid": "examplemod",
  "name": "examplemod",
  "author_list": ["yourname"],
  "description": "",
  "version": "0.0.1",
  "sts_version": "01-01-2019",  //Which version of Slay the Spire should be higher?
  "mts_version": "3.6.0", //I think it's a story about which version of Mod the Spire should be higher, so it's appropriate
  "dependencies": ["basemod"],
  "update_json": ""
}

thank you for your hard work. It should now work.

--+java ---Main.java ---+example_mod ----+cards -----TestAttack.java ----+paches -----LibraryTypeEnum.java -----AbstractCardEnum.java --+resource ---ModTheSpire.json ---+img ----+cards ----- Full of images for cards ---+localization ----cards-JPN.json ----cards-ENG.json

If you double-click "Lifecycle> package" in the window opened by "View> Tool Windows> Maven Projects", you can build it and you will have a .jar file in the target folder. Please deal with the error.

Launch the mod

Create a folder called mods in steamapps \ common \ SlayTheSpire in the Steam library. Put the example_mod.jar you created earlier and BaseMod.jar in this mod.

Directly under steamapps \ common \ SlayTheSpire, put MTS.cmd that was inside when you unzipped ModTheSpire.zip. Mac people seem to prefer .sh.

So, when I run MTS.cmd,

image.png

So, check BaseMod and example_mod, then check Debug and press Play. Somehow example_mod is gray and cannot be selected because the mts_version specified in ModTheSpire.json is wrong.

If it starts up safely, example_mod is added to the card library, and a cool card is added, it is successful so far. Thank you for your hard work.

Add a character ... Before, relic or power

If you want to add a character, you need a starter relic, and the card also needs power in addition to skill. So let's go all at once. From the addition of relics. Create a relics directory under java / example_mod and create the following classes.

TestRelic.java


package example_mod.relics;

import basemod.abstracts.CustomRelic;
import com.megacrit.cardcrawl.helpers.ImageMaster;
import com.megacrit.cardcrawl.relics.AbstractRelic;

//Enter by ability
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.powers.RegenPower;
import com.megacrit.cardcrawl.actions.common.ApplyPowerAction;
import com.megacrit.cardcrawl.actions.common.RelicAboveCreatureAction;

public class TestRelic extends CustomRelic {

    public static final String ID = "examplemod:TestRelic";
    public static final String IMG = "img/relics/TestRelic.png ";
    public static final String OUTLINE_IMG = "img/relics/outline/TestRelic.png ";

    public TestRelic(){
        super(
                ID,
                ImageMaster.loadImage(IMG),
                ImageMaster.loadImage(OUTLINE_IMG),
                RelicTier.STARTER,  //Rarity. Here is the starter relic
                LandingSound.FLAT
        );
    }

    public String getUpdatedDescription(){
        return DESCRIPTIONS[0];
    }

    @Override
    public AbstractRelic makeCopy(){
        return new TestRelic();
    }

    //Get 5 plays at the beginning of the battle
    @Override
    public void onEquip() {
        AbstractDungeon.rareRelicPool.remove("Dead Branch");
    }

    public void atBattleStart() {
        AbstractDungeon.actionManager.addToBottom(
                new RelicAboveCreatureAction(AbstractDungeon.player, this)
        );
        AbstractDungeon.actionManager.addToBottom(
                new ApplyPowerAction(
                        AbstractDungeon.player,
                        AbstractDungeon.player,
                        new RegenPower(AbstractDungeon.player, 5),
                        5
                )
        );
    }
}

Create a relics directory under resources / img and prepare the following image files.

TestRelic.png 128x128 transparent png

In addition, create an outline directory under the relics directory and prepare the following image files.

TestRelic.png 128x128 transparent png. The one with the image in the above directory painted white.

Now, we also need a file for text, so prepare the following file in resources / localization.

relics-JPN.json


{
  "examplemod:TestRelic": {
    "NAME": "Relic name",
    "FLAVOR": "Flavor text",
    "DESCRIPTIONS": [
      "Explanatory text. Note that it is plural with S. When putting a variable in the description, decompose it and put it in between"
    ]
  }
}

Since it is made overseas, please make relics-ENG.json and prepare it in English.

Next, add power. The power itself is added, and the card that gains power is added. Create a powers directory under example_mod and create the following classes.

TestPowerGold.java


package example_mod.powers;

import com.badlogic.gdx.graphics.Texture;
import com.megacrit.cardcrawl.core.AbstractCreature;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.powers.AbstractPower;
import com.megacrit.cardcrawl.localization.PowerStrings;

public class TestPowerGold extends AbstractPower {
    public static final String POWER_ID = "examplemod:TestPowerGold";
    private static final PowerStrings powerStrings =
            CardCrawlGame.languagePack.getPowerStrings(POWER_ID);
    public static final String NAME = powerStrings.NAME;
    public static final String[] DESCRIPTIONS = powerStrings.DESCRIPTIONS;

    public TestPowerGold(AbstractCreature owner, int amount) {
        this.name = NAME;
        this.ID = POWER_ID;
        this.owner = owner;
        this.amount = amount;
        this.type = AbstractPower.PowerType.BUFF;
        updateDescription();
        this.img = new Texture("img/powers/TestPower.png ");
    }

    public void updateDescription() {
        this.description = (DESCRIPTIONS[0] + this.amount + DESCRIPTIONS[1]); //This is how the description is divided.
    }
}

You will also need a text file, so prepare the following files in resources / localization.

powers-JPN.json


{
  "examplemod:TestPowerGold": {
    "NAME": "Power name",
    "DESCRIPTIONS": [
      "Gold at the end of the battle",
      "Get"
      ]
  }
}

What is prepared here is a model of power. The specific power needs to be written on the card. Prepare the following classes in example_mod / cards.

TestPower.java


package example_mod.cards;

import basemod.abstracts.CustomCard;
import com.megacrit.cardcrawl.actions.common.ApplyPowerAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.monsters.AbstractMonster;

import example_mod.patches.AbstractCardEnum;
import example_mod.powers.*; //The one with the power I made earlier. Because it's annoying*

import com.megacrit.cardcrawl.rooms.AbstractRoom.RoomPhase;


public class TestPower extends CustomCard {
    public static final String ID = "examplemod:TestPower";
    private static CardStrings cardStrings = CardCrawlGame.languagePack.getCardStrings(ID);
    public static final String NAME = cardStrings.NAME;
    public static final String DESCRIPTION = cardStrings.DESCRIPTION;
    public static final String IMG_PATH = "img/cards/card.png "; //Image of the card. I will use it for the time being
    private static final int COST = 0; //Energy cost
  //Card-specific variables
    private static final int GETMONEY = 100;
    private static final int UPGRADE_GETMONEY = 10;

    public TestPower() {
        super(ID, NAME, IMG_PATH, COST, DESCRIPTION,
                CardType.POWER,
                AbstractCardEnum.EXAMPLE_COLOR, //I say it many times, but the color name
                CardRarity.COMMON,   //Rarity
                CardTarget.SELF //The target of the card. Power is about SELF
        );
        this.magicNumber = this.baseMagicNumber = GETMONEY;
    }

    @Override
    public void use(AbstractPlayer p, AbstractMonster m) {
        //The ability to get money at the end of the battle
        if (AbstractDungeon.getCurrRoom().phase == RoomPhase.COMBAT) {
            AbstractDungeon.getCurrRoom().addGoldToRewards(this.magicNumber);
            AbstractDungeon.actionManager.addToBottom(
                    new ApplyPowerAction(
                            p,
                            p,
                            new TestPowerGold(p, this.magicNumber), //The one with the power I just made
                            this.magicNumber
                    )
            );
        }
    }

    @Override
    public AbstractCard makeCopy() {
        return new TestPower();
    }

    //Processing at the time of card upgrade
    @Override
    public void upgrade() {
        if (!this.upgraded) {
            this.upgradeName();
            this.upgradeDamage(UPGRADE_GETMONEY);
        }
    }
}

I also need a power icon image. Create a powers directory under resources / img and prepare the following image files.

TestPower.png 32x32 transparent file. This is the only power.

--+java ---Main.java ---+example_mod ----+cards -----TestAttack.java -----TestPower.java ----+relics -----TestRelic.java ----+paches -----LibraryTypeEnum.java -----AbstractCardEnum.java ----+powers -----TestPowerGold.java --+resource ---ModTheSpire.json ---+img ----+cards ----- Full of images for cards ----+relics -----TestRelic.png -----+outline ------TestRelic.png ----+powers -----TestPower.png ---+localization ----cards-JPN.json ----cards-ENG.json ----relics-JPN.json ----relics-ENG.json ----powers-JPN.json ----powers-ENG.json

Well, this alone will not work. It is necessary to add to example_mod / Main.java.

Edit Main.java


//The following additions are required for import.
import com.megacrit.cardcrawl.localization.RelicStrings;
import com.megacrit.cardcrawl.localization.PowerStrings;
import example_mod.relics.*;

//More places to implements
public class Main implements
        PostInitializeSubscriber,   //Is it a badge or something to display?
        EditCardsSubscriber,   //Implement when adding a card
        EditRelicsSubscriber,   //Implementation when adding a relic
        EditStringsSubscriber,  //Implement when reading a language file
{

//Increase language files
   @Override
    public void receiveEditStrings() {
        BaseMod.loadCustomStringsFile(CardStrings.class, "localization/cards-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(RelicStrings.class, "localization/relics-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(PowerStrings.class, "localization/powers-" + Settings.language + ".json");
    }

//Increase the number of cards to add
    @Override
    public void receiveEditCards() {
        BaseMod.addCard(new training_mod.cards.TestAttack());
        BaseMod.addCard(new training_mod.cards.TestPower());
    }

//Add relic
    public void receiveEditRelics() {
        //Add relic
        BaseMod.addRelicToCustomPool(
                new TestRelic(),
                AbstractCardEnum.EXAMPLE_COLOR //Add to color. Common relic becomes another method
        );
    }

If you build and the power card is in the card list and it is displayed in the starter relic in the relic list, it is successful. There is a skill card creation though I have not explained it, but you can do it for the time being just by changing the CardType of the attack to CardType.SKILL.

Finally make a character

Sorry I made you wait. Let's make a character. Thinking normally, you need a character image first, so let's make it. According to BaseMod's explanation, it says that you can use Sprite. The explanation after that is the setting by the image set made with Spine. …… Spine is paid software, so let's go with Spriter.

Download from here. https://brashmonkey.com/download_spriter/

After installing it, please take a look at the following tutorial for the time being. https://www.youtube.com/watch?v=aQy7eX_CWPM&list=PL8Vej0NhCcI5sxOT64-Z31LW59VUyAVfr&t=0s&index=2

It is a fun software that allows you to create a transparent png file for each part (arms, legs, etc.), place it in Sprite, and animate it on the timeline.

It's too fun to do, so first create a char / anim directory under img and then create a Sprite project under it. Well, I think the project name such as exampleChar is fine. Place the character image in it.

--+java ---Main.java ---+example_mod ----+cards ----+relics ----+paches ----+powers --+resource ---+img ----+char -----+anim ------ CharacterImage.png 512x512 transparent png file

Then CharacterImage.png will be visible on the Splitter, so double-click to determine the reference point, select directly below, throw in the image and place it at x0, y0.

image.png

It's okay, it's appropriate ... I don't usually animate it, and you haven't seen it, right? STS character.

I need more images than that. Please prepare the following.

--+java ---Main.java ---+example_mod ----+cards ----+relics ----+paches ----+powers --+resource ---+img ----+char ----- ExampleCorpse.png 512x512 transparent png file. It's the one when he died. ----- ExampleShoulder.png 1920x1136 transparent png file. It is an image from behind that is displayed at camps. -----+anim ------ CharacterImage.png 512x512 transparent png file. A little while ago ------ ExampleChar.scml File saved by Sprite. Information such as placement and animation. I will use it after this. ----+charSelect ----- ExampleButton.png 200x200 transparent png file. Button to press on the character selection screen ----- ExamplePortrait.png 1920x1200 transparent png file. The background that appears after you press the button above. Nice to meet you.

Good work. The materials are now complete. Let's add characters. First, add the following class to example_mod / paches.

ExampleClassEnum.java


package example_mod.patches;
import com.evacipated.cardcrawl.modthespire.lib.SpireEnum;
import com.megacrit.cardcrawl.characters.AbstractPlayer;

//The example of the class name can be the name of the mod you make.

public class ExampleClassEnum {
    @SpireEnum
    public static AbstractPlayer.PlayerClass ExampleClass;
}

Create an example_mod / character directory and add the following classes in it.

ExampleChar.java


package example_mod.character;

//I've imported it even though I'm not using it, but I think there will come a time when I need something.
import java.util.ArrayList;
import java.util.List;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import basemod.abstracts.CustomPlayer;
import basemod.animations.SpriterAnimation;

import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.core.EnergyManager;
import com.megacrit.cardcrawl.cutscenes.CutscenePanel;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.events.city.Vampires;
import com.megacrit.cardcrawl.helpers.*;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.localization.CharacterStrings;
import com.megacrit.cardcrawl.screens.CharSelectInfo;
import com.megacrit.cardcrawl.screens.stats.CharStat;
import com.megacrit.cardcrawl.unlock.UnlockTracker;
import com.megacrit.cardcrawl.actions.AbstractGameAction.AttackEffect;

import example_mod.cards.*;
import example_mod.relics.*;
import example_mod.patches.*;

public class TrainingChar extends CustomPlayer {
    public static final CharacterStrings charStrings = CardCrawlGame.languagePack.getCharacterString("examplemod:ExampleChar");
    public static final int ENERGY_PER_TURN = 3; //How much energy can I get for one turn?
    public static final String EXAMPLECHAR_SHOULDER_2 = "img/char/ExampleShoulder.png "; //Behind the scenes at the camp
    public static final String EXAMPLECHAR_SHOULDER_1 = "img/char/ExampleShoulder.png "; //Other behind images. If you want to divide it, make it.
    public static final String EXAMPLECHAR_CORPSE = "img/char/ExampleCorpse.png "; //Corpse image

    public ExampleChar(String name){
        super(name,
                ExampleClassEnum.ExampleClass,
                null,
                null,
                null,
                new SpriterAnimation("img/char/anim/ExampleChar.scml") //Files created with Spriter
        );
        initializeClass(null,
                EXAMPLECHAR_SHOULDER_2,
                EXAMPLECHAR_SHOULDER_1,
                EXAMPLECHAR_CORPSE,
                getLoadout(),
                20.0F,
                -10.0F,
                220.0F,
                290.0F,
                new EnergyManager(ENERGY_PER_TURN)
        );

    }

    @Override
    public String getTitle(AbstractPlayer.PlayerClass playerClass){
        return charStrings.NAMES[0];
    }
    @Override
    public String getSpireHeartText(){
        return charStrings.TEXT[0];
    }
    @Override
    public String getLocalizedCharacterName() {
        return charStrings.NAMES[0]; //I don't know where the above is different. Is it okay to be the same?
    }
    @Override
    public String getVampireText() {
        //A vampire event. I think 0 is iron clad and 1 is silent, but I don't know.
        return Vampires.DESCRIPTIONS[0];
    }
    public Color getCardRenderColor() {
        return CardHelper.getColor(100.0f, 50.0f, 50.0f);   //EXAMPLE_COLOR_Is it the same as BG?
    }
    public Color getCardTrailColor() {
        return CardHelper.getColor(100.0f, 50.0f, 50.0f);   //EXAMPLE_COLOR_Is it the same as BG?
    }
    public Color getSlashAttackColor() {
        return CardHelper.getColor(100.0f, 50.0f, 50.0f);   //EXAMPLE_COLOR_Is it the same as BG?
    }
    public AttackEffect[] getSpireHeartSlashEffect() {
        //It seems to be an effect, but I'm not sure what it is.
        return new AttackEffect[]{
                AttackEffect.SLASH_HEAVY,
                AttackEffect.FIRE,
                AttackEffect.SLASH_DIAGONAL,
                AttackEffect.SLASH_HEAVY,
                AttackEffect.FIRE,
                AttackEffect.SLASH_DIAGONAL
        };
    }
    public AbstractCard getStartCardForEvent() {
        return new TestPower() ;    //I don't know what it is for, but for the time being
    }
    public BitmapFont getEnergyNumFont() {
        return FontHelper.energyNumFontBlue;
    }
    public void doCharSelectScreenSelectEffect() {
        //I think it's the sound and screen effect when you select a character
        CardCrawlGame.sound.playV("AUTOMATON_ORB_SPAWN", 1.75f);
        CardCrawlGame.screenShake.shake(ScreenShake.ShakeIntensity.LOW, ScreenShake.ShakeDur.SHORT, true);
    }
    public String getCustomModeCharacterButtonSoundKey() {
        return "AUTOMATON_ORB_SPAWN";
    }

    public AbstractCard.CardColor getCardColor() {
        //The color of the card used for this character
        return AbstractCardEnum.EXAMPLE_COLOR; //Card color variable. Make no mistake.
    }

    public AbstractPlayer newInstance() {
        return new ExampleChar(this.name);
    }


    public ArrayList<String> getStartingDeck() { //The contents of the initial deck. Put the basic BASIC rarity. as you like.
        ArrayList<String> retVal = new ArrayList<>();
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        return retVal;
    }

    public ArrayList<String> getStartingRelics() { //Designation of starter relic.
        ArrayList<String> retVal = new ArrayList<>();
        retVal.add(TestRelic.ID);
        UnlockTracker.markRelicAsSeen(TestRelic.ID);
        return retVal;
    }

    //The part that determines the character ability
    private static final int STARTING_HP = 75;
    private static final int MAX_HP = 75;
    private static final int STARTING_GOLD = 99;
    private static final int HAND_SIZE = 5;
    private static final int ORB_SLOTS = 0;
    private static final int ASCENSION_MAX_HP_LOSS = 5;

    public int getAscensionMaxHPLoss() {
        return ASCENSION_MAX_HP_LOSS;
    }

    public CharSelectInfo getLoadout() { //I think it's the information that appears on the character selection screen
        return new CharSelectInfo(
                charStrings.NAMES[0],
                charStrings.TEXT[0],
                STARTING_HP,
                MAX_HP,
                ORB_SLOTS,
                STARTING_GOLD,
                HAND_SIZE,
                this,
                getStartingRelics(),
                getStartingDeck(),
                false);
    }
}

Add the following files to resources / localization.

char-JPN.json

{
  "examplemod:ExampleChar": {
    "NAMES": ["Character name"],
    "TEXT": ["Explanatory text. If you want to change it depending on the location, divide it"]
  }
}

Since it is an overseas ry, please also prepare char-ENG.json.

Now, the final edit of example_mod / Main.java.

Main.java

import basemod.BaseMod;
import basemod.interfaces.*;
import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer;
import com.megacrit.cardcrawl.core.Settings;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.megacrit.cardcrawl.helpers.CardHelper;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.localization.CharacterStrings;
import com.megacrit.cardcrawl.localization.RelicStrings;
import com.megacrit.cardcrawl.localization.PowerStrings;

//This is when you want to output debug log
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import example_mod.patches.*;
import example_mod.relics.*;

@SpireInitializer
public class Main implements
        EditCardsSubscriber,   //Implement when adding a card
        EditRelicsSubscriber,   //Implementation when adding a relic
        EditStringsSubscriber,  //Implement when reading a language file
        EditCharactersSubscriber //Implement when adding a character
{
    private static final Color EXAMPLE_COLOR_BG = CardHelper.getColor(100.0f, 50.0f, 50.0f);
    private static final String ATTACK_EXAMPLE         = "img/cards/bg_attack_512.png ";
    private static final String SKILL_EXAMPLE          = "img/cards/bg_skill_512.png ";
    private static final String POWER_EXAMPLE           = "img/cards/bg_power_512.png ";
    private static final String ENERGY_ORB_EXAMPLE         = "img/cards/orb_512.png ";
    private static final String ATTACK_PORT_EXAMPLE        = "img/cards/bg_attack_1024.png ";
    private static final String SKILL_PORT_EXAMPLE         = "img/cards/bg_skill_1024.png ";
    private static final String POWER_PORT_EXAMPLE         = "img/cards/bg_power_1024.png ";
    private static final String ENERGY_ORB_PORT_EXAMPLE = "img/cards/orb_1024.png ";
    private static final String ENERGY_ORB_CARD_EXAMPLE = "img/cards/orb_ui.png ";

    //When you want to output a debug log.
    public static final Logger logger = LogManager.getLogger(Main.class.getName());


    public Main(){
        BaseMod.subscribe(this);
        BaseMod.addColor(
                AbstractCardEnum.EXAMPLE_COLOR    //Color variables
                , EXAMPLE_COLOR_BG //bgColor
                , EXAMPLE_COLOR_BG//backColor
                , EXAMPLE_COLOR_BG//frameColor
                , EXAMPLE_COLOR_BG//frameOutlineColor
                , EXAMPLE_COLOR_BG//descBoxColor
                , EXAMPLE_COLOR_BG //trailVfColor
                , EXAMPLE_COLOR_BG//glowColor
                , ATTACK_EXAMPLE//attackBg
                , SKILL_EXAMPLE//skillBg
                , POWER_EXAMPLE//powerBG
                , ENERGY_ORB_EXAMPLE//energyOrb
                , ATTACK_PORT_EXAMPLE//attackBgPortrait
                , SKILL_PORT_EXAMPLE//skillBgPortrait
                , POWER_PORT_EXAMPLE//powerBgPortrait
                , ENERGY_ORB_PORT_EXAMPLE//energyOrbPortrait
                , ENERGY_ORB_CARD_EXAMPLE//CardEnergyOrb
        );
    }

    public static void initialize() {
        Main main = new Main();
    }

    @Override
    public void receiveEditStrings() {
        BaseMod.loadCustomStringsFile(CardStrings.class, "localization/cards-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(RelicStrings.class, "localization/relics-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(CharacterStrings.class, "localization/char-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(PowerStrings.class, "localization/powers-" + Settings.language + ".json");
    }

    @Override
    public void receiveEditCards() {
        BaseMod.addCard(new example_mod.cards.TestAttack());
        BaseMod.addCard(new example_mod.cards.TestPower());
        //I will explain later
    }

    @Override
    public void receiveEditCharacters() {
        //Addition of uniquely defined characters
        BaseMod.addCharacter(
                new example_mod.character.ExampleChar("ExampleCharacter"),
                "img/charSelect/ExampleButton.png ",
                "img/charSelect/ExamplePortrait.png ",
                ExampleClassEnum.ExampleClass
        );

    }

    public void receiveEditRelics() {
        //Add relic
        logger.info("begin editing relics"); //It is like this to output the debug log.
        BaseMod.addRelicToCustomPool(
                new TestRelic(),
                AbstractCardEnum.EXAMPLE_COLOR //Add to color. Common relic is another method
        );
        logger.info("done editing relics");
    }
}

Now, build it, select a character, and start the game, and you're done! Thank you for your hard work!

……e? Will it solidify when the battle is over?

Oh, that's because there aren't enough cards. Will you randomly display 3 cards at the end of the battle? If you proceeded like this now, there are only two types, so it will crash. I think that the debug log is also stopped at that point.

Then I don't know how many characters are needed for the character mod (please tell me).

I don't know, but the card selection is usually 3 cards, it may be increased by +1 by relic, and when the boss is defeated, all are rare Well, if there are 4 types of attack skill power COMMON, UNCOMMON, and RARE, it will work at least. 3 categories x 3 rare x 4 types, 36 sheets? If you look at other people's mods, there are about 70 or so ...

For the time being, I increased the production of cards with the same ability in my repository so that it works, so I think it has become a model.

It was embarrassing why the repository was "training" instead of "example". I wonder if the example is the same as BaseMod's explanation.

that's all.

Other notes ・ If you draw a card that is not normally available due to the effect, prepare a card color for it and make the rarity SPECIAL? -Prepare "UPGRADE_DESCRIPTION" when changing the description by upgrading.

Recommended Posts

How to make a mod for Slay the Spire
Introduction to Slay the Spire Mod Development (1) Introduction
Introduction to Slay the Spire Mod Development (2) Development Environment Construction
How to make a Java container
How to sign a Minecraft MOD
How to make a JDBC driver
How to make a splash screen
How to make a Jenkins plugin
How to make a Maven project
How to make a Java array
SDWebImage: How to clear the cache for a particular UIImageView
Introduction to Slay the Spire Mod Development (3) Original Card Definition
How to make a Java calendar Summary
How to create a Maven repository for 2020
How to make a Discord bot (Java)
How to identify the path that is easy to make a mistake
How to check for the contents of a java fixed-length string
How to make a groundbreaking diamond using Java for statement wwww
How to create a database for H2 Database anywhere
Make a margin to the left of the TextField
How to create pagination for a "kaminari" array
[Java] (for MacOS) How to set the classpath
How to make a follow function in Rails
[Java] How to make multiple for loops single
How to make shaded-jar
How to make a judgment method to search for an arbitrary character in an array
How to make a unique combination of data in the rails intermediate table
How to make a factory with a model with polymorphic association
How to run the SpringBoot app as a service
[Rails] How to create a signed URL for CloudFront
How to use an array for a TreeMap key
How to write a unit test for Spring Boot 2
[Spring Boot] How to create a project (for beginners)
How to write a core mod in Minecraft Forge 1.15.2
How to study kotlin for the first time ~ Part 2 ~
How to study kotlin for the first time ~ Part 1 ~
Java --How to make JTable
How to leave a comment
[Rails] How to make seed
How to insert a video
How to create a method
How to create a placeholder part to use in the IN clause
Learning Ruby with AtCoder 13 How to make a two-dimensional array
How to test a private method with RSpec for yourself
[Ruby] How to retrieve the contents of a double hash
How to output array values without using a for statement
How to add the same Indexes in a nested array
Minecraft Modding [1.12] How to attach a special render for Item
A memorandum to reach the itchy place for Java Gold
[jsoup] How to get the full amount of a document
How to create and launch a Dockerfile for Payara Micro
A little addictive story after updating the JDBC driver for PostgreSQL
How to make a JDBC driver
How to get the latest live stream ID for a channel without using the YouTube Data API
Mechanism for converting to a language that the browser can recognize
How to add columns to a table
How to use the form_with method
I want to create a chat screen for the Swift chat app!
Try to make a simple callback
How to specify validation for time_field
How to install JMeter for Mac
How to find the average angle