4.9 KiB
Overview
Architecture
HubPlugin (entry)
+-- HubWorldFilter decides if a world is a hub world (config-driven)
+-- HubWorldService applies world-level WorldConfig flags (PvP, fall damage)
+-- HubJoinListener PlayerReadyEvent: teleport to spawn + private welcome
+-- HubMessageListener replaces/suppresses Hytale's built-in join/leave broadcasts
+-- CancelBreakBlockSystem ECS: cancels BreakBlockEvent in hub worlds
+-- CancelPlaceBlockSystem ECS: cancels PlaceBlockEvent in hub worlds
+-- CancelDamageBlockSystem ECS: cancels DamageBlockEvent in hub worlds
What runs when
Boot-time, in start()
- Build
HubWorldFilterfrom config (which worlds are hubs). - Register
AllWorldsLoadedEventlistener ->HubWorldService.applyToConfiguredWorlds(). Also call it once now (covers reload after worlds already loaded). - Register
PlayerReadyEvent->HubJoinListener.onReady(...). - Register
AddPlayerToWorldEvent/RemovedPlayerFromWorldEvent->HubMessageListener(replaces Hytale's default join/leave broadcast text, or suppresses entirely). - Register the three
EntityEventSystemsubclasses (one per protected event) withgetEntityStoreRegistry(). Each is gated on its ownprotection.disable_*toggle.
HubWorldService walks every configured hub world and sets:
isPvpEnabled = false(ifprotection.disable_pvp)isFallDamageEnabled = false(ifprotection.disable_fall_damage)
Only the flags that actually differ from current state are written. markChanged() is called once if anything changed.
Per-player, on PlayerReadyEvent
For each player who's just finished joining a hub world (i.e. their world is in worlds.hub_worlds, or that list is empty meaning "every world on this server is a hub"):
- Look up the world's spawn point via
WorldConfig.getSpawnProvider().getSpawnPoint(world, uuid)and add aTeleportcomponent to the player's entity. - Send the welcome message privately to the joining player.
The join/leave announcements are NOT sent here. They are emitted by HubMessageListener riding on AddPlayerToWorldEvent / RemovedPlayerFromWorldEvent, which are the same events Hytale's own broadcast uses, so we get exactly one message instead of two.
Why ECS systems instead of GameMode.Adventure
In Hytale, GameMode only has two values: Adventure and Creative. Adventure is what Minecraft players would call "Survival" - the player can still break and place blocks. It is NOT a block-protection mode. So we cancel BreakBlockEvent, PlaceBlockEvent, and DamageBlockEvent directly via ECS systems that filter on world. This actually prevents the action and is the same approach the server's own gating uses.
Why intercept AddPlayerToWorldEvent for messages
Hytale's player-add system fires AddPlayerToWorldEvent with a default joinMessage, then broadcasts it after the listeners run (only if shouldBroadcastJoinMessage() returned true). We rewrite the message text (or call setBroadcastJoinMessage(false) to suppress entirely if the configured format is blank). Same pattern for RemovedPlayerFromWorldEvent. This way the hub's announcement is the one and only line players see; we never run alongside Hytale's default.
Why PlayerReadyEvent for spawn-teleport
PlayerConnectEvent fires earlier in the flow, before the player is fully in the world. Teleporting at that point is racy. PlayerReadyEvent fires when the player is actually playable.
File map
| File | Responsibility |
|---|---|
| HubPlugin.java | Entry. Lifecycle, wiring. |
| config/HubConfig.java | Config record. |
| config/HubConfigLoader.java | JSON loader. |
| protection/HubWorldFilter.java | Is-this-world-a-hub predicate. |
| protection/CancelBreakBlockSystem.java | ECS: cancel BreakBlockEvent in hubs. |
| protection/CancelPlaceBlockSystem.java | ECS: cancel PlaceBlockEvent in hubs. |
| protection/CancelDamageBlockSystem.java | ECS: cancel DamageBlockEvent in hubs. |
| service/HubWorldService.java | Applies world-level flags (PvP, fall damage). |
| listener/HubJoinListener.java | PlayerReadyEvent: spawn-teleport + private welcome. |
| listener/HubMessageListener.java | Replaces/suppresses Hytale's built-in join/leave broadcast. |