Skip to content

Commit 3dbf1f1

Browse files
authored
Merge pull request #58 from faststats-dev/feat/hytale
Add Hytale implementation and plugin example
2 parents 116a148 + d7d3c9c commit 3dbf1f1

File tree

13 files changed

+386
-4
lines changed

13 files changed

+386
-4
lines changed

.github/workflows/build.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ jobs:
1313
java-version: 21
1414
- name: Setup Gradle
1515
uses: gradle/actions/setup-gradle@v5
16+
- name: Cache Hytale Server
17+
uses: actions/cache@v4
18+
with:
19+
path: |
20+
hytale/libs/HytaleServer.jar
21+
hytale/build/download/hytale-downloader-linux-amd64
22+
key: ${{ runner.os }}-hytale-download
23+
restore-keys: |
24+
${{ runner.os }}-hytale-
25+
- name: Download Hytale Server
26+
env:
27+
HYTALE_DOWNLOADER_CREDENTIALS: ${{ secrets.HYTALE_DOWNLOADER_CREDENTIALS }}
28+
run: ./gradlew :hytale:download-server
1629
- name: Build with Gradle
1730
run: ./gradlew build
1831
- name: Test with Gradle

.github/workflows/maven-publish.yml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ on:
44
types: [ prereleased, released ]
55
jobs:
66
build:
7-
env:
8-
REPOSITORY_USER: ${{ secrets.REPOSITORY_USER }}
9-
REPOSITORY_TOKEN: ${{ secrets.REPOSITORY_TOKEN }}
107
runs-on: ubuntu-latest
118
steps:
129
- name: Checkout sources
@@ -18,5 +15,21 @@ jobs:
1815
java-version: 21
1916
- name: Setup Gradle
2017
uses: gradle/actions/setup-gradle@v5
18+
- name: Cache Hytale Server
19+
uses: actions/cache@v4
20+
with:
21+
path: |
22+
hytale/libs/HytaleServer.jar
23+
hytale/build/download/hytale-downloader-linux-amd64
24+
key: ${{ runner.os }}-hytale-download
25+
restore-keys: |
26+
${{ runner.os }}-hytale-
27+
- name: Download Hytale Server
28+
env:
29+
HYTALE_DOWNLOADER_CREDENTIALS: ${{ secrets.HYTALE_DOWNLOADER_CREDENTIALS }}
30+
run: ./gradlew :hytale:download-server
2131
- name: Publish with Gradle to Repository
32+
env:
33+
REPOSITORY_USER: ${{ secrets.REPOSITORY_USER }}
34+
REPOSITORY_TOKEN: ${{ secrets.REPOSITORY_TOKEN }}
2235
run: ./gradlew publish

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=0.8.1
1+
version=0.9.0

hytale/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Credentials ###
2+
.hytale-downloader-credentials.json
3+
libs

hytale/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Hytale Module
2+
3+
Since the Hytale API is not public yet, and redistribution is not allowed,
4+
you have to download the Hytale server yourself.
5+
6+
## Initial Setup
7+
8+
Before building this module, you need to authenticate with your Hytale account. You have two options:
9+
10+
### Option 1: Using Environment Variable (Recommended for CI)
11+
12+
Set the `HYTALE_DOWNLOADER_CREDENTIALS` environment variable with your Hytale authentication credentials:
13+
14+
```bash
15+
export HYTALE_DOWNLOADER_CREDENTIALS='{"your":"auth","json":"here"}'
16+
./gradlew :hytale:download-server
17+
```
18+
19+
This token can be obtained by running the download task without credentials once (
20+
see [Obtaining Hytale Authentication](#obtaining-hytale-authentication)).
21+
22+
### Option 2: Using Credentials File (Recommended for Local Development)
23+
24+
1. Create `.hytale-downloader-credentials.json` in the `hytale/` directory
25+
2. Paste your Hytale authentication JSON credentials in the file
26+
3. Run the download task:
27+
28+
```bash
29+
./gradlew :hytale:download-server
30+
```
31+
32+
The credentials file is gitignored and won't be committed.
33+
34+
## Obtaining Hytale Authentication
35+
36+
To get your Hytale authentication credentials:
37+
38+
1. Run the download task without credentials:
39+
```bash
40+
./gradlew :hytale:download-server
41+
```
42+
2. The Hytale downloader will prompt you to authenticate
43+
3. After successful authentication, the credentials will be saved to
44+
`.hytale-downloader-credentials.json` for future use
45+
46+
## Updating the Server
47+
48+
To update the Hytale server:
49+
50+
```bash
51+
./gradlew :hytale:update-server
52+
```

hytale/build.gradle.kts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
val moduleName by extra("dev.faststats.hytale")
2+
3+
val libsDir: Directory = layout.projectDirectory.dir("libs")
4+
val hytaleServerJar: RegularFile = libsDir.file("HytaleServer.jar")
5+
val credentialsFile: RegularFile = layout.projectDirectory.file(".hytale-downloader-credentials.json")
6+
val downloadDir: Provider<Directory> = layout.buildDirectory.dir("download")
7+
val hytaleZip: Provider<RegularFile> = downloadDir.map { it.file("hytale.zip") }
8+
9+
dependencies {
10+
api(project(":core"))
11+
compileOnly(files(hytaleServerJar))
12+
}
13+
14+
tasks.register("download-server") {
15+
group = "hytale"
16+
17+
doLast {
18+
if (hytaleServerJar.asFile.exists()) {
19+
println("HytaleServer.jar already exists, skipping download")
20+
return@doLast
21+
}
22+
23+
val downloaderZip: Provider<RegularFile> = downloadDir.map { it.file("hytale-downloader.zip") }
24+
25+
libsDir.asFile.mkdirs()
26+
downloadDir.get().asFile.mkdirs()
27+
28+
val os = org.gradle.internal.os.OperatingSystem.current()
29+
val downloaderExecutable = when {
30+
os.isLinux -> downloadDir.map { it.file("hytale-downloader-linux-amd64") }
31+
os.isWindows -> downloadDir.map { it.file("hytale-downloader-windows-amd64.exe") }
32+
else -> throw GradleException("Unsupported operating system: ${os.name}")
33+
}
34+
35+
if (!downloaderExecutable.get().asFile.exists()) {
36+
if (!downloaderZip.get().asFile.exists()) ant.invokeMethod(
37+
"get", mapOf(
38+
"src" to "https://downloader.hytale.com/hytale-downloader.zip",
39+
"dest" to downloaderZip.get().asFile.absolutePath
40+
)
41+
) else {
42+
println("hytale-downloader.zip already exists, skipping download")
43+
}
44+
45+
copy {
46+
from(zipTree(downloaderZip))
47+
include(downloaderExecutable.get().asFile.name)
48+
into(downloadDir)
49+
}
50+
} else {
51+
println("Hytale downloader binary already exists, skipping download and extraction")
52+
}
53+
54+
if (downloaderZip.get().asFile.delete()) {
55+
println("Deleted hytale-downloader.zip after extracting binaries")
56+
}
57+
58+
downloaderExecutable.get().asFile.setExecutable(true)
59+
60+
if (!hytaleZip.get().asFile.exists()) {
61+
val credentials = System.getenv("HYTALE_DOWNLOADER_CREDENTIALS")
62+
if (!credentials.isNullOrBlank()) {
63+
if (!credentialsFile.asFile.exists()) {
64+
credentialsFile.asFile.writeText(credentials)
65+
println("Hytale downloader credentials written from environment variable to ${credentialsFile.asFile.absolutePath}")
66+
} else {
67+
println("Using existing credentials file at ${credentialsFile.asFile.absolutePath}")
68+
}
69+
}
70+
71+
val processBuilder = ProcessBuilder(
72+
downloaderExecutable.get().asFile.absolutePath,
73+
"-download-path",
74+
"hytale",
75+
"-credentials-path",
76+
credentialsFile.asFile.absolutePath
77+
)
78+
processBuilder.directory(downloadDir.get().asFile)
79+
processBuilder.redirectErrorStream(true)
80+
val process = processBuilder.start()
81+
82+
process.inputStream.bufferedReader().use { reader ->
83+
reader.lines().forEach { line ->
84+
println(line)
85+
}
86+
}
87+
88+
val exitCode = process.waitFor()
89+
if (exitCode != 0) {
90+
throw GradleException("Hytale downloader failed with exit code: $exitCode")
91+
}
92+
} else {
93+
println("hytale.zip already exists, skipping download")
94+
}
95+
96+
if (hytaleZip.get().asFile.exists()) {
97+
val serverDir = downloadDir.map { it.dir("Server") }
98+
copy {
99+
from(zipTree(hytaleZip))
100+
include("Server/HytaleServer.jar")
101+
into(downloadDir)
102+
}
103+
104+
val extractedJar = serverDir.map { it.file("HytaleServer.jar") }
105+
if (extractedJar.get().asFile.exists()) {
106+
extractedJar.get().asFile.copyTo(hytaleServerJar.asFile, overwrite = true)
107+
serverDir.get().asFile.deleteRecursively()
108+
} else {
109+
throw GradleException("HytaleServer.jar was not found in Server/ subdirectory")
110+
}
111+
112+
if (!hytaleServerJar.asFile.exists()) {
113+
throw GradleException("HytaleServer.jar was not found in hytale.zip")
114+
}
115+
116+
hytaleZip.get().asFile.delete()
117+
println("Deleted hytale.zip after extracting HytaleServer.jar")
118+
} else {
119+
throw GradleException(
120+
"hytale.zip not found at ${hytaleZip.get().asFile.absolutePath}. " +
121+
"The downloader may not have completed successfully."
122+
)
123+
}
124+
}
125+
}
126+
127+
tasks.register("update-server") {
128+
group = "hytale"
129+
hytaleServerJar.asFile.delete()
130+
hytaleZip.get().asFile.delete()
131+
dependsOn(tasks.named("download-server"))
132+
}
133+
134+
tasks.compileJava {
135+
dependsOn(tasks.named("download-server"))
136+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
plugins {
2+
id("com.gradleup.shadow") version "9.3.1"
3+
}
4+
5+
val libsDir: Directory = project(":hytale").layout.projectDirectory.dir("libs")
6+
val hytaleServerJar: RegularFile = libsDir.file("HytaleServer.jar")
7+
8+
dependencies {
9+
compileOnly(files(hytaleServerJar))
10+
implementation(project(":hytale"))
11+
}
12+
13+
tasks.shadowJar {
14+
// optionally relocate faststats
15+
relocate("dev.faststats", "com.example.utils.faststats")
16+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.example;
2+
3+
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
4+
import dev.faststats.hytale.HytaleMetrics;
5+
import dev.faststats.core.Metrics;
6+
import dev.faststats.core.chart.Chart;
7+
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
8+
9+
import java.net.URI;
10+
11+
public class ExamplePlugin extends JavaPlugin {
12+
private final Metrics metrics = HytaleMetrics.factory()
13+
.url(URI.create("https://metrics.example.com/v1/collect")) // For self-hosted metrics servers only
14+
15+
// Custom example charts
16+
// For this to work you have to create a corresponding data source in your project settings first
17+
.addChart(Chart.number("example_chart", () -> 42))
18+
.addChart(Chart.string("example_string", () -> "Hello, World!"))
19+
.addChart(Chart.bool("example_boolean", () -> true))
20+
.addChart(Chart.stringArray("example_string_array", () -> new String[]{"Option 1", "Option 2"}))
21+
.addChart(Chart.numberArray("example_number_array", () -> new Number[]{1, 2, 3}))
22+
.addChart(Chart.booleanArray("example_boolean_array", () -> new Boolean[]{true, false}))
23+
24+
.debug(true) // Enable debug mode for development and testing
25+
26+
.token("YOUR_TOKEN_HERE") // required -> token can be found in the settings of your project
27+
.create(this);
28+
29+
public ExamplePlugin(JavaPluginInit init) {
30+
super(init);
31+
}
32+
33+
@Override
34+
protected void shutdown() {
35+
metrics.shutdown();
36+
}
37+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"group": "com.example",
3+
"name": "Example Plugin",
4+
"version": "1.0.0",
5+
"description": "An example plugin for Hytale",
6+
"authors": [
7+
{
8+
"name": "Your Name",
9+
"email": "yourname@example.com",
10+
"url": "https://yourname.example.com"
11+
}
12+
],
13+
"website": "https://example.com/example-plugin",
14+
"serverVersion": "*",
15+
"main": "com.example.ExamplePlugin"
16+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.faststats.hytale;
2+
3+
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
4+
import dev.faststats.core.Metrics;
5+
import org.jetbrains.annotations.Contract;
6+
7+
/**
8+
* Hytale metrics implementation.
9+
*
10+
* @since 0.9.0
11+
*/
12+
public sealed interface HytaleMetrics extends Metrics permits HytaleMetricsImpl {
13+
/**
14+
* Creates a new metrics factory for Hytale.
15+
*
16+
* @return the metrics factory
17+
* @since 0.9.0
18+
*/
19+
@Contract(pure = true)
20+
static Metrics.Factory<JavaPlugin> factory() {
21+
return new HytaleMetricsImpl.Factory();
22+
}
23+
}

0 commit comments

Comments
 (0)