Skip to content

Commit d111d6b

Browse files
authored
refactor: remove shared/phase module and migrate to Xerus (#171)
Closes #166 - Delete shared/phase module (net.elytrarace.api.phase.*) entirely - server: platform layer (MinestomPhaseScheduler, MinestomPhaseTask, MinestomEventRegistrar, MinestomEventListener) removed — Xerus phases handle scheduling internally without a separate PhaseScheduler - server: remove project(":shared:phase") dependency - plugins/game: delete old-style phase files (EndPhase, GamePhase, LobbyPhase, PreparationPhase, PhaseComponent, PhaseSystem, BukkitPhaseScheduler, BukkitEventRegistrar); strip phase references from ElytraRace, GameService, GameServiceImpl, DefaultListener - plugins/setup: delete BukkitPhaseScheduler and BukkitEventRegistrar (setup does not need a phase system); remove xerus dependency - settings.gradle.kts: remove include("shared:phase") - ArchUnit: drop shared_phase_must_not_use_minestom rule - CLAUDE.md: update module docs and Phase System section to reference Xerus
1 parent 8f1132f commit d111d6b

37 files changed

Lines changed: 5 additions & 1674 deletions

CLAUDE.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,10 @@ Tests use JUnit 5 with MockBukkit (plugins) or Minestom Testing (server) for API
3838

3939
## Module Structure
4040

41-
- **`server`** — Standalone Minestom game server. Handles gameplay, physics, scoring, cup flow, and UI. Entry point: `net.elytrarace.server.VoyagerServer` (own `main()`). Depends on `shared/common`, `shared/phase`, `shared/database`.
41+
- **`server`** — Standalone Minestom game server. Handles gameplay, physics, scoring, cup flow, and UI. Entry point: `net.elytrarace.server.VoyagerServer` (own `main()`). Depends on `shared/common`, `shared/database`.
4242
- **`plugins/game`** — Legacy Paper game plugin (`ElytraRace-Game`). Being replaced by `server`. Entry point: `net.elytrarace.game.ElytraRace`
4343
- **`plugins/setup`** — Setup plugin (`ElytraRace-Setup`) for map/cup/portal configuration via in-game conversations. Depends on FastAsyncWorldEdit. Entry point: `net.elytrarace.setup.ElytraRace`
4444
- **`shared/common`** — Shared utilities: ECS framework, map/cup services, file handling (Gson-based JSON), language/i18n, spline math, builders (Bukkit-frei)
45-
- **`shared/phase`** — Phase lifecycle framework (Phase -> TimedPhase/TickedPhase, with LinearPhaseSeries/CyclicPhaseSeries collections) (Bukkit-frei)
4645
- **`shared/conversation-api`** — Player conversation/prompt system, plattform-agnostisch (Bukkit-frei)
4746
- **`shared/database`** — Hibernate ORM + HikariCP + MariaDB persistence layer for player data
4847

@@ -58,7 +57,7 @@ The game uses a custom ECS pattern in `shared/common` (`net.elytrarace.common.ec
5857
Game-specific components are in `plugins/game/src/.../components/` (GameState, Phase, Cup, Map, World, Spline, Session). Systems are in `.../system/` (CollisionSystem, PhaseSystem, CupSystem, etc.).
5958

6059
### Phase System
61-
Game phases (Lobby → Preparation → Game → End) are managed via `shared/phase`. Phases have start/finish lifecycle with callbacks. `LinearPhaseSeries` chains phases sequentially.
60+
Game phases (Lobby → Preparation → Game → End) are managed via [Xerus](https://github.com/OneLiteFeatherNET/Xerus) (`net.theevilreaper.xerus.api.phase.*`). Phases have start/finish lifecycle with callbacks. `LinearPhaseSeries` chains phases sequentially.
6261

6362
### Elytra velocity authority
6463
Normal elytra flight is client-authoritative, matching vanilla Minecraft. `ElytraPhysicsSystem` runs the vanilla formula every tick to keep a server-tracked velocity for ring collision and boost math, but it does NOT call `player.setVelocity()`. Velocity is only sent to the client for external forces: firework boost burns (`FireworkBoostSystem`), ring `BOOST`/`SLOW` effects (`RingEffectSystem`), and out-of-bounds resets (`OutOfBoundsSystem`). See [ADR-0002](docs/decisions/0002-elytra-flight-client-authority.md) and [docs/elytra-physics-reference.md](docs/elytra-physics-reference.md) §7.

plugins/game/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ plugins {
1111
dependencies {
1212
compileOnly(libs.minecraft.paper)
1313
implementation(libs.minecraft.cloud.paper)
14-
implementation(project(":shared:phase"))
1514
implementation(project(":shared:common"))
1615
implementation(project(":shared:database"))
1716
// Math

plugins/game/src/main/java/net/elytrarace/game/ElytraRace.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,10 @@
44
import net.elytrarace.common.ecs.EntityManager;
55
import net.elytrarace.common.language.LanguageService;
66
import net.elytrarace.game.components.GameStateComponent;
7-
import net.elytrarace.game.components.PhaseComponent;
8-
import net.elytrarace.game.platform.BukkitEventRegistrar;
9-
import net.elytrarace.game.platform.BukkitPhaseScheduler;
107
import net.elytrarace.game.service.GameService;
118
import net.elytrarace.game.system.CollisionSystem;
129
import net.elytrarace.game.system.CupSystem;
1310
import net.elytrarace.game.system.GameStateSystem;
14-
import net.elytrarace.game.system.PhaseSystem;
1511
import net.elytrarace.game.system.PlayerUpdateSystem;
1612
import net.elytrarace.game.system.SplineSystem;
1713
import net.elytrarace.game.util.ElytraMarkers;
@@ -30,7 +26,6 @@ public class ElytraRace extends JavaPlugin {
3026

3127
private EntityManager entityManager;
3228
private PlayerUpdateSystem playerUpdateSystem;
33-
private PhaseSystem phaseSystem;
3429
private CupSystem cupSystem;
3530
private SplineSystem splineSystem;
3631

@@ -51,9 +46,6 @@ public void onEnable() {
5146
Entity gameStateEntity = new Entity();
5247
gameStateEntity.addComponent(GameStateComponent.create());
5348

54-
// Add phase component to game state entity
55-
gameStateEntity.addComponent(PhaseComponent.create(new BukkitPhaseScheduler(this), new BukkitEventRegistrar(this)));
56-
5749
// Add the entity to the entity manager
5850
entityManager.addEntity(gameStateEntity);
5951

@@ -64,9 +56,6 @@ public void onEnable() {
6456
playerUpdateSystem = new PlayerUpdateSystem();
6557
entityManager.addSystem(playerUpdateSystem);
6658

67-
phaseSystem = new PhaseSystem();
68-
entityManager.addSystem(phaseSystem);
69-
7059
cupSystem = new CupSystem();
7160
entityManager.addSystem(cupSystem);
7261

plugins/game/src/main/java/net/elytrarace/game/components/PhaseComponent.java

Lines changed: 0 additions & 55 deletions
This file was deleted.

plugins/game/src/main/java/net/elytrarace/game/listener/DefaultListener.java

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,9 @@
22

33
import net.elytrarace.api.database.entity.ElytraPlayerEntity;
44
import net.elytrarace.api.database.service.DatabaseService;
5-
import net.elytrarace.api.phase.LinearPhaseSeries;
6-
import net.elytrarace.api.phase.Phase;
75
import net.elytrarace.common.cup.model.ResolvedCupDTO;
8-
import net.elytrarace.game.phase.EndPhase;
9-
import net.elytrarace.game.phase.GamePhase;
10-
import net.elytrarace.game.phase.LobbyPhase;
11-
import net.elytrarace.game.phase.PreparationPhase;
126
import net.elytrarace.game.service.GameService;
13-
import net.elytrarace.game.util.ElytraMetadata;
147
import net.kyori.adventure.text.Component;
15-
import org.apache.commons.geometry.euclidean.threed.Vector3D;
168
import org.bukkit.block.Block;
179
import org.bukkit.block.Chest;
1810
import org.bukkit.block.Container;
@@ -98,49 +90,15 @@ public void onPlayerMove(PlayerMoveEvent event) {
9890
}
9991
if (event.getTo().getY() > event.getTo().getWorld().getMaxHeight()) {
10092
event.getPlayer().teleportAsync(event.getPlayer().getWorld().getSpawnLocation());
101-
return;
102-
}
103-
var player = event.getPlayer();
104-
var elytraPhase = this.gameService.getElytraPhase();
105-
if (elytraPhase == null) return;
106-
var currentPhase = elytraPhase.getCurrentPhase();
107-
if (currentPhase == null) return;
108-
if (currentPhase instanceof GamePhase) {
109-
if (player.hasMetadata(ElytraMetadata.LAST_POSITIONS)) {
110-
var metdata = player.getMetadata(ElytraMetadata.LAST_POSITIONS);
111-
var lastPositions = (Vector3D[]) metdata.getFirst().value();
112-
if (lastPositions == null) return;
113-
System.arraycopy(lastPositions, 0, lastPositions, 1, lastPositions.length - 1);
114-
lastPositions[0] = Vector3D.of(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ());
115-
}
11693
}
11794
}
11895

11996
@EventHandler
12097
public void onPlayerJoin(PlayerJoinEvent event) {
121-
LinearPhaseSeries<Phase> elytraPhase = this.gameService.getElytraPhase();
122-
if (elytraPhase == null) return;
123-
var currentPhase = elytraPhase.getCurrentPhase();
124-
if (currentPhase == null) return;
125-
if (currentPhase.isRunning() && currentPhase instanceof LobbyPhase lobbyPhase) {
126-
event.getPlayer().teleportAsync(lobbyPhase.getLobbyLocation());
127-
event.joinMessage(Component.translatable("phase.lobby.player.join", Component.translatable("plugin.prefix"), event.getPlayer().displayName(), Component.text(1)));
128-
}
12998
}
13099

131100
@EventHandler
132101
public void onPlayerLogin(PlayerLoginEvent event) {
133-
LinearPhaseSeries<Phase> elytraPhase = this.gameService.getElytraPhase();
134-
if (elytraPhase == null) return;
135-
var currentPhase = elytraPhase.getCurrentPhase();
136-
if (currentPhase == null) return;
137-
if (currentPhase.isRunning() && (currentPhase instanceof GamePhase || currentPhase instanceof EndPhase || currentPhase instanceof PreparationPhase)){
138-
event.disallow(PlayerLoginEvent.Result.KICK_OTHER, Component.empty());
139-
return;
140-
}
141-
if (currentPhase.isRunning() && currentPhase instanceof LobbyPhase lobbyPhase) {
142-
event.getPlayer().teleportAsync(lobbyPhase.getLobbyLocation());
143-
}
144102
DatabaseService databaseService = this.gameService.getDatabaseService();
145103
if (databaseService == null) return;
146104
databaseService.getElytraPlayerRepository()
@@ -156,19 +114,9 @@ public void onPlayerLogin(PlayerLoginEvent event) {
156114

157115
@EventHandler
158116
public void onServerListPing(ServerListPingEvent event) {
159-
var elytraPhase = this.gameService.getElytraPhase();
160-
if (elytraPhase == null) return;
161-
var currentPhase = elytraPhase.getCurrentPhase();
162-
if (currentPhase == null) return;
163117
var cup = this.gameService.getCurrentCup().filter(ResolvedCupDTO.class::isInstance).orElse(null);
164118
if (cup == null) return;
165-
if (currentPhase instanceof LobbyPhase) {
166-
event.motd(Component.translatable("game.motd.cup", Component.translatable("plugin.prefix"), cup.displayName()));
167-
return;
168-
}
169-
if (currentPhase instanceof GamePhase) {
170-
event.motd(Component.translatable("game.motd.ingame"));
171-
}
119+
event.motd(Component.translatable("game.motd.cup", Component.translatable("plugin.prefix"), cup.displayName()));
172120
}
173121

174122
@EventHandler

plugins/game/src/main/java/net/elytrarace/game/phase/EndPhase.java

Lines changed: 0 additions & 52 deletions
This file was deleted.

plugins/game/src/main/java/net/elytrarace/game/phase/GamePhase.java

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)