diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65fd529..cfbee71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,12 @@ name: Build Plugin +permissions: + contents: write + on: push: branches: [ main, develop ] + tags: [ 'v*' ] pull_request: branches: [ main ] @@ -11,62 +15,62 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Setup Java 25 - uses: actions/setup-java@v4 - with: - java-version: '25' - distribution: 'temurin' - - - name: Cache Gradle Dependencies - uses: actions/cache@v4 - with: - path: ~/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Grant Execute Permission for Gradlew - run: chmod +x gradlew - - - name: Build with Gradle - run: ./gradlew shadowJar - - - name: Run Tests - run: ./gradlew test - - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: plugin-jar - path: build/libs/*.jar - + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Java 25 + uses: actions/setup-java@v4 + with: + java-version: '25' + distribution: 'temurin' + + - name: Cache Gradle Dependencies + uses: actions/cache@v4 + with: + path: ~/.gradle + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant Execute Permission for Gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew shadowJar + + - name: Run Tests + run: ./gradlew test + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: plugin-jar + path: build/libs/*.jar + release: needs: build runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Setup Java 25 - uses: actions/setup-java@v4 - with: - java-version: '25' - distribution: 'temurin' - - - name: Grant Execute Permission for Gradlew - run: chmod +x gradlew - - - name: Build with Gradle - run: ./gradlew shadowJar - - - name: Create Release - uses: softprops/action-gh-release@v1 - with: - files: build/libs/*.jar - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Java 25 + uses: actions/setup-java@v4 + with: + java-version: '25' + distribution: 'temurin' + + - name: Grant Execute Permission for Gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew shadowJar + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: build/libs/*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index b7256e4..31342f7 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,9 @@ bin/ /.nb-gradle/ # VS Code -.vscode/ +.vscode/* +!.vscode/java-formatter.xml +!.vscode/settings.json # Mac OS .DS_Store diff --git a/README.md b/README.md index 5ec290c..0eae348 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A minimal, ready-to-use template for creating Hytale plugins with modern build t ## Features -✅ **Modern Build System** - Gradle with Kotlin DSL +✅ **Modern Build System** - Gradle with Groovy DSL ✅ **Automated Testing** - Custom Gradle plugin for one-command server testing ✅ **Java 25** - Latest Java features ✅ **ShadowJar** - Automatic dependency bundling @@ -51,9 +51,9 @@ Your plugin JAR will be in: `build/libs/TemplatePlugin-1.0.0.jar` When ready to customize, edit these files: -**`settings.gradle.kts`:** +**`settings.gradle`:** -```kotlin +```groovy rootProject.name = "your-plugin-name" ``` @@ -139,9 +139,9 @@ TemplatePlugin/ │ └── resources/ │ └── manifest.json # Plugin metadata ├── .gitignore # Git ignore rules -├── build.gradle.kts # Build configuration +├── build.gradle # Build configuration ├── gradle.properties # Project properties -├── settings.gradle.kts # Project settings +├── settings.gradle # Project settings ├── LICENSE # MIT License └── README.md # This file ``` @@ -200,9 +200,9 @@ rm -rf run/ ### Adding Dependencies -Edit `build.gradle.kts`: +Edit `build.gradle`: -```kotlin +```groovy dependencies { // Hytale API (provided by server) compileOnly(files("./HytaleServer.jar")) @@ -221,9 +221,9 @@ dependencies { **Usage:** -Edit `build.gradle.kts`: +Edit `build.gradle`: -```kotlin +```groovy runHytale { jarUrl = "url to hytale server jar" } @@ -322,7 +322,7 @@ GitHub Actions will automatically build and create a release with your plugin JA ### Server Won't Start -1. Check that `jarUrl` in `build.gradle.kts` is correct +1. Check that `jarUrl` in `build.gradle` is correct 2. Verify Java 25 is installed: `java -version` 3. Check logs in `run/logs/` diff --git a/build.gradle b/build.gradle index 8df6db3..e1ecb6b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'org.jetbrains.gradle.plugin.idea-ext' version '1.3' + id 'com.gradleup.shadow' version '9.3.1' } import org.gradle.internal.os.OperatingSystem @@ -8,8 +9,9 @@ import org.gradle.internal.os.OperatingSystem ext { if (project.hasProperty('hytale_home')) { hytaleHome = project.findProperty('hytale_home') - } - else { + } else if (System.getenv('HYTALE_HOME')) { + hytaleHome = System.getenv('HYTALE_HOME') + } else { def os = OperatingSystem.current() if (os.isWindows()) { hytaleHome = "${System.getProperty("user.home")}/AppData/Roaming/Hytale" @@ -26,11 +28,10 @@ ext { } } -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')}") +def hytaleHomePath = ext.has('hytaleHome') ? hytaleHome : null +def hasHytaleHome = (hytaleHomePath != null) && file(hytaleHomePath).exists() +if (!hasHytaleHome) { + logger.lifecycle("Hytale install not detected; run configs that launch the server will be skipped. Set -Phytale_home=/path/to/Hytale to enable them.") } java { @@ -45,16 +46,27 @@ javadoc { } // 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. +// compile against their code without bundling it. When a local install is +// present, we still use its jar for launching the server in IDE run configs. dependencies { - implementation(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar")) + compileOnly("com.hypixel.hytale:Server:$hytale_build") + if (hasHytaleHome) { + runtimeOnly(files("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar")) + } + + // Your dependencies here } -// Create the working directory to run the server if it does not already exist. -def serverRunDir = file("$projectDir/run") -if (!serverRunDir.exists()) { - serverRunDir.mkdirs() +repositories { + mavenCentral() + maven { + name = "hytale-release" + url = uri("https://maven.hytale.com/release") + } + maven { + name = "hytale-pre-release" + url = uri("https://maven.hytale.com/pre-release") + } } // Updates the manifest.json file with the latest properties defined in the @@ -78,54 +90,105 @@ 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 +tasks.named('shadowJar') { + archiveClassifier.set('') + mergeServiceFiles() } -// 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 - } +// Ensure the shaded jar is produced during a normal build. +tasks.named('build') { + dependsOn 'shadowJar' } -// 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() +if (hasHytaleHome) { + // Create the working directory to run the server if it does not already exist. + def serverRunDir = file("$projectDir/run") + if (!serverRunDir.exists()) { + serverRunDir.mkdirs() + } + + 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 } - 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" - ] + if (load_user_mods.toBoolean()) { + modPaths << "${hytaleHome}/UserData/Mods" + } + if (!modPaths.isEmpty()) { + programParameters += ' --mods="' + modPaths.join(',') + '"' + } + return programParameters + } + + def serverJar = file("$hytaleHome/install/$patchline/package/game/latest/Server/HytaleServer.jar") + def assetsZip = file("$hytaleHome/install/$patchline/package/game/latest/Assets.zip") + def shadowJarTask = tasks.named('shadowJar') + + tasks.register('runServerJar', JavaExec) { + dependsOn shadowJarTask + mainClass = 'com.hypixel.hytale.Main' + classpath = files(serverJar) + workingDir = serverRunDir.absolutePath + doFirst { + def modPaths = [shadowJarTask.get().archiveFile.get().asFile.absolutePath] + if (load_user_mods.toBoolean()) { + modPaths << "${hytaleHome}/UserData/Mods" + } + setArgs([ + '--allow-op', + '--disable-sentry', + "--assets=${assetsZip}", + "--mods=${modPaths.join(',')}" + ]) + } + } + + tasks.register('runServer') { + dependsOn 'runServerJar' + } + + // 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)) + launchFile.text = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(launchConfig)) + } + } +} else { + tasks.register('generateVSCodeLaunch') { + doLast { + logger.lifecycle("Skipped VSCode launch configuration because hytale_home is not set or the install path is missing.") + } } } diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 372dbb0..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,87 +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/HytaleServer.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 { - jarUrl = "./libs/HytaleServer.jar" - assetsPath = "./libs/Assets.zip" -} - -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)) - } -} diff --git a/gradle.properties b/gradle.properties index e0e99eb..ba33300 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,6 +19,11 @@ includes_pack=true # official launcher. patchline=release +# The exact Hytale build to compile against. Use the build string from the +# launcher (format YYYY.MM.DD-) so Gradle pulls the matching server jar +# for your selected patchline. +hytale_build=2026.01.27-734d39026 + # 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 @@ -29,4 +34,4 @@ load_user_mods=false # 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 \ No newline at end of file +# hytale_home=./test-file diff --git a/src/main/resources/manifest.json b/src/main/resources/manifest.json index 169a0cc..c39edab 100644 --- a/src/main/resources/manifest.json +++ b/src/main/resources/manifest.json @@ -9,7 +9,7 @@ } ], "Website": "example.org", - "ServerVersion": "*", + "ServerVersion": "2026.02.17-255364b8e", "Dependencies": { },