feat(02-01): add GravityFlipRegion POJO + BuilderCodec with round-trip tests

- GravityFlipRegion holds Name (String) + Box (Hytale AABB) + Enabled (boolean),
  serialised through a BuilderCodec keyed on "Name"/"Box"/"Enabled".
- Validators.nonNull() applied to Name and Box (correct package is
  com.hypixel.hytale.codec.validation.Validators, not codec.builder).
- Server jar 2026.03.26 uses com.hypixel.hytale.math.vector.Vector3d for Box.min/max
  (NOT org.joml.Vector3d, which only appeared in newer builds).
- 3 JUnit 5 round-trip tests cover name/box/enabled, enabled=false, and empty name.
- testImplementation added for the Server artifact so codec encode/decode is
  reachable from unit tests.
This commit is contained in:
2026-04-23 00:41:36 +02:00
parent 59b33615df
commit b046f44ab5
3 changed files with 142 additions and 0 deletions
+2
View File
@@ -24,6 +24,8 @@ dependencies {
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains:annotations:24.1.0") implementation("org.jetbrains:annotations:24.1.0")
// Tests need the Hytale Server API (codecs, ExtraInfo, BsonValue) on the classpath.
testImplementation("com.hypixel.hytale:Server:$hytaleServerVersion")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
} }
@@ -0,0 +1,68 @@
package com.mythlane.gravityflip.region;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.vector.Vector3d;
/**
* A named axis-aligned region in which gravity is inverted for any entity inside.
*
* <p>Persisted as part of {@code GravityFlipConfig} via {@link #CODEC}. The region
* is stored on disk as three keys:
* <ul>
* <li>{@code Name} — non-null string identifier</li>
* <li>{@code Box} — non-null AABB (composed of two {@code Vector3d} corners)</li>
* <li>{@code Enabled} — boolean toggle, default {@code true}</li>
* </ul>
*
* <p>Note: this build pins {@code com.hypixel.hytale:Server:2026.03.26-89796e57b}, in which
* {@code Box.min}/{@code Box.max} are {@code com.hypixel.hytale.math.vector.Vector3d}
* (Hytale's own type — NOT {@code org.joml.Vector3d}, which only appeared in later builds).
*
* <p>Fields are package-private mutable so the codec can write into them directly,
* mirroring the canonical {@code MythLoggerConfig} pattern. Public getters/setters
* are provided for runtime callers (Phase 4 commands).
*/
public final class GravityFlipRegion {
public static final BuilderCodec<GravityFlipRegion> CODEC =
BuilderCodec.builder(GravityFlipRegion.class, GravityFlipRegion::new)
.append(new KeyedCodec<>("Name", Codec.STRING),
(r, v) -> r.name = v, r -> r.name)
.addValidator(Validators.nonNull()).add()
.append(new KeyedCodec<>("Box", Box.CODEC),
(r, v) -> r.box = v, r -> r.box)
.addValidator(Validators.nonNull()).add()
.append(new KeyedCodec<>("Enabled", Codec.BOOLEAN),
(r, v) -> r.enabled = v, r -> r.enabled).add()
.build();
// Package-private mutable fields written directly by the codec setters.
String name = "";
Box box = new Box(new Vector3d(), new Vector3d());
boolean enabled = true;
public GravityFlipRegion() {}
public GravityFlipRegion(String name, Box box, boolean enabled) {
this.name = name;
this.box = box;
this.enabled = enabled;
}
public String getName() { return name; }
public Box getBox() { return box; }
public Vector3d getMin() { return box.min; }
public Vector3d getMax() { return box.max; }
public boolean isEnabled() { return enabled; }
public void setName(String n) { this.name = n; }
public void setBox(Box b) { this.box = b; }
public void setEnabled(boolean v) { this.enabled = v; }
/** Convenience accessor for tick-loop / physics consumers in Phase 02-02. */
public Box asBox() { return box; }
}
@@ -0,0 +1,72 @@
package com.mythlane.gravityflip.region;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.math.shape.Box;
import org.bson.BsonValue;
import com.hypixel.hytale.math.vector.Vector3d;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Round-trip tests for {@link GravityFlipRegion#CODEC}. Verifies the codec preserves
* Name + Box + Enabled fields across encode -> decode cycles via the BSON intermediate
* representation that all Hytale codecs share.
*/
class GravityFlipRegionCodecTest {
@Test
void roundTripPreservesNameBoxEnabled() {
GravityFlipRegion src = new GravityFlipRegion(
"zone1",
new Box(new Vector3d(1, 2, 3), new Vector3d(4, 5, 6)),
true);
GravityFlipRegion decoded = roundTrip(src);
assertEquals("zone1", decoded.getName());
assertNotNull(decoded.getBox());
assertEquals(1.0, decoded.getMin().x);
assertEquals(2.0, decoded.getMin().y);
assertEquals(3.0, decoded.getMin().z);
assertEquals(4.0, decoded.getMax().x);
assertEquals(5.0, decoded.getMax().y);
assertEquals(6.0, decoded.getMax().z);
assertTrue(decoded.isEnabled());
}
@Test
void roundTripPreservesEnabledFalse() {
GravityFlipRegion src = new GravityFlipRegion(
"off-zone",
new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1)),
false);
GravityFlipRegion decoded = roundTrip(src);
assertEquals("off-zone", decoded.getName());
assertFalse(decoded.isEnabled(), "default-true field must not clobber loaded false");
}
@Test
void roundTripPreservesEmptyName() {
GravityFlipRegion src = new GravityFlipRegion(
"",
new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1)),
true);
GravityFlipRegion decoded = roundTrip(src);
assertNotNull(decoded.getName());
assertEquals("", decoded.getName(), "empty name must survive round-trip without substitution");
}
private static GravityFlipRegion roundTrip(GravityFlipRegion src) {
ExtraInfo info = new ExtraInfo();
BsonValue encoded = GravityFlipRegion.CODEC.encode(src, info);
return GravityFlipRegion.CODEC.decode(encoded, info);
}
}