Web/_posts/rock_bottom_mod/2019-10-03-rock_bottom_mod.md
Ell 08fc481589
All checks were successful
/ web (push) Successful in 1m1s
move website content to main directory
2024-06-14 17:49:53 +02:00

10 KiB

layout title description tags discuss
blog ⬇️ How to make a Rock Bottom mod My adventures back into a game I stopped working on about two years ago and how I start on a mod for it
Programming
https://twitter.com/Ellpeck/status/1180092634410487808

So it's been a hot minute since I stopped working on my first big game project, Rock Bottom. Since then, I've changed a lot, but the game hasn't changed that much: For a long time, the project was vacant, until I decided to make it open source. From that point on, a couple of my friends started working on it, adding some new features and fixing some bugs, until it seemingly fell back into vacancy over the last couple of weeks.

So let's port my recent Minecraft mod, Nature's Aura, to Rock Bottom!

Note: This is not a tutorial.

Setting up the dev environment

Now I somewhat remember that, back when I was working on the game, I set up a pretty in-depth modding API as well as an actual Github project to easily allow people to set up a mod themselves.

Aha, here it is: the modding repo. It even has some nice documentation that I apparently made! Okay, here goes.

Okay, finding a build of the game was pretty easy. It even has an in-depth changelog! Good on you, raphy.

Hm. Okay.
So something's already broken.

It looks like here

dependencies {
    compile group: 'de.ellpeck.rockbottom', name: 'RockBottomAPI', version: '+'
}

where it says +, it should actally be saying a specific version. So let's replace that with 0.4.6-753 and refresh.

That didn't seem to fix it either. Huh. Taking a look at the maven, it seems to be currently down because of 502 bad gateway. Fun.

Okay, it's now the next day and it looks like the maven has been fixed, which is nice. So all the compile issues are finally resolved, I put the build into the /gamedata folder like explained in the tutorial, I renamed the examplemod to NaturesAura, and I can now finally try running the game!

Ah! That worked quite well in the end.

Actually making something

So I feel like the first thing I should add to the game is the golden leaves, because they're also the first thing that you have to create when getting started with the Minecraft version of Nature's Aura.

So I quickly made a new class that extends TileBasic, which I vaguely remember was like an extended, better version of the Tile class with some useful helper methods.

package de.ellpeck.naturesaura.tiles;

import de.ellpeck.rockbottom.api.tile.TileBasic;
import de.ellpeck.rockbottom.api.util.reg.ResourceName;

public class TileGoldenLeaves extends TileBasic {

    public TileGoldenLeaves(ResourceName name) {
        super(name);
    }
}

My Java is pretty rusty, but I seem to remember all of the naming conventions and, coming from C#, do find myself having trouble with the lowerCamelCase methods (because C# uses UpperCamelCase for method names, conventionally). I quickly made a Tiles class that's going to house all the tiles I create and made an instance of the golden leaves tile.

package de.ellpeck.naturesaura.tiles;

import de.ellpeck.naturesaura.NaturesAura;
import de.ellpeck.rockbottom.api.tile.Tile;

public final class Tiles {

    public static final Tile GOLDEN_LEAVES = new TileGoldenLeaves(NaturesAura.createRes("golden_leaves"));

}

I also created a dummy instance of the Tiles class so that the static variables in it are loaded. If you're not extremely familiar with Java, then this might appear a bit weird for you, but static variables of a class only get instantiated once the class is first used in some way (be it instance creation, method calling, stuff like that), and because I want the golden leaves to be initialized in the preInit phase of the game, I just do this:

@Override
public void preInit(IGameInstance game, IApiHandler apiHandler, IEventHandler eventHandler) {
    this.modLogger.info("Starting Nature's Aura for RockBottom");
    new Tiles();
}

So far, so good. Let's figure out how to add a texture to the thing.
I somewhat remember that I made a horrible json-based asset system (instead of just loading all of the assets in the mod's jar automatically), so I'm going to try to add my tile to the assets.json file the example mod provided me with and also add a texture into the actual file system.

This is the folder structure I decided on. I also created a quick golden version of the game's leaves texture by going to the asset repository, stealing the leaves texture and recoloring it to be golden-ish.

Also, I put this in the assets file, but I have no idea if that's actually the right path. We'll find out.

{
    "loc.": {
        "us_english": "/loc/us_english.json"
    },
    "tex.": {
        "tiles.": {
            "golden_leaves": "/tex/tiles/golden_leaves.png"
        }
    }
}

Okay, let's launch and see what happens. So I started the game, opened a lan server, opened the chat, typed /items to get a cheat menu, and... it's not showing up. What did I do wrong?

After some thinking, it turns out that I forgot to actually register the tile. So I went back into my Tiles class and appended .register() to the end of the whole thing, like this:

public static final Tile GOLDEN_LEAVES = new TileGoldenLeaves(NaturesAura.createRes("golden_leaves")).register();

Crisis averted.

Yaaay, it... worked? Somewhat?

You're probably yelling at your screen by now, but yes, I finally noticed it as well: My assets path says examplemod instead of naturesaura. Easy fix, though.

While I'm at it though, I can also add a localization entry for the golden leaves. When hovering over it, the console informed me that Localization with name naturesaura/item.golden_leaves is missing from locale with name us_english!, so I quickly added it:

{
    "naturesaura": {
        "item.": {
            "golden_leaves": "Golden Leaves"
        }
    }
}

Let's try again.

Ta-da! Success, at last.

After some investigation, I realized that Rock Bottom's normal leaves can be walked through, so let's see how to make that happen. Typing @Override while in the TileGoldenLeaves class causes my IDE to list all of the methods I can override. Among them are two that interest me:

@Override
public boolean isFullTile() {
    return false;
}

@Override
public BoundBox getBoundBox(IWorld world, TileState state, int x, int y, TileLayer layer) {
    return null;
}

It seems like this is what I have to do to make the tile walk-through...able. Let's try it out.

Yay, that seems to have worked. Great.

Making an item

Now that I have somewhat of a grip of this whole Rock Bottom stuff again, I'm quickly going to make an item. I won't bore you with the details as it's pretty similar to making a tile, but the gist of it is this: I made an ItemGoldPowder class that extends ItemBasic, and I initialized and registered an instance of that in my newly created Items class, of which I created an instance in my mod class's preInit method so that it gets initialized at the right time. Also, I did all of the annoying asset mumbo jumbo.

Oh, C#, you've ruined me.

Making stuff happen

Now, to actually make the gold powder turn normal leaves into golden leaves, I have to write some actual code that isn't just boring boilerplate. When right clicking the golden powder onto a leaves tile, I want that tile to turn into the golden leaves. Eventually, they should then transition somehow, but... I'll leave that for later.

Let's write some code!

@Override
public boolean onInteractWith(IWorld world, int x, int y, TileLayer layer, double mouseX, double mouseY, AbstractEntityPlayer player, ItemInstance instance) {
    var state = world.getState(layer, x, y);
    if (state.getTile() != GameContent.TILE_LEAVES)
        return false;
    world.setState(layer, x, y, Tiles.GOLDEN_LEAVES.getDefState());
    player.getInv().remove(player.getSelectedSlot(), 1);
    return true;
}

While writing this code, I quickly remembered that Rock Bottom has a tile state system similar to Minecraft, so I had to work with that. Before I realized, I sat there wondering why IWorld doesn't have a getTile method anywhere. Anyway, this should check the interacted tile; see if it's leaves; replace the leaves with golden leaves and deduct one from the item being currently held (which is obviously the gold powder, as this is in the ItemGoldPowder class. No need for checks there.)

Let's try it out! ...yea, no. The Java version that gradle uses to compile a Rock Bottom mod isn't new enough yet: You can't use the var keyword. Oh, my poor, poor C# soul. So let's swap that var out for a TileState.

Yay, it works! Except that, in the gif, the mouse position is weirdly offset for some reason. It's correct in person, I promise!

Conclusion

So yea, that was the start of my adventure back into Java, back into my old game and back into... well, modding, I guess. I hope you enjoyed reading this post and seeing my process of working out how to do stuff.

I think it's important to remember that, as a developer (especially an indie developer), you don't have to code everything perfectly or work stuff out correctly the first try. I mean, heck, this is my own game, and I completely forgot how to make a mod for it. But it was fun to figure it out again, and to get back into something I made two years ago.

Oh, also, if you really want, here's a build of the mod that you can try out yourself, as the game is now actually open source and available to everyone! I created this jar with the command gradlew build, and all you have to do to run it is download the game, run it once, and then stick the mod jar into its mods folder. It really doesn't do that much right now, though, so I don't know why you'd bother.

As always, thanks for reading!