mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-30 07:50:03 +00:00
Jigsaw junctions and studio tools
- Rotatable jigsaw candidates try their rotations in random order - Candidate connectors are tried by priority within each rotation - Planar candidates must agree with every occupied neighbor socket - Planar assembly falls back to source-only matching before it fails - Connectors whose branch placed nothing stay open - Spatial default kit covers all 24 Y-rotation topologies - Planar start pool includes Blank at weight 1 - Vanilla spatial projects omit Iris themes and placement caps - Vanilla spatial projects use max depth 4 - 1.21.1 export derives the vertical distance from the structure - Workcell floors map the active variant's connectors - Floor froglights mark up, down and up-down connectors - Connector Tool places, moves, re-orients and removes spatial connectors - Connector Tool re-seats planar face-center sockets - Main menu pages workcells five at a time - Spatial variants can be deleted while another variant remains - Tool sticks act on the variant they were made for - Destructive tool confirmation needs two separate clicks - Pistons cannot extend into protected cells - Autosave capture failures back off and notify the owner - Autosave plays the save bell - Hidden-connector air final states are stored as air - Reset Connector Blocks clears unsaved markers - Folia logout releases reset and resize reservations - Spatial workcell rename relabels its variant - Workcell teleports land inside the focused workcell - Replacement layouts keep rendering and hydration working - Container preview probes stay out of chat - Non-owners can run commands that are not recognized as mutating - Spatial resize or rotation past the workcell volume limit is refused - Tests cover default kit assembly, connector placement, floor glyphs and menu paging
This commit is contained in:
1 parent
b3579e623a
commit
7d73c4d150
56 files changed
+4869
-537
No files matched your search
@@ -83,7 +83,7 @@ public class SchemaBuilderIdentityTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "6935e8d3914e5cde852522151d87fe898f79de3b394826e544c5c5bfbfceba57";
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "b8dd16d1a3188e8150a68a9d5b2964aed5094d679c0f941e3703aec675f87e20";
|
||||
|
||||
private static final List<Class<?>> SCHEMA_ROOTS = List.of(
|
||||
IrisDimension.class,
|
||||
|
||||
+27
@@ -167,6 +167,33 @@ public class VanillaJigsawDatapackExporterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exports1211WithRuntimeDefaultSettingsDerivedFromTheStructure() throws Exception {
|
||||
when(IrisPlatforms.get().minecraftVersion()).thenReturn("1.21.1");
|
||||
try {
|
||||
TestGraph graph = connectorGraph();
|
||||
VanillaJigsawExportResult staticDefaults = new VanillaJigsawDatapackExporter().export(
|
||||
request(graph, temporaryFolder.getRoot().toPath().resolve("static-defaults-1211"))
|
||||
.settings(VanillaJigsawExportSettings.defaults()).build());
|
||||
assertTrue(hasCode(staticDefaults, VanillaJigsawExportDiagnostic.Code.INVALID_MAX_DISTANCE));
|
||||
|
||||
Path output = temporaryFolder.getRoot().toPath().resolve("runtime-defaults-1211");
|
||||
VanillaJigsawExportSettings settings = VanillaJigsawExportSettings.runtimeDefaults(graph.structure);
|
||||
VanillaJigsawExportResult result = new VanillaJigsawDatapackExporter().export(
|
||||
request(graph, output).namespace("studio").resourcePath("village/test").settings(settings).build());
|
||||
|
||||
assertEquals(graph.structure.getMaxSizeChunks() * 16, settings.maxDistanceVertical());
|
||||
assertTrue(result.diagnostics().toString(), result.isSuccess());
|
||||
JsonObject structure = json(output.resolve("data/studio/worldgen/structure/village/test.json"));
|
||||
assertEquals(graph.structure.getMaxSizeChunks() * 16,
|
||||
structure.get("max_distance_from_center").getAsInt());
|
||||
} finally {
|
||||
when(IrisPlatforms.get().minecraftVersion()).thenReturn("26.2");
|
||||
}
|
||||
assertEquals(VanillaJigsawExportSettings.defaults().maxDistanceVertical(),
|
||||
VanillaJigsawExportSettings.runtimeDefaults(connectorGraph().structure).maxDistanceVertical());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejects1211VanillaExportWithDifferentVerticalDistance() throws Exception {
|
||||
when(IrisPlatforms.get().minecraftVersion()).thenReturn("1.21.1");
|
||||
|
||||
@@ -38,7 +38,10 @@ import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -310,6 +313,82 @@ public class StructureAssemblerTest {
|
||||
assertEquals(west.getMaxX() + 1, start.getMinX());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rotatableCandidatesAttachThroughEitherCornerConnectorAcrossSeeds() {
|
||||
Fixture fixture = new Fixture();
|
||||
IrisJigsawPiece startPiece = piece("start-object", false,
|
||||
connector(IrisDirection.NORTH_NEGATIVE_Z, "turns", "door", "door")
|
||||
.setPosition(new IrisPosition(1, 0, 0))
|
||||
.setJoint(JigsawJoint.ALIGNED));
|
||||
IrisJigsawPiece cornerPiece = piece("corner-object", true,
|
||||
connector(IrisDirection.NORTH_NEGATIVE_Z, "", "door", "door")
|
||||
.setPosition(new IrisPosition(1, 0, 0))
|
||||
.setJoint(JigsawJoint.ALIGNED),
|
||||
connector(IrisDirection.EAST_POSITIVE_X, "", "door", "door")
|
||||
.setPosition(new IrisPosition(2, 0, 1))
|
||||
.setJoint(JigsawJoint.ALIGNED));
|
||||
fixture.pool("start", pool("", "start-piece"));
|
||||
fixture.pool("turns", pool("", "corner-piece"));
|
||||
fixture.piece("start-piece", startPiece);
|
||||
fixture.piece("corner-piece", cornerPiece);
|
||||
fixture.object("start-object", new IrisObject(3, 1, 3));
|
||||
fixture.object("corner-object", new IrisObject(3, 1, 3));
|
||||
StructureAssembler assembler = fixture.assembler(structure("start", 1), 0, 0, 0);
|
||||
|
||||
Set<Integer> attachedConnectors = new HashSet<>();
|
||||
for (long seed = 0; seed < 64; seed++) {
|
||||
PlacedStructurePiece corner = requirePlacedPiece(completedPieces(assembler, new RNG(seed)), cornerPiece);
|
||||
for (int index = 0; index < cornerPiece.getConnectors().size(); index++) {
|
||||
if (corner.getRotation().rotate(cornerPiece.getConnectors().get(index).getDirection())
|
||||
== IrisDirection.SOUTH_POSITIVE_Z) {
|
||||
attachedConnectors.add(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(Set.of(0, 1), attachedConnectors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planarJunctionPlacesAPieceOpenTowardEveryOccupiedNeighbor() {
|
||||
Fixture fixture = planarJunctionFixture("straight-piece", "corner-piece");
|
||||
IrisJigsawPiece corner = planarPiece(true,
|
||||
IrisDirection.NORTH_NEGATIVE_Z, IrisDirection.EAST_POSITIVE_X);
|
||||
fixture.piece("straight-piece", planarPiece(true,
|
||||
IrisDirection.NORTH_NEGATIVE_Z, IrisDirection.SOUTH_POSITIVE_Z));
|
||||
fixture.piece("corner-piece", corner);
|
||||
StructureAssembler assembler = fixture.assembler(planarJunctionStructure(), 0, 64, 0);
|
||||
|
||||
for (long seed = 0; seed < 32; seed++) {
|
||||
List<PlacedStructurePiece> placed = completedPieces(assembler, new RNG(seed));
|
||||
PlacedStructurePiece junction = placed.get(3);
|
||||
|
||||
assertEquals(4, placed.size());
|
||||
assertSame("seed " + seed, corner, junction.getPiece());
|
||||
assertEquals("seed " + seed,
|
||||
Set.of(IrisDirection.WEST_NEGATIVE_X, IrisDirection.SOUTH_POSITIVE_Z),
|
||||
worldFacings(junction));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planarJunctionWithoutAFittingPieceKeepsTheAttachmentOnlyPlacementInsteadOfFailing() {
|
||||
Fixture fixture = planarJunctionFixture("straight-piece");
|
||||
IrisJigsawPiece straight = planarPiece(true,
|
||||
IrisDirection.NORTH_NEGATIVE_Z, IrisDirection.SOUTH_POSITIVE_Z);
|
||||
fixture.piece("straight-piece", straight);
|
||||
StructureAssembler assembler = fixture.assembler(planarJunctionStructure(), 0, 64, 0);
|
||||
|
||||
for (long seed = 0; seed < 8; seed++) {
|
||||
List<PlacedStructurePiece> placed = completedPieces(assembler, new RNG(seed));
|
||||
|
||||
assertEquals(4, placed.size());
|
||||
assertSame(straight, placed.get(3).getPiece());
|
||||
assertEquals(Set.of(IrisDirection.WEST_NEGATIVE_X, IrisDirection.EAST_POSITIVE_X),
|
||||
worldFacings(placed.get(3)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectionPriorityProcessesHigherConnectorsFirstAndPreservesStableTies() {
|
||||
Fixture fixture = new Fixture();
|
||||
@@ -1115,6 +1194,53 @@ public class StructureAssemblerTest {
|
||||
return fixture.assembler(structure("start", 1), 0, 0, 0).assemble(new RNG(137L));
|
||||
}
|
||||
|
||||
private static Fixture planarJunctionFixture(String... junctionPieces) {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.pool("start", pool("", "start-piece"));
|
||||
fixture.pool("north", pool("", "north-elbow"));
|
||||
fixture.pool("east", pool("", "east-elbow"));
|
||||
fixture.pool("junction", pool("", junctionPieces));
|
||||
fixture.piece("start-piece", piece("cell-object", false,
|
||||
planarConnector(IrisDirection.NORTH_NEGATIVE_Z, "north"),
|
||||
planarConnector(IrisDirection.EAST_POSITIVE_X, "east")));
|
||||
fixture.piece("north-elbow", piece("cell-object", false,
|
||||
planarConnector(IrisDirection.SOUTH_POSITIVE_Z, ""),
|
||||
planarConnector(IrisDirection.EAST_POSITIVE_X, "junction")));
|
||||
fixture.piece("east-elbow", piece("cell-object", false,
|
||||
planarConnector(IrisDirection.WEST_NEGATIVE_X, ""),
|
||||
planarConnector(IrisDirection.NORTH_NEGATIVE_Z, "")));
|
||||
fixture.object("cell-object", new IrisObject(3, 1, 3));
|
||||
return fixture;
|
||||
}
|
||||
|
||||
private static IrisJigsawPiece planarPiece(boolean rotatable, IrisDirection... directions) {
|
||||
IrisJigsawPiece piece = new IrisJigsawPiece().setObject("cell-object").setRotatable(rotatable);
|
||||
for (IrisDirection direction : directions) {
|
||||
piece.getConnectors().add(planarConnector(direction, ""));
|
||||
}
|
||||
return piece;
|
||||
}
|
||||
|
||||
private static IrisJigsawConnector planarConnector(IrisDirection direction, String pool) {
|
||||
return connector(direction, pool, "door", "door")
|
||||
.setPosition(IrisJigsawConnector.canonicalPlanarPosition(new IrisPosition(3, 1, 3), direction))
|
||||
.setJoint(JigsawJoint.ALIGNED);
|
||||
}
|
||||
|
||||
private static IrisStructure planarJunctionStructure() {
|
||||
return structure("start", 3)
|
||||
.setMode(IrisJigsawMode.PLANAR_JIGSAW)
|
||||
.setCellSize(new IrisPosition(3, 1, 3));
|
||||
}
|
||||
|
||||
private static Set<IrisDirection> worldFacings(PlacedStructurePiece piece) {
|
||||
Set<IrisDirection> facings = EnumSet.noneOf(IrisDirection.class);
|
||||
for (IrisJigsawConnector connector : piece.getPiece().getConnectors()) {
|
||||
facings.add(piece.getRotation().rotate(connector.getDirection()));
|
||||
}
|
||||
return facings;
|
||||
}
|
||||
|
||||
private static IrisStructure structure(String startPool, int maxDepth) {
|
||||
return new IrisStructure().setStartPool(startPool).setMaxDepth(maxDepth).setMaxSizeChunks(8);
|
||||
}
|
||||
|
||||
+155
-131
@@ -2,8 +2,6 @@ package art.arcane.iris.studio.generation;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawPlanarArchetype;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawPlanarDirection;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawPlanarTopology;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioActivation;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioBay;
|
||||
@@ -52,6 +50,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
@@ -168,57 +167,123 @@ public class JigsawStudioGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paintsCanonicalWoolGlyphsBelowCaptureAndOneControlChest() {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void paintsActiveVariantConnectorLinesBelowCaptureAndOneControlChest() {
|
||||
IrisData source = mock(IrisData.class);
|
||||
ResourceLoader<IrisJigsawPiece> pieceLoader = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisObject> objectLoader = mock(ResourceLoader.class);
|
||||
when(source.getJigsawPieceLoader()).thenReturn(pieceLoader);
|
||||
when(source.getObjectLoader()).thenReturn(objectLoader);
|
||||
JigsawStudioCellDimensions dimensions = new JigsawStudioCellDimensions(7, 5, 7);
|
||||
IrisJigsawPiece piece = new IrisJigsawPiece()
|
||||
.setObject("village/corner")
|
||||
.setConnectors(new KList<>());
|
||||
piece.getConnectors().add(connector(new IrisPosition(3, 2, 0), IrisDirection.NORTH_NEGATIVE_Z));
|
||||
piece.getConnectors().add(connector(new IrisPosition(6, 2, 3), IrisDirection.EAST_POSITIVE_X));
|
||||
when(pieceLoader.load("village/corner", false)).thenReturn(piece);
|
||||
when(objectLoader.load("village/corner", false)).thenReturn(new IrisObject(7, 5, 7));
|
||||
JigsawStudioVariant variant = planarVariant("village/corner", JigsawPlanarTopology.NORTH_EAST_CORNER);
|
||||
GeneratorFixture fixture = fixture(
|
||||
source,
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
dimensions,
|
||||
JigsawStudioVariantCatalog.empty());
|
||||
new JigsawStudioVariantCatalog(List.of(variant)));
|
||||
JigsawStudioBay corner = fixture.layout().get("workcell/corner");
|
||||
int glyphY = corner.bounds().originY() - 1;
|
||||
int floorY = corner.bounds().originY() - 1;
|
||||
int centerX = dimensions.width() / 2;
|
||||
int centerZ = dimensions.depth() / 2;
|
||||
|
||||
assertSame(fixture.connectorCap(), stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX() + centerX,
|
||||
glyphY,
|
||||
corner.bounds().originZ()));
|
||||
assertSame(fixture.connectorCap(), stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().maxX(),
|
||||
glyphY,
|
||||
corner.bounds().originZ() + centerZ));
|
||||
assertSame(fixture.topologyPath(), stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX() + centerX,
|
||||
glyphY,
|
||||
corner.bounds().originZ() + 1));
|
||||
assertSame(fixture.topologyPath(), stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX() + centerX + 1,
|
||||
glyphY,
|
||||
corner.bounds().originZ() + centerZ));
|
||||
assertSame(fixture.topologyBase(), stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX(),
|
||||
glyphY,
|
||||
corner.bounds().originZ()));
|
||||
assertFalse(fixture.topologyBase() == stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX() + centerX,
|
||||
corner.bounds().originY(),
|
||||
corner.bounds().originZ() + centerZ));
|
||||
try (MockedStatic<B> ignored = mockStatic(B.class)) {
|
||||
assertSame(fixture.palette().horizontalSocket(), stateAt(
|
||||
fixture.generator(), corner.bounds().originX() + centerX, floorY, corner.bounds().originZ()));
|
||||
assertSame(fixture.palette().horizontalSocket(), stateAt(
|
||||
fixture.generator(), corner.bounds().maxX(), floorY, corner.bounds().originZ() + centerZ));
|
||||
assertSame(fixture.palette().line(), stateAt(
|
||||
fixture.generator(), corner.bounds().originX() + centerX, floorY, corner.bounds().originZ() + 1));
|
||||
assertSame(fixture.palette().line(), stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX() + centerX + 1,
|
||||
floorY,
|
||||
corner.bounds().originZ() + centerZ));
|
||||
assertSame(fixture.palette().base(), stateAt(
|
||||
fixture.generator(), corner.bounds().originX(), floorY, corner.bounds().originZ()));
|
||||
assertFalse(fixture.palette().base() == stateAt(
|
||||
fixture.generator(),
|
||||
corner.bounds().originX() + centerX,
|
||||
corner.bounds().originY(),
|
||||
corner.bounds().originZ() + centerZ));
|
||||
|
||||
JigsawStudioBay blank = fixture.layout().get("workcell/blank");
|
||||
assertSame(fixture.topologyBase(), stateAt(
|
||||
fixture.generator(),
|
||||
blank.bounds().originX() + centerX,
|
||||
blank.bounds().originY() - 1,
|
||||
blank.bounds().originZ() + centerZ));
|
||||
JigsawStudioControlPosition control = fixture.layout().controlPosition();
|
||||
assertSame(fixture.controlChest(), stateAt(
|
||||
fixture.generator(), control.worldX(), control.worldY(), control.worldZ()));
|
||||
JigsawStudioBay blank = fixture.layout().get("workcell/blank");
|
||||
assertSame(fixture.palette().base(), stateAt(
|
||||
fixture.generator(),
|
||||
blank.bounds().originX() + centerX,
|
||||
blank.bounds().originY() - 1,
|
||||
blank.bounds().originZ() + centerZ));
|
||||
JigsawStudioBay tee = fixture.layout().get("workcell/tee");
|
||||
assertSame(fixture.palette().base(), stateAt(
|
||||
fixture.generator(),
|
||||
tee.bounds().originX() + centerX,
|
||||
tee.bounds().originY() - 1,
|
||||
tee.bounds().originZ()));
|
||||
JigsawStudioControlPosition control = fixture.layout().controlPosition();
|
||||
assertSame(fixture.controlChest(), stateAt(
|
||||
fixture.generator(), control.worldX(), control.worldY(), control.worldZ()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void spatialWorkcellsPaintVerticalSocketsUnderTheirConnectorColumns() {
|
||||
IrisData source = mock(IrisData.class);
|
||||
ResourceLoader<IrisJigsawPiece> pieceLoader = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisObject> objectLoader = mock(ResourceLoader.class);
|
||||
when(source.getJigsawPieceLoader()).thenReturn(pieceLoader);
|
||||
when(source.getObjectLoader()).thenReturn(objectLoader);
|
||||
IrisJigsawPiece piece = new IrisJigsawPiece()
|
||||
.setObject("stronghold/shaft")
|
||||
.setConnectors(new KList<>());
|
||||
piece.getConnectors().add(connector(new IrisPosition(2, 4, 2), IrisDirection.UP_POSITIVE_Y)
|
||||
.setTop(IrisDirection.NORTH_NEGATIVE_Z));
|
||||
piece.getConnectors().add(connector(new IrisPosition(2, 0, 2), IrisDirection.DOWN_NEGATIVE_Y)
|
||||
.setTop(IrisDirection.NORTH_NEGATIVE_Z));
|
||||
piece.getConnectors().add(connector(new IrisPosition(0, 1, 4), IrisDirection.DOWN_NEGATIVE_Y)
|
||||
.setTop(IrisDirection.NORTH_NEGATIVE_Z));
|
||||
piece.getConnectors().add(connector(new IrisPosition(2, 2, 0), IrisDirection.NORTH_NEGATIVE_Z));
|
||||
when(pieceLoader.load("stronghold/shaft", false)).thenReturn(piece);
|
||||
when(objectLoader.load("stronghold/shaft", false)).thenReturn(new IrisObject(5, 5, 5));
|
||||
JigsawStudioVariant variant = new JigsawStudioVariant(
|
||||
"stronghold/shaft",
|
||||
"stronghold/shaft",
|
||||
"",
|
||||
Optional.of(new JigsawStudioCellDimensions(5, 5, 5)),
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
Optional.empty(),
|
||||
true,
|
||||
true,
|
||||
List.of(),
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
List.of());
|
||||
GeneratorFixture fixture = fixture(
|
||||
source,
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
new JigsawStudioCellDimensions(5, 5, 5),
|
||||
new JigsawStudioVariantCatalog(List.of(variant)));
|
||||
JigsawStudioBay workcell = fixture.layout().bays().getFirst();
|
||||
int originX = workcell.bounds().originX();
|
||||
int originZ = workcell.bounds().originZ();
|
||||
int floorY = workcell.bounds().originY() - 1;
|
||||
|
||||
try (MockedStatic<B> ignored = mockStatic(B.class)) {
|
||||
assertSame(fixture.palette().upAndDownSocket(), stateAt(fixture.generator(), originX + 2, floorY, originZ + 2));
|
||||
assertSame(fixture.palette().downSocket(), stateAt(fixture.generator(), originX, floorY, originZ + 4));
|
||||
assertSame(fixture.palette().horizontalSocket(), stateAt(fixture.generator(), originX + 2, floorY, originZ));
|
||||
assertSame(fixture.palette().line(), stateAt(fixture.generator(), originX + 2, floorY, originZ + 1));
|
||||
assertSame(fixture.palette().line(), stateAt(fixture.generator(), originX + 1, floorY, originZ + 3));
|
||||
assertSame(fixture.palette().base(), stateAt(fixture.generator(), originX + 4, floorY, originZ + 4));
|
||||
}
|
||||
assertSame(
|
||||
fixture.palette().upAndDownSocket(),
|
||||
fixture.generator().floorState(fixture.generator().floorGlyph(workcell).cell(2, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -247,77 +312,6 @@ public class JigsawStudioGeneratorTest {
|
||||
workcell.bounds().originZ()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void glyphMathHandlesAllArchetypesAndSmallEvenAndOddCells() {
|
||||
int[] sizes = {1, 2, 3, 4, 7, 16};
|
||||
for (int size : sizes) {
|
||||
JigsawStudioCellDimensions dimensions = new JigsawStudioCellDimensions(size, 1, size);
|
||||
int center = size / 2;
|
||||
for (JigsawPlanarArchetype archetype : JigsawPlanarArchetype.values()) {
|
||||
JigsawPlanarTopology topology = archetype.canonicalTopology();
|
||||
boolean centerPath = JigsawStudioGenerator.isTopologyPath(
|
||||
topology, center, center, center, center);
|
||||
assertEquals(archetype != JigsawPlanarArchetype.BLANK, centerPath);
|
||||
if (topology.connects(JigsawPlanarDirection.NORTH)) {
|
||||
assertTrue(JigsawStudioGenerator.isConnectorCap(
|
||||
topology, dimensions, center, 0, center, center));
|
||||
}
|
||||
if (topology.connects(JigsawPlanarDirection.EAST)) {
|
||||
assertTrue(JigsawStudioGenerator.isConnectorCap(
|
||||
topology, dimensions, size - 1, center, center, center));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectorCapsOnlyOccupyConnectedFaceCenters() {
|
||||
int[] sizes = {3, 4, 7, 16};
|
||||
for (int size : sizes) {
|
||||
JigsawStudioCellDimensions dimensions = new JigsawStudioCellDimensions(size, 1, size);
|
||||
int center = size / 2;
|
||||
for (JigsawPlanarArchetype archetype : JigsawPlanarArchetype.values()) {
|
||||
JigsawPlanarTopology topology = archetype.canonicalTopology();
|
||||
for (int z = 0; z < size; z++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
boolean expected = topology.connects(JigsawPlanarDirection.NORTH)
|
||||
&& x == center && z == 0
|
||||
|| topology.connects(JigsawPlanarDirection.EAST)
|
||||
&& x == size - 1 && z == center
|
||||
|| topology.connects(JigsawPlanarDirection.SOUTH)
|
||||
&& x == center && z == size - 1
|
||||
|| topology.connects(JigsawPlanarDirection.WEST)
|
||||
&& x == 0 && z == center;
|
||||
assertEquals(expected, JigsawStudioGenerator.isConnectorCap(
|
||||
topology, dimensions, x, z, center, center));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canonicalFiveByFivePathsMatchEveryArchetype() {
|
||||
Map<JigsawPlanarArchetype, List<String>> expected = Map.of(
|
||||
JigsawPlanarArchetype.BLANK, List.of(".....", ".....", ".....", ".....", "....."),
|
||||
JigsawPlanarArchetype.END, List.of("..#..", "..#..", "..#..", ".....", "....."),
|
||||
JigsawPlanarArchetype.STRAIGHT, List.of("..#..", "..#..", "..#..", "..#..", "..#.."),
|
||||
JigsawPlanarArchetype.CORNER, List.of("..#..", "..#..", "..###", ".....", "....."),
|
||||
JigsawPlanarArchetype.TEE, List.of("..#..", "..#..", "#####", ".....", "....."),
|
||||
JigsawPlanarArchetype.CROSS, List.of("..#..", "..#..", "#####", "..#..", "..#.."));
|
||||
|
||||
for (JigsawPlanarArchetype archetype : JigsawPlanarArchetype.values()) {
|
||||
List<String> rows = expected.get(archetype);
|
||||
for (int z = 0; z < 5; z++) {
|
||||
for (int x = 0; x < 5; x++) {
|
||||
boolean path = JigsawStudioGenerator.isTopologyPath(
|
||||
archetype.canonicalTopology(), x, z, 2, 2);
|
||||
assertEquals(rows.get(z).charAt(x) == '#', path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clipsLargeGlyphPaintingToOneChunkFootprint() {
|
||||
GeneratorFixture fixture = fixture(
|
||||
@@ -331,10 +325,7 @@ public class JigsawStudioGeneratorTest {
|
||||
|
||||
fixture.generator().paintChunk(chunk, chunkX, chunkZ);
|
||||
|
||||
int glyphBlocks = chunk.countState(fixture.topologyBase())
|
||||
+ chunk.countState(fixture.topologyPath())
|
||||
+ chunk.countState(fixture.connectorCap());
|
||||
assertEquals(256, glyphBlocks);
|
||||
assertEquals(256, chunk.countState(fixture.palette().base()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -467,6 +458,29 @@ public class JigsawStudioGeneratorTest {
|
||||
assertNotSame(nextGeneration, fixture.generator().renderVariant(workcell, variant, 5L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void workcellsFromAnEquivalentReplacedLayoutStillRender() {
|
||||
JigsawStudioCellDimensions dimensions = new JigsawStudioCellDimensions(5, 5, 5);
|
||||
GeneratorFixture fixture = fixture(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
dimensions,
|
||||
JigsawStudioVariantCatalog.empty());
|
||||
JigsawStudioBay stale = fixture.layout().get("workcell/end");
|
||||
|
||||
fixture.generator().getSession().replaceLayout(JigsawStudioLayout.create(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
dimensions,
|
||||
JigsawStudioVariantCatalog.empty()));
|
||||
|
||||
assertNotSame(stale, fixture.generator().getLayout().get("workcell/end"));
|
||||
assertTrue(fixture.generator().renderBay(stale).valid());
|
||||
fixture.generator().getSession().replaceLayout(JigsawStudioLayout.create(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
new JigsawStudioCellDimensions(6, 5, 5),
|
||||
JigsawStudioVariantCatalog.empty()));
|
||||
assertThrows(IllegalArgumentException.class, () -> fixture.generator().renderBay(stale));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapsAllVanillaJigsawOrientations() {
|
||||
assertEquals("north_up", JigsawStudioGenerator.orientation(
|
||||
@@ -535,9 +549,13 @@ public class JigsawStudioGeneratorTest {
|
||||
NativeBlockState lightFloor = mock(NativeBlockState.class);
|
||||
NativeBlockState darkFloor = mock(NativeBlockState.class);
|
||||
NativeBlockState frame = mock(NativeBlockState.class);
|
||||
NativeBlockState topologyBase = mock(NativeBlockState.class);
|
||||
NativeBlockState topologyPath = mock(NativeBlockState.class);
|
||||
NativeBlockState connectorCap = mock(NativeBlockState.class);
|
||||
JigsawStudioGenerator.FloorPalette palette = new JigsawStudioGenerator.FloorPalette(
|
||||
mock(NativeBlockState.class),
|
||||
mock(NativeBlockState.class),
|
||||
mock(NativeBlockState.class),
|
||||
mock(NativeBlockState.class),
|
||||
mock(NativeBlockState.class),
|
||||
mock(NativeBlockState.class));
|
||||
NativeBlockState invalidMarker = mock(NativeBlockState.class);
|
||||
NativeBlockState controlChest = mock(NativeBlockState.class);
|
||||
JigsawStudioGenerator generator = new JigsawStudioGenerator(
|
||||
@@ -547,9 +565,7 @@ public class JigsawStudioGeneratorTest {
|
||||
lightFloor,
|
||||
darkFloor,
|
||||
frame,
|
||||
topologyBase,
|
||||
topologyPath,
|
||||
connectorCap,
|
||||
palette,
|
||||
invalidMarker,
|
||||
controlChest,
|
||||
JigsawStudioGeneratorTest::renderFinalState);
|
||||
@@ -557,12 +573,22 @@ public class JigsawStudioGeneratorTest {
|
||||
generator,
|
||||
layout,
|
||||
frame,
|
||||
topologyBase,
|
||||
topologyPath,
|
||||
connectorCap,
|
||||
palette,
|
||||
controlChest);
|
||||
}
|
||||
|
||||
private static IrisJigsawConnector connector(IrisPosition position, IrisDirection direction) {
|
||||
return new IrisJigsawConnector()
|
||||
.setPosition(position)
|
||||
.setDirection(direction)
|
||||
.setTop(IrisDirection.UP_POSITIVE_Y)
|
||||
.setPool("test/pieces")
|
||||
.setName("iris:planar")
|
||||
.setTargetName("iris:planar")
|
||||
.setJoint(JigsawJoint.ALIGNED)
|
||||
.setFinalState("minecraft:structure_void");
|
||||
}
|
||||
|
||||
private static JigsawStudioVariant planarVariant(String key, JigsawPlanarTopology topology) {
|
||||
return new JigsawStudioVariant(
|
||||
key,
|
||||
@@ -596,9 +622,7 @@ public class JigsawStudioGeneratorTest {
|
||||
JigsawStudioGenerator generator,
|
||||
JigsawStudioLayout layout,
|
||||
NativeBlockState frame,
|
||||
NativeBlockState topologyBase,
|
||||
NativeBlockState topologyPath,
|
||||
NativeBlockState connectorCap,
|
||||
JigsawStudioGenerator.FloorPalette palette,
|
||||
NativeBlockState controlChest
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,509 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.pack.value.IrisDirection;
|
||||
import art.arcane.iris.pack.value.IrisPosition;
|
||||
import art.arcane.iris.structure.authoring.StructureResourceBundle;
|
||||
import art.arcane.iris.structure.authoring.StructureWriteResult;
|
||||
import art.arcane.iris.structure.graph.StructureAssemblyResult;
|
||||
import art.arcane.iris.structure.graph.StructureAssemblyStatus;
|
||||
import art.arcane.iris.structure.graph.StructureGraphCompilation;
|
||||
import art.arcane.iris.structure.graph.StructureResourceBundleGraphCompiler;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawConnector;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawPiece;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawPieceEntry;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawPool;
|
||||
import art.arcane.iris.structure.jigsaw.JigsawJoint;
|
||||
import art.arcane.iris.structure.object.IrisObject;
|
||||
import art.arcane.iris.structure.placement.IrisStructure;
|
||||
import art.arcane.iris.structure.placement.PlacedStructurePiece;
|
||||
import art.arcane.iris.structure.placement.StructureAssembler;
|
||||
import art.arcane.iris.testsupport.DurabilityMode;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JigsawDefaultKitAssemblyTest {
|
||||
@ClassRule
|
||||
public static final DurabilityMode DURABILITY = DurabilityMode.relaxed();
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final int SEEDS = 160;
|
||||
private static final int SPATIAL_IRIS_PIECE_BOUND = 23 * 4 + 1;
|
||||
private static final int SPATIAL_PORTABLE_PIECE_BOUND = 129;
|
||||
private static final List<JigsawStudioCellDimensions> PLANAR_DIMENSIONS = List.of(
|
||||
new JigsawStudioCellDimensions(16, 16, 16),
|
||||
new JigsawStudioCellDimensions(15, 9, 15));
|
||||
private static final List<JigsawStudioCellDimensions> SPATIAL_DIMENSIONS = List.of(
|
||||
new JigsawStudioCellDimensions(15, 15, 15),
|
||||
new JigsawStudioCellDimensions(16, 12, 16));
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void planarDefaultKitOpensEveryOccupiedNeighborOnTheCellGrid() throws IOException {
|
||||
for (JigsawStudioCellDimensions dimensions : PLANAR_DIMENSIONS) {
|
||||
for (JigsawStudioCompatibilityTarget target : JigsawStudioCompatibilityTarget.values()) {
|
||||
StructureGraphCompilation compilation = compile(JigsawStudioMode.PLANAR_JIGSAW, target, dimensions);
|
||||
int closedFaces = 0;
|
||||
for (long seed = 0; seed < SEEDS; seed++) {
|
||||
List<PlacedStructurePiece> pieces = complete(compilation, seed);
|
||||
for (PlacedStructurePiece piece : pieces) {
|
||||
for (IrisJigsawConnector connector : piece.getPiece().getConnectors()) {
|
||||
IrisDirection facing = piece.getRotation().rotate(connector.getDirection());
|
||||
IrisPosition targetCell = offset(planarSocket(piece, facing), facing);
|
||||
PlacedStructurePiece neighbor = containing(pieces, targetCell);
|
||||
if (neighbor == null) {
|
||||
continue;
|
||||
}
|
||||
String context = target + " " + dimensions + " seed " + seed + ": "
|
||||
+ piece.getPiece().getObject() + " faces " + facing + " into "
|
||||
+ neighbor.getPiece().getObject();
|
||||
assertTrue(context, hasPlanarSocket(
|
||||
neighbor,
|
||||
facing.reverse(),
|
||||
targetCell,
|
||||
piece.getRotation().rotate(connector.getTop())));
|
||||
assertSharedFace(context, piece, neighbor, facing);
|
||||
closedFaces++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(closedFaces > SEEDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planarDefaultKitTurnsBothWaysThroughCornersAndTees() throws IOException {
|
||||
StructureGraphCompilation compilation = compile(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
JigsawStudioCompatibilityTarget.IRIS_EXTENDED,
|
||||
PLANAR_DIMENSIONS.getFirst());
|
||||
Map<String, Set<Integer>> attachments = new TreeMap<>();
|
||||
for (long seed = 0; seed < SEEDS; seed++) {
|
||||
List<PlacedStructurePiece> pieces = complete(compilation, seed);
|
||||
for (int index = 1; index < pieces.size(); index++) {
|
||||
PlacedStructurePiece piece = pieces.get(index);
|
||||
int attachment = soleEarlierPlanarAttachment(pieces, index);
|
||||
if (attachment >= 0) {
|
||||
attachments.computeIfAbsent(baseName(piece), ignored -> new HashSet<>()).add(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(attachments.toString(), Set.of(0, 1), attachments.get("corner"));
|
||||
Set<Integer> teeAttachments = attachments.get("tee");
|
||||
assertNotNull(attachments.toString(), teeAttachments);
|
||||
assertTrue(attachments.toString(), teeAttachments.contains(0));
|
||||
assertTrue(attachments.toString(), teeAttachments.contains(1) || teeAttachments.contains(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectorlessBlankPiecesGenerateAsStandaloneStructures() throws IOException {
|
||||
for (JigsawStudioMode mode : JigsawStudioMode.values()) {
|
||||
JigsawStudioCellDimensions dimensions = mode == JigsawStudioMode.PLANAR_JIGSAW
|
||||
? PLANAR_DIMENSIONS.getFirst()
|
||||
: SPATIAL_DIMENSIONS.getFirst();
|
||||
StructureGraphCompilation compilation = compile(
|
||||
mode,
|
||||
JigsawStudioCompatibilityTarget.IRIS_EXTENDED,
|
||||
dimensions);
|
||||
int standaloneBlanks = 0;
|
||||
int multiPieceAssemblies = 0;
|
||||
for (long seed = 0; seed < SEEDS * 8 && (standaloneBlanks == 0 || seed < SEEDS); seed++) {
|
||||
List<PlacedStructurePiece> pieces = complete(compilation, seed);
|
||||
if (pieces.size() == 1 && pieces.getFirst().getPiece().getConnectors().isEmpty()) {
|
||||
assertEquals("blank", baseName(pieces.getFirst()));
|
||||
standaloneBlanks++;
|
||||
continue;
|
||||
}
|
||||
multiPieceAssemblies++;
|
||||
for (PlacedStructurePiece piece : pieces) {
|
||||
assertFalse(mode + " seed " + seed, piece.getPiece().getConnectors().isEmpty());
|
||||
}
|
||||
}
|
||||
assertTrue(mode + " never generated its connectorless piece", standaloneBlanks > 0);
|
||||
assertTrue(mode + " generated only standalone pieces", multiPieceAssemblies > standaloneBlanks);
|
||||
}
|
||||
|
||||
StructureResourceBundle planar = JigsawStudioProjectCreator.bundle(options(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
JigsawStudioCompatibilityTarget.VANILLA_PORTABLE,
|
||||
PLANAR_DIMENSIONS.getLast()));
|
||||
IrisJigsawPool start = pool(planar, "kit/test/start");
|
||||
assertEquals(2, start.getPieces().size());
|
||||
assertEquals("kit/test/cross", start.getPieces().get(0).getPiece());
|
||||
assertEquals(3, start.getPieces().get(0).getWeight());
|
||||
assertEquals("kit/test/blank", start.getPieces().get(1).getPiece());
|
||||
assertEquals(1, start.getPieces().get(1).getWeight());
|
||||
for (IrisJigsawPieceEntry entry : pool(planar, "kit/test/pieces").getPieces()) {
|
||||
assertFalse("kit/test/blank".equals(entry.getPiece()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spatialDefaultKitCoversEveryYRotationTopologyOnce() throws IOException {
|
||||
for (JigsawStudioCompatibilityTarget target : JigsawStudioCompatibilityTarget.values()) {
|
||||
JigsawStudioCellDimensions dimensions = SPATIAL_DIMENSIONS.getLast();
|
||||
StructureResourceBundle bundle = JigsawStudioProjectCreator.bundle(
|
||||
options(JigsawStudioMode.SPATIAL_JIGSAW, target, dimensions));
|
||||
Map<String, IrisJigsawPiece> pieces = pieces(bundle);
|
||||
Map<Integer, String> bySignature = new HashMap<>();
|
||||
for (Map.Entry<String, IrisJigsawPiece> entry : pieces.entrySet()) {
|
||||
IrisJigsawPiece piece = entry.getValue();
|
||||
int horizontal = 0;
|
||||
int vertical = 0;
|
||||
for (IrisJigsawConnector connector : piece.getConnectors()) {
|
||||
IrisDirection direction = connector.getDirection();
|
||||
assertEquals(spatialFaceCenter(dimensions, direction), connector.getPosition());
|
||||
assertEquals(direction.isVertical() ? IrisDirection.NORTH_NEGATIVE_Z : IrisDirection.UP_POSITIVE_Y,
|
||||
connector.getTop());
|
||||
assertEquals(JigsawJoint.ROLLABLE, connector.getJoint());
|
||||
assertEquals("kit/test/pieces", connector.getPool());
|
||||
horizontal |= horizontalBit(direction);
|
||||
vertical |= verticalBit(direction);
|
||||
}
|
||||
assertTrue(piece.isRotatable());
|
||||
assertEquals(entry.getKey(), piece.getObject());
|
||||
assertEquals(expectedSpatialKey(canonicalMask(horizontal), vertical), entry.getKey());
|
||||
String previous = bySignature.put(canonicalMask(horizontal) | vertical, entry.getKey());
|
||||
assertEquals("duplicate topology " + entry.getKey() + " and " + previous, null, previous);
|
||||
if (target == JigsawStudioCompatibilityTarget.IRIS_EXTENDED) {
|
||||
assertEquals(List.of("variant-1"), List.copyOf(piece.getThemes()));
|
||||
assertEquals(4, piece.resolvedRules().getMaximumPlacements());
|
||||
} else {
|
||||
assertTrue(piece.getThemes().isEmpty());
|
||||
assertEquals(0, piece.resolvedRules().getMaximumPlacements());
|
||||
}
|
||||
}
|
||||
assertEquals(24, pieces.size());
|
||||
assertEquals(24, bySignature.size());
|
||||
|
||||
IrisJigsawPool start = pool(bundle, "kit/test/start");
|
||||
assertEquals(24, start.getPieces().size());
|
||||
IrisJigsawPool piecePool = pool(bundle, "kit/test/pieces");
|
||||
assertEquals(24, piecePool.getPieces().size());
|
||||
assertTrue(piecePool.getPieces().getLast().isEmpty());
|
||||
for (IrisJigsawPieceEntry entry : piecePool.getPieces()) {
|
||||
assertFalse("kit/test/blank".equals(entry.getPiece()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spatialDefaultKitAttachesEveryPieceThroughAlignedConnectorsAndTurnsBothWays() throws IOException {
|
||||
for (JigsawStudioCellDimensions dimensions : SPATIAL_DIMENSIONS) {
|
||||
for (JigsawStudioCompatibilityTarget target : JigsawStudioCompatibilityTarget.values()) {
|
||||
StructureGraphCompilation compilation = compile(JigsawStudioMode.SPATIAL_JIGSAW, target, dimensions);
|
||||
int bound = target == JigsawStudioCompatibilityTarget.IRIS_EXTENDED
|
||||
? SPATIAL_IRIS_PIECE_BOUND
|
||||
: SPATIAL_PORTABLE_PIECE_BOUND;
|
||||
Set<String> placedKinds = new HashSet<>();
|
||||
Set<Integer> cornerAttachments = new HashSet<>();
|
||||
for (long seed = 0; seed < SEEDS; seed++) {
|
||||
List<PlacedStructurePiece> pieces = complete(compilation, seed);
|
||||
String context = target + " " + dimensions + " seed " + seed;
|
||||
assertTrue(context + " placed " + pieces.size(), pieces.size() <= bound);
|
||||
for (int index = 0; index < pieces.size(); index++) {
|
||||
placedKinds.add(baseName(pieces.get(index)));
|
||||
if (index == 0) {
|
||||
continue;
|
||||
}
|
||||
List<Integer> attachments = earlierSpatialAttachments(pieces, index);
|
||||
assertFalse(context + " left " + baseName(pieces.get(index)) + " unattached",
|
||||
attachments.isEmpty());
|
||||
if (attachments.size() == 1 && baseName(pieces.get(index)).startsWith("corner")
|
||||
&& attachments.getFirst() < 2) {
|
||||
cornerAttachments.add(attachments.getFirst());
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(target + " " + dimensions, Set.of(0, 1), cornerAttachments);
|
||||
assertFalse(placedKinds.contains("blank"));
|
||||
assertEquals(target + " " + dimensions + " placed " + placedKinds, 23, placedKinds.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaPortableSpatialProjectIsCreatedWithAViableGraph() throws IOException {
|
||||
Path packRoot = temporaryFolder.getRoot().toPath();
|
||||
JigsawStudioProjectCreator.Options options = options(
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
JigsawStudioCompatibilityTarget.VANILLA_PORTABLE,
|
||||
SPATIAL_DIMENSIONS.getFirst());
|
||||
|
||||
StructureWriteResult result = JigsawStudioProjectCreator.create(packRoot, options);
|
||||
|
||||
assertTrue(result.successful());
|
||||
IrisStructure structure = GSON.fromJson(Files.readString(
|
||||
packRoot.resolve("structures/kit/test.json"), StandardCharsets.UTF_8), IrisStructure.class);
|
||||
assertEquals(4, structure.getMaxDepth());
|
||||
assertTrue(Files.isRegularFile(packRoot.resolve("jigsaw-pieces/kit/test/corner-up-down.json")));
|
||||
assertTrue(Files.isRegularFile(packRoot.resolve("objects/kit/test/blank.iob")));
|
||||
}
|
||||
|
||||
private static JigsawStudioProjectCreator.Options options(
|
||||
JigsawStudioMode mode,
|
||||
JigsawStudioCompatibilityTarget target,
|
||||
JigsawStudioCellDimensions dimensions
|
||||
) {
|
||||
return new JigsawStudioProjectCreator.Options("kit/test", mode, target, dimensions);
|
||||
}
|
||||
|
||||
private static StructureGraphCompilation compile(
|
||||
JigsawStudioMode mode,
|
||||
JigsawStudioCompatibilityTarget target,
|
||||
JigsawStudioCellDimensions dimensions
|
||||
) throws IOException {
|
||||
StructureGraphCompilation compilation = StructureResourceBundleGraphCompiler.compile(
|
||||
JigsawStudioProjectCreator.bundle(options(mode, target, dimensions))).getFirst();
|
||||
assertFalse(compilation.getDiagnostics().toString(), compilation.hasErrors());
|
||||
return compilation;
|
||||
}
|
||||
|
||||
private static List<PlacedStructurePiece> complete(StructureGraphCompilation compilation, long seed) {
|
||||
StructureAssemblyResult result = StructureAssembler.forCompilation(
|
||||
compilation,
|
||||
new IrisPosition(0, 64, 0)).assemble(new RNG(seed));
|
||||
assertEquals("seed " + seed + ": " + result.detail(), StructureAssemblyStatus.COMPLETE, result.status());
|
||||
return result.pieces();
|
||||
}
|
||||
|
||||
private static int soleEarlierPlanarAttachment(List<PlacedStructurePiece> pieces, int index) {
|
||||
PlacedStructurePiece piece = pieces.get(index);
|
||||
int attachment = -1;
|
||||
int pairings = 0;
|
||||
for (int connectorIndex = 0; connectorIndex < piece.getPiece().getConnectors().size(); connectorIndex++) {
|
||||
IrisJigsawConnector connector = piece.getPiece().getConnectors().get(connectorIndex);
|
||||
IrisDirection facing = piece.getRotation().rotate(connector.getDirection());
|
||||
IrisPosition targetCell = offset(planarSocket(piece, facing), facing);
|
||||
for (int earlier = 0; earlier < index; earlier++) {
|
||||
PlacedStructurePiece neighbor = pieces.get(earlier);
|
||||
if (contains(neighbor, targetCell) && hasPlanarSocket(
|
||||
neighbor,
|
||||
facing.reverse(),
|
||||
targetCell,
|
||||
piece.getRotation().rotate(connector.getTop()))) {
|
||||
attachment = connectorIndex;
|
||||
pairings++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairings == 1 ? attachment : -1;
|
||||
}
|
||||
|
||||
private static List<Integer> earlierSpatialAttachments(List<PlacedStructurePiece> pieces, int index) {
|
||||
PlacedStructurePiece piece = pieces.get(index);
|
||||
List<Integer> attachments = new ArrayList<>();
|
||||
for (int connectorIndex = 0; connectorIndex < piece.getPiece().getConnectors().size(); connectorIndex++) {
|
||||
IrisJigsawConnector connector = piece.getPiece().getConnectors().get(connectorIndex);
|
||||
IrisPosition socket = spatialSocket(piece, connector);
|
||||
IrisDirection facing = piece.getRotation().rotate(connector.getDirection());
|
||||
for (int earlier = 0; earlier < index; earlier++) {
|
||||
PlacedStructurePiece neighbor = pieces.get(earlier);
|
||||
for (IrisJigsawConnector other : neighbor.getPiece().getConnectors()) {
|
||||
IrisDirection otherFacing = neighbor.getRotation().rotate(other.getDirection());
|
||||
if (otherFacing == facing.reverse()
|
||||
&& offset(spatialSocket(neighbor, other), otherFacing).equals(socket)) {
|
||||
attachments.add(connectorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private static boolean hasPlanarSocket(
|
||||
PlacedStructurePiece piece,
|
||||
IrisDirection facing,
|
||||
IrisPosition position,
|
||||
IrisDirection top
|
||||
) {
|
||||
if (!planarSocket(piece, facing).equals(position)) {
|
||||
return false;
|
||||
}
|
||||
for (IrisJigsawConnector connector : piece.getPiece().getConnectors()) {
|
||||
if (piece.getRotation().rotate(connector.getDirection()) == facing
|
||||
&& piece.getRotation().rotate(connector.getTop()) == top) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void assertSharedFace(
|
||||
String context,
|
||||
PlacedStructurePiece piece,
|
||||
PlacedStructurePiece neighbor,
|
||||
IrisDirection facing
|
||||
) {
|
||||
assertEquals(context, piece.getMinY(), neighbor.getMinY());
|
||||
assertEquals(context, piece.getMaxY(), neighbor.getMaxY());
|
||||
switch (facing) {
|
||||
case EAST_POSITIVE_X -> assertEquals(context, piece.getMaxX() + 1, neighbor.getMinX());
|
||||
case WEST_NEGATIVE_X -> assertEquals(context, piece.getMinX() - 1, neighbor.getMaxX());
|
||||
case SOUTH_POSITIVE_Z -> assertEquals(context, piece.getMaxZ() + 1, neighbor.getMinZ());
|
||||
case NORTH_NEGATIVE_Z -> assertEquals(context, piece.getMinZ() - 1, neighbor.getMaxZ());
|
||||
case UP_POSITIVE_Y, DOWN_NEGATIVE_Y -> throw new AssertionError(context + " uses a vertical socket");
|
||||
}
|
||||
if (facing.x() != 0) {
|
||||
assertEquals(context, piece.getMinZ(), neighbor.getMinZ());
|
||||
assertEquals(context, piece.getMaxZ(), neighbor.getMaxZ());
|
||||
} else {
|
||||
assertEquals(context, piece.getMinX(), neighbor.getMinX());
|
||||
assertEquals(context, piece.getMaxX(), neighbor.getMaxX());
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisPosition planarSocket(PlacedStructurePiece piece, IrisDirection facing) {
|
||||
IrisPosition size = new IrisPosition(
|
||||
piece.getMaxX() - piece.getMinX() + 1,
|
||||
piece.getMaxY() - piece.getMinY() + 1,
|
||||
piece.getMaxZ() - piece.getMinZ() + 1);
|
||||
IrisPosition local = IrisJigsawConnector.canonicalPlanarPosition(size, facing);
|
||||
return new IrisPosition(
|
||||
piece.getMinX() + local.getX(),
|
||||
piece.getMinY() + local.getY(),
|
||||
piece.getMinZ() + local.getZ());
|
||||
}
|
||||
|
||||
private static IrisPosition spatialSocket(PlacedStructurePiece piece, IrisJigsawConnector connector) {
|
||||
IrisObject object = piece.getObject();
|
||||
IrisPosition relative = new IrisPosition(
|
||||
connector.getPosition().getX() - object.getW() / 2,
|
||||
connector.getPosition().getY() - object.getH() / 2,
|
||||
connector.getPosition().getZ() - object.getD() / 2);
|
||||
IrisPosition rotated = piece.getRotation().rotate(relative);
|
||||
return new IrisPosition(
|
||||
piece.getX() + rotated.getX(),
|
||||
piece.getY() + rotated.getY(),
|
||||
piece.getZ() + rotated.getZ());
|
||||
}
|
||||
|
||||
private static IrisPosition spatialFaceCenter(JigsawStudioCellDimensions dimensions, IrisDirection direction) {
|
||||
int centerX = dimensions.width() / 2;
|
||||
int centerY = dimensions.height() / 2;
|
||||
int centerZ = dimensions.depth() / 2;
|
||||
return switch (direction) {
|
||||
case NORTH_NEGATIVE_Z -> new IrisPosition(centerX, centerY, 0);
|
||||
case SOUTH_POSITIVE_Z -> new IrisPosition(centerX, centerY, dimensions.depth() - 1);
|
||||
case EAST_POSITIVE_X -> new IrisPosition(dimensions.width() - 1, centerY, centerZ);
|
||||
case WEST_NEGATIVE_X -> new IrisPosition(0, centerY, centerZ);
|
||||
case UP_POSITIVE_Y -> new IrisPosition(centerX, dimensions.height() - 1, centerZ);
|
||||
case DOWN_NEGATIVE_Y -> new IrisPosition(centerX, 0, centerZ);
|
||||
};
|
||||
}
|
||||
|
||||
private static IrisPosition offset(IrisPosition position, IrisDirection direction) {
|
||||
return new IrisPosition(
|
||||
position.getX() + direction.x(),
|
||||
position.getY() + direction.y(),
|
||||
position.getZ() + direction.z());
|
||||
}
|
||||
|
||||
private static PlacedStructurePiece containing(List<PlacedStructurePiece> pieces, IrisPosition position) {
|
||||
for (PlacedStructurePiece piece : pieces) {
|
||||
if (contains(piece, position)) {
|
||||
return piece;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean contains(PlacedStructurePiece piece, IrisPosition position) {
|
||||
return position.getX() >= piece.getMinX() && position.getX() <= piece.getMaxX()
|
||||
&& position.getY() >= piece.getMinY() && position.getY() <= piece.getMaxY()
|
||||
&& position.getZ() >= piece.getMinZ() && position.getZ() <= piece.getMaxZ();
|
||||
}
|
||||
|
||||
private static String baseName(PlacedStructurePiece piece) {
|
||||
String key = piece.getPiece().getObject();
|
||||
return key.substring(key.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
private static int horizontalBit(IrisDirection direction) {
|
||||
return switch (direction) {
|
||||
case NORTH_NEGATIVE_Z -> 1;
|
||||
case EAST_POSITIVE_X -> 2;
|
||||
case SOUTH_POSITIVE_Z -> 4;
|
||||
case WEST_NEGATIVE_X -> 8;
|
||||
case UP_POSITIVE_Y, DOWN_NEGATIVE_Y -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int verticalBit(IrisDirection direction) {
|
||||
return switch (direction) {
|
||||
case UP_POSITIVE_Y -> 16;
|
||||
case DOWN_NEGATIVE_Y -> 32;
|
||||
case NORTH_NEGATIVE_Z, EAST_POSITIVE_X, SOUTH_POSITIVE_Z, WEST_NEGATIVE_X -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int canonicalMask(int mask) {
|
||||
int canonical = mask;
|
||||
for (int quarterTurns = 1; quarterTurns < 4; quarterTurns++) {
|
||||
canonical = Math.min(canonical, ((mask << quarterTurns) | (mask >>> (4 - quarterTurns))) & 15);
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
private static String expectedSpatialKey(int canonicalHorizontal, int vertical) {
|
||||
String horizontal = switch (canonicalHorizontal) {
|
||||
case 0 -> "blank";
|
||||
case 1 -> "end";
|
||||
case 3 -> "corner";
|
||||
case 5 -> "straight";
|
||||
case 7 -> "tee";
|
||||
case 15 -> "cross";
|
||||
default -> throw new AssertionError("Unexpected horizontal topology " + canonicalHorizontal);
|
||||
};
|
||||
String suffix = switch (vertical) {
|
||||
case 0 -> "";
|
||||
case 16 -> "-up";
|
||||
case 32 -> "-down";
|
||||
case 48 -> "-up-down";
|
||||
default -> throw new AssertionError("Unexpected vertical topology " + vertical);
|
||||
};
|
||||
return "kit/test/" + horizontal + suffix;
|
||||
}
|
||||
|
||||
private static Map<String, IrisJigsawPiece> pieces(StructureResourceBundle bundle) {
|
||||
Map<String, IrisJigsawPiece> pieces = new TreeMap<>();
|
||||
for (StructureResourceBundle.Resource resource : bundle.resources().values()) {
|
||||
String path = resource.relativePath();
|
||||
if (path.startsWith("jigsaw-pieces/") && path.endsWith(".json")) {
|
||||
pieces.put(
|
||||
path.substring("jigsaw-pieces/".length(), path.length() - ".json".length()),
|
||||
GSON.fromJson(new String(resource.content(), StandardCharsets.UTF_8), IrisJigsawPiece.class));
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
|
||||
private static IrisJigsawPool pool(StructureResourceBundle bundle, String key) {
|
||||
StructureResourceBundle.Resource resource = bundle.resources().get("jigsaw-pools/" + key + ".json");
|
||||
assertNotNull(key, resource);
|
||||
return GSON.fromJson(new String(resource.content(), StandardCharsets.UTF_8), IrisJigsawPool.class);
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.pack.value.IrisDirection;
|
||||
import art.arcane.iris.pack.value.IrisPosition;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawConnector;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawPiece;
|
||||
import art.arcane.iris.structure.jigsaw.JigsawJoint;
|
||||
import art.arcane.iris.structure.object.IrisObject;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JigsawStudioCaptureFinalStateTest {
|
||||
@Test
|
||||
public void hiddenConnectorOverDugOutAirStoresExplicitAirLikeVisibleMarkers() throws Throwable {
|
||||
JigsawStudioBounds bounds = new JigsawStudioBounds(0, 64, 0, new JigsawStudioCellDimensions(1, 1, 1));
|
||||
IrisJigsawPiece piece = new IrisJigsawPiece().setConnectors(new KList<>());
|
||||
piece.getConnectors().add(new IrisJigsawConnector()
|
||||
.setPosition(new IrisPosition(0, 0, 0))
|
||||
.setDirection(IrisDirection.NORTH_NEGATIVE_Z)
|
||||
.setTop(IrisDirection.UP_POSITIVE_Y)
|
||||
.setPool("fort/pieces")
|
||||
.setName("iris:door")
|
||||
.setTargetName("iris:door")
|
||||
.setJoint(JigsawJoint.ALIGNED)
|
||||
.setFinalState("minecraft:structure_void"));
|
||||
BlockData air = mock(BlockData.class);
|
||||
when(air.getMaterial()).thenReturn(Material.AIR);
|
||||
when(air.getAsString()).thenReturn("minecraft:air");
|
||||
Block block = mock(Block.class);
|
||||
when(block.getBlockData()).thenReturn(air);
|
||||
World world = mock(World.class);
|
||||
when(world.getBlockAt(0, 64, 0)).thenReturn(block);
|
||||
JigsawStudioCapture.ChunkCaptureArea area = JigsawStudioCapture.chunkIntersections(bounds).getFirst();
|
||||
|
||||
JigsawStudioCapture.ChunkSnapshot snapshot = JigsawStudioCapture.captureChunkIntersection(
|
||||
world, bounds, piece, new IrisObject(1, 1, 1), area, 0, false);
|
||||
JigsawStudioCapture.Capture capture = JigsawStudioCapture.aggregateSnapshots(
|
||||
bounds, List.of(area), List.of(snapshot));
|
||||
|
||||
assertEquals("minecraft:air", capture.connectors().getFirst().getFinalState());
|
||||
NativeBlockState restoredAir = mock(NativeBlockState.class);
|
||||
when(restoredAir.key()).thenReturn("minecraft:air");
|
||||
IrisObject restored = read(capture.objectContent(), restoredAir);
|
||||
assertTrue(restored.getBlocks().containsKey(restored.getSigned(0, 0, 0)));
|
||||
assertEquals("minecraft:air", restored.getBlocks().get(restored.getSigned(0, 0, 0)).key());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autosaveNotificationsFallBackToTheOnlineOwner() {
|
||||
Player explicit = mock(Player.class);
|
||||
Player owner = mock(Player.class);
|
||||
UUID ownerId = UUID.randomUUID();
|
||||
try (MockedStatic<Bukkit> server = mockStatic(Bukkit.class)) {
|
||||
server.when(() -> Bukkit.getPlayer(ownerId)).thenReturn(owner);
|
||||
|
||||
assertSame(explicit, JigsawStudioCaptureScheduler.notificationTarget(explicit, ownerId));
|
||||
assertSame(owner, JigsawStudioCaptureScheduler.notificationTarget(null, ownerId));
|
||||
assertNull(JigsawStudioCaptureScheduler.notificationTarget(null, null));
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisObject read(byte[] content, NativeBlockState restoredState) throws Throwable {
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(registries.block(anyString())).thenReturn(restoredState);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.unbind();
|
||||
IrisPlatforms.bind(platform);
|
||||
try {
|
||||
IrisObject restored = new IrisObject();
|
||||
restored.read(new ByteArrayInputStream(content));
|
||||
return restored;
|
||||
} finally {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
}
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.pack.value.IrisDirection;
|
||||
import art.arcane.iris.pack.value.IrisPosition;
|
||||
import art.arcane.iris.structure.jigsaw.IrisJigsawConnector;
|
||||
import art.arcane.iris.structure.jigsaw.JigsawJoint;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioCapture.LocalPosition;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Box;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.BlockClick;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Cell;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.CellContent;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.CellContents;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Decision;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Frame;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Identity;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Operation;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Ray;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.ReseatResult;
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioConnectorPlacement.Target;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JigsawStudioConnectorPlacementTest {
|
||||
private static final Box BOX = new Box(16, 65, 16, 20, 69, 20);
|
||||
private static final CellContents EMPTY = position -> Optional.empty();
|
||||
private static final Identity SPATIAL = new Identity("demo/pieces", "iris:spatial", "iris:spatial", JigsawJoint.ROLLABLE);
|
||||
|
||||
@Test
|
||||
public void clickedBlockInsideTheVariantBecomesTheConnectorFacingOutOfTheClickedFace() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(18.5D, 67.5D, 25.0D, 0.0D, 0.0D, -1.0D),
|
||||
Optional.of(new BlockClick(18, 67, 20, IrisDirection.SOUTH_POSITIVE_Z)),
|
||||
Set.of(),
|
||||
false);
|
||||
|
||||
assertEquals(Optional.of(new Target(18, 67, 20, IrisDirection.SOUTH_POSITIVE_Z)), target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sneakingFacesTheConnectorIntoTheClickedFace() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(18.5D, 67.5D, 25.0D, 0.0D, 0.0D, -1.0D),
|
||||
Optional.of(new BlockClick(18, 67, 20, IrisDirection.SOUTH_POSITIVE_Z)),
|
||||
Set.of(),
|
||||
true);
|
||||
|
||||
assertEquals(Optional.of(new Target(18, 67, 20, IrisDirection.NORTH_NEGATIVE_Z)), target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clickingTheStudioFloorPlacesADownConnectorOnTheBottomLayer() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(18.5D, 70.0D, 18.5D, 0.0D, -1.0D, 0.0D),
|
||||
Optional.of(new BlockClick(18, 64, 18, IrisDirection.UP_POSITIVE_Y)),
|
||||
Set.of(),
|
||||
false);
|
||||
|
||||
assertEquals(Optional.of(new Target(18, 65, 18, IrisDirection.DOWN_NEGATIVE_Y)), target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pointingAtAirFromInsideUsesTheBoundaryCellWhereTheRayLeaves() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(18.5D, 67.5D, 18.5D, 0.0D, 0.0D, -1.0D),
|
||||
Optional.empty(),
|
||||
Set.of(),
|
||||
false);
|
||||
|
||||
assertEquals(Optional.of(new Target(18, 67, 16, IrisDirection.NORTH_NEGATIVE_Z)), target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pointingAtAirFromOutsideUsesTheBoundaryCellWhereTheRayEnters() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(18.5D, 67.5D, 30.0D, 0.0D, 0.0D, -1.0D),
|
||||
Optional.empty(),
|
||||
Set.of(),
|
||||
false);
|
||||
|
||||
assertEquals(Optional.of(new Target(18, 67, 20, IrisDirection.SOUTH_POSITIVE_Z)), target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectorCellsInFrontOfTheClickedBlockWinSoHiddenConnectorsStaySelectable() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(18.5D, 67.5D, 18.5D, 0.0D, 0.0D, -1.0D),
|
||||
Optional.of(new BlockClick(18, 67, 16, IrisDirection.SOUTH_POSITIVE_Z)),
|
||||
Set.of(new Cell(18, 67, 17)),
|
||||
false);
|
||||
|
||||
assertEquals(Optional.of(new Target(18, 67, 17, IrisDirection.SOUTH_POSITIVE_Z)), target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void raysThatMissTheVariantResolveNothing() {
|
||||
Optional<Target> target = JigsawStudioConnectorPlacement.target(
|
||||
BOX,
|
||||
new Ray(30.5D, 67.5D, 30.5D, 0.0D, 0.0D, 1.0D),
|
||||
Optional.empty(),
|
||||
Set.of(),
|
||||
false);
|
||||
|
||||
assertTrue(target.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spatialRightClicksAddSelectMoveReorientAndDeselect() {
|
||||
LocalPosition door = new LocalPosition(2, 1, 0);
|
||||
LocalPosition other = new LocalPosition(4, 1, 2);
|
||||
LocalPosition empty = new LocalPosition(0, 1, 2);
|
||||
Map<LocalPosition, IrisDirection> connectors = Map.of(
|
||||
door, IrisDirection.NORTH_NEGATIVE_Z,
|
||||
other, IrisDirection.EAST_POSITIVE_X);
|
||||
|
||||
assertEquals(Operation.ADD, spatial(false, empty, IrisDirection.WEST_NEGATIVE_X, connectors, null));
|
||||
assertEquals(Operation.SELECT, spatial(false, door, IrisDirection.NORTH_NEGATIVE_Z, connectors, null));
|
||||
assertEquals(Operation.MOVE, spatial(false, empty, IrisDirection.WEST_NEGATIVE_X, connectors, door));
|
||||
assertEquals(Operation.DESELECT, spatial(false, door, IrisDirection.NORTH_NEGATIVE_Z, connectors, door));
|
||||
assertEquals(Operation.REORIENT, spatial(false, door, IrisDirection.UP_POSITIVE_Y, connectors, door));
|
||||
assertEquals(Operation.SELECT, spatial(false, other, IrisDirection.EAST_POSITIVE_X, connectors, door));
|
||||
assertEquals(Operation.ADD, spatial(false, empty, IrisDirection.WEST_NEGATIVE_X, connectors, empty));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spatialLeftClicksRemoveOrClearTheSelection() {
|
||||
LocalPosition door = new LocalPosition(2, 1, 0);
|
||||
Map<LocalPosition, IrisDirection> connectors = Map.of(door, IrisDirection.NORTH_NEGATIVE_Z);
|
||||
LocalPosition empty = new LocalPosition(1, 1, 1);
|
||||
|
||||
assertEquals(Operation.REMOVE, spatial(true, door, IrisDirection.NORTH_NEGATIVE_Z, connectors, null));
|
||||
assertEquals(Operation.CLEAR_SELECTION, spatial(true, empty, IrisDirection.UP_POSITIVE_Y, connectors, door));
|
||||
assertEquals(Operation.NONE, spatial(true, empty, IrisDirection.UP_POSITIVE_Y, connectors, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planarRightClicksReseatOnlyTheWorkcellsTopologySides() {
|
||||
JigsawStudioCellDimensions dimensions = new JigsawStudioCellDimensions(5, 5, 5);
|
||||
Map<LocalPosition, IrisDirection> connectors = Map.of();
|
||||
|
||||
Decision east = JigsawStudioConnectorPlacement.decidePlanar(false, new LocalPosition(4, 2, 2),
|
||||
IrisDirection.EAST_POSITIVE_X, dimensions, JigsawPlanarTopology.NORTH_EAST_WEST_TEE, connectors, "T Junction");
|
||||
Decision south = JigsawStudioConnectorPlacement.decidePlanar(false, new LocalPosition(2, 2, 4),
|
||||
IrisDirection.SOUTH_POSITIVE_Z, dimensions, JigsawPlanarTopology.NORTH_EAST_WEST_TEE, connectors, "T Junction");
|
||||
Decision floor = JigsawStudioConnectorPlacement.decidePlanar(false, new LocalPosition(0, 0, 3),
|
||||
IrisDirection.DOWN_NEGATIVE_Y, dimensions, JigsawPlanarTopology.NORTH_EAST_WEST_TEE, connectors, "T Junction");
|
||||
|
||||
assertEquals(Operation.RESEAT, east.operation());
|
||||
assertEquals(JigsawPlanarDirection.EAST, east.side());
|
||||
assertEquals(Operation.REFUSE, south.operation());
|
||||
assertTrue(south.message().contains("north, east, and west"));
|
||||
assertEquals(Operation.RESEAT, floor.operation());
|
||||
assertEquals(JigsawPlanarDirection.WEST, floor.side());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planarLeftClicksRefuseTheOnlySocketOfAConnectedSideButRemoveStrays() {
|
||||
JigsawStudioCellDimensions dimensions = new JigsawStudioCellDimensions(5, 5, 5);
|
||||
LocalPosition socket = new LocalPosition(2, 2, 0);
|
||||
LocalPosition stray = new LocalPosition(1, 2, 0);
|
||||
Map<LocalPosition, IrisDirection> single = Map.of(socket, IrisDirection.NORTH_NEGATIVE_Z);
|
||||
Map<LocalPosition, IrisDirection> doubled = Map.of(
|
||||
socket, IrisDirection.NORTH_NEGATIVE_Z,
|
||||
stray, IrisDirection.NORTH_NEGATIVE_Z);
|
||||
|
||||
Decision refused = JigsawStudioConnectorPlacement.decidePlanar(true, socket,
|
||||
IrisDirection.NORTH_NEGATIVE_Z, dimensions, JigsawPlanarTopology.NORTH_END, single, "End Cap");
|
||||
Decision removed = JigsawStudioConnectorPlacement.decidePlanar(true, stray,
|
||||
IrisDirection.NORTH_NEGATIVE_Z, dimensions, JigsawPlanarTopology.NORTH_END, doubled, "End Cap");
|
||||
|
||||
assertEquals(Operation.REFUSE, refused.operation());
|
||||
assertEquals(Operation.REMOVE, removed.operation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void framesRoundTripPositionsAndDirectionsForEveryQuarterTurn() {
|
||||
JigsawStudioCellDimensions source = new JigsawStudioCellDimensions(6, 3, 4);
|
||||
for (int quarterTurns = 0; quarterTurns < 4; quarterTurns++) {
|
||||
Frame frame = new Frame(source, quarterTurns);
|
||||
for (int x = 0; x < 6; x++) {
|
||||
for (int z = 0; z < 4; z++) {
|
||||
IrisPosition position = new IrisPosition(x, 1, z);
|
||||
LocalPosition display = frame.display(position);
|
||||
JigsawStudioCellDimensions dimensions = frame.displayDimensions();
|
||||
assertTrue(display.x() >= 0 && display.x() < dimensions.width());
|
||||
assertTrue(display.z() >= 0 && display.z() < dimensions.depth());
|
||||
assertEquals(position, frame.source(display));
|
||||
}
|
||||
}
|
||||
for (IrisDirection direction : IrisDirection.values()) {
|
||||
assertEquals(direction, frame.sourceDirection(frame.displayDirection(direction)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addConvertsDisplayPlacementIntoSourceSpaceWithDefaultIdentity() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(6, 3, 4), 1);
|
||||
LocalPosition target = new LocalPosition(0, 1, 2);
|
||||
|
||||
List<IrisJigsawConnector> added = JigsawStudioConnectorPlacement.add(
|
||||
List.of(), frame, target, IrisDirection.WEST_NEGATIVE_X, IrisDirection.UP_POSITIVE_Y, SPATIAL, EMPTY);
|
||||
|
||||
IrisJigsawConnector connector = added.getFirst();
|
||||
assertEquals(target, frame.display(connector.getPosition()));
|
||||
assertEquals(IrisDirection.WEST_NEGATIVE_X, frame.displayDirection(connector.getDirection()));
|
||||
assertEquals(IrisDirection.UP_POSITIVE_Y, connector.getTop());
|
||||
assertEquals("demo/pieces", connector.getPool());
|
||||
assertEquals("iris:spatial", connector.getName());
|
||||
assertEquals("iris:spatial", connector.getTargetName());
|
||||
assertEquals(JigsawJoint.ROLLABLE, connector.getJoint());
|
||||
assertEquals("minecraft:structure_void", connector.getFinalState());
|
||||
assertEquals("", connector.getChannel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectorsPlacedOnBuiltBlocksKeepThatBlockAsTheirFinalState() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(5, 5, 5), 0);
|
||||
CellContents wall = position -> Optional.of(new CellContent("minecraft:stone_bricks", false));
|
||||
CellContents chest = position -> Optional.of(new CellContent("minecraft:chest[facing=north]", true));
|
||||
|
||||
List<IrisJigsawConnector> added = JigsawStudioConnectorPlacement.add(
|
||||
List.of(), frame, new LocalPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z,
|
||||
IrisDirection.UP_POSITIVE_Y, SPATIAL, wall);
|
||||
|
||||
assertEquals("minecraft:stone_bricks", added.getFirst().getFinalState());
|
||||
assertThrows(IllegalArgumentException.class, () -> JigsawStudioConnectorPlacement.add(
|
||||
List.of(), frame, new LocalPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z,
|
||||
IrisDirection.UP_POSITIVE_Y, SPATIAL, chest));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addingOntoAnExistingConnectorIsRejected() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(5, 5, 5), 0);
|
||||
List<IrisJigsawConnector> source = List.of(connector(new IrisPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> JigsawStudioConnectorPlacement.add(
|
||||
source, frame, new LocalPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z,
|
||||
IrisDirection.UP_POSITIVE_Y, SPATIAL, EMPTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void moveKeepsIdentityAndOrderAndOnlyReplacesTheFinalStateOnBuiltBlocks() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(5, 5, 5), 0);
|
||||
IrisJigsawConnector first = connector(new IrisPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z)
|
||||
.setChannel("hall").setFinalState("minecraft:air");
|
||||
IrisJigsawConnector second = connector(new IrisPosition(4, 1, 2), IrisDirection.EAST_POSITIVE_X);
|
||||
List<IrisJigsawConnector> source = List.of(first, second);
|
||||
|
||||
List<IrisJigsawConnector> moved = JigsawStudioConnectorPlacement.move(
|
||||
source, frame, new LocalPosition(2, 1, 0), new LocalPosition(0, 1, 2),
|
||||
IrisDirection.WEST_NEGATIVE_X, IrisDirection.UP_POSITIVE_Y, EMPTY);
|
||||
List<IrisJigsawConnector> onWall = JigsawStudioConnectorPlacement.move(
|
||||
source, frame, new LocalPosition(2, 1, 0), new LocalPosition(0, 1, 2),
|
||||
IrisDirection.WEST_NEGATIVE_X, IrisDirection.UP_POSITIVE_Y,
|
||||
position -> Optional.of(new CellContent("minecraft:stone", false)));
|
||||
|
||||
assertEquals(2, moved.size());
|
||||
assertEquals(new IrisPosition(0, 1, 2), moved.getFirst().getPosition());
|
||||
assertEquals(IrisDirection.WEST_NEGATIVE_X, moved.getFirst().getDirection());
|
||||
assertEquals("hall", moved.getFirst().getChannel());
|
||||
assertEquals("demo/pieces", moved.getFirst().getPool());
|
||||
assertEquals("minecraft:air", moved.getFirst().getFinalState());
|
||||
assertEquals("minecraft:stone", onWall.getFirst().getFinalState());
|
||||
assertEquals(second.getPosition(), moved.get(1).getPosition());
|
||||
assertEquals(new IrisPosition(2, 1, 0), first.getPosition());
|
||||
assertThrows(IllegalArgumentException.class, () -> JigsawStudioConnectorPlacement.move(
|
||||
source, frame, new LocalPosition(2, 1, 0), new LocalPosition(4, 1, 2),
|
||||
IrisDirection.EAST_POSITIVE_X, IrisDirection.UP_POSITIVE_Y, EMPTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeDropsOnlyTheConnectorAtThatDisplayPosition() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(5, 5, 5), 2);
|
||||
IrisJigsawConnector north = connector(new IrisPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z);
|
||||
IrisJigsawConnector east = connector(new IrisPosition(4, 1, 2), IrisDirection.EAST_POSITIVE_X);
|
||||
|
||||
List<IrisJigsawConnector> remaining = JigsawStudioConnectorPlacement.remove(
|
||||
List.of(north, east), frame, frame.display(north.getPosition()));
|
||||
|
||||
assertEquals(1, remaining.size());
|
||||
assertEquals(east.getPosition(), remaining.getFirst().getPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reseatSnapsAMovedSocketBackToTheSourceFaceCenterAndDropsStrays() {
|
||||
JigsawStudioCellDimensions source = new JigsawStudioCellDimensions(4, 4, 6);
|
||||
Frame frame = new Frame(source, 1);
|
||||
IrisJigsawConnector moved = connector(new IrisPosition(0, 1, 0), IrisDirection.NORTH_NEGATIVE_Z).setChannel("road");
|
||||
IrisJigsawConnector stray = connector(new IrisPosition(3, 1, 0), IrisDirection.NORTH_NEGATIVE_Z);
|
||||
IrisJigsawConnector south = connector(
|
||||
IrisJigsawConnector.canonicalPlanarPosition(new IrisPosition(4, 4, 6), IrisDirection.SOUTH_POSITIVE_Z),
|
||||
IrisDirection.SOUTH_POSITIVE_Z);
|
||||
JigsawPlanarDirection displaySide = JigsawPlanarDirection.EAST;
|
||||
assertEquals(IrisDirection.NORTH_NEGATIVE_Z, frame.sourceDirection(displaySide.irisDirection()));
|
||||
|
||||
ReseatResult result = JigsawStudioConnectorPlacement.reseat(
|
||||
List.of(moved, stray, south), frame, displaySide, SPATIAL, EMPTY);
|
||||
|
||||
assertTrue(result.changed());
|
||||
assertEquals(1, result.removedStrays());
|
||||
assertEquals(2, result.connectors().size());
|
||||
IrisJigsawConnector seated = result.connectors().getFirst();
|
||||
assertEquals(IrisJigsawConnector.canonicalPlanarPosition(
|
||||
new IrisPosition(4, 4, 6), IrisDirection.NORTH_NEGATIVE_Z), seated.getPosition());
|
||||
assertEquals("road", seated.getChannel());
|
||||
assertEquals(south.getPosition(), result.connectors().get(1).getPosition());
|
||||
|
||||
ReseatResult unchanged = JigsawStudioConnectorPlacement.reseat(
|
||||
result.connectors(), frame, displaySide, SPATIAL, EMPTY);
|
||||
assertFalse(unchanged.changed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reseatAddsAMissingSocketWithTheDefaultIdentity() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(5, 5, 5), 0);
|
||||
Identity planar = new Identity("demo/pieces", "iris:planar", "iris:planar", JigsawJoint.ALIGNED);
|
||||
|
||||
ReseatResult result = JigsawStudioConnectorPlacement.reseat(
|
||||
List.of(), frame, JigsawPlanarDirection.WEST, planar, EMPTY);
|
||||
|
||||
assertTrue(result.changed());
|
||||
IrisJigsawConnector added = result.connectors().getFirst();
|
||||
assertEquals(new IrisPosition(0, 2, 2), added.getPosition());
|
||||
assertEquals(IrisDirection.WEST_NEGATIVE_X, added.getDirection());
|
||||
assertEquals(IrisDirection.UP_POSITIVE_Y, added.getTop());
|
||||
assertEquals(JigsawJoint.ALIGNED, added.getJoint());
|
||||
assertEquals("iris:planar", added.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changedPositionsListEveryDisplayCellWhoseConnectorDiffers() {
|
||||
Frame frame = new Frame(new JigsawStudioCellDimensions(5, 5, 5), 0);
|
||||
IrisJigsawConnector north = connector(new IrisPosition(2, 1, 0), IrisDirection.NORTH_NEGATIVE_Z);
|
||||
IrisJigsawConnector east = connector(new IrisPosition(4, 1, 2), IrisDirection.EAST_POSITIVE_X);
|
||||
List<IrisJigsawConnector> before = List.of(north, east);
|
||||
List<IrisJigsawConnector> after = JigsawStudioConnectorPlacement.move(
|
||||
before, frame, new LocalPosition(2, 1, 0), new LocalPosition(0, 1, 2),
|
||||
IrisDirection.WEST_NEGATIVE_X, IrisDirection.UP_POSITIVE_Y, EMPTY);
|
||||
|
||||
Set<LocalPosition> changed = JigsawStudioConnectorPlacement.changedPositions(before, after, frame);
|
||||
|
||||
assertEquals(Set.of(new LocalPosition(2, 1, 0), new LocalPosition(0, 1, 2)), changed);
|
||||
}
|
||||
|
||||
private static Operation spatial(
|
||||
boolean primary,
|
||||
LocalPosition target,
|
||||
IrisDirection facing,
|
||||
Map<LocalPosition, IrisDirection> connectors,
|
||||
LocalPosition selection
|
||||
) {
|
||||
return JigsawStudioConnectorPlacement.decideSpatial(
|
||||
primary, target, facing, new LinkedHashMap<>(connectors), Optional.ofNullable(selection)).operation();
|
||||
}
|
||||
|
||||
private static IrisJigsawConnector connector(IrisPosition position, IrisDirection direction) {
|
||||
return new IrisJigsawConnector()
|
||||
.setPosition(position)
|
||||
.setDirection(direction)
|
||||
.setTop(IrisDirection.UP_POSITIVE_Y)
|
||||
.setPool("demo/pieces")
|
||||
.setName("iris:spatial")
|
||||
.setTargetName("iris:spatial")
|
||||
.setJoint(JigsawJoint.ROLLABLE)
|
||||
.setFinalState("minecraft:structure_void");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.studio.jigsaw.JigsawStudioCapture.LocalPosition;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JigsawStudioConnectorRepairTest {
|
||||
@Test
|
||||
public void resetFindsUnsavedMarkersSoTopologyErrorsCanBeRepaired() {
|
||||
JigsawStudioBounds bounds = new JigsawStudioBounds(0, 65, 0, new JigsawStudioCellDimensions(3, 2, 3));
|
||||
World world = mock(World.class);
|
||||
Block air = mock(Block.class);
|
||||
when(air.getType()).thenReturn(Material.AIR);
|
||||
Block saved = mock(Block.class);
|
||||
when(saved.getType()).thenReturn(Material.JIGSAW);
|
||||
Block stray = mock(Block.class);
|
||||
when(stray.getType()).thenReturn(Material.JIGSAW);
|
||||
when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenReturn(air);
|
||||
when(world.getBlockAt(1, 65, 0)).thenReturn(saved);
|
||||
when(world.getBlockAt(1, 66, 1)).thenReturn(stray);
|
||||
JigsawStudioCapture.ChunkCaptureArea area = JigsawStudioCapture.chunkIntersections(bounds).getFirst();
|
||||
Set<LocalPosition> savedConnectors = Set.of(new LocalPosition(1, 0, 0));
|
||||
|
||||
assertEquals(
|
||||
Set.of(new LocalPosition(1, 1, 1)),
|
||||
JigsawStudioMaterializer.strayMarkerPositions(world, bounds, area, savedConnectors, true));
|
||||
assertEquals(
|
||||
Set.of(new LocalPosition(1, 0, 0), new LocalPosition(1, 1, 1)),
|
||||
JigsawStudioMaterializer.strayMarkerPositions(world, bounds, area, savedConnectors, false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.studio.generation.JigsawStudioGenerator;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JigsawStudioEditorRoutingTest {
|
||||
private static final UUID REQUEST = UUID.fromString("66666666-6666-6666-6666-666666666666");
|
||||
|
||||
@Test
|
||||
public void renamingASpatialWorkcellRenamesTheVariantThatLabelsIt() throws Exception {
|
||||
JigsawStudioVariant variant = new JigsawStudioVariant(
|
||||
"demo/cross",
|
||||
"demo/cross",
|
||||
"",
|
||||
Optional.of(new JigsawStudioCellDimensions(15, 15, 15)),
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
Optional.empty(),
|
||||
true,
|
||||
true,
|
||||
List.of(),
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
List.of());
|
||||
JigsawStudioLayout layout = JigsawStudioLayout.create(
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
new JigsawStudioCellDimensions(15, 15, 15),
|
||||
new JigsawStudioVariantCatalog(List.of(variant)));
|
||||
JigsawStudioSession session = new JigsawStudioSession(REQUEST, "overworld", "demo", layout);
|
||||
JigsawStudioService service = new JigsawStudioService();
|
||||
JigsawStudioVariantProperties properties = mock(JigsawStudioVariantProperties.class);
|
||||
setField(service, "variantProperties", properties);
|
||||
Player player = register(service, session);
|
||||
|
||||
try (MockedStatic<J> scheduling = mockStatic(J.class)) {
|
||||
scheduling.when(() -> J.isOwnedByCurrentRegion(any(Player.class))).thenReturn(true);
|
||||
|
||||
service.workcellEditor.updateWorkcellDisplayName(player, JigsawStudioLayout.SPATIAL_WORKCELL_ID, "Great Hall");
|
||||
|
||||
verify(properties).updateVariantDisplayName(
|
||||
player, JigsawStudioLayout.SPATIAL_WORKCELL_ID, "demo/cross", "Great Hall");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pistonHeadsCannotExtendIntoProtectedStudioCells() throws Exception {
|
||||
JigsawStudioService service = new JigsawStudioService();
|
||||
JigsawStudioProtection protection = mock(JigsawStudioProtection.class);
|
||||
setField(service, "protection", protection);
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||
Block piston = mock(Block.class);
|
||||
Block head = mock(Block.class);
|
||||
when(piston.getWorld()).thenReturn(world);
|
||||
when(piston.getRelative(BlockFace.EAST)).thenReturn(head);
|
||||
when(protection.isImmutableStudioBlock(head)).thenReturn(true);
|
||||
BlockPistonExtendEvent event = new BlockPistonExtendEvent(piston, List.of(), BlockFace.EAST);
|
||||
|
||||
service.protectionListener.onProtectedPistonExtend(event);
|
||||
|
||||
assertTrue(event.isCancelled());
|
||||
}
|
||||
|
||||
private static Player register(JigsawStudioService service, JigsawStudioSession session) throws Exception {
|
||||
UUID worldId = UUID.randomUUID();
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(worldId);
|
||||
Player player = mock(Player.class);
|
||||
when(player.getWorld()).thenReturn(world);
|
||||
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
JigsawStudioActivation.Request request = new JigsawStudioActivation.Request(
|
||||
REQUEST,
|
||||
"overworld",
|
||||
"demo",
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
JigsawStudioCompatibilityTarget.IRIS_EXTENDED,
|
||||
new JigsawStudioCellDimensions(15, 15, 15),
|
||||
mock(IrisData.class),
|
||||
null);
|
||||
JigsawStudioGenerator generator = mock(JigsawStudioGenerator.class);
|
||||
when(generator.getRequest()).thenReturn(request);
|
||||
when(generator.getSession()).thenReturn(session);
|
||||
when(generator.getLayout()).thenAnswer(invocation -> session.layout());
|
||||
Class<?> studioType = Class.forName(JigsawStudioService.class.getName() + "$ActiveStudio");
|
||||
Constructor<?> constructor = studioType.getDeclaredConstructor(
|
||||
UUID.class,
|
||||
World.class,
|
||||
Engine.class,
|
||||
JigsawStudioGenerator.class,
|
||||
ConcurrentHashMap.class,
|
||||
Set.class,
|
||||
AtomicLong.class);
|
||||
constructor.setAccessible(true);
|
||||
Object studio = constructor.newInstance(
|
||||
worldId,
|
||||
world,
|
||||
mock(Engine.class),
|
||||
generator,
|
||||
new ConcurrentHashMap<>(),
|
||||
ConcurrentHashMap.newKeySet(),
|
||||
new AtomicLong());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<UUID, Object> studios = (Map<UUID, Object>) getField(service, "studios");
|
||||
studios.put(worldId, studio);
|
||||
return player;
|
||||
}
|
||||
|
||||
private static void setField(Object target, String name, Object value) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private static Object getField(Object target, String name) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.pack.value.IrisDirection;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JigsawStudioFloorGlyphTest {
|
||||
@Test
|
||||
public void emptyConnectorListPaintsOnlyTheBase() {
|
||||
JigsawStudioFloorGlyph glyph = JigsawStudioFloorGlyph.of(5, 5, List.of());
|
||||
|
||||
assertRows(glyph, 5, 5, List.of(".....", ".....", ".....", ".....", "....."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canonicalPlanarSocketsDrawStraightLinesFromTheCenter() {
|
||||
Map<JigsawPlanarArchetype, List<String>> expected = Map.of(
|
||||
JigsawPlanarArchetype.END, List.of("..o..", "..#..", "..#..", ".....", "....."),
|
||||
JigsawPlanarArchetype.STRAIGHT, List.of("..o..", "..#..", "..#..", "..#..", "..o.."),
|
||||
JigsawPlanarArchetype.CORNER, List.of("..o..", "..#..", "..##o", ".....", "....."),
|
||||
JigsawPlanarArchetype.TEE, List.of("..o..", "..#..", "o###o", ".....", "....."),
|
||||
JigsawPlanarArchetype.CROSS, List.of("..o..", "..#..", "o###o", "..#..", "..o.."));
|
||||
|
||||
for (Map.Entry<JigsawPlanarArchetype, List<String>> entry : expected.entrySet()) {
|
||||
List<JigsawStudioFloorGlyph.Marker> markers = new ArrayList<>();
|
||||
for (JigsawPlanarDirection direction : entry.getKey().canonicalTopology().directions()) {
|
||||
markers.add(canonicalMarker(5, 5, direction.irisDirection()));
|
||||
}
|
||||
assertRows(JigsawStudioFloorGlyph.of(5, 5, markers), 5, 5, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evenFootprintsUseFloorDivisionForTheCenter() {
|
||||
JigsawStudioFloorGlyph glyph = JigsawStudioFloorGlyph.of(4, 4, List.of(
|
||||
canonicalMarker(4, 4, IrisDirection.NORTH_NEGATIVE_Z),
|
||||
canonicalMarker(4, 4, IrisDirection.WEST_NEGATIVE_X)));
|
||||
|
||||
assertRows(glyph, 4, 4, List.of("..o.", "..#.", "o##.", "...."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalConnectorsUseDistinctSocketsAndMergeOnOneColumn() {
|
||||
JigsawStudioFloorGlyph up = JigsawStudioFloorGlyph.of(5, 5, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(2, 2, IrisDirection.UP_POSITIVE_Y)));
|
||||
JigsawStudioFloorGlyph down = JigsawStudioFloorGlyph.of(5, 5, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(2, 2, IrisDirection.DOWN_NEGATIVE_Y)));
|
||||
JigsawStudioFloorGlyph both = JigsawStudioFloorGlyph.of(5, 5, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(2, 2, IrisDirection.UP_POSITIVE_Y),
|
||||
new JigsawStudioFloorGlyph.Marker(2, 2, IrisDirection.DOWN_NEGATIVE_Y),
|
||||
canonicalMarker(5, 5, IrisDirection.EAST_POSITIVE_X)));
|
||||
|
||||
assertEquals(JigsawStudioFloorGlyph.Cell.UP_SOCKET, up.cell(2, 2));
|
||||
assertEquals(JigsawStudioFloorGlyph.Cell.DOWN_SOCKET, down.cell(2, 2));
|
||||
assertRows(both, 5, 5, List.of(".....", ".....", "..B#o", ".....", "....."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offCenterVerticalConnectorsGetALineToTheirColumn() {
|
||||
JigsawStudioFloorGlyph glyph = JigsawStudioFloorGlyph.of(5, 5, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(0, 2, IrisDirection.DOWN_NEGATIVE_Y)));
|
||||
|
||||
assertRows(glyph, 5, 5, List.of(".....", ".....", "v##..", ".....", "....."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interiorHorizontalConnectorsExtendTheirLineOutOfTheFaceTheyOpen() {
|
||||
JigsawStudioFloorGlyph glyph = JigsawStudioFloorGlyph.of(5, 5, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(3, 2, IrisDirection.SOUTH_POSITIVE_Z)));
|
||||
|
||||
assertRows(glyph, 5, 5, List.of(".....", ".....", "..#o.", "...#.", "...#."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diagonalLinesStayConnectedAndInsideTheFootprint() {
|
||||
JigsawStudioFloorGlyph glyph = JigsawStudioFloorGlyph.of(7, 5, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(6, 0, IrisDirection.EAST_POSITIVE_X)));
|
||||
|
||||
assertEquals(JigsawStudioFloorGlyph.Cell.HORIZONTAL_SOCKET, glyph.cell(6, 0));
|
||||
assertEquals(JigsawStudioFloorGlyph.Cell.LINE, glyph.cell(3, 2));
|
||||
int lineCells = 0;
|
||||
for (int z = 0; z < 5; z++) {
|
||||
for (int x = 0; x < 7; x++) {
|
||||
if (glyph.cell(x, z) == JigsawStudioFloorGlyph.Cell.LINE) {
|
||||
lineCells++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(3, lineCells);
|
||||
assertEquals(JigsawStudioFloorGlyph.Cell.BASE, glyph.cell(-1, 0));
|
||||
assertEquals(JigsawStudioFloorGlyph.Cell.BASE, glyph.cell(7, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void markersOutsideTheFootprintAreClampedToItsEdge() {
|
||||
JigsawStudioFloorGlyph glyph = JigsawStudioFloorGlyph.of(3, 3, List.of(
|
||||
new JigsawStudioFloorGlyph.Marker(9, 1, IrisDirection.EAST_POSITIVE_X)));
|
||||
|
||||
assertRows(glyph, 3, 3, List.of("...", ".#o", "..."));
|
||||
}
|
||||
|
||||
private static JigsawStudioFloorGlyph.Marker canonicalMarker(int width, int depth, IrisDirection direction) {
|
||||
int centerX = width / 2;
|
||||
int centerZ = depth / 2;
|
||||
return switch (direction) {
|
||||
case NORTH_NEGATIVE_Z -> new JigsawStudioFloorGlyph.Marker(centerX, 0, direction);
|
||||
case SOUTH_POSITIVE_Z -> new JigsawStudioFloorGlyph.Marker(centerX, depth - 1, direction);
|
||||
case EAST_POSITIVE_X -> new JigsawStudioFloorGlyph.Marker(width - 1, centerZ, direction);
|
||||
case WEST_NEGATIVE_X -> new JigsawStudioFloorGlyph.Marker(0, centerZ, direction);
|
||||
case UP_POSITIVE_Y, DOWN_NEGATIVE_Y -> new JigsawStudioFloorGlyph.Marker(centerX, centerZ, direction);
|
||||
};
|
||||
}
|
||||
|
||||
private static void assertRows(JigsawStudioFloorGlyph glyph, int width, int depth, List<String> rows) {
|
||||
for (int z = 0; z < depth; z++) {
|
||||
StringBuilder actual = new StringBuilder(width);
|
||||
for (int x = 0; x < width; x++) {
|
||||
actual.append(symbol(glyph.cell(x, z)));
|
||||
}
|
||||
assertEquals("row " + z, rows.get(z), actual.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static char symbol(JigsawStudioFloorGlyph.Cell cell) {
|
||||
return switch (cell) {
|
||||
case BASE -> '.';
|
||||
case LINE -> '#';
|
||||
case HORIZONTAL_SOCKET -> 'o';
|
||||
case UP_SOCKET -> '^';
|
||||
case DOWN_SOCKET -> 'v';
|
||||
case UP_AND_DOWN_SOCKET -> 'B';
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,60 @@ public class JigsawStudioGraphMapperTest {
|
||||
assertEquals(new JigsawStudioCellDimensions(24, 32, 24), dimensions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void spatialFootprintRejectsChangesThatExceedTheWorkcellVolume() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisObject> objectLoader = mock(ResourceLoader.class);
|
||||
when(data.getObjectLoader()).thenReturn(objectLoader);
|
||||
when(objectLoader.load("stronghold/tower")).thenReturn(new IrisObject(16, 192, 16));
|
||||
when(objectLoader.load("stronghold/hall")).thenReturn(new IrisObject(128, 10, 20));
|
||||
JigsawStudioVariantCatalog catalog = new JigsawStudioVariantCatalog(List.of(
|
||||
spatialVariant("stronghold/tower", new JigsawStudioCellDimensions(16, 192, 16), false),
|
||||
spatialVariant("stronghold/hall", new JigsawStudioCellDimensions(128, 10, 20), false)));
|
||||
JigsawStudioCellDimensions configured = new JigsawStudioCellDimensions(16, 16, 16);
|
||||
|
||||
assertEquals("", JigsawStudioGraphMapper.spatialFootprintFailure(
|
||||
data,
|
||||
configured,
|
||||
catalog,
|
||||
new JigsawStudioGraphMapper.SpatialChange("stronghold/hall", new JigsawStudioCellDimensions(128, 10, 20), false)));
|
||||
assertFalse(JigsawStudioGraphMapper.spatialFootprintFailure(
|
||||
data,
|
||||
configured,
|
||||
catalog,
|
||||
new JigsawStudioGraphMapper.SpatialChange("stronghold/hall", new JigsawStudioCellDimensions(128, 10, 20), true)).isEmpty());
|
||||
assertFalse(JigsawStudioGraphMapper.spatialFootprintFailure(
|
||||
data,
|
||||
configured,
|
||||
catalog,
|
||||
new JigsawStudioGraphMapper.SpatialChange("stronghold/hall", new JigsawStudioCellDimensions(128, 10, 128), false)).isEmpty());
|
||||
assertEquals("", JigsawStudioGraphMapper.spatialFootprintFailure(
|
||||
data,
|
||||
configured,
|
||||
catalog,
|
||||
new JigsawStudioGraphMapper.SpatialChange("stronghold/tower", new JigsawStudioCellDimensions(16, 64, 16), false)));
|
||||
}
|
||||
|
||||
private static JigsawStudioVariant spatialVariant(
|
||||
String key,
|
||||
JigsawStudioCellDimensions dimensions,
|
||||
boolean rotatable
|
||||
) {
|
||||
return new JigsawStudioVariant(
|
||||
key,
|
||||
key,
|
||||
"",
|
||||
Optional.of(dimensions),
|
||||
JigsawStudioMode.SPATIAL_JIGSAW,
|
||||
Optional.empty(),
|
||||
rotatable,
|
||||
true,
|
||||
List.of(),
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void mapsAuthorLabelsAndCanonicalPerVariantDimensionsIndependently() {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class JigsawStudioLayoutColumnTest {
|
||||
@Test
|
||||
public void columnLookupFocusesTheWorkcellFromItsFloorToTheTeleportHeight() {
|
||||
JigsawStudioLayout layout = JigsawStudioLayout.create(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
new JigsawStudioCellDimensions(7, 5, 7),
|
||||
JigsawStudioVariantCatalog.empty());
|
||||
JigsawStudioBay cross = layout.get("workcell/cross");
|
||||
JigsawStudioBounds bounds = cross.bounds();
|
||||
int x = bounds.originX() + 3;
|
||||
int z = bounds.originZ() + 3;
|
||||
|
||||
assertSame(cross, layout.findColumn(x, bounds.originY() - 1, z));
|
||||
assertSame(cross, layout.findColumn(x, bounds.maxY() + 2, z));
|
||||
assertSame(cross, layout.findColumn(x, bounds.maxY() + JigsawStudioLayout.COLUMN_HEADROOM, z));
|
||||
assertNull(layout.findColumn(x, bounds.maxY() + JigsawStudioLayout.COLUMN_HEADROOM + 1, z));
|
||||
assertNull(layout.findColumn(bounds.originX() - 1, bounds.originY(), z));
|
||||
assertNull(layout.findAt(x, bounds.maxY() + 2, z));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JigsawStudioMainMenuRowTest {
|
||||
@Test
|
||||
public void planarRowKeepsAllSixWorkcellsLeftOfTheRowControls() {
|
||||
JigsawStudioMainMenu.WorkcellRow row = JigsawStudioMainMenu.workcellRow(6, 4);
|
||||
|
||||
assertEquals(List.of(0, 1, 2, 3, 4, 5), row.indices());
|
||||
assertEquals(List.of(-3, -2, -1, 0, 1, 2), row.slots());
|
||||
assertEquals(-1, row.previousTarget());
|
||||
assertEquals(-1, row.nextTarget());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void largeSpatialRowsPageWithoutHidingOrOverlappingWorkcells() {
|
||||
for (int count : new int[]{7, 24}) {
|
||||
Set<Integer> reachable = new HashSet<>();
|
||||
for (int viewed = 0; viewed < count; viewed++) {
|
||||
JigsawStudioMainMenu.WorkcellRow row = JigsawStudioMainMenu.workcellRow(count, viewed);
|
||||
assertTrue(row.indices().contains(viewed));
|
||||
assertEquals(row.indices().size(), new HashSet<>(row.slots()).size());
|
||||
for (int slot : row.slots()) {
|
||||
assertTrue(slot >= -3 && slot <= 1);
|
||||
}
|
||||
reachable.addAll(row.indices());
|
||||
if (row.nextTarget() >= 0) {
|
||||
assertTrue(JigsawStudioMainMenu.workcellRow(count, row.nextTarget()).indices()
|
||||
.contains(row.nextTarget()));
|
||||
}
|
||||
}
|
||||
assertEquals(count, reachable.size());
|
||||
}
|
||||
JigsawStudioMainMenu.WorkcellRow last = JigsawStudioMainMenu.workcellRow(24, 23);
|
||||
assertEquals(List.of(20, 21, 22, 23), last.indices());
|
||||
assertEquals(15, last.previousTarget());
|
||||
assertEquals(-1, last.nextTarget());
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -491,7 +491,7 @@ public class JigsawStudioPersistenceEditorsTest {
|
||||
JigsawStudioGraphEditor.VariantFamilyCreation creation = JigsawStudioGraphEditor.duplicateActiveFamily(
|
||||
packRoot,
|
||||
"studio/spatial",
|
||||
Map.of(JigsawStudioLayout.SPATIAL_WORKCELL_ID, "studio/spatial/start"),
|
||||
Map.of(JigsawStudioLayout.SPATIAL_WORKCELL_ID, "studio/spatial/blank"),
|
||||
"variant-2");
|
||||
|
||||
assertTrue(creation.writeResult().successful());
|
||||
@@ -499,7 +499,7 @@ public class JigsawStudioPersistenceEditorsTest {
|
||||
"studio/spatial/variants/spatial/variant-2",
|
||||
creation.pieceKeysByWorkcell().get(JigsawStudioLayout.SPATIAL_WORKCELL_ID));
|
||||
JsonObject pool = readJson(packRoot.resolve("jigsaw-pools/studio/spatial/start.json"));
|
||||
assertEquals(8, pool.getAsJsonArray("pieces").size());
|
||||
assertEquals(25, pool.getAsJsonArray("pieces").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -507,10 +507,13 @@ public class JigsawStudioPersistenceEditorsTest {
|
||||
Path packRoot = createSpatialProject("spatial-theme-row");
|
||||
|
||||
Map<String, String> sources = new LinkedHashMap<>();
|
||||
sources.put(JigsawStudioLayout.SPATIAL_WORKCELL_ID, "studio/spatial/start");
|
||||
for (int connectorCount = 1; connectorCount <= 6; connectorCount++) {
|
||||
String pieceKey = "studio/spatial/connectors-" + connectorCount;
|
||||
sources.put(JigsawStudioLayout.SPATIAL_WORKCELL_ID + "/" + pieceKey, pieceKey);
|
||||
for (String vertical : List.of("", "-up", "-down", "-up-down")) {
|
||||
for (String horizontal : List.of("blank", "end", "straight", "corner", "tee", "cross")) {
|
||||
String pieceKey = "studio/spatial/" + horizontal + vertical;
|
||||
sources.put(sources.isEmpty()
|
||||
? JigsawStudioLayout.SPATIAL_WORKCELL_ID
|
||||
: JigsawStudioLayout.SPATIAL_WORKCELL_ID + "/" + pieceKey, pieceKey);
|
||||
}
|
||||
}
|
||||
JigsawStudioGraphEditor.VariantFamilyCreation creation = JigsawStudioGraphEditor.duplicateActiveFamily(
|
||||
packRoot,
|
||||
@@ -519,13 +522,13 @@ public class JigsawStudioPersistenceEditorsTest {
|
||||
"variant-2");
|
||||
|
||||
assertTrue(creation.writeResult().successful());
|
||||
assertEquals(7, creation.pieceKeysByWorkcell().size());
|
||||
assertEquals(24, creation.pieceKeysByWorkcell().size());
|
||||
for (String targetPieceKey : creation.pieceKeysByWorkcell().values()) {
|
||||
assertTrue(Files.isRegularFile(packRoot.resolve("jigsaw-pieces/" + targetPieceKey + ".json")));
|
||||
assertTrue(Files.isRegularFile(packRoot.resolve("objects/" + targetPieceKey + ".iob")));
|
||||
}
|
||||
JsonObject pool = readJson(packRoot.resolve("jigsaw-pools/studio/spatial/start.json"));
|
||||
assertEquals(14, pool.getAsJsonArray("pieces").size());
|
||||
assertEquals(48, pool.getAsJsonArray("pieces").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -557,7 +560,7 @@ public class JigsawStudioPersistenceEditorsTest {
|
||||
"END").get("displayName").getAsString());
|
||||
|
||||
Path spatialRoot = createSpatialProject("labels-spatial");
|
||||
Path spatialObject = spatialRoot.resolve("objects/studio/spatial/start.iob");
|
||||
Path spatialObject = spatialRoot.resolve("objects/studio/spatial/blank.iob");
|
||||
byte[] spatialBefore = Files.readAllBytes(spatialObject);
|
||||
|
||||
assertTrue(JigsawStudioStructureEditor.updateSpatialWorkcellDisplayName(
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class JigsawStudioPersistenceIdentityTest {
|
||||
private static final String PIECE_POOL_KEY = "studio/identity/pieces";
|
||||
private static final String RECORDED_AT_FIELD = "recordedAtEpochMilli";
|
||||
private static final String SCRIPTED_SESSION_DIGEST =
|
||||
"318d9338a63a44063c85e59a9e74f869473d6e0f15c362536172f7723d960faa";
|
||||
"42b5d3bed8d74b2ef7a010a58302544d0d425c83faba0dc86efb0e41e69ba627";
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
+48
-29
@@ -102,9 +102,15 @@ public class JigsawStudioProjectCreatorTest {
|
||||
JsonObject startPool = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pools/settlement/test/start.json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
assertEquals(1, startPool.getAsJsonArray("pieces").size());
|
||||
assertEquals(2, startPool.getAsJsonArray("pieces").size());
|
||||
assertEquals("settlement/test/cross", startPool.getAsJsonArray("pieces").get(0)
|
||||
.getAsJsonObject().get("piece").getAsString());
|
||||
assertEquals(3, startPool.getAsJsonArray("pieces").get(0)
|
||||
.getAsJsonObject().get("weight").getAsInt());
|
||||
assertEquals("settlement/test/blank", startPool.getAsJsonArray("pieces").get(1)
|
||||
.getAsJsonObject().get("piece").getAsString());
|
||||
assertEquals(1, startPool.getAsJsonArray("pieces").get(1)
|
||||
.getAsJsonObject().get("weight").getAsInt());
|
||||
JsonObject piecePool = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pools/settlement/test/pieces.json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
@@ -229,7 +235,7 @@ public class JigsawStudioProjectCreatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spatialProjectSeedsZeroThroughSixConnectorVariants() throws Exception {
|
||||
public void spatialProjectSeedsEveryYRotationTopologyVariant() throws Exception {
|
||||
Path temporaryDirectory = temporaryFolder.getRoot().toPath();
|
||||
JigsawStudioProjectCreator.Options options = new JigsawStudioProjectCreator.Options(
|
||||
"stronghold/gallery",
|
||||
@@ -246,35 +252,48 @@ public class JigsawStudioProjectCreatorTest {
|
||||
JsonObject piecePool = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pools/stronghold/gallery/pieces.json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
assertEquals(7, startPool.getAsJsonArray("pieces").size());
|
||||
assertEquals(7, piecePool.getAsJsonArray("pieces").size());
|
||||
assertEquals("stronghold/gallery/connectors-1", piecePool.getAsJsonArray("pieces").get(0)
|
||||
assertEquals(24, startPool.getAsJsonArray("pieces").size());
|
||||
assertEquals("stronghold/gallery/blank", startPool.getAsJsonArray("pieces").get(0)
|
||||
.getAsJsonObject().get("piece").getAsString());
|
||||
assertTrue(piecePool.getAsJsonArray("pieces").get(6).getAsJsonObject()
|
||||
assertEquals(24, piecePool.getAsJsonArray("pieces").size());
|
||||
assertEquals("stronghold/gallery/end", piecePool.getAsJsonArray("pieces").get(0)
|
||||
.getAsJsonObject().get("piece").getAsString());
|
||||
assertTrue(piecePool.getAsJsonArray("pieces").get(23).getAsJsonObject()
|
||||
.get("empty").getAsBoolean());
|
||||
for (int connectorCount = 0; connectorCount <= 6; connectorCount++) {
|
||||
String pieceName = connectorCount == 0 ? "start" : "connectors-" + connectorCount;
|
||||
JsonObject piece = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pieces/stronghold/gallery/"
|
||||
+ pieceName + ".json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
assertEquals(connectorCount, piece.getAsJsonArray("connectors").size());
|
||||
assertEquals(connectorCount + (connectorCount == 1 ? " Connector" : " Connectors"),
|
||||
piece.get("displayName").getAsString());
|
||||
assertEquals(16, piece.getAsJsonObject("rules").get("maximumPlacements").getAsInt());
|
||||
assertEquals(new IrisBlockVector(15, 15, 15), IrisObject.sampleSize(
|
||||
temporaryDirectory.resolve("objects/stronghold/gallery/"
|
||||
+ pieceName + ".iob").toFile()));
|
||||
List<String> horizontalClasses = List.of("blank", "end", "straight", "corner", "tee", "cross");
|
||||
List<String> verticalClasses = List.of("", "-up", "-down", "-up-down");
|
||||
List<Integer> horizontalCounts = List.of(0, 1, 2, 2, 3, 4);
|
||||
for (String vertical : verticalClasses) {
|
||||
for (int index = 0; index < horizontalClasses.size(); index++) {
|
||||
String pieceName = horizontalClasses.get(index) + vertical;
|
||||
JsonObject piece = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pieces/stronghold/gallery/" + pieceName + ".json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
int verticalCount = vertical.isEmpty() ? 0 : vertical.equals("-up-down") ? 2 : 1;
|
||||
assertEquals(pieceName, horizontalCounts.get(index) + verticalCount,
|
||||
piece.getAsJsonArray("connectors").size());
|
||||
assertEquals(4, piece.getAsJsonObject("rules").get("maximumPlacements").getAsInt());
|
||||
assertEquals(new IrisBlockVector(15, 15, 15), IrisObject.sampleSize(
|
||||
temporaryDirectory.resolve("objects/stronghold/gallery/"
|
||||
+ pieceName + ".iob").toFile()));
|
||||
}
|
||||
}
|
||||
JsonObject six = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pieces/stronghold/gallery/connectors-6.json"),
|
||||
JsonObject cornerShaft = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pieces/stronghold/gallery/corner-up-down.json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
assertEquals("NORTH_NEGATIVE_Z", six.getAsJsonArray("connectors").get(0)
|
||||
assertEquals("L Junction + Up/Down", cornerShaft.get("displayName").getAsString());
|
||||
assertEquals("NORTH_NEGATIVE_Z", cornerShaft.getAsJsonArray("connectors").get(0)
|
||||
.getAsJsonObject().get("direction").getAsString());
|
||||
assertEquals("DOWN_NEGATIVE_Y", six.getAsJsonArray("connectors").get(5)
|
||||
assertEquals("EAST_POSITIVE_X", cornerShaft.getAsJsonArray("connectors").get(1)
|
||||
.getAsJsonObject().get("direction").getAsString());
|
||||
assertEquals(0, six.getAsJsonArray("connectors").get(5)
|
||||
assertEquals("UP_POSITIVE_Y", cornerShaft.getAsJsonArray("connectors").get(2)
|
||||
.getAsJsonObject().get("direction").getAsString());
|
||||
assertEquals("DOWN_NEGATIVE_Y", cornerShaft.getAsJsonArray("connectors").get(3)
|
||||
.getAsJsonObject().get("direction").getAsString());
|
||||
assertEquals(0, cornerShaft.getAsJsonArray("connectors").get(3)
|
||||
.getAsJsonObject().getAsJsonObject("position").get("y").getAsInt());
|
||||
assertEquals("NORTH_NEGATIVE_Z", cornerShaft.getAsJsonArray("connectors").get(3)
|
||||
.getAsJsonObject().get("top").getAsString());
|
||||
|
||||
StructureGraphCompilation compilation = StructureResourceBundleGraphCompiler.compile(
|
||||
JigsawStudioProjectCreator.bundle(options)).getFirst();
|
||||
@@ -283,7 +302,7 @@ public class JigsawStudioProjectCreatorTest {
|
||||
new IrisPosition(0, 0, 0)).assemble(new RNG(1337L));
|
||||
assertEquals(preview.detail(), StructureAssemblyStatus.COMPLETE, preview.status());
|
||||
assertTrue(preview.pieces().size() > 1);
|
||||
assertTrue(preview.pieces().size() <= 96);
|
||||
assertTrue(preview.pieces().size() <= 93);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -411,10 +430,10 @@ public class JigsawStudioProjectCreatorTest {
|
||||
JsonObject pool = JsonParser.parseString(Files.readString(
|
||||
temporaryDirectory.resolve("jigsaw-pools/stronghold/test/start.json"),
|
||||
StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
assertEquals(8, pool.getAsJsonArray("pieces").size());
|
||||
assertEquals("stronghold/test/hall", pool.getAsJsonArray("pieces").get(7)
|
||||
assertEquals(25, pool.getAsJsonArray("pieces").size());
|
||||
assertEquals("stronghold/test/hall", pool.getAsJsonArray("pieces").get(24)
|
||||
.getAsJsonObject().get("piece").getAsString());
|
||||
assertEquals(3, pool.getAsJsonArray("pieces").get(7)
|
||||
assertEquals(3, pool.getAsJsonArray("pieces").get(24)
|
||||
.getAsJsonObject().get("weight").getAsInt());
|
||||
|
||||
StructureWriteResult resized = JigsawStudioStructureEditor.updateCellSize(
|
||||
@@ -435,7 +454,7 @@ public class JigsawStudioProjectCreatorTest {
|
||||
IrisBlockVector hallSize = IrisObject.sampleSize(
|
||||
temporaryDirectory.resolve("objects/stronghold/test/hall.iob").toFile());
|
||||
IrisBlockVector starterSize = IrisObject.sampleSize(
|
||||
temporaryDirectory.resolve("objects/stronghold/test/start.iob").toFile());
|
||||
temporaryDirectory.resolve("objects/stronghold/test/blank.iob").toFile());
|
||||
assertEquals(new IrisBlockVector(16, 12, 18), hallSize);
|
||||
assertEquals(new IrisBlockVector(12, 10, 14), starterSize);
|
||||
|
||||
|
||||
@@ -56,6 +56,21 @@ public class JigsawStudioProtectionProbeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedWorkcellProbesAreDeniedWithoutChatMessages() throws Exception {
|
||||
JigsawStudioService service = new JigsawStudioService();
|
||||
JigsawStudioProtection protection = mock(JigsawStudioProtection.class);
|
||||
setField(service, "protection", protection);
|
||||
PlayerInteractEvent event = probe();
|
||||
when(protection.studioEditRefusal(event.getPlayer(), event.getClickedBlock()))
|
||||
.thenReturn("This Jigsaw Studio variant is read-only.");
|
||||
|
||||
service.protectionListener.onUnauthorizedInteract(event);
|
||||
|
||||
assertTrue(event.isCancelled());
|
||||
verify(protection, never()).isUnauthorizedStudioEdit(any(Player.class), any(Block.class));
|
||||
}
|
||||
|
||||
private static PlayerInteractEvent probe() {
|
||||
Player player = mock(Player.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
+12
-12
@@ -80,20 +80,20 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
JigsawStudioResourceBundleAssembler.assemble(
|
||||
packRoot,
|
||||
"fort",
|
||||
"fort/start",
|
||||
"fort/blank",
|
||||
emptyObject(),
|
||||
List.of(connector),
|
||||
false);
|
||||
|
||||
assertEquals(17, assembly.bundle().resources().size());
|
||||
assertEquals("fort/start", assembly.objectKey());
|
||||
assertEquals(51, assembly.bundle().resources().size());
|
||||
assertEquals("fort/blank", assembly.objectKey());
|
||||
assertEquals(1, assembly.piece().getConnectors().size());
|
||||
StructureWriteResult result = new StructureTransactionWriter(packRoot)
|
||||
.write(assembly.bundle(), StructureWriteMode.OVERWRITE);
|
||||
assertTrue(result.successful());
|
||||
assertTrue(result.committed());
|
||||
|
||||
Path piecePath = packRoot.resolve("jigsaw-pieces/fort/start.json");
|
||||
Path piecePath = packRoot.resolve("jigsaw-pieces/fort/blank.json");
|
||||
IrisJigsawPiece persisted = new Gson().fromJson(
|
||||
Files.readString(piecePath, StandardCharsets.UTF_8), IrisJigsawPiece.class);
|
||||
assertEquals(1, persisted.getConnectors().size());
|
||||
@@ -143,7 +143,7 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
@Test
|
||||
public void capturePreservesAbsentDefaultsAndUnknownPieceMetadata() throws Exception {
|
||||
Path packRoot = createProject();
|
||||
String pieceResource = "jigsaw-pieces/fort/start.json";
|
||||
String pieceResource = "jigsaw-pieces/fort/blank.json";
|
||||
Path piecePath = packRoot.resolve(pieceResource);
|
||||
JsonObject source = JsonParser.parseString(
|
||||
Files.readString(piecePath, StandardCharsets.UTF_8)).getAsJsonObject();
|
||||
@@ -158,7 +158,7 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
JigsawStudioResourceBundleAssembler.assemble(
|
||||
packRoot,
|
||||
STRUCTURE_KEY,
|
||||
"fort/start",
|
||||
"fort/blank",
|
||||
emptyObject(),
|
||||
List.copyOf(sourcePiece.getConnectors()),
|
||||
false);
|
||||
@@ -173,7 +173,7 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
JigsawStudioResourceBundleAssembler.assemble(
|
||||
packRoot,
|
||||
STRUCTURE_KEY,
|
||||
"fort/start",
|
||||
"fort/blank",
|
||||
emptyObject(),
|
||||
List.of(updated),
|
||||
false);
|
||||
@@ -202,7 +202,7 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
() -> JigsawStudioResourceBundleAssembler.assemble(
|
||||
packRoot,
|
||||
STRUCTURE_KEY,
|
||||
"fort/start",
|
||||
"fort/blank",
|
||||
emptyObject(),
|
||||
List.of(connector()),
|
||||
false));
|
||||
@@ -213,7 +213,7 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
@Test
|
||||
public void refusesManagedDatapackOwnershipWithoutAssemblingAStudioSave() throws Exception {
|
||||
Path packRoot = createProject();
|
||||
Path objectPath = packRoot.resolve("objects/fort/start.iob");
|
||||
Path objectPath = packRoot.resolve("objects/fort/blank.iob");
|
||||
byte[] before = Files.readAllBytes(objectPath);
|
||||
markManagedDatapack(packRoot, STRUCTURE_KEY);
|
||||
|
||||
@@ -221,7 +221,7 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
() -> JigsawStudioResourceBundleAssembler.assemble(
|
||||
packRoot,
|
||||
STRUCTURE_KEY,
|
||||
"fort/start",
|
||||
"fort/blank",
|
||||
emptyObject(),
|
||||
List.of(connector()),
|
||||
false));
|
||||
@@ -234,13 +234,13 @@ public class JigsawStudioResourceBundleAssemblerTest {
|
||||
@Test
|
||||
public void writerRejectsAResourceModifiedOutsideTheOwnershipTransaction() throws Exception {
|
||||
Path packRoot = createProject();
|
||||
Path piecePath = packRoot.resolve("jigsaw-pieces/fort/start.json");
|
||||
Path piecePath = packRoot.resolve("jigsaw-pieces/fort/blank.json");
|
||||
Files.writeString(piecePath, Files.readString(piecePath, StandardCharsets.UTF_8) + "\n");
|
||||
JigsawStudioResourceBundleAssembler.Assembly assembly =
|
||||
JigsawStudioResourceBundleAssembler.assemble(
|
||||
packRoot,
|
||||
STRUCTURE_KEY,
|
||||
"fort/start",
|
||||
"fort/blank",
|
||||
emptyObject(),
|
||||
List.of(connector().setPool("fort/start")),
|
||||
false);
|
||||
|
||||
+8
-4
@@ -181,11 +181,13 @@ public class JigsawStudioServiceCaptureTest {
|
||||
when(state.isCustom()).thenReturn(false);
|
||||
when(state.nativeHandle()).thenReturn(blockData);
|
||||
|
||||
JigsawStudioChunkWriter.restoreConnectorChunk(
|
||||
JigsawStudioCapture.LocalPosition position = new JigsawStudioCapture.LocalPosition(1, 2, 3);
|
||||
JigsawStudioChunkWriter.restoreChunkPositions(
|
||||
world,
|
||||
workcell,
|
||||
List.of(renderedConnector),
|
||||
Map.of(new JigsawStudioCapture.LocalPosition(1, 2, 3), renderedBlock),
|
||||
Set.of(position),
|
||||
Map.of(position, renderedConnector),
|
||||
Map.of(position, renderedBlock),
|
||||
false);
|
||||
|
||||
verify(world).getBlockAt(11, 22, 33);
|
||||
@@ -293,7 +295,9 @@ public class JigsawStudioServiceCaptureTest {
|
||||
assertFalse(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/iris jigsaw status"));
|
||||
assertFalse(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/msg owner hello"));
|
||||
assertTrue(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/execute run say bypass"));
|
||||
assertTrue(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/unknownplugin mutate"));
|
||||
assertTrue(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/setblock 0 0 0 stone"));
|
||||
assertFalse(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/unknownplugin status"));
|
||||
assertFalse(JigsawStudioProtection.blocksMutatingCommand(ownerId, otherId, "/spawn"));
|
||||
assertTrue(JigsawStudioProtection.blocksNonEditableWorkcellMutation(
|
||||
true, "/fill 0 0 0 1 1 1 stone"));
|
||||
assertTrue(JigsawStudioProtection.blocksNonEditableWorkcellMutation(true, "//paste -a"));
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.studio.generation.JigsawStudioGenerator;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JigsawStudioToolbeltTest {
|
||||
private static final UUID REQUEST = UUID.fromString("55555555-5555-5555-5555-555555555555");
|
||||
private static final JigsawStudioToolPayload DELETE = JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.DELETE_VARIANT, REQUEST, "workcell/end", "test/end");
|
||||
|
||||
@Test
|
||||
public void heldUseRepeatsCannotConfirmADestructiveTool() {
|
||||
JigsawStudioToolbelt.ToolConfirmation armed =
|
||||
new JigsawStudioToolbelt.ToolConfirmation(DELETE, 1_000_000_000L, 11_000_000_000L);
|
||||
|
||||
assertEquals(JigsawStudioToolbelt.ToolConfirmationState.ARMED,
|
||||
JigsawStudioToolbelt.confirmationState(null, DELETE, 1_000_000_000L));
|
||||
assertEquals(JigsawStudioToolbelt.ToolConfirmationState.HELD,
|
||||
JigsawStudioToolbelt.confirmationState(armed, DELETE, 1_200_000_000L));
|
||||
assertEquals(JigsawStudioToolbelt.ToolConfirmationState.CONFIRMED,
|
||||
JigsawStudioToolbelt.confirmationState(armed, DELETE, 1_600_000_000L));
|
||||
assertEquals(JigsawStudioToolbelt.ToolConfirmationState.ARMED,
|
||||
JigsawStudioToolbelt.confirmationState(armed, DELETE, 12_000_000_000L));
|
||||
assertEquals(JigsawStudioToolbelt.ToolConfirmationState.ARMED,
|
||||
JigsawStudioToolbelt.confirmationState(
|
||||
armed,
|
||||
JigsawStudioToolPayload.request(JigsawStudioToolAction.DELETE_PROJECT, REQUEST),
|
||||
1_600_000_000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void variantBoundSticksRequireTheirBoundVariant() {
|
||||
assertTrue(JigsawStudioToolbelt.requiresBoundVariant(JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.TOGGLE_ROTATION, REQUEST, "workcell/end", "test/end")));
|
||||
assertTrue(JigsawStudioToolbelt.requiresBoundVariant(JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.EXPAND_TO_CELL, REQUEST, "workcell/end", "test/end")));
|
||||
assertTrue(JigsawStudioToolbelt.requiresBoundVariant(JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.DUPLICATE_VARIANT, REQUEST, "workcell/end", "test/end")));
|
||||
assertFalse(JigsawStudioToolbelt.requiresBoundVariant(JigsawStudioToolPayload.workcell(
|
||||
JigsawStudioToolAction.CREATE_VARIANT, REQUEST, "workcell/end")));
|
||||
assertFalse(JigsawStudioToolbelt.requiresBoundVariant(JigsawStudioToolPayload.request(
|
||||
JigsawStudioToolAction.CONNECTOR_TOOL, REQUEST)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staleRotationStickDoesNotToggleTheNewlyLoadedVariant() throws Exception {
|
||||
JigsawStudioVariant first = variant("test/end");
|
||||
JigsawStudioVariant second = variant("test/end-b");
|
||||
JigsawStudioLayout layout = JigsawStudioLayout.create(
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
new JigsawStudioCellDimensions(16, 16, 16),
|
||||
new JigsawStudioVariantCatalog(List.of(second, first)));
|
||||
JigsawStudioSession session = new JigsawStudioSession(REQUEST, "overworld", "test/project", layout);
|
||||
assertEquals("test/end-b", session.activeVariant("workcell/end").orElseThrow().pieceKey());
|
||||
JigsawStudioService service = new JigsawStudioService();
|
||||
JigsawStudioVariantProperties properties = mock(JigsawStudioVariantProperties.class);
|
||||
setField(service, "variantProperties", properties);
|
||||
Player player = player(service, session);
|
||||
|
||||
try (MockedStatic<J> scheduling = mockStatic(J.class)) {
|
||||
scheduling.when(() -> J.isOwnedByCurrentRegion(any(Player.class))).thenReturn(true);
|
||||
|
||||
boolean used = service.toolbelt.useTool(
|
||||
player,
|
||||
JigsawStudioToolPayload.variant(
|
||||
JigsawStudioToolAction.TOGGLE_ROTATION, REQUEST, "workcell/end", "test/end"),
|
||||
null,
|
||||
false);
|
||||
|
||||
assertFalse(used);
|
||||
verify(properties, never()).toggleVariantRotatable(any(Player.class), anyString());
|
||||
}
|
||||
}
|
||||
|
||||
private static Player player(JigsawStudioService service, JigsawStudioSession session) throws Exception {
|
||||
UUID worldId = UUID.randomUUID();
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(worldId);
|
||||
Player player = mock(Player.class);
|
||||
when(player.getWorld()).thenReturn(world);
|
||||
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
JigsawStudioActivation.Request request = new JigsawStudioActivation.Request(
|
||||
REQUEST,
|
||||
"overworld",
|
||||
"test/project",
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
JigsawStudioCompatibilityTarget.IRIS_EXTENDED,
|
||||
new JigsawStudioCellDimensions(16, 16, 16),
|
||||
mock(IrisData.class),
|
||||
null);
|
||||
JigsawStudioGenerator generator = mock(JigsawStudioGenerator.class);
|
||||
when(generator.getRequest()).thenReturn(request);
|
||||
when(generator.getSession()).thenReturn(session);
|
||||
when(generator.getLayout()).thenAnswer(invocation -> session.layout());
|
||||
Class<?> studioType = Class.forName(JigsawStudioService.class.getName() + "$ActiveStudio");
|
||||
Constructor<?> constructor = studioType.getDeclaredConstructor(
|
||||
UUID.class,
|
||||
World.class,
|
||||
Engine.class,
|
||||
JigsawStudioGenerator.class,
|
||||
ConcurrentHashMap.class,
|
||||
Set.class,
|
||||
AtomicLong.class);
|
||||
constructor.setAccessible(true);
|
||||
Object studio = constructor.newInstance(
|
||||
worldId,
|
||||
world,
|
||||
mock(Engine.class),
|
||||
generator,
|
||||
new ConcurrentHashMap<>(),
|
||||
ConcurrentHashMap.newKeySet(),
|
||||
new AtomicLong());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<UUID, Object> studios = (Map<UUID, Object>) getField(service, "studios");
|
||||
studios.put(worldId, studio);
|
||||
return player;
|
||||
}
|
||||
|
||||
private static JigsawStudioVariant variant(String pieceKey) {
|
||||
return new JigsawStudioVariant(
|
||||
pieceKey,
|
||||
pieceKey,
|
||||
"",
|
||||
Optional.of(new JigsawStudioCellDimensions(16, 16, 16)),
|
||||
JigsawStudioMode.PLANAR_JIGSAW,
|
||||
Optional.of(JigsawPlanarTopology.NORTH_END),
|
||||
true,
|
||||
true,
|
||||
List.of(),
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
List.of());
|
||||
}
|
||||
|
||||
private static void setField(Object target, String name, Object value) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private static Object getField(Object target, String name) throws Exception {
|
||||
Field field = target.getClass().getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(target);
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package art.arcane.iris.studio.jigsaw;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JigsawStudioVariantDeletionRuleTest {
|
||||
private static final UUID WORLD = UUID.randomUUID();
|
||||
private static final UUID REQUEST = UUID.randomUUID();
|
||||
|
||||
@Test
|
||||
public void spatialVariantsCanBeDeletedWhileLoadedUnlessTheyAreTheLastOne() {
|
||||
JigsawStudioMenuState.Variant first = variant("demo/end", true, true);
|
||||
JigsawStudioMenuState.Variant second = variant("demo/cross", true, true);
|
||||
JigsawStudioMenuState.Workcell firstCell = workcell("workcell/spatial", first);
|
||||
JigsawStudioMenuState.Workcell secondCell = workcell("workcell/spatial/demo/cross", second);
|
||||
JigsawStudioMenuState twoCells = state(JigsawStudioMode.SPATIAL_JIGSAW, List.of(firstCell, secondCell));
|
||||
JigsawStudioMenuState oneCell = state(JigsawStudioMode.SPATIAL_JIGSAW, List.of(firstCell));
|
||||
|
||||
assertTrue(twoCells.canDeleteVariant(firstCell, first));
|
||||
assertFalse(oneCell.canDeleteVariant(firstCell, first));
|
||||
List<JigsawStudioToolboxMenu.ToolboxTool> tools = JigsawStudioToolboxMenu.toolboxTools(twoCells, firstCell);
|
||||
assertTrue(tools.stream().anyMatch(tool -> tool.payload().action() == JigsawStudioToolAction.DELETE_VARIANT
|
||||
&& tool.payload().pieceKey().equals("demo/end")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planarVariantsStillRequireAnotherLoadedVariantFirst() {
|
||||
JigsawStudioMenuState.Variant loaded = variant("demo/end", true, true);
|
||||
JigsawStudioMenuState.Variant spare = variant("demo/end-b", false, true);
|
||||
JigsawStudioMenuState.Variant readOnly = variant("demo/end-c", false, false);
|
||||
JigsawStudioMenuState.Workcell cell = new JigsawStudioMenuState.Workcell(
|
||||
"workcell/end",
|
||||
"End Cap",
|
||||
"End Cap",
|
||||
new JigsawStudioCellDimensions(16, 16, 16),
|
||||
true,
|
||||
loaded.pieceKey(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
List.of(loaded, spare, readOnly));
|
||||
JigsawStudioMenuState state = state(JigsawStudioMode.PLANAR_JIGSAW, List.of(cell));
|
||||
|
||||
assertFalse(state.canDeleteVariant(cell, loaded));
|
||||
assertTrue(state.canDeleteVariant(cell, spare));
|
||||
assertFalse(state.canDeleteVariant(cell, readOnly));
|
||||
}
|
||||
|
||||
private static JigsawStudioMenuState state(JigsawStudioMode mode, List<JigsawStudioMenuState.Workcell> workcells) {
|
||||
return new JigsawStudioMenuState(
|
||||
WORLD,
|
||||
REQUEST,
|
||||
"demo",
|
||||
mode,
|
||||
JigsawStudioCompatibilityTarget.IRIS_EXTENDED,
|
||||
false,
|
||||
List.of(new JigsawStudioMenuState.ThemeSet("variant-1", 1)),
|
||||
workcells.getFirst().stableId(),
|
||||
JigsawStudioMenuState.Evaluation.pending(),
|
||||
workcells);
|
||||
}
|
||||
|
||||
private static JigsawStudioMenuState.Workcell workcell(String stableId, JigsawStudioMenuState.Variant active) {
|
||||
return new JigsawStudioMenuState.Workcell(
|
||||
stableId,
|
||||
"Spatial",
|
||||
active.displayName(),
|
||||
new JigsawStudioCellDimensions(15, 15, 15),
|
||||
true,
|
||||
active.pieceKey(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
List.of(active));
|
||||
}
|
||||
|
||||
private static JigsawStudioMenuState.Variant variant(String pieceKey, boolean active, boolean owned) {
|
||||
return new JigsawStudioMenuState.Variant(
|
||||
pieceKey,
|
||||
pieceKey.substring(pieceKey.lastIndexOf('/') + 1),
|
||||
Optional.of(new JigsawStudioCellDimensions(15, 15, 15)),
|
||||
active,
|
||||
owned,
|
||||
true,
|
||||
owned,
|
||||
active,
|
||||
List.of(),
|
||||
new JigsawStudioPieceRules(0, 30, 0, 0, false),
|
||||
List.of());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user