init
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Build output
|
||||||
|
target/
|
||||||
|
out/
|
||||||
|
build/
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
.vscode/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Local config / secrets
|
||||||
|
config.json.local
|
||||||
|
*.env
|
||||||
|
.env
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Hub
|
||||||
|
|
||||||
|
Lobby behavior for KweebecNet hubs. Drop this on any server you want to act as a hub.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- **Protection**: applies `WorldConfig` flags to disable PvP and fall damage, and forces every connecting player to `GameMode.Adventure` (no block break/place).
|
||||||
|
- **Welcome message**: privately sent to each joining player.
|
||||||
|
- **Join announcement**: broadcast to everyone else on this server.
|
||||||
|
- **Spawn-on-join**: every player is teleported to the world's configured spawn point on join. No more "where did I end up" after a transfer.
|
||||||
|
|
||||||
|
## Requires
|
||||||
|
|
||||||
|
- **NetworkCore** loaded (for the plugin dependency declaration; Hub doesn't actually use any NetworkCore services at runtime in v1, but the manifest expresses the order).
|
||||||
|
|
||||||
|
## Why this is critical
|
||||||
|
|
||||||
|
Hubs are where players land. If they:
|
||||||
|
- Can break/place blocks: they grief the lobby.
|
||||||
|
- Don't spawn at spawn: they end up wherever they last logged out, often outside the playable area.
|
||||||
|
- Don't get a welcome: the server feels broken on first visit.
|
||||||
|
|
||||||
|
So the hub behaviors aren't optional polish - they're the user's first impression of the network.
|
||||||
|
|
||||||
|
## Read before changing
|
||||||
|
|
||||||
|
- [docs/01_Overview.md](docs/01_Overview.md) - architecture
|
||||||
|
- [docs/02_Configuration.md](docs/02_Configuration.md) - config.json fields
|
||||||
|
- [docs/03_Limits_and_Gotchas.md](docs/03_Limits_and_Gotchas.md) - what we can't (yet) protect against
|
||||||
|
|
||||||
|
## Where this is NOT installed
|
||||||
|
|
||||||
|
Don't install Hub on minigame servers, persistent worlds, or anywhere players are supposed to actually do things. Its protection flags will lock the world to read-only mode. It's a hub-only plugin.
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# 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. |
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
## Location
|
||||||
|
|
||||||
|
`<plugin data dir>/config.json`. Written with defaults on first boot.
|
||||||
|
|
||||||
|
## Full config
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"worlds": {
|
||||||
|
"hub_worlds": []
|
||||||
|
},
|
||||||
|
"protection": {
|
||||||
|
"disable_block_break": true,
|
||||||
|
"disable_block_place": true,
|
||||||
|
"disable_block_damage": true,
|
||||||
|
"disable_pvp": true,
|
||||||
|
"disable_fall_damage": true,
|
||||||
|
"apply_world_config": true
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"welcome": "Welcome to the hub, {player}!",
|
||||||
|
"join_announcement": "{player} joined the hub.",
|
||||||
|
"leave_announcement": ""
|
||||||
|
},
|
||||||
|
"spawn": {
|
||||||
|
"teleport_on_join": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
### worlds
|
||||||
|
|
||||||
|
| Field | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `hub_worlds` | `[]` | World names this plugin treats as hubs. **Empty = every world this server hosts is treated as a hub.** Most hub servers run one world, so empty is fine. If you have multiple worlds and only some are hubs, list the hub names here. |
|
||||||
|
|
||||||
|
### protection
|
||||||
|
|
||||||
|
| Field | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `disable_block_break` | `true` | Register an ECS system that cancels `BreakBlockEvent` in hub worlds. Players cannot mine blocks. |
|
||||||
|
| `disable_block_place` | `true` | Register an ECS system that cancels `PlaceBlockEvent` in hub worlds. Players cannot place blocks. |
|
||||||
|
| `disable_block_damage` | `true` | Register an ECS system that cancels `DamageBlockEvent` in hub worlds. Blocks cannot be damaged (the partial-damage step before a break). |
|
||||||
|
| `disable_pvp` | `true` | Apply `WorldConfig.setPvpEnabled(false)` on the hub world. Players can't damage each other. |
|
||||||
|
| `disable_fall_damage` | `true` | Apply `WorldConfig.setFallDamageEnabled(false)`. Players don't take fall damage. |
|
||||||
|
| `apply_world_config` | `true` | Master toggle for the world-flag application (PvP + fall damage). Set false if you want to manage these flags yourself in Hytale's world config and have Hub leave them alone. Does NOT affect the ECS block protections - toggle those individually via the `disable_block_*` flags. |
|
||||||
|
|
||||||
|
> Note: Hytale's `GameMode` only has `Adventure` and `Creative`. `Adventure` is the equivalent of Minecraft's *Survival* (players can still break/place). There is no gamemode that prevents block actions, which is why hub protection uses ECS event cancellation instead.
|
||||||
|
|
||||||
|
### messages
|
||||||
|
|
||||||
|
| Field | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `welcome` | `"Welcome to the hub, {player}!"` | Sent privately to the joining player on `PlayerReadyEvent`. `{player}` is replaced with their username. Empty/null disables. |
|
||||||
|
| `join_announcement` | `"{player} joined the hub."` | **Replaces** Hytale's built-in join broadcast on `AddPlayerToWorldEvent`. `{player}` is replaced. Empty/null suppresses the broadcast entirely (no replacement and no default). |
|
||||||
|
| `leave_announcement` | `""` (empty) | **Replaces** Hytale's built-in leave broadcast on `RemovedPlayerFromWorldEvent`. `{player}` is replaced. Default is empty so no leave message is shown - staff plugin handles network-wide leave announcements. Set to e.g. `"{player} left the hub."` if you want a per-server leave line. |
|
||||||
|
|
||||||
|
### spawn
|
||||||
|
|
||||||
|
| Field | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `teleport_on_join` | `true` | On `PlayerReadyEvent`, teleport the player to the world's spawn point. The spawn point comes from `WorldConfig.getSpawnProvider().getSpawnPoint(...)` - same source Hytale uses for new players. |
|
||||||
|
|
||||||
|
## Recommended setups
|
||||||
|
|
||||||
|
**Single-world dedicated hub server (most common):**
|
||||||
|
- Defaults are fine. `hub_worlds: []` means "everything on this server is a hub".
|
||||||
|
|
||||||
|
**Multi-world server where only one world is a hub:**
|
||||||
|
- `hub_worlds: ["lobby"]` (or whatever your hub world is named).
|
||||||
|
- Other worlds are unaffected by protection / spawn-on-join / messages.
|
||||||
|
|
||||||
|
**Hub world managed entirely outside this plugin:**
|
||||||
|
- `protection.apply_world_config: false` to leave the world's PvP/fall flags alone.
|
||||||
|
- `protection.disable_block_*: false` to leave block events alone.
|
||||||
|
- Hub will still do welcome message + spawn-on-join + join/leave message replacement.
|
||||||
|
|
||||||
|
**Allow building in the hub (e.g. for staff):**
|
||||||
|
- Hytale's permission system would need a per-player override; this plugin cancels at the world level, not per-player. If you want admins-can-build behavior, that needs a small change to the protection systems to check a permission node before cancelling.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Limits and Gotchas
|
||||||
|
|
||||||
|
What Hub v1 protects against, and what it doesn't.
|
||||||
|
|
||||||
|
## Protected
|
||||||
|
|
||||||
|
- **Block break**: `CancelBreakBlockSystem` cancels `BreakBlockEvent` for any breaker entity inside a hub world.
|
||||||
|
- **Block place**: `CancelPlaceBlockSystem` cancels `PlaceBlockEvent` likewise.
|
||||||
|
- **Block damage** (the partial-damage ticks before a break): `CancelDamageBlockSystem` cancels `DamageBlockEvent`.
|
||||||
|
- **PvP**: `WorldConfig.isPvpEnabled = false`. Players can't damage other players in the hub world.
|
||||||
|
- **Fall damage**: `WorldConfig.isFallDamageEnabled = false`.
|
||||||
|
- **Spawn location**: every joining player is teleported to the spawn point. They can't end up somewhere weird from a previous session.
|
||||||
|
- **Duplicate join/leave broadcasts**: Hub replaces (or suppresses) Hytale's built-in `AddPlayerToWorldEvent.joinMessage` and `RemovedPlayerFromWorldEvent.leaveMessage` rather than emitting alongside them.
|
||||||
|
|
||||||
|
## NOT protected against in v1
|
||||||
|
|
||||||
|
If you find a specific destructive action that's still possible in the hub, file it - the fix is usually a new ECS system following the same pattern as the existing three (extend `EntityEventSystem<EntityStore, SomeEvent>`, `Query.any()`, cancel if world is hub).
|
||||||
|
|
||||||
|
- **Mob damage**: hostile NPCs can still hit players. If your hub has no hostile mobs (typical), this doesn't matter.
|
||||||
|
- **Item drops**: Players might be able to drop items via `DropItemEvent`.
|
||||||
|
- **Item pickups**: Same for `InteractivelyPickupItemEvent`.
|
||||||
|
- **Inventory edits**: `InventoryChangeEvent` not intercepted.
|
||||||
|
- **Crafting**: `PlayerCraftEvent` / `CraftRecipeEvent` not intercepted.
|
||||||
|
- **Explosions**: `ExplosionEvent` not intercepted (creeper-equivalents could still damage terrain).
|
||||||
|
|
||||||
|
## Why ECS systems and not GameMode
|
||||||
|
|
||||||
|
In some Minecraft-style servers `GameMode.Adventure` prevents block break/place. Hytale's `GameMode` enum only has `Adventure` and `Creative`, and `Adventure` is the equivalent of Survival - the player can still freely break and place. There is no gamemode that blocks block actions. So Hub uses ECS event cancellation at the world level, which is what actually works.
|
||||||
|
|
||||||
|
## Per-server, not network
|
||||||
|
|
||||||
|
Hub doesn't know about other Hub servers. Each Hub server protects its own world(s). If you have multiple lobbies, install Hub on each. Config can differ per server.
|
||||||
|
|
||||||
|
## Timing of world flag application
|
||||||
|
|
||||||
|
`AllWorldsLoadedEvent` fires after Hytale finishes loading worlds at boot. Hub's `applyToConfiguredWorlds()` runs then. There's a brief window during boot (before `AllWorldsLoadedEvent`) where the world's flags are whatever was saved on disk. Connecting players in that window get the saved-disk flags. In practice nobody connects during this window because the server isn't accepting players yet.
|
||||||
|
|
||||||
|
If you do a hot plugin reload mid-run (someone removes and re-adds the plugin without restarting the server), we also call `applyToConfiguredWorlds()` in `start()` directly to catch already-loaded worlds.
|
||||||
|
|
||||||
|
## ECS system registration is start()-time only
|
||||||
|
|
||||||
|
Registering an `EntityEventSystem` after the server has booted requires a registry refresh that we don't trigger here. If you toggle a `disable_block_*` flag at runtime via config reload, the change won't take effect until the next server start. Restart for protection-flag changes to apply.
|
||||||
|
|
||||||
|
## Join/leave message timing
|
||||||
|
|
||||||
|
The welcome message and spawn teleport fire on `PlayerReadyEvent` - by definition the player is "ready" and the world is loaded for them. Should be visible in chat as they appear in-world.
|
||||||
|
|
||||||
|
The join/leave broadcasts ride on `AddPlayerToWorldEvent` / `RemovedPlayerFromWorldEvent` and are sent by Hytale itself (we just rewrite the text). They behave exactly like Hytale's defaults - same broadcast scope, same timing - except worded the way you configured.
|
||||||
|
|
||||||
|
If `messages.join_announcement` or `leave_announcement` is empty/blank, `setBroadcast*Message(false)` is called, which fully suppresses Hytale's default (no replacement, no original).
|
||||||
|
|
||||||
|
## Plugin install order
|
||||||
|
|
||||||
|
Hub declares no dependencies in its manifest in v1 (it's standalone). If you want network-wide hub messages later, add `Dependencies: {kewwbec:NetworkCore: "*"}` and pull the MessageBus from `NetworkCore.getInstance()` in `start()`.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Hub Docs
|
||||||
|
|
||||||
|
1. [01_Overview.md](01_Overview.md) - architecture and what each piece does
|
||||||
|
2. [02_Configuration.md](02_Configuration.md) - config.json fields
|
||||||
|
3. [03_Limits_and_Gotchas.md](03_Limits_and_Gotchas.md) - what we don't (yet) protect against, and why
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>net.kewwbec</groupId>
|
||||||
|
<artifactId>hub</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Hub</name>
|
||||||
|
<description>Lobby/hub behavior: protection, welcome message, spawn-on-join</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<hytale.server.jar>${user.home}/AppData/Roaming/Hytale/install/release/package/game/latest/Server/HytaleServer.jar</hytale.server.jar>
|
||||||
|
<networkcore.jar>${project.basedir}/../Core/target/NetworkCore-0.1.0.jar</networkcore.jar>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hypixel.hytale</groupId>
|
||||||
|
<artifactId>hytale-server</artifactId>
|
||||||
|
<version>local</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${hytale.server.jar}</systemPath>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kewwbec</groupId>
|
||||||
|
<artifactId>networkcore</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${networkcore.jar}</systemPath>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.findbugs</groupId>
|
||||||
|
<artifactId>jsr305</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}-${project.version}</finalName>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package net.kewwbec.hub;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.RemovedPlayerFromWorldEvent;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.events.AllWorldsLoadedEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import net.kewwbec.hub.config.HubConfig;
|
||||||
|
import net.kewwbec.hub.config.HubConfigLoader;
|
||||||
|
import net.kewwbec.hub.listener.HubJoinListener;
|
||||||
|
import net.kewwbec.hub.listener.HubMessageListener;
|
||||||
|
import net.kewwbec.hub.protection.CancelDamageBlockSystem;
|
||||||
|
import net.kewwbec.hub.protection.CancelPlaceBlockSystem;
|
||||||
|
import net.kewwbec.hub.protection.CancelBreakBlockSystem;
|
||||||
|
import net.kewwbec.hub.protection.HubWorldFilter;
|
||||||
|
import net.kewwbec.hub.service.HubWorldService;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public final class HubPlugin extends JavaPlugin {
|
||||||
|
|
||||||
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private HubConfig config;
|
||||||
|
private HubWorldFilter filter;
|
||||||
|
private HubWorldService worldService;
|
||||||
|
|
||||||
|
public HubPlugin(@Nonnull JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setup() {
|
||||||
|
try {
|
||||||
|
this.config = HubConfigLoader.loadOrCreate(getDataDirectory());
|
||||||
|
} catch (IOException e) {
|
||||||
|
((HytaleLogger.Api) LOGGER.at(Level.SEVERE).withCause(e)).log("Failed to load Hub config");
|
||||||
|
this.config = new HubConfig();
|
||||||
|
}
|
||||||
|
this.filter = new HubWorldFilter(config);
|
||||||
|
LOGGER.at(Level.INFO).log("Hub setup complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void start() {
|
||||||
|
this.worldService = new HubWorldService(config);
|
||||||
|
HubJoinListener joinListener = new HubJoinListener(config, filter);
|
||||||
|
HubMessageListener messageListener = new HubMessageListener(config, filter);
|
||||||
|
|
||||||
|
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, e -> worldService.applyToConfiguredWorlds());
|
||||||
|
worldService.applyToConfiguredWorlds();
|
||||||
|
|
||||||
|
getEventRegistry().registerGlobal(PlayerReadyEvent.class, joinListener::onReady);
|
||||||
|
getEventRegistry().registerGlobal(AddPlayerToWorldEvent.class, messageListener::onAdd);
|
||||||
|
getEventRegistry().registerGlobal(RemovedPlayerFromWorldEvent.class, messageListener::onRemove);
|
||||||
|
|
||||||
|
if (config.protection.disable_block_break) {
|
||||||
|
getEntityStoreRegistry().registerSystem(new CancelBreakBlockSystem(filter));
|
||||||
|
}
|
||||||
|
if (config.protection.disable_block_place) {
|
||||||
|
getEntityStoreRegistry().registerSystem(new CancelPlaceBlockSystem(filter));
|
||||||
|
}
|
||||||
|
if (config.protection.disable_block_damage) {
|
||||||
|
getEntityStoreRegistry().registerSystem(new CancelDamageBlockSystem(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.at(Level.INFO).log("Hub started");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutdown() {
|
||||||
|
LOGGER.at(Level.INFO).log("Hub shutdown complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package net.kewwbec.hub.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class HubConfig {
|
||||||
|
|
||||||
|
public WorldsSection worlds = new WorldsSection();
|
||||||
|
public ProtectionSection protection = new ProtectionSection();
|
||||||
|
public MessagesSection messages = new MessagesSection();
|
||||||
|
public SpawnSection spawn = new SpawnSection();
|
||||||
|
|
||||||
|
public static final class WorldsSection {
|
||||||
|
/**
|
||||||
|
* World names this plugin treats as hubs. Empty = apply to every world this server hosts.
|
||||||
|
*/
|
||||||
|
public List<String> hub_worlds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ProtectionSection {
|
||||||
|
/** Cancel BreakBlockEvent in hub worlds. */
|
||||||
|
public boolean disable_block_break = true;
|
||||||
|
/** Cancel PlaceBlockEvent in hub worlds. */
|
||||||
|
public boolean disable_block_place = true;
|
||||||
|
/** Cancel DamageBlockEvent in hub worlds (mining progress). */
|
||||||
|
public boolean disable_block_damage = true;
|
||||||
|
/** WorldConfig.isPvpEnabled = false on hub worlds. */
|
||||||
|
public boolean disable_pvp = true;
|
||||||
|
/** WorldConfig.isFallDamageEnabled = false on hub worlds. */
|
||||||
|
public boolean disable_fall_damage = true;
|
||||||
|
/**
|
||||||
|
* Master toggle for writing the WorldConfig flags (PvP, fall damage). Set false to manage
|
||||||
|
* them yourself in Hytale's world config and have Hub leave them alone.
|
||||||
|
*/
|
||||||
|
public boolean apply_world_config = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class MessagesSection {
|
||||||
|
/**
|
||||||
|
* Private welcome shown only to the joining player. {player} is substituted.
|
||||||
|
* Empty/null disables.
|
||||||
|
*/
|
||||||
|
public String welcome = "Welcome to the hub, {player}!";
|
||||||
|
/**
|
||||||
|
* Replaces Hytale's default join broadcast on hub worlds. {player} is substituted.
|
||||||
|
* Empty/null suppresses the join broadcast entirely.
|
||||||
|
*/
|
||||||
|
public String join_announcement = "{player} joined the hub.";
|
||||||
|
/**
|
||||||
|
* Replaces Hytale's default leave broadcast on hub worlds. {player} is substituted.
|
||||||
|
* Empty/null suppresses the leave broadcast entirely.
|
||||||
|
*/
|
||||||
|
public String leave_announcement = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class SpawnSection {
|
||||||
|
/**
|
||||||
|
* If true, teleport every connecting player to the world's spawn point.
|
||||||
|
*/
|
||||||
|
public boolean teleport_on_join = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package net.kewwbec.hub.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class HubConfigLoader {
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
|
private HubConfigLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static HubConfig loadOrCreate(@Nonnull Path dataDirectory) throws IOException {
|
||||||
|
Path file = dataDirectory.resolve("config.json");
|
||||||
|
if (!Files.exists(file)) {
|
||||||
|
Files.createDirectories(dataDirectory);
|
||||||
|
HubConfig fresh = new HubConfig();
|
||||||
|
Files.writeString(file, GSON.toJson(fresh), StandardCharsets.UTF_8);
|
||||||
|
return fresh;
|
||||||
|
}
|
||||||
|
String json = Files.readString(file, StandardCharsets.UTF_8);
|
||||||
|
HubConfig parsed = GSON.fromJson(json, HubConfig.class);
|
||||||
|
if (parsed == null) parsed = new HubConfig();
|
||||||
|
if (parsed.worlds == null) parsed.worlds = new HubConfig.WorldsSection();
|
||||||
|
if (parsed.protection == null) parsed.protection = new HubConfig.ProtectionSection();
|
||||||
|
if (parsed.messages == null) parsed.messages = new HubConfig.MessagesSection();
|
||||||
|
if (parsed.spawn == null) parsed.spawn = new HubConfig.SpawnSection();
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package net.kewwbec.hub.listener;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.math.vector.Transform;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.WorldConfig;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import net.kewwbec.hub.config.HubConfig;
|
||||||
|
import net.kewwbec.hub.protection.HubWorldFilter;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On PlayerReadyEvent in a hub world: teleport to spawn and send a private welcome.
|
||||||
|
*
|
||||||
|
* Join/leave broadcasts are handled by HubMessageListener (which replaces Hytale's built-in
|
||||||
|
* join/leave messages on AddPlayerToWorldEvent / RemovedPlayerFromWorldEvent).
|
||||||
|
*/
|
||||||
|
public final class HubJoinListener {
|
||||||
|
|
||||||
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final HubConfig config;
|
||||||
|
private final HubWorldFilter filter;
|
||||||
|
|
||||||
|
public HubJoinListener(@Nonnull HubConfig config, @Nonnull HubWorldFilter filter) {
|
||||||
|
this.config = config;
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReady(@Nonnull PlayerReadyEvent event) {
|
||||||
|
Ref<EntityStore> ref = event.getPlayerRef();
|
||||||
|
if (ref == null || !ref.isValid()) return;
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
World world = store.getExternalData().getWorld();
|
||||||
|
if (world == null || !filter.isHub(world)) return;
|
||||||
|
|
||||||
|
PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType());
|
||||||
|
if (playerRef == null) return;
|
||||||
|
|
||||||
|
if (config.spawn.teleport_on_join) {
|
||||||
|
try {
|
||||||
|
WorldConfig wcfg = world.getWorldConfig();
|
||||||
|
ISpawnProvider sp = (wcfg == null) ? null : wcfg.getSpawnProvider();
|
||||||
|
if (sp != null) {
|
||||||
|
Transform spawn = sp.getSpawnPoint(world, playerRef.getUuid());
|
||||||
|
if (spawn != null) {
|
||||||
|
store.putComponent(ref, Teleport.getComponentType(),
|
||||||
|
Teleport.createForPlayer(world, spawn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Hub: teleport to spawn failed for %s: %s", playerRef.getUsername(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.messages.welcome != null && !config.messages.welcome.isBlank()) {
|
||||||
|
String welcome = config.messages.welcome.replace("{player}", playerRef.getUsername());
|
||||||
|
try {
|
||||||
|
playerRef.sendMessage(Message.raw(welcome).color(Color.YELLOW));
|
||||||
|
} catch (RuntimeException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package net.kewwbec.hub.listener;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Holder;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.RemovedPlayerFromWorldEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import net.kewwbec.hub.config.HubConfig;
|
||||||
|
import net.kewwbec.hub.protection.HubWorldFilter;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces (or suppresses) Hytale's default join/leave broadcasts on hub worlds.
|
||||||
|
*
|
||||||
|
* AddPlayerToWorldEvent / RemovedPlayerFromWorldEvent are fired by Hytale's player-add/remove
|
||||||
|
* systems with a default joinMessage / leaveMessage. The systems check shouldBroadcastJoinMessage
|
||||||
|
* (or leave) after dispatch and broadcast accordingly. We rewrite the message text here, or
|
||||||
|
* suppress entirely if the configured format is blank.
|
||||||
|
*/
|
||||||
|
public final class HubMessageListener {
|
||||||
|
|
||||||
|
private final HubConfig config;
|
||||||
|
private final HubWorldFilter filter;
|
||||||
|
|
||||||
|
public HubMessageListener(@Nonnull HubConfig config, @Nonnull HubWorldFilter filter) {
|
||||||
|
this.config = config;
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAdd(@Nonnull AddPlayerToWorldEvent event) {
|
||||||
|
if (!filter.isHub(event.getWorld())) return;
|
||||||
|
String username = resolveUsername(event.getHolder());
|
||||||
|
String template = config.messages.join_announcement;
|
||||||
|
if (template == null || template.isBlank()) {
|
||||||
|
event.setBroadcastJoinMessage(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.setJoinMessage(Message.raw(template.replace("{player}", username)));
|
||||||
|
event.setBroadcastJoinMessage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRemove(@Nonnull RemovedPlayerFromWorldEvent event) {
|
||||||
|
if (!filter.isHub(event.getWorld())) return;
|
||||||
|
String username = resolveUsername(event.getHolder());
|
||||||
|
String template = config.messages.leave_announcement;
|
||||||
|
if (template == null || template.isBlank()) {
|
||||||
|
event.setBroadcastLeaveMessage(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.setLeaveMessage(Message.raw(template.replace("{player}", username)));
|
||||||
|
event.setBroadcastLeaveMessage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static String resolveUsername(@Nullable Holder<EntityStore> holder) {
|
||||||
|
if (holder == null) return "?";
|
||||||
|
PlayerRef ref = holder.getComponent(PlayerRef.getComponentType());
|
||||||
|
return (ref == null) ? "?" : ref.getUsername();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package net.kewwbec.hub.protection;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.ArchetypeChunk;
|
||||||
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.component.query.Query;
|
||||||
|
import com.hypixel.hytale.component.system.EntityEventSystem;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECS system that cancels BreakBlockEvent inside hub worlds.
|
||||||
|
*
|
||||||
|
* BreakBlockEvent extends CancellableEcsEvent and is dispatched per-entity
|
||||||
|
* (the breaker) via entityStore.invoke(ref, event). Hytale's BlockHarvestUtils
|
||||||
|
* checks event.isCancelled() and aborts the actual break if true.
|
||||||
|
*/
|
||||||
|
public final class CancelBreakBlockSystem extends EntityEventSystem<EntityStore, BreakBlockEvent> {
|
||||||
|
|
||||||
|
private final HubWorldFilter filter;
|
||||||
|
|
||||||
|
public CancelBreakBlockSystem(@Nonnull HubWorldFilter filter) {
|
||||||
|
super(BreakBlockEvent.class);
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public Query<EntityStore> getQuery() {
|
||||||
|
return Query.any();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(int chunkSlot,
|
||||||
|
@Nonnull ArchetypeChunk<EntityStore> chunk,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
||||||
|
@Nonnull BreakBlockEvent event) {
|
||||||
|
if (event.isCancelled()) return;
|
||||||
|
World world = store.getExternalData().getWorld();
|
||||||
|
if (world == null || !filter.isHub(world)) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package net.kewwbec.hub.protection;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.ArchetypeChunk;
|
||||||
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.component.query.Query;
|
||||||
|
import com.hypixel.hytale.component.system.EntityEventSystem;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public final class CancelDamageBlockSystem extends EntityEventSystem<EntityStore, DamageBlockEvent> {
|
||||||
|
|
||||||
|
private final HubWorldFilter filter;
|
||||||
|
|
||||||
|
public CancelDamageBlockSystem(@Nonnull HubWorldFilter filter) {
|
||||||
|
super(DamageBlockEvent.class);
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public Query<EntityStore> getQuery() {
|
||||||
|
return Query.any();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(int chunkSlot,
|
||||||
|
@Nonnull ArchetypeChunk<EntityStore> chunk,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
||||||
|
@Nonnull DamageBlockEvent event) {
|
||||||
|
if (event.isCancelled()) return;
|
||||||
|
World world = store.getExternalData().getWorld();
|
||||||
|
if (world == null || !filter.isHub(world)) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package net.kewwbec.hub.protection;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.ArchetypeChunk;
|
||||||
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.component.query.Query;
|
||||||
|
import com.hypixel.hytale.component.system.EntityEventSystem;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.PlaceBlockEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public final class CancelPlaceBlockSystem extends EntityEventSystem<EntityStore, PlaceBlockEvent> {
|
||||||
|
|
||||||
|
private final HubWorldFilter filter;
|
||||||
|
|
||||||
|
public CancelPlaceBlockSystem(@Nonnull HubWorldFilter filter) {
|
||||||
|
super(PlaceBlockEvent.class);
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public Query<EntityStore> getQuery() {
|
||||||
|
return Query.any();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(int chunkSlot,
|
||||||
|
@Nonnull ArchetypeChunk<EntityStore> chunk,
|
||||||
|
@Nonnull Store<EntityStore> store,
|
||||||
|
@Nonnull CommandBuffer<EntityStore> commandBuffer,
|
||||||
|
@Nonnull PlaceBlockEvent event) {
|
||||||
|
if (event.isCancelled()) return;
|
||||||
|
World world = store.getExternalData().getWorld();
|
||||||
|
if (world == null || !filter.isHub(world)) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.kewwbec.hub.protection;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import net.kewwbec.hub.config.HubConfig;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether a given world should have hub behavior applied.
|
||||||
|
*
|
||||||
|
* If {@link HubConfig.WorldsSection#hub_worlds} is empty, every world is a hub world.
|
||||||
|
* Otherwise only worlds whose name matches (case-insensitive) are hubs.
|
||||||
|
*/
|
||||||
|
public final class HubWorldFilter {
|
||||||
|
|
||||||
|
private final List<String> configured;
|
||||||
|
|
||||||
|
public HubWorldFilter(@Nonnull HubConfig config) {
|
||||||
|
this.configured = config.worlds.hub_worlds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHub(@Nonnull World world) {
|
||||||
|
if (configured == null || configured.isEmpty()) return true;
|
||||||
|
String name = world.getName();
|
||||||
|
for (String hw : configured) {
|
||||||
|
if (hw != null && hw.equalsIgnoreCase(name)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package net.kewwbec.hub.service;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.WorldConfig;
|
||||||
|
import net.kewwbec.hub.config.HubConfig;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies hub-protection flags to configured worlds at startup. Reads
|
||||||
|
* {@link HubConfig.ProtectionSection} and pushes settings into each world's WorldConfig.
|
||||||
|
*
|
||||||
|
* Only writes settings whose values differ; calls markChanged() once if anything changed.
|
||||||
|
*/
|
||||||
|
public final class HubWorldService {
|
||||||
|
|
||||||
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final HubConfig config;
|
||||||
|
|
||||||
|
public HubWorldService(@Nonnull HubConfig config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyToConfiguredWorlds() {
|
||||||
|
if (!config.protection.apply_world_config) {
|
||||||
|
LOGGER.at(Level.INFO).log("Hub: apply_world_config=false, skipping world-flag enforcement");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Universe universe = Universe.get();
|
||||||
|
if (universe == null) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Hub: Universe.get() is null at apply time");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<World> targets = resolveWorlds(universe);
|
||||||
|
if (targets.isEmpty()) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Hub: no hub worlds resolved (configured: %s)", config.worlds.hub_worlds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (World w : targets) {
|
||||||
|
applyToWorld(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private Collection<World> resolveWorlds(@Nonnull Universe universe) {
|
||||||
|
Set<World> out = new HashSet<>();
|
||||||
|
List<String> configured = config.worlds.hub_worlds;
|
||||||
|
if (configured == null || configured.isEmpty()) {
|
||||||
|
out.addAll(universe.getWorlds().values());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
for (String name : configured) {
|
||||||
|
World w = universe.getWorld(name);
|
||||||
|
if (w == null) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Hub: configured world '%s' not loaded", name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.add(w);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyToWorld(@Nonnull World world) {
|
||||||
|
WorldConfig cfg = world.getWorldConfig();
|
||||||
|
if (cfg == null) return;
|
||||||
|
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
if (config.protection.disable_pvp && cfg.isPvpEnabled()) {
|
||||||
|
cfg.setPvpEnabled(false);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (config.protection.disable_fall_damage && cfg.isFallDamageEnabled()) {
|
||||||
|
cfg.setFallDamageEnabled(false);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
cfg.markChanged();
|
||||||
|
LOGGER.at(Level.INFO).log("Hub: applied protection flags to world '%s'", world.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"Group": "kewwbec",
|
||||||
|
"Name": "Hub",
|
||||||
|
"Version": "0.1.0",
|
||||||
|
"Description": "Lobby/hub behavior: protection, welcome message, spawn-on-join",
|
||||||
|
"IncludesAssetPack": false,
|
||||||
|
"Authors": [
|
||||||
|
{"Name": "kewwbec"}
|
||||||
|
],
|
||||||
|
"ServerVersion": "*",
|
||||||
|
"Dependencies": {
|
||||||
|
"kewwbec:NetworkCore": "*"
|
||||||
|
},
|
||||||
|
"OptionalDependencies": {},
|
||||||
|
"DisabledByDefault": false,
|
||||||
|
"Main": "net.kewwbec.hub.HubPlugin"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user