Hytale Stable-5 Migration
All Glymera plugins updated for Hytale Server 0.5.x (Stable 5)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
All Glymera plugins have been updated for compatibility with Hytale Server 0.5.x (Stable 5). The previous versions targeted Hytale Server 0.4.x (Stable 4).
This document covers what changed in the Hytale Server API between Stable 4 and Stable 5, and which adjustments were required across plugins so that things keep working.
◆ Why a new major version
Stable 5 introduced a number of source-incompatible API changes. A plugin compiled against Stable 4 will either fail to load, fail silently on first use, or — in a few cases — crash the world thread. Because the surface of change is large, every plugin received a new major version number, even when the only functional behaviour change was a manifest flag.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ What changed in Hytale Stable 5
▸ Vector math
- Internal Hytale vector types were replaced by JOML.
- com.hypixel.hytale.math.vector.Vector3d/3i/3f → org.joml.Vector3d/3i/3f
- Rotation helpers now use the Rotation3fc interface where the older API took a concrete Rotation3f.
▸ Particle / Sound API
- SpawnParticleSystem constructors gained additional arguments.
- PlaySoundEvent3D / ambient sound registration semantics tightened — long-running or looped sounds attached to a block must now use the block-state AmbientSoundEventId path; firing them with PlaySoundEvent3D from a tick handler no longer behaves correctly.
▸ Asset pack registration
- AssetModule.registerPack(...) now requires a PackSource parameter (use PackSource.MODS for plugin-installed packs).
- The IncludesAssetPack manifest flag is now strictly enforced. If a plugin both declares IncludesAssetPack: true and registers a runtime pack from its setup code, Hytale detects this as a duplicate registration and aborts boot with "Duplicate asset pack ... Remove the duplicate." For affected plugins IncludesAssetPack is now set to false; the actual asset pack is installed by the plugin at startup as before.
▸ HUD / UI system
- The HudManager API has moved to a keyed model — HUD entries now require a stable key and explicit lifecycle hooks.
- InteractiveCustomUIPage event bindings are wiped if the page is rebuilt while the client is processing a click. Plugins refreshing a HUD per tick had to throttle the rebuild rate (commonly to 1 Hz) so button presses are not lost.
▸ Player handles
- The Player component and the PlayerRef reference have been split. Plugins previously juggling one or the other now need to consider both.
- ServerPlayerListPlayer requires a worldUuid field — entries without it are silently dropped from the tab list.
▸ Entity utilities
- EntityUtils.toHolder(...) was removed. Lookups need to go through the entity store directly or via Ref<EntityStore>.
▸ Damage system
- Resistance fields on EntityEffect (damageResistanceValuesRaw, damageResistanceValues) now expect ResistanceModifier[], not StaticModifier[]. Plugins that previously injected resistances via reflection now use the ResistanceModifier(ResistanceCalculationType, float) constructor.
- The corresponding JSON CalculationType value "Multiplicative" is no longer accepted for damage resistance — it must be "Percent". Migrating large packs needed a bulk rewrite of Potion/Effect JSONs.
- Lethal-damage detection: EntityStatValue#getMin() is no longer a usable death-threshold. Lethal checks now compare the post-damage health against 0.0f directly. Plugins relying on the old behaviour would silently fail to fire and the player just died.
▸ ECS / system tick safety
- It is no longer legal to call mutating store methods (addComponent, removeEntity, …) directly from inside a system's tick handler. Doing so now throws IllegalStateException: Store is currently processing! Such calls must be deferred: either via the system's CommandBuffer, or by scheduling onto the next world tick with world.execute(...).
▸ Items, weapons and recipes
- Custom swords must declare Tags.Type: "Weapon" (not "Tool"), and they need their own complete InteractionVars block — these are no longer inherited from the parent template under Stable 5. A custom weapon without its own InteractionVars will spawn but cannot deal damage.
- Recipe bench IDs and recipe categories are now validated against the asset registry. Mistyped values cause Hytale to silently drop the recipe with no error message.
▸ Cosmetics
- CosmeticsToHide now only accepts a restricted, enumerated set of values. Custom values are rejected at asset-load time.
▸ Server authentication
After every Stable update the server's OAuth credentials must be refreshed:
If the server is still on cached OAUTH_STORE credentials after a Stable update, every incoming player connection is rejected with serverAuthUnavailable and the client only sees "QUIC handshake failed".
- auth logout
- auth login device
- Open the printed verification URL, complete the device authorisation
- auth select <profile>
▸ Manifest / ServerVersion
- Stable 5 enforces a recognisable ServerVersion declaration in each plugin's manifest.json. A malformed value prevents the server from booting. Glymera plugins declare "ServerVersion": "*" to remain agnostic across patch versions.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Plugin-data path
The plugin-data directory used by all Glymera plugins is now:
mods/de.glymera_<PluginName>/
(Previously it wasplugins/<PluginName>/.) Existing configuration files are not migrated automatically — if you want to keep custom configuration from an older install, move the relevant files from the old plugins/ location into the new one before first launch.
GlymeraArmorStand - Changelog
v6.0.0 (2026-05-16)
Bug Fixes
- Free-repair exploit closed: Equipping a damaged item (armor, weapon, or tool) onto an armor stand and then unequipping it no longer returns a fully-repaired item. Players could previously place a near-broken iron chestpiece on a stand, hit the stand with an empty hand, and recover the same piece at full durability — effectively turning every armor stand into a free repair bench. Reported by user_1bj5dukinaiflsam on CurseForge (2026-05-13). Verified reproducible in v5.0.0; fixed and confirmed by in-game testing in v6.0.0.
Root Cause
- StandData only stored each equipped slot as a String (the item ID), so the original ItemStack's durability was discarded when the item was placed on the stand. When the player took the item back, the plugin built a brand-new ItemStack(itemId, 1) via the regular constructor, which produces a stack at full max-durability.
Changes
- Per-slot durability tracking in StandData: Added six new double fields — headDur, chestDur, handsDur, legsDur, rightHandDur, leftHandDur — that mirror the existing six item-ID slots.
- setEquipSlot signature extended with a durability parameter. Both equip code paths (hit-with-item in StandInteractSystem, and the /armorstand equip command) now pass heldItem.getDurability() when storing.
- New getEquipSlotDur helper for symmetry.
- popAllEquipment now returns List<StoredEquip> instead of List<String>. StoredEquip is a small static inner record carrying slot, itemId, and dur, so the strip-all path (empty-hand hit) can rebuild each item with its saved durability.
- giveItemDirect overloaded: the legacy 4-argument signature delegates to a new 5-argument variant that accepts a durability and applies ItemStack.withDurability(dur) when dur > 0.
Migration / Legacy Stands
- Armor stands persisted under v5.0.0 (or earlier) deserialize cleanly: Gson populates the new *Dur fields with the Java default 0.0. The restore logic treats dur <= 0 as a "no stored value" sentinel and returns a fresh, fully-durable stack — no exception, no data loss. No migration step is required.
Coverage
- All six slots are covered: helmet, chest, gauntlets, pants, right hand (weapons + tools), left hand. detectSlot() already routes tools and weapons into the right-hand slot; their durability is now preserved like armor's.
v5.0.0 (2026-04-29)
Changes
- Asset-based slot detection: detectSlot() now reads the item's declared Armor.ArmorSlot field and maps it directly to Head/Chest/Hands/Legs. The previous substring-based behavior is kept as a fallback for items without an Armor block.
v4.0.0 (2026-04-22)
Bug Fixes
- Naked Player Ghost Fix: Resolved the long-standing issue where naked player-model NPCs would appear next to armor stands. New event-driven lifecycle with delayed sweep tasks at 1s, 3s, and 5s after ChunkPreLoadProcessEvent.
v3.0.0 (2026-04-15)
Bug Fixes
- Duplicate NPC Fix: Resolved issue where armor stands would duplicate (sometimes 100+) when players switched between worlds. Cleanup-first architecture with respawn cooldown.
v2.0.0 (2026-04-11)
Bug Fixes
- Skin Persistence Fix: Armor Stand appearances no longer randomize after chunk persistence cycles. Removed ApplyRandomSkinPersistedComponent.
v1.0.0 (2026-04-10)
- Initial release: Craftable armor stand blocks that spawn random player-looking NPCs
- Seed-based deterministic appearance (stored in item durability)
- Equipment system (hit with armor/weapon to equip, empty hand to unequip/break)
- Persistent across server restarts (position, equipment, appearance)
- Chunk load/unload handling with duplicate cleanup
- /armorstand give|list|removeall|reload commands
GlymeraArmorStand - Changelog
v5.0.0 (2026-04-29)
Changes
- Asset-based slot detection: detectSlot() now reads the item's declared Armor.ArmorSlot field via Item.getAssetStore().getAssetMap().getAsset(itemId).getArmor().getArmorSlot() and maps it directly to Head/Chest/Hands/Legs. The previous behavior — guessing the slot from substrings in the item ID (_head, _helmet, _hat, _chest, _torso, _hands, _gloves, _legs, _pants, ...) — is kept as a fallback for items without an Armor block (weapons, tools, sticks).
- Result: Custom plugin armor items (e.g. GlymeraHelm_Bunny from GlymeraHeads, or any third-party armor with non-standard naming) now land in the correct slot when equipped via /armorstand equip instead of falling through to the right hand. Vanilla helmets, chestpieces and gauntlets are unaffected — they declare Armor.ArmorSlot and are simply detected faster now.
No Functional Changes
All commands, equipment mechanics, skin generation, persistence, and recipes remain identical to v4.0.0.
v4.0.0 (2026-04-22)
Bug Fixes
- Naked Player Ghost Fix: Resolved the long-standing issue where naked player-model NPCs would appear next to (and sometimes inside) armor stands after players moved away and returned, after server restarts, or when chunks reloaded. Ghosts kept reappearing even after repeated rejoins, accumulating over days of play. Root cause: Hytale persists the NPCEntity in chunk data during chunk unload, but not the plugin's runtime ModelComponent and PlayerSkinComponent. On chunk reload Hytale rehydrates the entity with an old UUID and the DEFAULT_PLAYER_MODEL but no skin — visible as a "naked player". Critically, this rehydration happens 0.5 to 3 seconds after the ChunkPreLoadProcessEvent fires, so a synchronous cleanup at that event finds nothing.
Changes
- New lifecycle architecture: Replaced the entire runtime detection system (periodic checkStandSkins tick, skin-drift checks, position-drift checks, respawn cooldowns, initial-cleanup lockout, player-join hooks) with a single event-driven flow: On ChunkPreLoadProcessEvent: immediately spawn the fresh stand NPCs at their registered positions. Three delayed sweep tasks at 1s, 3s, and 5s after the event catch the late-rehydrating ghost entities that Hytale adds to the chunk afterward.
- Precise ghost criteria (all four must match): (a) entity sits on the exact block coordinates of a registered stand, (b) has no valid PlayerSkinComponent (or bodyCharacteristic is null), (c) is not in the plugin's entityLookup (not a freshly-spawned tracked stand), (d) lies in the chunk that was just reloaded.
- Role config change: ApplySeparation: false in GlymeraArmorStandStatic.json. Without this, rehydrated ghost entities drift 2-3 blocks away from the stand block within milliseconds and escape the exact-block cleanup. SeparationDistance must be set to 1, not 0 — Hytale rejects 0 at role load time.
- Stripped complexity: Removed the periodic checkStandSkins tick entirely, along with initialCleanupDone, cleanupCyclesRemaining, respawnCooldowns, onPlayerJoin hook, GhostSuppressorSystem, and all related bookkeeping. The plugin is now about 250 lines shorter and considerably simpler.
Notes
- Old ghost entities that accumulated in chunk data over previous sessions are cleaned up automatically on the next chunk load.
- The 1s delay means newly loaded stand chunks show a very brief (<1 second) window where ghosts are visible before being removed.
- All commands, equipment mechanics, skin generation, item metadata, persistence, and recipes remain identical to v3.0.0.
v3.0.0 (2026-04-15)
Bug Fixes
- Duplicate NPC Fix: Resolved issue where armor stands would duplicate (sometimes 100+) when players switched between worlds/instances and returned. Duplicates appeared as skinless NPCs that walked around.
Changes
- Cleanup-first architecture adopted from GlymeraFarmer. On first player join after server start, all stand NPCs are deleted and entity tracking is reset.
- Respawn cooldown (3 seconds per stand) prevents rapid-fire duplication during chunk loading.
- Pre-spawn position cleanup removes all untracked NPCs at the same position before spawning.
- Spawn lockout during cleanup prevents respawning while any cleanup cycle is active.
- Periodic cleanup reduced from every 1 second to every 5 seconds.
v2.0.0 (2026-04-11)
Bug Fixes
- Skin Persistence Fix: Armor Stand appearances no longer randomize after chunk persistence cycles. Removed ApplyRandomSkinPersistedComponent from NPC spawn logic.
v1.0.0 (2026-04-10)
- Initial release: Craftable armor stand blocks that spawn random player-looking NPCs
- Seed-based deterministic appearance (stored in item durability)
- Equipment system (hit with armor/weapon to equip, empty hand to unequip/break)
- Persistent across server restarts (position, equipment, appearance)
- Chunk load/unload handling with duplicate cleanup
- /armorstand give|list|removeall|reload commands
GlymeraArmorStand - Changelog
v4.0.0 (2026-04-22)
Bug Fixes
- Naked Player Ghost Fix: Resolved the long-standing issue where naked player-model NPCs would appear next to (and sometimes inside) armor stands after players moved away and returned, after server restarts, or when chunks reloaded. Ghosts kept reappearing even after repeated rejoins, accumulating over days of play. Root cause: Hytale persists the NPCEntity in chunk data during chunk unload, but not the plugin's runtime ModelComponent and PlayerSkinComponent. On chunk reload Hytale rehydrates the entity with an old UUID and the DEFAULT_PLAYER_MODEL but no skin — visible as a "naked player". Critically, this rehydration happens 0.5 to 3 seconds after the ChunkPreLoadProcessEvent fires, so a synchronous cleanup at that event finds nothing.
Changes
- New lifecycle architecture: Replaced the entire runtime detection system (periodic checkStandSkins tick, skin-drift checks, position-drift checks, respawn cooldowns, initial-cleanup lockout, player-join hooks) with a single event-driven flow: On ChunkPreLoadProcessEvent: immediately spawn the fresh stand NPCs at their registered positions. Three delayed sweep tasks at 1s, 3s, and 5s after the event catch the late-rehydrating ghost entities that Hytale adds to the chunk afterward.
- Precise ghost criteria (all four must match): (a) entity sits on the exact block coordinates of a registered stand, (b) has no valid PlayerSkinComponent (or bodyCharacteristic is null), (c) is not in the plugin's entityLookup (not a freshly-spawned tracked stand), (d) lies in the chunk that was just reloaded. This prevents false positives for legitimate other NPCs (Farmers, Companions, Merchants) that might be near a stand.
- Role config change: ApplySeparation: false in GlymeraArmorStandStatic.json. Without this, rehydrated ghost entities drift 2-3 blocks away from the stand block within milliseconds (Hytale's built-in separation physics) and escape the exact-block cleanup. SeparationDistance must be set to 1, not 0 — Hytale rejects 0 at role load time.
- Stripped complexity: Removed the periodic checkStandSkins tick entirely, along with initialCleanupDone, cleanupCyclesRemaining, respawnCooldowns, onPlayerJoin hook, GhostSuppressorSystem, and all related bookkeeping. The plugin is now about 250 lines shorter and considerably simpler.
Notes
- Old ghost entities that accumulated in chunk data over previous sessions are cleaned up automatically on the next chunk load (observed cleanup of 17+ ghosts in a 13-stand cluster during testing).
- The 1s delay means newly loaded stand chunks show a very brief (<1 second) window where ghosts are visible before being removed. Acceptable for the functional fix.
- All commands, equipment mechanics, skin generation, item metadata, persistence, and recipes remain identical to v3.0.0.
v3.0.0 (2026-04-15)
Bug Fixes
- Duplicate NPC Fix: Resolved issue where armor stands would duplicate (sometimes 100+) when players switched between worlds/instances and returned. Duplicates appeared as skinless NPCs that walked around. Root cause: the plugin respawned NPCs immediately when getEntityRef briefly returned null during chunk transitions, while Hytale simultaneously restored its own persisted copies from chunk data.
Changes
- Cleanup-first architecture: Adopted the proven approach from GlymeraFarmer. On first player join after server start, all stand NPCs are deleted and entity tracking is reset. A 3-cycle lockout prevents any respawning until cleanup completes.
- Respawn cooldown (3 seconds per stand): When a stand NPC is detected as missing, the plugin now waits 3 seconds before respawning. This prevents rapid-fire duplication when entity references are briefly invalid during chunk loading.
- Pre-spawn position cleanup: Before spawning a new stand NPC, all untracked NPCs at the same position are removed first.
- Spawn lockout during cleanup: No stand NPCs are respawned while any cleanup cycle is active.
- Periodic cleanup reduced: Duplicate scan interval changed from every 1 second to every 5 seconds to reduce unnecessary overhead.
No Functional Changes
All commands, equipment mechanics, skin generation, persistence, and recipes remain identical to v2.0.0.
v2.0.0 (2026-04-11)
Bug Fixes
- Skin Persistence Fix: Armor Stand appearances no longer randomize after chunk persistence cycles (auto-save, player disconnect/reconnect). Previously, ApplyRandomSkinPersistedComponent caused Hytale to re-randomize the NPC skin on every persistence cycle, overriding the seed-based deterministic skin set by the plugin.
Technical Details
- Removed ApplyRandomSkinPersistedComponent from NPC spawn logic
- The plugin's own persistence system (seed in item durability + skins.json registry + ModelComponent/PlayerSkinComponent) is sufficient and correct
v1.0.0 (2026-04-10)
- Initial release: Craftable armor stand blocks that spawn random player-looking NPCs
- Seed-based deterministic appearance (stored in item durability)
- Equipment system (hit with armor/weapon to equip, empty hand to unequip/break)
- Persistent across server restarts (position, equipment, appearance)
- Chunk load/unload handling with duplicate cleanup
- /armorstand give|list|removeall|reload commands
GlymeraArmorStand - Changelog
v3.0.0 (2026-04-15)
Bug Fixes
- Duplicate NPC Fix: Resolved issue where armor stands would duplicate (sometimes 100+) when players switched between worlds/instances and returned. Duplicates appeared as skinless NPCs that walked around. Root cause: the plugin respawned NPCs immediately when getEntityRef briefly returned null during chunk transitions, while Hytale simultaneously restored its own persisted copies from chunk data.
Changes
- Cleanup-first architecture: Adopted the proven approach from GlymeraFarmer. On first player join after server start, all stand NPCs are deleted and entity tracking is reset. A 3-cycle lockout prevents any respawning until cleanup completes.
- Respawn cooldown (3 seconds per stand): When a stand NPC is detected as missing, the plugin now waits 3 seconds before respawning. This prevents rapid-fire duplication when entity references are briefly invalid during chunk loading.
- Pre-spawn position cleanup: Before spawning a new stand NPC, all untracked NPCs at the same position are removed first.
- Spawn lockout during cleanup: No stand NPCs are respawned while any cleanup cycle is active.
- Periodic cleanup reduced: Duplicate scan interval changed from every 1 second to every 5 seconds to reduce unnecessary overhead.
No Functional Changes
All commands, equipment mechanics, skin generation, persistence, and recipes remain identical to v2.0.0.
v2.0.0 (2026-04-11)
Bug Fixes
- Skin Persistence Fix: Armor Stand appearances no longer randomize after chunk persistence cycles (auto-save, player disconnect/reconnect). Previously, ApplyRandomSkinPersistedComponent caused Hytale to re-randomize the NPC skin on every persistence cycle, overriding the seed-based deterministic skin set by the plugin.
Technical Details
- Removed ApplyRandomSkinPersistedComponent from NPC spawn logic
- The plugin's own persistence system (seed in item durability + skins.json registry + ModelComponent/PlayerSkinComponent) is sufficient and correct
v1.0.0 (2026-04-10)
- Initial release: Craftable armor stand blocks that spawn random player-looking NPCs
- Seed-based deterministic appearance (stored in item durability)
- Equipment system (hit with armor/weapon to equip, empty hand to unequip/break)
- Persistent across server restarts (position, equipment, appearance)
- Chunk load/unload handling with duplicate cleanup
- /armorstand give|list|removeall|reload commands
GlymeraArmorStand - Changelog
v2.0.0 (2026-04-11)
Bug Fixes
- Skin Persistence Fix: Armor Stand appearances no longer randomize after chunk persistence cycles (auto-save, player disconnect/reconnect). Previously, ApplyRandomSkinPersistedComponent caused Hytale to re-randomize the NPC skin on every persistence cycle, overriding the seed-based deterministic skin set by the plugin. This was hidden before because chunk unloading triggered a respawn cycle that re-applied the correct skin. With GlymeraChunkLoader keeping chunks permanently loaded, the respawn cycle never triggered, making the bug visible.
Technical Details
- Removed ApplyRandomSkinPersistedComponent from NPC spawn logic
- The plugin's own persistence system (seed in item durability + skins.json registry + ModelComponent/PlayerSkinComponent) is sufficient and correct
- Removed unused ApplyRandomSkinPersistedComponent import
v1.0.0 (2026-04-10)
- Initial release: Craftable armor stand blocks that spawn random player-looking NPCs
- Seed-based deterministic appearance (stored in item durability)
- Equipment system (hit with armor/weapon to equip, empty hand to unequip/break)
- Persistent across server restarts (position, equipment, appearance)
- Chunk load/unload handling with duplicate cleanup
- /armorstand give|list|removeall|reload commands
