Creating a Minecraft boss fight using coding and algorithms
This is the most powerful sheep Minecraft-kind has ever seen.
# Where to begin
I like to make games and play them with my friends and nothing has empowered that drive quite like Minecraft. Over the years I’ve made many a custom map, texture pack and mod for me and my friends to play around with. Making anything like this is always a time consuming process so a few years ago I envisioned the the idea of my new Minecraft experience being built using a server-side plugin. The benefits of this approach is that users don’t have to install any dependencies and the ecosystem around creating server-side plugins is very mature. But most excitedly, you have full access to Java which speeds up development a great deal (compared to vanilla map-making tools) and you’re only limited to your imagination (and the Minecraft client). While the Minecraft datapack/commands system has some nice properties, it has obtuse restrictions, bizarre syntax and rudimentary tooling for use on any substantial project. This lead to the creation of the UltraPlugin.
The UltraPlugin plays out The Ultra Event, a short boss fight sequence made up of random toys I played around with in Bukkit. I enjoyed making it but I knew if I wanted to make anything bigger, it would probably take a lot of work. This was the precursor to the HyperPlugin, a supposed sequel which quickly grew out of control as I thought of new ideas (what a surprise). To keep the complexity of the codebase in check, I eventually needed to make some structural decisions about the codebase.
# Architecture
I’m going to try not to bash on Java too much but writing expressive code or even just an API that allows you to write expressive code I’ve found to be a very difficult task. Even though Java 8’s lambdas and streams can be pleasing to use in the right situations, Java’s basic object-oriented model, the lack of syntactical sugar and the barebones generics makes writing succinct code often times impossible. There are definitely creative ways to use Java that may have let me solve this but I didn’t want to stray too far from regular patterns for performance and sanity reasons.
I created a basic modules system using an interface called a Module
with two void methods: onEnable
and onDisable
(I also put each of these methods into their own interface for flexibility). This is a very general concept as it represents anything you can turn on and off.
Two concepts that I used very frequently from Bukkit were event listeners and runnables (which can be executed on a periodic number of ticks) so I made ModuleListener
and ModuleRunnable
. This allowed me to very quickly add this functionality to any new modules by extending these classes. In an attempt to favour composition over inheritance, I tried to use interfaces where I could but ModuleRunnable
needs to be a class because it handles data. I used ModuleRunnable
and ModuleListener
so frequently together that I ended up creating a class which extends both of them simultaneously. A bit of a code smell but I’m not here to achieve perfection first time. A ModuleBattery
was a nice abstraction I found I could make; it is just an simple implementation of Collection<Module>
but it is also a Module
itself, allowing you to enable and disable all of its elements with one call.
Data flow is a difficult concept to overcome in game development because it is most convenient when you have access to all of the game’s systems globally—a recipe for spaghetti. My system makes little attempt to solve this but it’s something I want to overcome in the future. At the moment you can easily embed modules inside other modules but calling onEnable and onDisable is manual. I wanted to avoid making modules singletons but this also means I cannot access other modules globally which may sometimes be useful. I think a greater architecture would draw more similarities from ECS or the trees of nested components from web UI frameworks. I find it difficult to envision an efficient and expressive API for doing these things in Java in the context of Minecraft plugins so I’ll leave it up to Josh for another day.
Oh yeah so there’s this other important thing called testing and iteration.
# Testing: Reloaded
Bukkit has a convenient /reload
command which completely resets all the plugins on the server. I setup a script which automatically builds a new jar and copies it into the server’s plugins folder and in combination with /reload
provides an OK experience that takes about 10 seconds to reload everything. It’s not perfect, there’s no fancy hot-loading or iterative compilation to speed up the process, but it’s pretty seamless for how much time I spent on the situation.
However, there were two common scenarios that were becoming annoyingly time consuming:
- Every time I wanted to test a new module, I would have to write some code to execute it through commands or events.
- Testing many different values quickly wasn’t possible.
I was tempted to solve this by expanding my module system but then I realised how few generalisations I would need to make if I solved this using reflection. I started programming a Minecraft command which gave you access to any static variable with syntax completion and before I knew it I was on a dangerous path to implementing a poorly structured Java interpreter. After I had execution of static methods working, I implemented a global variable system that allowed you to store values, construct new objects and call methods on them. I even came up with my own strange syntax where you could use the variable @s
(inspired by Minecraft commands) as a reference to your player so you could use your current location or other player data in debugging. Syntax completion for @s
method calls and enums really helped with speeding up testing.
I didn’t know what to expect at first but it solved my problem amazingly well. Unfortunately, it is by far the worst code in project with having been written in almost a single class file with an unsettling amount of obfuscated logic. There are still a few bugs in it but I can’t bear to look at it any longer without feeling like I need to rewrite the whole thing. The problem is, doing it properly would require a lot of thinking. Do I use Java 9’s new REPL system? Would I still need to implement a proper parser to do type inference? I solved this by working on something else. Besides, it does just enough for what I need it for. I later found out someone else had previously made something very similar; it was more suitably maintained than my version but was missing a few of my “innovative” features.
# Sheep
A lot of time had passed since I started HyperPlugin and I didn’t have anything to show my friends yet. So I suddenly steered the development into a different event from the one I originally planned (with a more realistic scope), that I called Hyper Sheep. Using all of the previous tools that I had made, I wanted to make something to show them off, and do it fast. Over the course of a week, I took the core concept of “battling an unrealistically powerful sheep in creative mode” and just went wild testing out potential ideas and programming new modules.
I’m a bit of a perfectionist so I needed to make sure that while I was going sacrifice a bit of code quality during this speedy development, I wanted this boss fight to feel like a full experience. To me this meant jam-packing it full of visual and sound effects, crafting and balancing a story arc with points of build up, climax and cool-down, and creating a sensation of teamwork as a multiplayer experience. This is already something that I was working towards before so I was able to carry on that momentum and code onto this derivative project.
I got to reuse many of my existing modules including boss bars, title animations, and custom “ghost block” entities which are all featured in the YouTube video at the end of this article. I was trying to do as little work as possible without sacrificing quality but I still ended up making a lot of original modules. Even so, I think the workflow that I had created for myself catapulted me with enough momentum to get me to the finish line of this project.
# When I can’t find an API, I guess I’ll make it myself
A few years ago the Structure Block was introduced, a method for saving and loading a section of 3D space in Minecraft. Something that I really wanted to play around with in my plugin was some real-time generation of structures. I ended up fantasizing about the idea of a block-by-block construction of a huge model of a Minecraft sheep. Structure blocks seemed to be the perfect pairing for this concept but no one had created an API for this. The closest example I could find was a library called StructureBlockLib which is extremely close to what I need but it only replicates the behaviour of structure blocks and there are no methods for actually reading the contents of structures without loading them into the world. I wasn’t going to let this go without a battle, and this time I was armed with my knowledge of Fabric modding.
Fabric truly is one of the most fantastic community projects I’ve had the chance to explore. As well as being the newest Minecraft mod loader on the block, it’s developed an open and free ecosystem for Minecraft modding and decompilation. Due to copyright and history, all of Minecraft’s class names are regularly updated on 4 separate occasions: the Mojang names, the MCP mappings for Forge modding (funnily enough originally created by someone who now works at Mojang), the NMS mappings by and for Bukkit/Spigot, and the Yarn mappings for Fabric. All of these different mappings vary in quality, coverage and copyright. Since Fabric’s mappings are so good, I used them to look up functions related to structure blocks and straight up found a method which loads the list of blocks stored in a structure saved by structure blocks.
I have a secondary library called TwineLib which interacts with Minecraft’s original class files through Bukkit’s NMS names. Using a secondary library is a common solution for separating code using Bukkit’s APIs from NMS which needs to be explicitly updated for each Minecraft version. Doing a bit of a dot to dot, I was able to translate yarn names to NMS names and track down the structure APIs that I needed (NMS is missing many method names so in this case it was just called “a
”). Add in some wrapper classes and a bit of reflection and I had a working structure loading API. I built some structures with WorldEdit, saved them with regular structure blocks and used my API to load them in and the world rejoiced as I made big sheep.
Twine doesn’t do too much (for now) but the source code for the library is available here.
# Custom Music
One thing I had been looking forward to using HyperPlugin for was to play any music I wanted during the gameplay. I solved this very easily by hosting a WebSocket server on the plugin and sending commands to a Node.js client which plays audio files. It also works great for multiplayer as I just have to send them the client to run in the background. I thought about using a Discord bot to play the music instead but preferred my solution for the lighter bandwidth and better audio quality. The client used a WebSocket library and an mpv interface so everything was pretty straightforward. That was up until I decided I wanted the music to fade out between tracks. While I could control the volume in JavaScript, I wasn’t able to find a way to make the music fade-out the right way.
The right way to fade out audio is to work with raw audio data and reduce the amplitude over time (and don’t forget to do it logarithmically). I couldn’t find a library that would let me do this in Node.js so I slowly came to the conclusion that I would need to work lower-level. My recent fascination with Rust pushed me to look for ways I could use it to solve this and I had stars in my eyes when I found the library Rodio.
For someone without a lot of experience in Rust, sometimes it can be difficult to understand how to use an API. There wasn’t enough examples for me to understand how the library could be used in a way where I could fade-out the audio for an arbitrary track. I was on the verge of giving up but I called in some favours and found a friend of a friend who actually knows Rust who pointed me in the right direction. Thus, rust-ws-speaker was born, an exceedingly lightweight and optimised solution (compared to Node.js) for playing audio controlled by console or WebSocket commands. Working with JavaScript so much, I rarely get an opportunity to multithread my programs but Rust made it easy to use separate threads for the networking and console interface.
# Hyper Sheep
After I finally stopped endlessly extending the scope of my project and settled on a concrete finish line, I was able to apply the finishing touches to complete my envisioning of a Minecraft boss battle spectacular. Whenever it comes to presenting these things there’s always moments beforehand where you wonder if what you’ve made will really live up to the hype you’ve built up in your head. I played it on my server with my friends and we had a blast.
The 7-day nonstop coding journey was over and it was super satisfying for all that creativity, building, testing and Java boogaloo to pay off in one short but intense moment. It went down almost hitch-free which I’m incredibly happy with; the only hiccup was when the rust music client crashed on my end (I’m not exactly sure why, I don’t think it was something I did on my end, perhaps a macOS specific bug with Rodio), but thankfully due to implementing console commands I was able to play the song manually. I don’t currently have any plans to release this publicly, but possibly as part of something larger in the far future. Now… how am I supposed to do something bigger and better for next year?