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:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user