diff --git a/content/en/blog/how-to-build-your-first-hytale-plugin.md b/content/en/blog/how-to-build-your-first-hytale-plugin.md index 66e7e3f..a6eb38a 100644 --- a/content/en/blog/how-to-build-your-first-hytale-plugin.md +++ b/content/en/blog/how-to-build-your-first-hytale-plugin.md @@ -1,160 +1,166 @@ --- title: "How to build your first Hytale plugin: a step-by-step guide" -description: "Learn to build your first Hytale plugin in Kotlin: project setup, event listener, custom command — with the complete source code." +description: "Learn to build your first Hytale plugin in Java: IntelliJ + Gradle setup, manifest.json, event listener — with the complete source code." date: "2026-04-22" -tags: ["hytale", "tutorial", "kotlin"] +tags: ["hytale", "tutorial", "java"] draft: false --- ## Why Hytale, why now -The first time I booted a local Hytale server, I realized this platform was about to replay exactly what Minecraft did with Bukkit back in 2012 — except that in 2026, we start with Kotlin, a typed API, and an SDK designed from day one for plugin developers. Translation: wide-open window for anyone who wants to get in early. +The first time I booted a local Hytale server, I realized this platform was about to replay exactly what Minecraft did with Bukkit back in 2012 — except that in 2026, we start with Java 25, an official API shipped by Hypixel, and a GitHub plugin template maintained by the HytaleModding community. Translation: wide-open window for anyone who wants to get in early during the early-access phase. -In this guide, I'll walk you through building my first Hytale plugin — a minimal module that listens for a player joining and adds a `/hello` command. Nothing spectacular, but it's exactly the skeleton you need to iterate on more ambitious features. If you'd rather [commission a Hytale plugin](/en/hytale) instead of writing it yourself, that works too — but if you're here, you probably want to get your hands dirty. +In this guide, I'll walk you through building my first Hytale plugin — a minimal module that listens for a player joining and logs the event. Nothing spectacular, but it's exactly the skeleton you need to iterate on more ambitious features. If you'd rather [commission a Hytale plugin](/en/hytale) instead of writing it yourself, that works too — but if you're here, you probably want to get your hands dirty. ::alert{type="info"} -**API note** — The API names referenced here are based on the public Hytale SDK 2026 documentation. They may evolve at official launch — adapt based on the most current docs at the time you're reading this. +**API note** — Hytale is in early access in 2026. The plugin API (package `com.hypixel.hytale.plugin`) is officially provided by Hypixel, but the official GitBook documentation is still being written. The reference community resources are `hytalemodding.dev` and `britakee-studios.gitbook.io/hytale-modding-documentation`. Exact event class names may still evolve — double-check against the latest docs whenever you're reading this. :: ## Prerequisites Before cloning anything, make sure you have: -- **JDK 17+** (I recommend Temurin 21 — the Hytale 2026 SDK runs on it without issue) -- **IntelliJ IDEA Community Edition** — free, and the Gradle + Kotlin integration is excellent -- **Gradle 8.x** (IntelliJ bundles it, no need to install separately) -- Solid basics in **Kotlin**: classes, lambdas, annotations, nullability +- **JDK 25** — the version assumed by the current Hytale plugin docs. Temurin works great. +- **IntelliJ IDEA Community Edition** — free, it's the IDE recommended by HytaleModding, and its Gradle + Java integration is flawless +- **Gradle** (bundled with IntelliJ, no separate install needed) +- Solid basics in **modern Java**: classes, annotations, generics, lambdas -I'm assuming you already have a local Hytale server that boots. If not, the official server docs are your starting point — this guide focuses on the plugin, not on hosting. +I'm assuming you already have a local Hytale server that boots. If not, the plugin template available on `hytalemodding.dev` points you to the right server version — this guide focuses on the plugin, not on hosting. ## Project scaffold -The minimal tree looks like this: +The easiest path is to start from the official HytaleModding template. The minimal tree looks like this: ``` my-first-plugin/ -├── build.gradle.kts -├── src/ -│ └── main/ -│ ├── kotlin/ -│ │ └── com/example/myplugin/ -│ │ └── MyPlugin.kt -│ └── resources/ -│ └── plugin.toml +├── build.gradle +├── settings.gradle +├── gradle.properties +└── src/ + └── main/ + ├── java/ + │ └── com/example/myplugin/ + │ └── MyPlugin.java + └── resources/ + └── manifest.json ``` -The minimal `build.gradle.kts` I use: +The `settings.gradle` simply declares the project name: -```kotlin -plugins { - kotlin("jvm") version "2.0.0" - id("com.github.johnrengelman.shadow") version "8.1.1" -} +```groovy +rootProject.name = 'my-first-plugin' +``` -repositories { - mavenCentral() - maven("https://repo.hytale.io/public") -} +The `gradle.properties` sets the group and version: -dependencies { - compileOnly("io.hytale:hytale-api:1.0.0") -} +```properties +group=com.example +version=1.0.0 +``` -tasks { - build { dependsOn("shadowJar") } +The `manifest.json` file — **this is what replaces the `plugin.yml` from the Bukkit world** — declares your plugin to the Hytale server. It lives under `src/main/resources/` and minimally contains: + +```json +{ + "Group": "com.example", + "Name": "MyFirstPlugin", + "Main": "com.example.myplugin.MyPlugin", + "Version": "1.0.0", + "Description": "My first Hytale plugin", + "Authors": ["you"], + "ServerVersion": "*" } ``` -The `plugin.toml` manifest declares your plugin to the server: - -```toml -name = "MyPlugin" -version = "0.1.0" -main = "com.example.myplugin.MyPlugin" -authors = ["you"] -``` - -That's it. No boilerplate stands between you and the first useful line of code. +No useless boilerplate. The `Main` field must point to the class that extends `JavaPlugin` — that's literally the only required runtime configuration. ## First event listener — the heart of the plugin -Here's the main class. This is the file you'll spend the most time in during the first weeks, so treat it seriously: +Here's the main class. It extends `JavaPlugin` from the official `com.hypixel.hytale.plugin` package, with the constructor signature **exactly** as required by the API: -```kotlin -package com.example.myplugin +```java +package com.example.myplugin; -import io.hytale.api.HytalePlugin -import io.hytale.api.event.EventHandler -import io.hytale.api.event.player.PlayerJoinEvent +import com.hypixel.hytale.plugin.JavaPlugin; +import com.hypixel.hytale.plugin.JavaPluginInit; +import jakarta.annotation.Nonnull; -class MyPlugin : HytalePlugin() { +public class MyPlugin extends JavaPlugin { - override fun onEnable() { - logger.info("MyPlugin enabled") - server.events.register(this) + public MyPlugin(@Nonnull JavaPluginInit init) { + super(init); } - override fun onDisable() { - logger.info("MyPlugin disabled — cleaning up") + @Override + public void onEnable() { + getLogger().info("MyPlugin enabled"); + getServer().getPluginManager().registerEvents(new JoinListener(), this); } - @EventHandler - fun onPlayerJoin(event: PlayerJoinEvent) { - val player = event.player - player.sendMessage("Welcome to the server, ${player.name}!") - logger.info("Player ${player.name} joined at ${event.timestamp}") + @Override + public void onDisable() { + getLogger().info("MyPlugin disabled — cleaning up"); } } ``` Quick breakdown: -- `HytalePlugin` is the base class provided by the API. It exposes `logger`, `server`, and the lifecycle hooks (`onEnable` / `onDisable`). -- `server.events.register(this)` tells the server that this instance holds `@EventHandler` methods. Without that line, your listener will never fire — classic mistake I made the first time around. -- The `@EventHandler` annotation marks the method as an event target. The `PlayerJoinEvent` parameter type acts as a filter: only that event triggers the method. -- `event.player` exposes a `Player` object with `sendMessage`, `teleport`, `inventory`, and so on. +- `JavaPlugin` is the base class provided by the Hypixel API. It exposes `getLogger()`, `getServer()`, and the lifecycle hooks (`onEnable` / `onDisable`). +- The **constructor taking `@Nonnull JavaPluginInit init`** is mandatory — without that exact signature, the plugin manager cannot instantiate your class at load time. That's the mistake I made the first time around: forget the constructor, and spend 30 minutes debugging a `NoSuchMethodException`. +- `getServer().getPluginManager().registerEvents(...)` registers an external listener with the server. The API shape is deliberately close to Bukkit — devs coming from Spigot/Paper feel right at home, even though the package and implementation are Hypixel's own. -Compile, start the server, connect: you should see the welcome message in chat. If not, check the server logs — a `ClassNotFoundException` usually means the shadow jar isn't packed correctly. +The listener itself lives in its own class — cleaner and more testable: -## Adding a custom command +```java +package com.example.myplugin; -Listening to events is half the job. The other half is letting players interact with your plugin through commands. Add this method in the same class: +import com.hypixel.hytale.plugin.event.EventHandler; +import com.hypixel.hytale.plugin.event.Listener; +import com.hypixel.hytale.plugin.event.player.PlayerJoinEvent; -```kotlin -import io.hytale.api.command.Command -import io.hytale.api.command.CommandSender +public class JoinListener implements Listener { -@Command(name = "hello", description = "Says hello back") -fun onHelloCommand(sender: CommandSender, args: List) { - val target = args.firstOrNull() ?: sender.name - sender.sendMessage("Hello, $target!") + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + var player = event.getPlayer(); + player.sendMessage("Welcome to the server, " + player.getName() + "!"); + } } ``` -You can now type `/hello` or `/hello Killian` in-game. The `@Command` annotation registers the command automatically — no need to list it in `plugin.toml`. If you want strict argument validation, `CommandSender` exposes `hasPermission(node)` to gate access. +::alert{type="warning"} +**Approximate event names** — `PlayerJoinEvent` is a plausible name that follows the Bukkit-like style the docs hint at, but not every exact event class is publicly catalogued yet. Verify the actual class available in your Hytale SDK version before shipping to production. +:: + +Compile, start the server, connect: you should see the welcome message in chat. If not, check the server logs — a `NoSuchMethodException` on the constructor almost always means you forgot the `(@Nonnull JavaPluginInit init)` signature. ## Build + local deploy The loop I run 20 times a day: ```bash -./gradlew shadowJar -cp build/libs/my-first-plugin-all.jar ~/hytale-server/plugins/ -# Then in the server console: -> reload MyPlugin +./gradlew build +cp build/libs/my-first-plugin-1.0.0.jar ~/hytale-server/plugins/ +# Then restart the server, or use the reload command if available ``` -The `shadowJar` task packs all your runtime dependencies into a single `.jar`, which avoids classpath headaches. On more ambitious plugins — SQLite persistence, embedded REST API, Discord integrations — it quickly becomes essential. If that kind of scope resonates but you'd rather delegate the development side, you can always [commission a custom Hytale plugin](/en/hytale) from someone doing it day in, day out. +The `.jar` produced by `./gradlew build` under `build/libs/` drops directly into your Hytale server's `plugins/` folder. The name follows the `{rootProject.name}-{version}.jar` pattern defined in your Gradle files. Once the plugin grows — persistence, external integrations, third-party libs — you'll switch to a `shadowJar` to bundle dependencies, but for a first plugin the base config is plenty. If that kind of scope resonates but you'd rather delegate the development side, you can always [commission a custom Hytale plugin](/en/hytale) from someone doing it day in, day out. ## Next steps Once your first plugin is running, the natural paths forward are: -- Listen to more events: `BlockBreakEvent`, `PlayerChatEvent`, `EntityDamageEvent` — the full list lives in the `io.hytale.api.event` package. -- Persist data: start with a simple `JsonFile` in your plugin folder, switch to SQLite once you cross 50 KB of state. -- Add permissions: the Hytale API ships with a node system like `myplugin.admin.reload` that integrates with server groups. -- Profile your handlers: a slow event listener directly impacts the server's TPS. `logger.info` with timestamps is your first tool, then Flight Recorder when you get serious. +- Listen to more events — the exact list of available event classes is easiest to discover through IntelliJ autocomplete on the `com.hypixel.hytale.plugin.event` package. +- Persist data: start with a simple JSON file in your plugin folder, switch to SQLite once you cross 50 KB of state. +- Structure your code: a growing plugin deserves a clean listener / service / repository separation the moment you cross 300 lines. +- Profile your handlers: a slow event listener directly impacts the server's TPS. `getLogger().info` with timestamps is your first tool, then Flight Recorder when you go to production. + +## Further reading + +- [hytalemodding.dev](https://hytalemodding.dev) — plugin template and FR+EN guides +- [britakee-studios.gitbook.io/hytale-modding-documentation](https://britakee-studios.gitbook.io/hytale-modding-documentation) — community GitBook, the most up-to-date source on the API ## Wrapping up -A Hytale plugin is essentially a Kotlin class that extends `HytalePlugin`, registers listeners, and exposes commands. Everything else — persistence, UI, integrations — builds on top of that 50-line foundation. If you've followed along this far, you already have the technical base to ship any idea you have in mind. Code an ugly first thing, get it running, iterate. That's always how it starts. +A Hytale plugin is essentially a Java class that extends `JavaPlugin`, exposes the right constructor, registers listeners via the `PluginManager`, and describes itself in a `manifest.json`. Everything else — persistence, integrations, UI — builds on top of that 50-line foundation. If you've followed along this far, you already have the technical base to ship any idea you have in mind during this early-access window. Code an ugly first thing, get it running, iterate. That's always how it starts.