Build environment
Requirements
- JDK 21+ — the project targets a toolchain that older JDKs cannot compile
- Gradle wrapper (included)
- A local Hytale install, for deploying
Building
./gradlew jar # build only
./gradlew deploy # build and copy into Hytale's Mods folder
The deploy task is atomic on purpose
deploy writes to a temporary file and then calls Files.move(..., ATOMIC_MOVE).
The previous version was a plain Gradle Copy, writing straight over
Mods/SimTale-x.y.z.jar. Hytale watches that folder and reloads the mod as soon as the file
changes — so it fired a reload the instant writing began and read a half-written jar:
FAIL: /Server/NPC/Roles/SimTale_Human_Male.json: Failed to load builder:
java.util.zip.ZipException: invalid LOC header (bad signature)
followed by Reloading nonexistent role ... for every generated role. The jar on disk was fine the
whole time; the problem was purely the moment of reading. With 11 MB, the corruption window is wide.
Verifying without compiling
Sometimes you cannot compile — a sandbox with an older JDK, for instance. These checks catch most mistakes:
Brace and paren balance
python3 -c "
import re
s = open('File.java').read()
s = re.sub(r'//.*', '', s)
s = re.sub(r'/\*.*?\*/', '', s, flags=re.S)
s = re.sub(r'\".*?\"', '\"\"', s)
print('braces', s.count('{') - s.count('}'))
print('parens', s.count('(') - s.count(')'))
"
Checking an engine API actually exists
Unzip the server jar and read the constant pool. Method names, field names and descriptors are all in there as plain strings:
unzip -q HytaleServer.jar "com/hypixel/hytale/.../Target.class" -d out
python3 -c "
import re
d = open('out/.../Target.class','rb').read()
print([s.decode('utf8','ignore') for s in re.findall(rb'[ -~]{3,80}', d)])
"
This is how ItemIcon's #Icon.ItemId property was confirmed: the literal string " #Icon.ItemId"
sits in the constant pool of ItemRepairElement.
Checking an asset exists
Game assets live under Server/ and Common/ in the Hytale install. Item ids are file names:
find Server/Item/Items -iname "*popcorn*"
# Server/Item/Items/Food/Food_Popcorn.json -> the id is Food_Popcorn
Language files
src/main/resources/Server/Languages/<locale>/*.lang, in key = value form.
Both pt-BR and en-US must be updated together. A key present in one and missing in the other
renders as the raw key in game — that is how prof.hunter went missing for a long time without
anyone noticing.
UI files
src/main/resources/Common/UI/Custom/<Page>/<Page>.ui.
Every #id is a contract with the Java page class. Removing or renaming one throws
KeyNotFoundException and the screen fails to load entirely.
Rows have fixed heights, and container heights are sums that must be maintained by hand. Getting the sum wrong does not clip the content — it lets it spill over whatever comes next.