Initial project setup with Hytale plugin template

Add a minimal, ready-to-use Hytale plugin template including Gradle build scripts, GitHub Actions CI workflow, example plugin class, configuration and manifest files, and supporting documentation. This setup provides modern build tooling, automated server testing, and best practices for plugin development.
This commit is contained in:
Britakee
2026-01-09 20:08:26 +01:00
commit 0dc5695def
17 changed files with 1204 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
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")
}
}