# 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()` 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 three `EntityEventSystem` subclasses (one per protected event) 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. ### 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](../src/main/java/net/kewwbec/hub/HubPlugin.java) | Entry. Lifecycle, wiring. | | [config/HubConfig.java](../src/main/java/net/kewwbec/hub/config/HubConfig.java) | Config record. | | [config/HubConfigLoader.java](../src/main/java/net/kewwbec/hub/config/HubConfigLoader.java) | JSON loader. | | [protection/HubWorldFilter.java](../src/main/java/net/kewwbec/hub/protection/HubWorldFilter.java) | Is-this-world-a-hub predicate. | | [protection/CancelBreakBlockSystem.java](../src/main/java/net/kewwbec/hub/protection/CancelBreakBlockSystem.java) | ECS: cancel BreakBlockEvent in hubs. | | [protection/CancelPlaceBlockSystem.java](../src/main/java/net/kewwbec/hub/protection/CancelPlaceBlockSystem.java) | ECS: cancel PlaceBlockEvent in hubs. | | [protection/CancelDamageBlockSystem.java](../src/main/java/net/kewwbec/hub/protection/CancelDamageBlockSystem.java) | ECS: cancel DamageBlockEvent in hubs. | | [service/HubWorldService.java](../src/main/java/net/kewwbec/hub/service/HubWorldService.java) | Applies world-level flags (PvP, fall damage). | | [listener/HubJoinListener.java](../src/main/java/net/kewwbec/hub/listener/HubJoinListener.java) | PlayerReadyEvent: spawn-teleport + private welcome. | | [listener/HubMessageListener.java](../src/main/java/net/kewwbec/hub/listener/HubMessageListener.java) | Replaces/suppresses Hytale's built-in join/leave broadcast. |