diff --git a/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java b/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java index eef05d8..7baf6ec 100644 --- a/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java +++ b/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java @@ -2,6 +2,8 @@ package com.mythlane.gravityflip; import com.hypixel.hytale.server.core.plugin.JavaPlugin; import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.util.Config; +import com.mythlane.gravityflip.config.GravityFlipConfig; import java.util.logging.Level; @@ -9,25 +11,53 @@ import java.util.logging.Level; * Entry point for the Gravity Flip plugin. * *
Extends {@link JavaPlugin} from the resolved Hytale Server API
- * ({@code com.hypixel.hytale:Server:2026.03.26-89796e57b}). The lifecycle
- * hooks in this API version are {@code setup()} and {@code shutdown()}
- * (NOT {@code onEnable()} / {@code onDisable()}, which belong to older
- * docs). See {@code .planning/phases/01-scaffold-load/JAVAPLUGIN_RESOLUTION.md}
- * for the empirical resolution.
+ * ({@code com.hypixel.hytale:Server:2026.03.26-89796e57b}). Lifecycle hooks
+ * for this version are {@code setup()} / {@code shutdown()} (not the legacy
+ * {@code onEnable()} / {@code onDisable()}). See
+ * {@code .planning/phases/01-scaffold-load/JAVAPLUGIN_RESOLUTION.md} for the
+ * empirical resolution.
*/
public class GravityFlipPlugin extends JavaPlugin {
+ /**
+ * Persisted region store, materialised on disk as
+ * {@code The named {@code withConfig(name, codec)} overload is REQUIRED — the
+ * 1-arg overload hardcodes the filename to {@code config.json}.
+ */
+ private final Config Persisted as {@code The decoded list is wrapped in a mutable {@code ArrayList}
+ * (NOT {@code List.of(arr)}, which is immutable) so Phase 4 command handlers
+ * can {@code add}/{@code remove} regions at runtime. Callers that mutate the
+ * list MUST call {@code Config.save()} afterwards — there is no auto-save
+ * lifecycle hook.
+ */
+public final class GravityFlipConfig {
+
+ public static final BuilderCodec
+ *
+ */
+class GravityFlipConfigCodecTest {
+
+ @Test
+ void newConfigHasNonNullEmptyRegions() {
+ GravityFlipConfig cfg = new GravityFlipConfig();
+ assertNotNull(cfg.getRegions());
+ assertEquals(0, cfg.getRegions().size());
+ }
+
+ @Test
+ void roundTripPreservesTwoRegionsInOrder() {
+ GravityFlipConfig src = new GravityFlipConfig();
+ src.getRegions().add(region("alpha", 0, 0, 0, 1, 1, 1));
+ src.getRegions().add(region("beta", 5, 5, 5, 9, 9, 9));
+
+ GravityFlipConfig decoded = roundTrip(src);
+
+ assertEquals(2, decoded.getRegions().size());
+ assertEquals("alpha", decoded.getRegions().get(0).getName());
+ assertEquals("beta", decoded.getRegions().get(1).getName());
+ }
+
+ @Test
+ void roundTripOfEmptyListYieldsNonNullEmptyList() {
+ GravityFlipConfig src = new GravityFlipConfig();
+ // src.regions is the default empty ArrayList.
+ GravityFlipConfig decoded = roundTrip(src);
+
+ assertNotNull(decoded.getRegions(), "decoded regions list must never be null");
+ assertEquals(0, decoded.getRegions().size());
+ }
+
+ @Test
+ void decodedListIsMutable() {
+ GravityFlipConfig src = new GravityFlipConfig();
+ src.getRegions().add(region("seed", 0, 0, 0, 1, 1, 1));
+
+ GravityFlipConfig decoded = roundTrip(src);
+
+ // CRITICAL: must not throw UnsupportedOperationException.
+ // Phase 4 commands (define / delete / toggle) all mutate this list.
+ assertDoesNotThrow(() -> decoded.getRegions().add(region("added", 2, 2, 2, 3, 3, 3)));
+ assertDoesNotThrow(() -> decoded.getRegions().remove(0));
+ assertTrue(decoded.getRegions() instanceof ArrayList,
+ "decoded regions list should be a mutable ArrayList, not List.of(...)");
+ }
+
+ private static GravityFlipRegion region(String name,
+ double minX, double minY, double minZ,
+ double maxX, double maxY, double maxZ) {
+ return new GravityFlipRegion(
+ name,
+ new Box(new Vector3d(minX, minY, minZ), new Vector3d(maxX, maxY, maxZ)),
+ true);
+ }
+
+ private static GravityFlipConfig roundTrip(GravityFlipConfig src) {
+ ExtraInfo info = new ExtraInfo();
+ BsonValue encoded = GravityFlipConfig.CODEC.encode(src, info);
+ return GravityFlipConfig.CODEC.decode(encoded, info);
+ }
+
+ // Suppress unused-import warning if List is not directly referenced in any final assertion.
+ @SuppressWarnings("unused")
+ private static List