rock bottom modding post
|
@ -39,5 +39,11 @@
|
||||||
"id": "big_projects",
|
"id": "big_projects",
|
||||||
"date": "9/15/2019",
|
"date": "9/15/2019",
|
||||||
"discuss": "https://twitter.com/Ellpeck/status/1173247686654517249"
|
"discuss": "https://twitter.com/Ellpeck/status/1173247686654517249"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "How to make a Rock Bottom mod",
|
||||||
|
"summary": "My adventures back into a game I stopped working on about two years ago and how I start on a mod for it",
|
||||||
|
"id": "rock_bottom_mod",
|
||||||
|
"date": "10/3/2019"
|
||||||
}
|
}
|
||||||
]
|
]
|
BIN
blog/res/rock_bottom_mod/1.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
blog/res/rock_bottom_mod/2.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
blog/res/rock_bottom_mod/3.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
blog/res/rock_bottom_mod/4.png
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
blog/res/rock_bottom_mod/5.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
blog/res/rock_bottom_mod/6.png
Normal file
After Width: | Height: | Size: 156 KiB |
BIN
blog/res/rock_bottom_mod/7.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
blog/res/rock_bottom_mod/8.gif
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
blog/res/rock_bottom_mod/NaturesAuraRockBottom-0.1.jar
Normal file
177
blog/rock_bottom_mod.md
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
So it's been a hot minute since I stopped working on my first big game project, [Rock Bottom](https://rockbottomgame.com). 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](https://www.curseforge.com/minecraft/mc-mods/natures-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](https://github.com/RockBottomGame/Modding).* It even has some nice documentation that I apparently made! Okay, here goes.
|
||||||
|
|
||||||
|
Okay, finding [a build of the game](https://github.com/RockBottomGame/RockBottom/releases) was pretty easy. It even has an in-depth changelog! Good on you, raphy.
|
||||||
|
|
||||||
|
Hm. Okay.
|
||||||
|
So something's already broken.
|
||||||
|
![](blog/res/rock_bottom_mod/1.png =100%x*)
|
||||||
|
|
||||||
|
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](https://maven.chaosfield.at/de/ellpeck/rockbottom/RockBottomAPI/0.4.6-753/), 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!
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/2.png =100%x*)
|
||||||
|
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.
|
||||||
|
```java
|
||||||
|
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.
|
||||||
|
|
||||||
|
```java
|
||||||
|
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:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@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.
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/3.png =100%x*)
|
||||||
|
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](https://github.com/RockBottomGame/Assets), 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.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"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*.
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/4.png =100%x*)
|
||||||
|
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:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"naturesaura": {
|
||||||
|
"item.": {
|
||||||
|
"golden_leaves": "Golden Leaves"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's try again.
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/5.png =100%x*)
|
||||||
|
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:
|
||||||
|
```java
|
||||||
|
@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.
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/6.png =100%x*)
|
||||||
|
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.
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/7.png =100%x*)
|
||||||
|
*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!
|
||||||
|
```java
|
||||||
|
@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`.
|
||||||
|
|
||||||
|
![](blog/res/rock_bottom_mod/8.gif =100%x*)
|
||||||
|
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](https://ellpeck.de/blog/res/rock_bottom_mod/NaturesAuraRockBottom-0.1.jar) 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](https://github.com/RockBottomGame/RockBottom/releases), 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!
|