Migrate to new Hytale plugin template structure

Replaces the previous Kotlin-based Gradle build and template files with a new Java-based Gradle build system and updated example plugin code. Removes old build scripts, plugin template, and configuration, and introduces a new ExamplePlugin with updated manifest, command, and recipe example. Updates documentation and project configuration to match the new structure and usage.
This commit is contained in:
Britakee
2026-01-16 07:24:15 +01:00
parent 4f7cabf1d1
commit 41dd40e48b
20 changed files with 367 additions and 786 deletions

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

30
.gitignore vendored
View File

@@ -1,15 +1,14 @@
# Gradle
.gradle/
### Gradle ###
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
.kotlin/
# Server testing directory
### Hytale ###
run/
# IntelliJ IDEA
### IntelliJ IDEA ###
.idea/
*.iws
*.iml
@@ -18,7 +17,7 @@ out/
!**/src/main/**/out/
!**/src/test/**/out/
# Eclipse
### Eclipse ###
.apt_generated
.classpath
.factorypath
@@ -30,28 +29,15 @@ bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
# NetBeans
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
# VS Code
### VS Code ###
.vscode/
# Mac OS
### Mac OS ###
.DS_Store
# Windows
Thumbs.db
desktop.ini
# Logs
*.log
# Temporary files
*.tmp
*.bak
*.swp
*~

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Hytale Modding Community
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

442
README.md
View File

@@ -1,371 +1,99 @@
# Hytale Plugin Template
# Hytale Example Plugin
A minimal, ready-to-use template for creating Hytale plugins with modern build tools and automated testing.
An example project that can build and run plugins for the game Hytale!
> **✨ Builds immediately without any changes!** Clone and run `./gradlew shadowJar` to get a working plugin JAR.
> **⚠️ Warning: Early Access**
> The game Hytale is in early access, and so is this project! Features may be
> incomplete, unstable, or change frequently. Please be patient and understanding as development
> continues.
## Features
## Introduction
This project contains a Gradle project that can be imported into IDEA and used
as the foundation for custom Hytale plugins. The template will add the Hytale
server to your classpath and create a run configuration that can be used to
run your plugin on the server. It can also be used to build a sharable JAR file
that contains your plugin.
**Modern Build System** - Gradle with Kotlin DSL
**Automated Testing** - Custom Gradle plugin for one-command server testing
**Java 25** - Latest Java features
**ShadowJar** - Automatic dependency bundling
**CI/CD Ready** - GitHub Actions workflow included
**Minimal Structure** - Only essential files, write your own code
## Requirements
Please ensure all the requirements are met before getting started.
---
1. Download Hytale using the official launcher.
2. Have Intellij IDEA installed. Community edition is fine.
3. Download Java 25 and set it as the SDK in IDEA.
## Quick Start
## Configuring Template
It is important to configure the project before using it as a template. Doing
this before importing the project will help avoid running into caching issues
later on.
### Prerequisites
### 1: Project Name
Set the name of the project in `settings.gradle`. This should be the name of
your plugin. We recommend capitalizing your project name and avoiding
whitespace and most special characters. This will be used as the base name for
any files produced by Gradle, like the sharable JAR file.
- **Java 25 JDK** - [Download here](https://www.oracle.com/java/technologies/downloads/)
- **IntelliJ IDEA** - [Download here](https://www.jetbrains.com/idea/download/) (Community Edition is fine)
- **Git** - [Download here](https://git-scm.com/)
### 2: Gradle Properties
Review the properties defined in `gradle.properties`. You should change the
`maven_group` to match your project. You should also change the `version`
property before making a new release, or set up CI/CD to automate it.
### 1. Clone or Download
### 3: Manifest
The manifest file provides important information about your plugin to Hytale.
You should update every property in this file to reflect your project. The
most important property to set is `Main` which tells the game which class
file to load as the entry point for your plugin. The file can be found at
`src/main/resources/manifest.json`.
```bash
git clone https://github.com/yourusername/hytale-plugin-template.git
cd hytale-plugin-template
**This template has configured Gradle to automatically update the `Version` and
`IncludesAssetPack` property to reflect your Gradle properties every time you
run the game in development, or build the plugin. This is a workaround to allow
the in-game asset editor to be used when working on your project.**
## Importing into IDEA
When opening the project in IDEA it should automatically create the
`HytaleServer` run configuration and a `./run` folder. When you run the game it
will generate all the relevant files in there. It will also load the default
assets from the games.
**If you do not see the `HytaleServer` run configuration, you may need to open
the dropdown or click `Edit Configurations...` once to unhide it.**
## Importing into VSCode
While VSCode is not officially supported, you can generate launch configs by
running `./gradlew generateVSCodeLaunch`.
## Connecting to Server
Once the server is running in IDEA you should be able to connect to
`Local Server` using your standard Hytale client. If the server does not show
up automatically, add the IP as `127.0.0.1` manually.
### You MUST authenticate your test server!
In order to connect to the test server, you must authenticate it with Hytale.
This is done by running the `auth login device` command in the server terminal.
This command will print a URL that you can use to authenticate the server using
your Hytale account. Once authenticated, you can run the
`auth persistence Encrypted` command to keep your server authenticated after
restarting it.
**Never share your encrypted auth file!**
If you are unable to run commands from the IDEA terminal, you can also run the
command from code like this. Make sure to remove the code after your server is
authenticated.
```java
@Override
protected void start() {
CommandManager.get().handleCommand(ConsoleSender.INSTANCE, "auth login device");
}
```
**The template builds immediately without any changes!**
You can customize it later when you're ready to develop your plugin.
### 2. Build Immediately (No Changes Needed!)
## Verifying The Example Plugin
You can verify the Example plugin has loaded by running the `/test` command
in game. It will print the name and version of your plugin. This is for
demonstration purposes, and should **NOT** be included in your final build.
The template works out-of-the-box:
```bash
# Windows
gradlew.bat shadowJar
# Linux/Mac
./gradlew shadowJar
```
Your plugin JAR will be in: `build/libs/TemplatePlugin-1.0.0.jar`
### 3. Customize Your Plugin (Optional)
When ready to customize, edit these files:
**`settings.gradle.kts`:**
```kotlin
rootProject.name = "your-plugin-name"
```
**`gradle.properties`:**
```properties
pluginGroup=com.yourname
pluginVersion=1.0.0
pluginDescription=Your plugin description
```
**`src/main/resources/manifest.json`:**
```json
{
"Group": "YourName",
"Name": "YourPluginName",
"Main": "com.yourname.yourplugin.YourPlugin"
}
```
**Rename the main plugin class:**
- Rename `src/main/java/com/example/templateplugin/TemplatePlugin.java`
- Update package name to match your `pluginGroup`
### 4. Build Your Plugin
```bash
# Windows
gradlew.bat shadowJar
# Linux/Mac
./gradlew shadowJar
```
Your plugin JAR will be in: `build/libs/YourPluginName-1.0.0.jar`
### 5. Implement Your Plugin
Write your plugin code in `src/main/java/`:
- Commands
- Event listeners
- Services
- Storage
- Utilities
See our [documentation](../Documentation/) for examples and patterns.
### 6. Test Your Plugin (Automated!)
```bash
# Windows
gradlew.bat runServer
# Linux/Mac
./gradlew runServer
```
This will:
1. Download the Hytale server (cached for future runs)
2. Build your plugin
3. Copy it to the server's plugins folder
4. Start the server with interactive console
---
## Project Structure
```
TemplatePlugin/
├── .github/workflows/
│ └── build.yml # CI/CD workflow
├── buildSrc/
│ ├── build.gradle.kts # Custom plugin configuration
│ └── src/main/kotlin/
│ └── RunHytalePlugin.kt # Automated server testing
├── src/main/
│ ├── java/com/example/templateplugin/
│ │ └── TemplatePlugin.java # Minimal main class (example)
│ └── resources/
│ └── manifest.json # Plugin metadata
├── .gitignore # Git ignore rules
├── build.gradle.kts # Build configuration
├── gradle.properties # Project properties
├── settings.gradle.kts # Project settings
├── LICENSE # MIT License
└── README.md # This file
```
**Note:** This is a minimal template. Create your own folder structure:
- `commands/` - For command implementations
- `listeners/` - For event listeners
- `services/` - For business logic
- `storage/` - For data persistence
- `utils/` - For utility classes
- `config/` - For configuration management
---
## Development Workflow
### Building
```bash
# Compile only
./gradlew compileJava
# Build plugin JAR
./gradlew shadowJar
# Clean and rebuild
./gradlew clean shadowJar
```
### Testing
```bash
# Run server with your plugin
./gradlew runServer
# Run unit tests
./gradlew test
# Clean test server
rm -rf run/
```
### Debugging
```bash
# Run server in debug mode
./gradlew runServer -Pdebug
# Then connect your IDE debugger to localhost:5005
```
---
## Customization
### Adding Dependencies
Edit `build.gradle.kts`:
```kotlin
dependencies {
// Hytale API (provided by server)
compileOnly(files("libs/hytale-server.jar"))
// Your dependencies (will be bundled)
implementation("com.google.code.gson:gson:2.10.1")
// Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
```
### Configuring Server Testing
**Run Hytale Server** - A Gradle plugin to download and run a Hytale server for development and testing purposes. The server files will be located in the `run/` directory of the project. Before starting the server it will compile (shadowJar task) and copy the plugin jar to the server's `plugins/` folder.
**Usage:**
Edit `build.gradle.kts`:
```kotlin
runHytale {
jarUrl = "url to hytale server jar"
}
```
Run the server with:
```bash
# Windows
gradlew.bat runServer
# Linux/Mac
./gradlew runServer
```
**Features:**
- ✅ Automatic server JAR download and caching
- ✅ Compiles and deploys your plugin automatically
- ✅ Starts server with interactive console
- ✅ One-command workflow: `./gradlew runServer`
- ✅ Server files in `run/` directory (gitignored)
### Implementing Your Plugin
**Recommended folder structure:**
```
src/main/java/com/yourname/yourplugin/
├── YourPlugin.java # Main class
├── commands/ # Commands
├── listeners/ # Event listeners
├── services/ # Business logic
├── storage/ # Data persistence
├── config/ # Configuration
└── utils/ # Utilities
```
**See our documentation for examples:**
- [Getting Started with Plugins](../Documentation/07-getting-started-with-plugins.md)
- [Advanced Plugin Patterns](../Documentation/12-advanced-plugin-patterns.md)
- [Common Plugin Features](../Documentation/14-common-plugin-features.md)
---
## CI/CD
This template includes a GitHub Actions workflow that:
1. ✅ Builds your plugin on every push
2. ✅ Runs tests
3. ✅ Uploads artifacts
4. ✅ Creates releases (when you tag)
### Creating a Release
```bash
git tag v1.0.0
git push origin v1.0.0
```
GitHub Actions will automatically build and create a release with your plugin JAR.
---
## Best Practices
### ✅ DO:
- Use the Service-Storage pattern for data management
- Write unit tests for your business logic
- Use structured logging (not `System.out.println`)
- Handle errors gracefully
- Document your public API
- Version your releases semantically (1.0.0, 1.1.0, etc.)
### ❌ DON'T:
- Hardcode configuration values
- Block the main thread with heavy operations
- Ignore exceptions
- Use deprecated APIs
- Commit sensitive data (API keys, passwords)
---
## Troubleshooting
### Build Fails
```bash
# Clean and rebuild
./gradlew clean build --refresh-dependencies
```
### Server Won't Start
1. Check that `jarUrl` in `build.gradle.kts` is correct
2. Verify Java 25 is installed: `java -version`
3. Check logs in `run/logs/`
### Plugin Not Loading
1. Verify `manifest.json` has correct `Main` class
2. Check server logs for errors
3. Ensure all dependencies are bundled in JAR
---
## Documentation
For detailed guides on plugin development, see:
- [Hytale Modding Documentation](https://github.com/yourusername/hytale-modding/tree/main/Documentation)
- [Getting Started with Plugins](../Documentation/07-getting-started-with-plugins.md)
- [Advanced Plugin Patterns](../Documentation/12-advanced-plugin-patterns.md)
- [Common Plugin Features](../Documentation/14-common-plugin-features.md)
---
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
---
## License
This template is released under the MIT License. You are free to use it for any purpose.
---
## Support
- **Issues:** [GitHub Issues](https://github.com/yourusername/hytale-plugin-template/issues)
- **Documentation:** [Hytale Modding Docs](https://github.com/yourusername/hytale-modding)
- **Community:** Join the Hytale modding community
---
## Credits
Created by the Hytale modding community.
Based on best practices from production Hytale plugins.
---
**Happy Modding! 🎮**
The example plugin also includes a recipe defined by an asset pack. This recipe
allows you to craft 10 dirt into 1 dirt using the crafting window. This is also
an example and should not be removed before you release the plugin.

131
build.gradle Normal file
View File

@@ -0,0 +1,131 @@
plugins {
id 'java'
id 'org.jetbrains.gradle.plugin.idea-ext' version '1.3'
}
import org.gradle.internal.os.OperatingSystem
ext {
if (project.hasProperty('hytale_home')) {
hytaleHome = project.findProperty('hytale_home')
}
else {
def os = OperatingSystem.current()
if (os.isWindows()) {
hytaleHome = "${System.getProperty("user.home")}/AppData/Roaming/Hytale"
}
else if (os.isMacOsX()) {
hytaleHome = "${System.getProperty("user.home")}/Library/Application Support/Hytale"
}
else if (os.isLinux()) {
hytaleHome = "${System.getProperty("user.home")}/.var/app/com.hypixel.HytaleLauncher/data/Hytale"
if (!file(hytaleHome).exists()) {
hytaleHome = "${System.getProperty("user.home")}/.local/share/Hytale"
}
}
}
}
if (!project.hasProperty('hytaleHome')) {
throw new GradleException('Your Hytale install could not be detected automatically. If you are on an unsupported platform or using a custom install location, please define the install location using the hytale_home property.');
}
else if (!file(project.findProperty('hytaleHome')).exists()) {
throw new GradleException("Failed to find Hytale at the expected location. Please make sure you have installed the game. The expected location can be changed using the hytale_home property. Currently looking in ${project.findProperty('hytaleHome')}")
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(java_version)
withSourcesJar()
withJavadocJar()
}
// Quiet warnings about missing Javadocs.
javadoc {
options.addStringOption('Xdoclint:-missing', '-quiet')
}
// Adds the Hytale server as a build dependency, allowing you to reference and
// compile against their code. This requires you to have Hytale installed using
// the official launcher for now.
dependencies {
implementation(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar"))
}
// Create the working directory to run the server if it does not already exist.
def serverRunDir = file("$projectDir/run")
if (!serverRunDir.exists()) {
serverRunDir.mkdirs()
}
// Updates the manifest.json file with the latest properties defined in the
// build.properties file. Currently we update the version and if packs are
// included with the plugin.
tasks.register('updatePluginManifest') {
def manifestFile = file('src/main/resources/manifest.json')
doLast {
if (!manifestFile.exists()) {
throw new GradleException("Could not find manifest.json at ${manifestFile.path}!")
}
def manifestJson = new groovy.json.JsonSlurper().parseText(manifestFile.text)
manifestJson.Version = version
manifestJson.IncludesAssetPack = includes_pack.toBoolean()
manifestFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(manifestJson))
}
}
// Makes sure the plugin manifest is up to date.
tasks.named('processResources') {
dependsOn 'updatePluginManifest'
}
def createServerRunArguments(String srcDir) {
def programParameters = '--allow-op --disable-sentry --assets="' + "${hytaleHome}/install/$patchline/package/game/latest/Assets.zip" + '"'
def modPaths = []
if (includes_pack.toBoolean()) {
modPaths << srcDir
}
if (load_user_mods.toBoolean()) {
modPaths << "${hytaleHome}/UserData/Mods"
}
if (!modPaths.isEmpty()) {
programParameters += ' --mods="' + modPaths.join(',') + '"'
}
return programParameters
}
// Creates a run configuration in IDEA that will run the Hytale server with
// your plugin and the default assets.
idea.project.settings.runConfigurations {
'HytaleServer'(org.jetbrains.gradle.ext.Application) {
mainClass = 'com.hypixel.hytale.Main'
moduleName = project.idea.module.name + '.main'
programParameters = createServerRunArguments(sourceSets.main.java.srcDirs.first().parentFile.absolutePath)
workingDirectory = serverRunDir.absolutePath
}
}
// Creates a launch.json file for VSCode with the same configuration
tasks.register('generateVSCodeLaunch') {
def vscodeDir = file("$projectDir/.vscode")
def launchFile = file("$vscodeDir/launch.json")
doLast {
if (!vscodeDir.exists()) {
vscodeDir.mkdirs()
}
def programParams = createServerRunArguments("\${workspaceFolder}")
def launchConfig = [
version: "0.2.0",
configurations: [
[
type: "java",
name: "HytaleServer",
request: "launch",
mainClass: "com.hypixel.hytale.Main",
args: programParams,
cwd: "\${workspaceFolder}/run"
]
]
]
launchFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(launchConfig))
}
}

View File

@@ -1,88 +0,0 @@
plugins {
id("java-library")
id("com.gradleup.shadow") version "9.3.1"
id("run-hytale")
}
group = findProperty("pluginGroup") as String? ?: "com.example"
version = findProperty("pluginVersion") as String? ?: "1.0.0"
description = findProperty("pluginDescription") as String? ?: "A Hytale plugin template"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// Hytale Server API (provided by server at runtime)
compileOnly(files("libs/hytale-server.jar"))
// Common dependencies (will be bundled in JAR)
implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains:annotations:24.1.0")
// Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// Configure server testing
runHytale {
// TODO: Update this URL when Hytale server is available
// Using Paper server as placeholder for testing the runServer functionality
jarUrl = "https://fill-data.papermc.io/v1/objects/d5f47f6393aa647759f101f02231fa8200e5bccd36081a3ee8b6a5fd96739057/paper-1.21.10-115.jar"
}
tasks {
// Configure Java compilation
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release = 25
}
// Configure resource processing
processResources {
filteringCharset = Charsets.UTF_8.name()
// Replace placeholders in manifest.json
val props = mapOf(
"group" to project.group,
"version" to project.version,
"description" to project.description
)
inputs.properties(props)
filesMatching("manifest.json") {
expand(props)
}
}
// Configure ShadowJar (bundle dependencies)
shadowJar {
archiveBaseName.set(rootProject.name)
archiveClassifier.set("")
// Relocate dependencies to avoid conflicts
relocate("com.google.gson", "com.yourplugin.libs.gson")
// Minimize JAR size (removes unused classes)
minimize()
}
// Configure tests
test {
useJUnitPlatform()
}
// Make build depend on shadowJar
build {
dependsOn(shadowJar)
}
}
// Configure Java toolchain
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}

View File

@@ -1,17 +0,0 @@
plugins {
`kotlin-dsl`
`java-gradle-plugin`
}
gradlePlugin {
plugins {
create("runHytale") {
id = "run-hytale"
implementationClass = "RunHytalePlugin"
}
}
}
repositories {
mavenCentral()
}

View File

@@ -1,166 +0,0 @@
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.net.URI
import java.security.MessageDigest
/**
* Custom Gradle plugin for automated Hytale server testing.
*
* Usage:
* runHytale {
* jarUrl = "https://example.com/hytale-server.jar"
* }
*
* ./gradlew runServer
*/
open class RunHytalePlugin : Plugin<Project> {
override fun apply(project: Project) {
// Create extension for configuration
val extension = project.extensions.create("runHytale", RunHytaleExtension::class.java)
// Register the runServer task
val runTask: TaskProvider<RunServerTask> = project.tasks.register(
"runServer",
RunServerTask::class.java
) {
jarUrl.set(extension.jarUrl)
group = "hytale"
description = "Downloads and runs the Hytale server with your plugin"
}
// Make runServer depend on shadowJar (build plugin first)
project.tasks.findByName("shadowJar")?.let {
runTask.configure {
dependsOn(it)
}
}
}
}
/**
* Extension for configuring the RunHytale plugin.
*/
open class RunHytaleExtension {
var jarUrl: String = "https://example.com/hytale-server.jar"
}
/**
* Task that downloads, sets up, and runs a Hytale server with the plugin.
*/
open class RunServerTask : DefaultTask() {
@Input
val jarUrl = project.objects.property(String::class.java)
@TaskAction
fun run() {
// Create directories
val runDir = File(project.projectDir, "run").apply { mkdirs() }
val pluginsDir = File(runDir, "plugins").apply { mkdirs() }
val jarFile = File(runDir, "server.jar")
// Cache directory for downloaded server JARs
val cacheDir = File(
project.layout.buildDirectory.asFile.get(),
"hytale-cache"
).apply { mkdirs() }
// Compute hash of URL for caching
val urlHash = MessageDigest.getInstance("SHA-256")
.digest(jarUrl.get().toByteArray())
.joinToString("") { "%02x".format(it) }
val cachedJar = File(cacheDir, "$urlHash.jar")
// Download server JAR if not cached
if (!cachedJar.exists()) {
println("Downloading Hytale server from ${jarUrl.get()}")
try {
URI.create(jarUrl.get()).toURL().openStream().use { input ->
cachedJar.outputStream().use { output ->
input.copyTo(output)
}
}
println("Server JAR downloaded and cached")
} catch (e: Exception) {
println("ERROR: Failed to download server JAR")
println("Make sure the jarUrl in build.gradle.kts is correct")
println("Error: ${e.message}")
return
}
} else {
println("Using cached server JAR")
}
// Copy server JAR to run directory
cachedJar.copyTo(jarFile, overwrite = true)
// Copy plugin JAR to plugins folder
project.tasks.findByName("shadowJar")?.outputs?.files?.firstOrNull()?.let { shadowJar ->
val targetFile = File(pluginsDir, shadowJar.name)
shadowJar.copyTo(targetFile, overwrite = true)
println("Plugin copied to: ${targetFile.absolutePath}")
} ?: run {
println("WARNING: Could not find shadowJar output")
}
println("Starting Hytale server...")
println("Press Ctrl+C to stop the server")
// Check if debug mode is enabled
val debugMode = project.hasProperty("debug")
val javaArgs = mutableListOf<String>()
if (debugMode) {
javaArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005")
println("Debug mode enabled. Connect debugger to port 5005")
}
javaArgs.addAll(listOf("-jar", jarFile.name))
// Start the server process
val process = ProcessBuilder("java", *javaArgs.toTypedArray())
.directory(runDir)
.start()
// Handle graceful shutdown
project.gradle.buildFinished {
if (process.isAlive) {
println("\nStopping server...")
process.destroy()
}
}
// Forward stdout to console
Thread {
process.inputStream.bufferedReader().useLines { lines ->
lines.forEach { println(it) }
}
}.start()
// Forward stderr to console
Thread {
process.errorStream.bufferedReader().useLines { lines ->
lines.forEach { System.err.println(it) }
}
}.start()
// Forward stdin to server (for commands)
Thread {
System.`in`.bufferedReader().useLines { lines ->
lines.forEach {
process.outputStream.write((it + "\n").toByteArray())
process.outputStream.flush()
}
}
}.start()
// Wait for server to exit
val exitCode = process.waitFor()
println("Server exited with code $exitCode")
}
}

View File

@@ -1,10 +1,32 @@
# Project Information
pluginGroup=com.example
pluginVersion=1.0.0
pluginDescription=A Hytale plugin template with best practices
# The current version of your project. Please use semantic versioning!
version=0.0.2
# Gradle Configuration
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# The group ID used for maven publishing. Usually the same as your package name
# but not the same as your plugin group!
maven_group=org.example
# The version of Java used by your plugin. The game is built on Java 21 but
# actually runs on Java 25.
java_version=25
# Determines if your plugin should also be loaded as an asset pack. If your
# pack contains assets, or you intend to use the in-game asset editor, you
# want this to be true.
includes_pack=true
# The release channel your plugin should be built and ran against. This is
# usually release or pre-release. You can verify your settings in the
# official launcher.
patchline=release
# Determines if the development server should also load mods from the user's
# standard mods folder. This lets you test mods by installing them where a
# normal player would, instead of adding them as dependencies or adding them
# to the development server manually.
load_user_mods=false
# If Hytale was installed to a custom location, you must set the home path
# manually. You may also want to use a custom path if you are building in
# a non-standard environment like a build server. The home path should
# the folder that contains the install and UserData folder.
# hytale_home=./test-file

View File

@@ -1,7 +1,6 @@
#Tue Nov 25 02:23:10 MST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

18
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,11 +80,11 @@ do
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@@ -143,16 +143,12 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -209,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'ExamplePlugin'

View File

@@ -1 +0,0 @@
rootProject.name = "TemplatePlugin"

View File

@@ -1,54 +0,0 @@
package com.example.templateplugin;
/**
* Main plugin class.
*
* TODO: Implement your plugin logic here.
*
* @author YourName
* @version 1.0.0
*/
public class TemplatePlugin {
private static TemplatePlugin instance;
/**
* Constructor - Called when plugin is loaded.
*/
public TemplatePlugin() {
instance = this;
System.out.println("[TemplatePlugin] Plugin loaded!");
}
/**
* Called when plugin is enabled.
*/
public void onEnable() {
System.out.println("[TemplatePlugin] Plugin enabled!");
// TODO: Initialize your plugin here
// - Load configuration
// - Register event listeners
// - Register commands
// - Start services
}
/**
* Called when plugin is disabled.
*/
public void onDisable() {
System.out.println("[TemplatePlugin] Plugin disabled!");
// TODO: Cleanup your plugin here
// - Save data
// - Stop services
// - Close connections
}
/**
* Get plugin instance.
*/
public static TemplatePlugin getInstance() {
return instance;
}
}

View File

@@ -0,0 +1,29 @@
package org.example.plugin;
import com.hypixel.hytale.protocol.GameMode;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase;
import javax.annotation.Nonnull;
/**
* This is an example command that will simply print the name of the plugin in chat when used.
*/
public class ExampleCommand extends CommandBase {
private final String pluginName;
private final String pluginVersion;
public ExampleCommand(String pluginName, String pluginVersion) {
super("test", "Prints a test message from the " + pluginName + " plugin.");
this.setPermissionGroup(GameMode.Adventure); // Allows the command to be used by anyone, not just OP
this.pluginName = pluginName;
this.pluginVersion = pluginVersion;
}
@Override
protected void executeSync(@Nonnull CommandContext ctx) {
ctx.sendMessage(Message.raw("Hello from the " + pluginName + " v" + pluginVersion + " plugin!"));
}
}

View File

@@ -0,0 +1,27 @@
package org.example.plugin;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import javax.annotation.Nonnull;
/**
* This class serves as the entrypoint for your plugin. Use the setup method to register into game registries or add
* event listeners.
*/
public class ExamplePlugin extends JavaPlugin {
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
public ExamplePlugin(@Nonnull JavaPluginInit init) {
super(init);
LOGGER.atInfo().log("Hello from " + this.getName() + " version " + this.getManifest().getVersion().toString());
}
@Override
protected void setup() {
LOGGER.atInfo().log("Setting up plugin " + this.getName());
this.getCommandRegistry().registerCommand(new ExampleCommand(this.getName(), this.getManifest().getVersion().toString()));
}
}

View File

@@ -0,0 +1,22 @@
{
"Input": [
{
"ItemId": "Soil_Dirt",
"Quantity": 10
}
],
"PrimaryOutput": {
"ItemId": "Soil_Dirt",
"Quantity": 1
},
"BenchRequirement": [
{
"Id": "Fieldcraft",
"Type": "Crafting",
"Categories": [
"Tools"
]
}
],
"Seconds": 1
}

View File

@@ -1,5 +0,0 @@
{
"pluginName": "TemplatePlugin",
"version": "1.0.0",
"debugMode": false
}

View File

@@ -1,19 +1,22 @@
{
"Group": "TemplatePlugin",
"Name": "TemplatePlugin",
"Version": "1.0.0",
"Description": "A Hytale plugin template",
"Group": "Example",
"Name": "ExamplePlugin",
"Version": "0.0.2",
"Description": "An example plugin for HyTale!",
"Authors": [
{
"Name": "YourName",
"Email": "your.email@example.com",
"Url": "https://your-website.com"
"Name": "It's you!"
}
],
"Website": "https://github.com/yourusername/hytale-plugin-template",
"Main": "com.example.templateplugin.TemplatePlugin",
"Website": "example.org",
"ServerVersion": "*",
"Dependencies": {},
"OptionalDependencies": {},
"DisabledByDefault": false
"Dependencies": {
},
"OptionalDependencies": {
},
"DisabledByDefault": false,
"Main": "org.example.plugin.ExamplePlugin",
"IncludesAssetPack": true
}