Files
2026-06-07 14:44:56 +01:00

6.4 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
  +-- CancelDamageSystem        ECS: cancels entity damage in hub worlds
  +-- DamageBypassFlags         shared temporary opt-in for combat plugins
  +-- HubBypassFlags            shared temporary opt-in for normal hub controls

What runs when

Boot-time, in start()

  1. Build HubWorldFilter from config (which worlds are hubs).
  2. Register AllWorldsLoadedEvent listener -> HubWorldService.applyToConfiguredWorlds(). Also call it once now (covers reload after worlds already loaded).
  3. Register PlayerReadyEvent -> HubJoinListener.onReady(...).
  4. Register AddPlayerToWorldEvent / RemovedPlayerFromWorldEvent -> HubMessageListener (replaces Hytale's default join/leave broadcast text, or suppresses entirely).
  5. Register the protection ECS systems with getEntityStoreRegistry(). Each is gated on its own protection.disable_* toggle.

HubWorldService walks every configured hub world and sets:

  • isPvpEnabled = false (if protection.disable_pvp)
  • isFallDamageEnabled = false (if protection.disable_fall_damage)

Only the flags that actually differ from current state are written. markChanged() is called once if anything changed.

DamageBypassFlags lets another plugin temporarily allow incoming damage for a player in a hub world, for example while that player is inside a duel arena. For hub-world duel arenas, keep disable_damage=true and disable_pvp=false: Hytale world PvP must be enabled, then Hub cancels damage unless the victim is bypassed.

HubBypassFlags lets another plugin temporarily bypass hub control behavior for a player. Bypassed players keep normal HUD/hotbar controls and do not trigger the hub menu from hotbar slot changes.

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"):

  1. Look up the world's spawn point via WorldConfig.getSpawnProvider().getSpawnPoint(world, uuid) and add a Teleport component to the player's entity.
  2. 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.
protection/CancelDamageSystem.java ECS: cancel entity damage in hubs.
protection/DamageBypassFlags.java Shared temporary damage opt-in.
protection/HubBypassFlags.java Shared temporary hub-control opt-in.
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.
listener/HubInventoryListener.java PlayerReadyEvent: clear inventory, set resting slot, hide HUD.
listener/MenuItemActivateSystem.java ECS: hotbar slot hub-menu trigger.
listener/HubHudVisibility.java Applies hidden/default hub HUD layouts.