From 8acc2a498c1995824ace4372bd7a1acda69788d8 Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 11:56:33 +0200 Subject: [PATCH 1/7] feat: ship a gallery pack with a working scene of each feature Pipeworks Gallery, off by default, builds nine scenes in rows of three, each with a sign that rebuilds it when clicked. Its game tests run libworks' GalleryCheck a row at a time, and check the Outlet scene's water and a sign's replay. Takes libworks-runtime 0.1.9 for GalleryPack. Closes #26 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 + GLOSSARY.md | 6 + README.md | 4 + REUSE.toml | 1 + build.gradle | 2 +- .../github/_5thlayer/pipeworks/Pipeworks.java | 2 + .../pipeworks/gametest/GalleryTests.java | 193 ++++++++++++++++++ .../gametest/PipeworksGameTests.java | 3 + .../assets/pipeworks/lang/en_us.json | 38 ++++ .../function/gallery/breaking.mcfunction | 22 ++ .../function/gallery/buckets.mcfunction | 16 ++ .../function/gallery/build.mcfunction | 13 ++ .../function/gallery/clear.mcfunction | 8 + .../function/gallery/creative_pipe.mcfunction | 16 ++ .../function/gallery/dismantle.mcfunction | 21 ++ .../function/gallery/one_fluid.mcfunction | 21 ++ .../function/gallery/outlet.mcfunction | 19 ++ .../function/gallery/pipes.mcfunction | 16 ++ .../function/gallery/pool.mcfunction | 9 + .../function/gallery/pump.mcfunction | 19 ++ .../function/gallery/sign.mcfunction | 3 + .../function/gallery/tank_level.mcfunction | 9 + src/main/resources/gallery_pack/pack.mcmeta | 7 + 23 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/build.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/clear.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/creative_pipe.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/dismantle.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/one_fluid.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/outlet.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/pipes.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/pool.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/pump.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/sign.mcfunction create mode 100644 src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction create mode 100644 src/main/resources/gallery_pack/pack.mcmeta diff --git a/CHANGELOG.md b/CHANGELOG.md index 188c4b5..bb0dfa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Written for Consumers: what a mod building against Pipeworks can use, or will se ## Unreleased +- The Gallery pack, **Pipeworks Gallery**: a datapack in the jar, off by default, whose `/function pipeworks:gallery/build` builds a working scene of each README feature, in rows of three north-east of the player, each with a sign naming it that rebuilds the scene when clicked; `pipeworks:gallery/` builds one. It needs libworks-runtime 0.1.9, which the jar nests (libworks ADR 0010, #26). + ## 0.3.1 ### Fixed diff --git a/GLOSSARY.md b/GLOSSARY.md index 71ae191..bac009d 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -73,6 +73,12 @@ _Avoid_: dismantle group The creative tab the suite's mods share, `libworks:works`, registered by the libworks runtime the Module nests. Every Pipeworks item shows there; Pipeworks registers no tab of its own (FactoryWorks ADR-0128). _Avoid_: Pipeworks tab, creative tab (alone) +**Gallery pack**: +The datapack in this Module's jar, "Pipeworks Gallery", off until a player turns it on, whose +functions build a working scene of each README feature, each with a sign naming it. Functions only +(libworks ADR 0010). +_Avoid_: demo pack, showcase pack, gallery datapack + ### Parties **Pipeworks**: diff --git a/README.md b/README.md index e76861b..91f8d36 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ To play Pipeworks beside every other FactoryWorks mod, install the [FactoryWorks Not yet: the in-line pump and pipe-to-ground. +## Gallery + +Pipeworks carries a gallery of its features: one working scene each, with a sign naming it, built in rows of three north and east of where you stand. It is a datapack in the jar, **Pipeworks Gallery**, off until you turn it on in a world's Data Packs screen or with `/datapack enable "mod/pipeworks:gallery_pack"`. Then `/function pipeworks:gallery/build` builds every scene, and `/function pipeworks:gallery/` one of them, such as `outlet`; clicking a scene's sign builds it again. A function needs cheats in single player (on at world creation, or Open to LAN with cheats) or operator rights on a server. Pipeworks registers no fluid, so the scenes use water and lava. The gallery clears the ground it builds on and changes nothing else; it adds no recipes, loot or worldgen. + ## For pack authors - **Closing a pipe's sides.** A pipe's side can be closed, to keep two runs apart or cut a pipe off from a block holding fluid. A right-click on a pipe with an item in the item tag `pipeworks:closes_sides` toggles the side clicked. The tag ships a stick; a pack replaces the tag to name other items, or adds to it and keeps the stick. Opening a side that would mix two fluids is refused. diff --git a/REUSE.toml b/REUSE.toml index 61381d5..ae94b37 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -15,6 +15,7 @@ path = [ "**/*.md", "src/main/resources/**/*.json", "src/main/resources/**/*.nbt", + "src/main/resources/gallery_pack/**", "LICENSE", "publish/summary.txt", ".github/**", diff --git a/build.gradle b/build.gradle index 3ef0691..c73f0be 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ libworks { } // Nested by jar-in-jar, so players install nothing more; FML loads the newest nested copy in range, // which runs to the next minor. - runtime = '0.1.8' + runtime = '0.1.9' } repositories { diff --git a/src/main/java/io/github/_5thlayer/pipeworks/Pipeworks.java b/src/main/java/io/github/_5thlayer/pipeworks/Pipeworks.java index e062707..7ef380f 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/Pipeworks.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/Pipeworks.java @@ -3,6 +3,7 @@ package io.github._5thlayer.pipeworks; +import io.github._5thlayer.libworks.gallery.GalleryPack; import io.github._5thlayer.pipeworks.dismantle.PipeFamily; import io.github._5thlayer.pipeworks.gametest.PipeworksGameTests; import io.github._5thlayer.pipeworks.network.CreativePipeFluidPayload; @@ -36,6 +37,7 @@ public Pipeworks(IEventBus modBus, ModContainer container) { PipeFamily.register(); PipeLegs.register(); PipeworksGameTests.register(modBus); + GalleryPack.register(modBus, MOD_ID); modBus.addListener(Pipeworks::registerPayloads); modBus.addListener(TankFluidPayload::register); NeoForge.EVENT_BUS.addListener(FluidSegments::onLevelTick); diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java new file mode 100644 index 0000000..6b790c8 --- /dev/null +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2026 5thlayer +// SPDX-License-Identifier: MIT + +package io.github._5thlayer.pipeworks.gametest; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import io.github._5thlayer.libworks.gallery.GalleryPack; +import io.github._5thlayer.libworks.testkit.GalleryCheck; +import io.github._5thlayer.libworks.testkit.GalleryCheck.Scene; +import io.github._5thlayer.pipeworks.Pipeworks; +import io.github._5thlayer.pipeworks.PipeworksRegistries; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.gametest.framework.GameTestListener; +import net.minecraft.gametest.framework.GameTestRunner; +import net.minecraft.gametest.framework.StructureUtils; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.AABB; + +/** + * The Gallery pack (libworks ADR 0010), through libworks' {@link GalleryCheck}: offered and off, its + * build running every command without failing, and each scene's key block where it stands. The scenes + * stand in rows of three, each row 16 blocks north of the last, which keeps them near enough to where + * the gallery is run for their pumps to tick; {@link GalleryCheck#builds} lays one long row, so the + * test runs its parts a row at a time. Beyond the build, the Outlet scene sets its water down, and a + * scene's sign, clicked, builds it again. Each test turns the pack on, which reloads the server's + * data, and off again, passing or failing, so they run in a batch of their own. + */ +final class GalleryTests { + + private static final String PACK = GalleryPack.id(Pipeworks.MOD_ID); + + /** How many scenes a row holds, and how far north each row is of the last. */ + private static final int ROW = 3; + private static final int ROW_DEPTH = 16; + + /** Where the gallery is run from: the platform's south-west corner, its plots spilling north and east of it. */ + private static final BlockPos ORIGIN = new BlockPos(0, 1, 8); + /** Where a scene's sign stands, from where the scene is run. */ + private static final BlockPos SIGN = new BlockPos(14, 0, 0); + /** Where the Outlet scene's outlet sets its water down, from where the scene is run. */ + private static final BlockPos OUTLET_FRONT = new BlockPos(16, 0, -6); + + private GalleryTests() { + } + + /** The scenes in the order {@code build} lays them, each with a block it places. */ + private static List scenes() { + var tank = PipeworksRegistries.STORAGE_TANK.get(); + var pump = PipeworksRegistries.PUMP.get(); + return List.of( + new Scene("pipes", new BlockPos(18, 0, -6), tank), + new Scene("buckets", new BlockPos(18, 1, -6), tank), + new Scene("one_fluid", new BlockPos(20, 0, -6), pump), + new Scene("breaking", new BlockPos(20, 0, -6), tank), + new Scene("tank_level", new BlockPos(14, 2, -6), tank), + new Scene("creative_pipe", new BlockPos(8, 0, -6), PipeworksRegistries.CREATIVE_PIPE.get()), + new Scene("pump", new BlockPos(21, 0, -6), pump), + new Scene("outlet", new BlockPos(15, 0, -6), PipeworksRegistries.OUTLET.get()), + new Scene("dismantle", new BlockPos(6, 0, -6), PipeworksRegistries.PIPE.get())); + } + + static void register(PipeworksGameTests.Registrar tests) { + tests.test("gallery_builds_a_scene_of_each_feature", GalleryCheck.TICKS, GalleryTests::builds); + tests.test("gallery_outlet_scene_sets_its_water_down", GalleryCheck.TICKS, GalleryTests::outletSetsWater); + tests.test("gallery_scenes_sign_clicked_builds_the_scene_again", GalleryCheck.TICKS, GalleryTests::signReplays); + } + + private static void builds(GameTestHelper helper) { + var scenes = scenes(); + withPack(helper, scenes.size(), source -> { + GalleryCheck.assertLoads(helper, source, Pipeworks.MOD_ID); + for (var scene : scenes) { + helper.assertTrue(helper.getLevel().getServer().getFunctions().get(GalleryPack.scene(Pipeworks.MOD_ID, scene.name())).isPresent(), + "there is no scene function " + scene.name()); + } + GalleryCheck.assertRuns(helper, source, GalleryPack.build(Pipeworks.MOD_ID)); + for (int row = 0; row * ROW < scenes.size(); row++) { + GalleryCheck.assertKeyBlocks(helper, rowOrigin(helper, row), + scenes.subList(row * ROW, Math.min(scenes.size(), (row + 1) * ROW))); + } + }, null); + } + + /** The Outlet scene: its Pump fills the run, and the Outlet sets a water source down in its basin. */ + private static void outletSetsWater(GameTestHelper helper) { + var front = helper.absolutePos(ORIGIN).offset(OUTLET_FRONT); + withPack(helper, 1, source -> GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "outlet")), + () -> { + var fluid = helper.getLevel().getFluidState(front); + helper.assertTrue(fluid.isSource() && fluid.is(Fluids.WATER), "the Outlet has set no water down in its basin yet"); + }); + } + + /** A scene's sign, clicked, builds the scene again where it stands, leaving nothing loose: here the Outlet, taken away. */ + private static void signReplays(GameTestHelper helper) { + var outlet = helper.absolutePos(ORIGIN).offset(15, 0, -6); + var sign = helper.absolutePos(ORIGIN).offset(SIGN); + withPack(helper, 1, source -> { + GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "outlet")); + helper.getLevel().removeBlock(outlet, false); + helper.assertTrue(helper.getLevel().getBlockEntity(sign) instanceof SignBlockEntity entity + && entity.executeClickCommandsIfPresent(helper.getLevel(), source.getPlayer(), sign, true), + "the scene's sign runs no command when clicked"); + helper.assertTrue(helper.getLevel().getBlockState(outlet).is(PipeworksRegistries.OUTLET.get()), + "clicking the sign did not build the Outlet again where it stood"); + var plot = GalleryCheck.plot(helper.absolutePos(ORIGIN)); + var loose = helper.getLevel().getEntitiesOfClass(ItemEntity.class, AABB.of(plot)); + helper.assertTrue(loose.isEmpty(), "building the scene again left items loose: " + loose.stream().map(ItemEntity::getItem).toList()); + helper.assertTrue(helper.getLevel().getBlockState(sign).is(Blocks.OAK_SIGN), "building the scene again left no sign"); + }, null); + } + + /** + * Turns the pack on over the loaded plots of a gallery of {@code scenes}, runs {@code body} as an + * operator player standing where it is run, waits for {@code until} if any, then clears the plots + * and turns the pack off, passing or failing. + */ + private static void withPack(GameTestHelper helper, int scenes, Consumer body, Runnable until) { + var server = helper.getLevel().getServer(); + GalleryCheck.assertOfferedAndOff(helper, PACK); + var area = area(helper, scenes); + area.intersectingChunks().forEach(chunk -> helper.getLevel().setChunkForced(chunk.x(), chunk.z(), true)); + var source = GalleryCheck.operator(helper, helper.absolutePos(ORIGIN)); + var on = GalleryCheck.select(server, PACK, true); + helper.testInfo.addListener(new ClearAwayIfFailed(helper, area, on)); + var off = new AtomicReference>(); + helper.startSequence() + .thenWaitUntil(() -> helper.assertTrue(on.isDone(), PACK + " is not on yet")) + .thenWaitUntil(() -> helper.assertTrue(area.intersectingChunks().allMatch(chunk -> helper.getLevel().hasChunk(chunk.x(), chunk.z())), + "the gallery's plots are not loaded yet")) + .thenExecute(() -> body.accept(source)) + .thenWaitUntil(() -> { + if (until != null) { + until.run(); + } + }) + .thenExecute(() -> off.set(clearAway(helper, area))) + .thenWaitUntil(() -> helper.assertTrue(off.get() != null && off.get().isDone(), PACK + " is not off yet")) + .thenSucceed(); + } + + /** Where row {@code row} of the gallery is run from, absolute. */ + private static BlockPos rowOrigin(GameTestHelper helper, int row) { + return helper.absolutePos(ORIGIN).north(ROW_DEPTH * row); + } + + /** Every plot of a gallery's first {@code scenes} scenes, their floors included. */ + private static BoundingBox area(GameTestHelper helper, int scenes) { + int rows = Math.ceilDiv(scenes, ROW); + return BoundingBox.encapsulatingBoxes(List.of( + GalleryCheck.plot(helper.absolutePos(ORIGIN)), + GalleryCheck.strip(rowOrigin(helper, rows - 1), Math.min(scenes, ROW)))).orElseThrow(); + } + + /** Air where the plots stood, their entities gone, and the pack off. The runner releases the forced chunks once the batch ends. */ + private static CompletableFuture clearAway(GameTestHelper helper, BoundingBox area) { + StructureUtils.clearSpaceForStructure(area, helper.getLevel()); + return GalleryCheck.select(helper.getLevel().getServer(), PACK, false); + } + + /** A failed test skips its own last steps, which would leave the pack on; it turns it off once turning it on is done. */ + private record ClearAwayIfFailed(GameTestHelper helper, BoundingBox area, CompletableFuture on) implements GameTestListener { + + @Override + public void testStructureLoaded(GameTestInfo testInfo) { + } + + @Override + public void testPassed(GameTestInfo testInfo, GameTestRunner runner) { + } + + @Override + public void testFailed(GameTestInfo testInfo, GameTestRunner runner) { + var server = helper.getLevel().getServer(); + on.whenComplete((done, failed) -> server.execute(() -> clearAway(helper, area))); + } + + @Override + public void testAddedForRerun(GameTestInfo original, GameTestInfo copy, GameTestRunner runner) { + } + } +} diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java index d950a74..8da13b8 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java @@ -63,6 +63,9 @@ private static void registerTests(RegisterGameTestsEvent event) { // Registered rather than borrowed, since the event hands out no lookup for vanilla's. var environment = event.registerEnvironment(id("default"), new TestEnvironmentDefinition.AllOf(List.of())); var tests = new Registrar(event, environment); + // Turning the Gallery pack on reloads the server's data under every test in its batch (libworks ADR 0010). + var gallery = event.registerEnvironment(id("gallery"), new TestEnvironmentDefinition.AllOf(List.of())); + GalleryTests.register(new Registrar(event, gallery)); LoadTests.register(tests); PlaysAloneTests.register(tests); SegmentTests.register(tests); diff --git a/src/main/resources/assets/pipeworks/lang/en_us.json b/src/main/resources/assets/pipeworks/lang/en_us.json index 104376a..7a97829 100644 --- a/src/main/resources/assets/pipeworks/lang/en_us.json +++ b/src/main/resources/assets/pipeworks/lang/en_us.json @@ -17,6 +17,44 @@ "message.pipeworks.mixed_fluids": "Those pipes hold different fluids", "message.pipeworks.pump.no_source": "A Pump must touch a still source block.", "message.pipeworks.stretch.pipe_blocked": "Something a pipe can't replace stands in the way.", + "pipeworks.gallery.breaking": "Breaking", + "pipeworks.gallery.breaking.1": "break a pipe:", + "pipeworks.gallery.breaking.2": "each side keeps", + "pipeworks.gallery.breaking.3": "its share", + "pipeworks.gallery.buckets": "Buckets", + "pipeworks.gallery.buckets.1": "use a bucket on", + "pipeworks.gallery.buckets.2": "a pipe or tank", + "pipeworks.gallery.buckets.3": "to fill or empty", + "pipeworks.gallery.creative_pipe": "Creative", + "pipeworks.gallery.creative_pipe.1": "click the pipe,", + "pipeworks.gallery.creative_pipe.2": "then its slot", + "pipeworks.gallery.creative_pipe.3": "with a bucket", + "pipeworks.gallery.dismantle": "Dismantle", + "pipeworks.gallery.dismantle.1": "sneak-click two", + "pipeworks.gallery.dismantle.2": "with a pickaxe;", + "pipeworks.gallery.dismantle.3": "drag to lay", + "pipeworks.gallery.one_fluid": "One Fluid", + "pipeworks.gallery.one_fluid.1": "a segment holds", + "pipeworks.gallery.one_fluid.2": "one fluid: a", + "pipeworks.gallery.one_fluid.3": "mix is refused", + "pipeworks.gallery.outlet": "Outlet", + "pipeworks.gallery.outlet.1": "sets fluid down", + "pipeworks.gallery.outlet.2": "a bucket at", + "pipeworks.gallery.outlet.3": "a time", + "pipeworks.gallery.pack": "Pipeworks Gallery", + "pipeworks.gallery.pack.description": "Working scenes: /function pipeworks:gallery/build", + "pipeworks.gallery.pipes": "Pipes", + "pipeworks.gallery.pipes.1": "a pump fills", + "pipeworks.gallery.pipes.2": "pipes and a", + "pipeworks.gallery.pipes.3": "storage tank", + "pipeworks.gallery.pump": "Pump", + "pipeworks.gallery.pump.1": "water stays,", + "pipeworks.gallery.pump.2": "lava is taken", + "pipeworks.gallery.pump.3": "whole", + "pipeworks.gallery.tank_level": "Tank Level", + "pipeworks.gallery.tank_level.1": "the glass shows", + "pipeworks.gallery.tank_level.2": "how full it is;", + "pipeworks.gallery.tank_level.3": "Jade adds more", "screen.pipeworks.creative_pipe.fluid": "Click with a bucket to set the fluid, or with an empty cursor to clear it", "tooltip.pipeworks.jade.closed": "Closed: %s", "tooltip.pipeworks.jade.side.down": "down", diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction new file mode 100644 index 0000000..1ef06d9 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction @@ -0,0 +1,22 @@ +# A Pump on a water source filling a long run with a storage tank at each end, and a pickaxe in a chest. +# A pipe broken splits the run: the far tank keeps its share and stops rising while the near one fills. +function pipeworks:gallery/clear +execute positioned ~5 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} +setblock ~6 ~ ~-6 pipeworks:pump[facing=east] +setblock ~7 ~ ~-6 pipeworks:pipe +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~13 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:pipe +setblock ~18 ~ ~-6 pipeworks:pipe +setblock ~19 ~ ~-6 pipeworks:pipe +setblock ~9 ~1 ~-6 pipeworks:storage_tank +setblock ~20 ~ ~-6 pipeworks:storage_tank +setblock ~14 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"minecraft:iron_pickaxe",count:1}]} +function pipeworks:gallery/sign {scene:"breaking"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction new file mode 100644 index 0000000..9a6bf0c --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction @@ -0,0 +1,16 @@ +# An empty run of pipes and two storage tanks, and a chest of buckets to fill and empty it with. +function pipeworks:gallery/clear +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~13 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:pipe +setblock ~18 ~ ~-6 pipeworks:storage_tank +setblock ~18 ~1 ~-6 pipeworks:storage_tank +setblock ~13 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"minecraft:water_bucket",count:1},{Slot:1b,id:"minecraft:water_bucket",count:1},{Slot:2b,id:"minecraft:water_bucket",count:1},{Slot:3b,id:"minecraft:lava_bucket",count:1},{Slot:4b,id:"minecraft:bucket",count:1},{Slot:5b,id:"minecraft:bucket",count:1}]} +function pipeworks:gallery/sign {scene:"buckets"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/build.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/build.mcfunction new file mode 100644 index 0000000..f4494e9 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/build.mcfunction @@ -0,0 +1,13 @@ +# The Pipeworks Gallery: a scene of each README feature, in README order, each building and clearing a +# plot of its own, 24 blocks wide and 13 deep (libworks ADR 0010). The scenes stand in rows of three, +# read west to east and then north, each row 16 blocks north of the one before, so the whole gallery +# stays near enough to where it is run for its pumps to keep working. +function pipeworks:gallery/pipes +execute positioned ~24 ~ ~ run function pipeworks:gallery/buckets +execute positioned ~48 ~ ~ run function pipeworks:gallery/one_fluid +execute positioned ~ ~ ~-16 run function pipeworks:gallery/breaking +execute positioned ~24 ~ ~-16 run function pipeworks:gallery/tank_level +execute positioned ~48 ~ ~-16 run function pipeworks:gallery/creative_pipe +execute positioned ~ ~ ~-32 run function pipeworks:gallery/pump +execute positioned ~24 ~ ~-32 run function pipeworks:gallery/outlet +execute positioned ~48 ~ ~-32 run function pipeworks:gallery/dismantle diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/clear.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/clear.mcfunction new file mode 100644 index 0000000..8cd8e2a --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/clear.mcfunction @@ -0,0 +1,8 @@ +# Clears a scene's plot, from ~2 to ~25 east and from ~-12 to ~0 south, 16 high, onto a stone floor: +# its blocks, then the items dropped, both those left lying and those the fills knock loose, such as +# the sign, whose floor goes before it does. structure_void first, which no scene places, so each fill +# changes a block, and water and lava go with the rest. +fill ~2 ~-1 ~-12 ~25 ~15 ~ minecraft:structure_void +fill ~2 ~-1 ~-12 ~25 ~-1 ~ minecraft:stone +fill ~2 ~ ~-12 ~25 ~15 ~ minecraft:air +execute align xyz positioned ~2 ~-1 ~-12 as @e[type=minecraft:item,dx=23,dy=16,dz=12] run kill @s diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/creative_pipe.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/creative_pipe.mcfunction new file mode 100644 index 0000000..51cb7ec --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/creative_pipe.mcfunction @@ -0,0 +1,16 @@ +# A creative pipe, its fluid unset, feeding a run and two storage tanks, and a chest of buckets whose +# fluid its screen's slot takes. +function pipeworks:gallery/clear +setblock ~8 ~ ~-6 pipeworks:creative_pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~13 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:storage_tank +setblock ~17 ~1 ~-6 pipeworks:storage_tank +setblock ~12 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"minecraft:water_bucket",count:1},{Slot:1b,id:"minecraft:lava_bucket",count:1}]} +function pipeworks:gallery/sign {scene:"creative_pipe"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/dismantle.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/dismantle.mcfunction new file mode 100644 index 0000000..def5ecb --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/dismantle.mcfunction @@ -0,0 +1,21 @@ +# A run of pipes to take up, and a chest with a pickaxe and pipes to lay: sneak-click a pipe with the +# pickaxe and click another to take up the run between them, and drag a line of pipes to lay one. +function pipeworks:gallery/clear +setblock ~6 ~ ~-6 pipeworks:pipe +setblock ~7 ~ ~-6 pipeworks:pipe +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~13 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:pipe +setblock ~18 ~ ~-6 pipeworks:pipe +setblock ~19 ~ ~-6 pipeworks:pipe +setblock ~20 ~ ~-6 pipeworks:pipe +setblock ~21 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"minecraft:iron_pickaxe",count:1},{Slot:1b,id:"pipeworks:pipe",count:32}]} +function pipeworks:gallery/sign {scene:"dismantle"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/one_fluid.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/one_fluid.mcfunction new file mode 100644 index 0000000..b59c8f0 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/one_fluid.mcfunction @@ -0,0 +1,21 @@ +# A run of water and a run of lava, one block apart, each filled by its own Pump, and a chest of pipes: +# a pipe set in the gap would join them, so it is refused. +function pipeworks:gallery/clear +execute positioned ~5 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} +setblock ~6 ~ ~-6 pipeworks:pump[facing=east] +setblock ~7 ~ ~-6 pipeworks:pipe +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:pipe +setblock ~18 ~ ~-6 pipeworks:pipe +setblock ~19 ~ ~-6 pipeworks:storage_tank +execute positioned ~21 ~ ~-6 run function pipeworks:gallery/pool {fluid:"lava"} +setblock ~20 ~ ~-6 pipeworks:pump[facing=west] +setblock ~13 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"pipeworks:pipe",count:4}]} +function pipeworks:gallery/sign {scene:"one_fluid"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/outlet.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/outlet.mcfunction new file mode 100644 index 0000000..afd8357 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/outlet.mcfunction @@ -0,0 +1,19 @@ +# A Pump on a water source filling a run and a storage tank, which an Outlet empties a bucket at a time +# into the glass basin it faces. The run alone would hold less than the bucket the Outlet waits for. +function pipeworks:gallery/clear +execute positioned ~6 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} +setblock ~7 ~ ~-6 pipeworks:pump[facing=east] +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~13 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~11 ~1 ~-6 pipeworks:storage_tank +setblock ~15 ~ ~-6 pipeworks:outlet[facing=east] +setblock ~17 ~ ~-6 minecraft:glass +setblock ~16 ~ ~-5 minecraft:glass +setblock ~16 ~ ~-7 minecraft:glass +setblock ~16 ~1 ~-6 minecraft:glass +function pipeworks:gallery/sign {scene:"outlet"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pipes.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pipes.mcfunction new file mode 100644 index 0000000..e379ec6 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pipes.mcfunction @@ -0,0 +1,16 @@ +# A Pump on a water source filling a run of pipes and a storage tank, whose glass shows it filling. +function pipeworks:gallery/clear +execute positioned ~6 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} +setblock ~7 ~ ~-6 pipeworks:pump[facing=east] +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:pipe +setblock ~12 ~ ~-6 pipeworks:pipe +setblock ~13 ~ ~-6 pipeworks:pipe +setblock ~14 ~ ~-6 pipeworks:pipe +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:pipe +setblock ~18 ~ ~-6 pipeworks:storage_tank +function pipeworks:gallery/sign {scene:"pipes"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pool.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pool.mcfunction new file mode 100644 index 0000000..d5990b0 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pool.mcfunction @@ -0,0 +1,9 @@ +# A still source of $(fluid) where it is run, walled in glass on its four sides and over it, so it never +# flows out of its plot: a fluid outside the plot would survive the next clear. The scene then sets the +# Pump that draws it in place of one wall. No two pools share a wall, which would fail its setblock. +setblock ~1 ~ ~ minecraft:glass +setblock ~-1 ~ ~ minecraft:glass +setblock ~ ~ ~1 minecraft:glass +setblock ~ ~ ~-1 minecraft:glass +setblock ~ ~1 ~ minecraft:glass +$setblock ~ ~ ~ minecraft:$(fluid) diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pump.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pump.mcfunction new file mode 100644 index 0000000..39197ef --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/pump.mcfunction @@ -0,0 +1,19 @@ +# Two Pumps: one on a water source, which stays and keeps giving, and one on a lava source, which it +# takes whole, as a bucket would. +function pipeworks:gallery/clear +execute positioned ~6 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} +setblock ~7 ~ ~-6 pipeworks:pump[facing=east] +setblock ~8 ~ ~-6 pipeworks:pipe +setblock ~9 ~ ~-6 pipeworks:pipe +setblock ~10 ~ ~-6 pipeworks:pipe +setblock ~11 ~ ~-6 pipeworks:storage_tank +setblock ~14 ~ ~-6 pipeworks:storage_tank +setblock ~15 ~ ~-6 pipeworks:pipe +setblock ~16 ~ ~-6 pipeworks:pipe +setblock ~17 ~ ~-6 pipeworks:pipe +setblock ~18 ~ ~-6 pipeworks:pipe +setblock ~19 ~ ~-6 pipeworks:pipe +setblock ~20 ~ ~-6 pipeworks:pipe +execute positioned ~22 ~ ~-6 run function pipeworks:gallery/pool {fluid:"lava"} +setblock ~21 ~ ~-6 pipeworks:pump[facing=west] +function pipeworks:gallery/sign {scene:"pump"} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/sign.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/sign.mcfunction new file mode 100644 index 0000000..a0722fc --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/sign.mcfunction @@ -0,0 +1,3 @@ +# A scene's sign at ~14 ~ ~0, facing south, waxed. Its name is in bold, and clicking it runs the +# scene's function again from the plot's corner, 14 blocks west of the sign, which builds it afresh. +$setblock ~14 ~ ~ minecraft:oak_sign[rotation=0]{is_waxed:1b,front_text:{messages:[{translate:"pipeworks.gallery.$(scene)",bold:true,click_event:{action:"run_command",command:"execute positioned ~-14 ~ ~ run function pipeworks:gallery/$(scene)"}},{translate:"pipeworks.gallery.$(scene).1"},{translate:"pipeworks.gallery.$(scene).2"},{translate:"pipeworks.gallery.$(scene).3"}]}} diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction new file mode 100644 index 0000000..8297e38 --- /dev/null +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction @@ -0,0 +1,9 @@ +# A Pump on a water source filling a column of three storage tanks, whose glass rises step by step. +# Jade, which the gallery may not need, adds the segment's amount and capacity. +function pipeworks:gallery/clear +execute positioned ~12 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} +setblock ~13 ~ ~-6 pipeworks:pump[facing=east] +setblock ~14 ~ ~-6 pipeworks:storage_tank +setblock ~14 ~1 ~-6 pipeworks:storage_tank +setblock ~14 ~2 ~-6 pipeworks:storage_tank +function pipeworks:gallery/sign {scene:"tank_level"} diff --git a/src/main/resources/gallery_pack/pack.mcmeta b/src/main/resources/gallery_pack/pack.mcmeta new file mode 100644 index 0000000..5619b52 --- /dev/null +++ b/src/main/resources/gallery_pack/pack.mcmeta @@ -0,0 +1,7 @@ +{ + "pack": { + "description": {"translate": "pipeworks.gallery.pack.description"}, + "min_format": [101, 1], + "max_format": [101, 1] + } +} From 5a013a56c12c6e5f4441ea416aeae421de5c58ad Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 12:01:06 +0200 Subject: [PATCH 2/7] fix: say the gallery's gestures on their signs, and fill the buckets scene's tank visibly Co-Authored-By: Claude Opus 5.5 --- .../pipeworks/gametest/GalleryTests.java | 24 +++++++++---------- .../assets/pipeworks/lang/en_us.json | 12 +++++----- .../function/gallery/breaking.mcfunction | 5 ++-- .../function/gallery/buckets.mcfunction | 6 ++--- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java index 6b790c8..8ef3a76 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java @@ -40,7 +40,7 @@ final class GalleryTests { private static final String PACK = GalleryPack.id(Pipeworks.MOD_ID); - /** How many scenes a row holds, and how far north each row is of the last. */ + /** How many scenes a row holds, and how far north each row is of the last, as {@code gallery/build} lays them. */ private static final int ROW = 3; private static final int ROW_DEPTH = 16; @@ -48,6 +48,8 @@ final class GalleryTests { private static final BlockPos ORIGIN = new BlockPos(0, 1, 8); /** Where a scene's sign stands, from where the scene is run. */ private static final BlockPos SIGN = new BlockPos(14, 0, 0); + /** Where the Outlet scene's outlet stands, from where the scene is run. */ + private static final BlockPos OUTLET = new BlockPos(15, 0, -6); /** Where the Outlet scene's outlet sets its water down, from where the scene is run. */ private static final BlockPos OUTLET_FRONT = new BlockPos(16, 0, -6); @@ -60,13 +62,13 @@ private static List scenes() { var pump = PipeworksRegistries.PUMP.get(); return List.of( new Scene("pipes", new BlockPos(18, 0, -6), tank), - new Scene("buckets", new BlockPos(18, 1, -6), tank), + new Scene("buckets", new BlockPos(18, 0, -6), tank), new Scene("one_fluid", new BlockPos(20, 0, -6), pump), new Scene("breaking", new BlockPos(20, 0, -6), tank), new Scene("tank_level", new BlockPos(14, 2, -6), tank), new Scene("creative_pipe", new BlockPos(8, 0, -6), PipeworksRegistries.CREATIVE_PIPE.get()), new Scene("pump", new BlockPos(21, 0, -6), pump), - new Scene("outlet", new BlockPos(15, 0, -6), PipeworksRegistries.OUTLET.get()), + new Scene("outlet", OUTLET, PipeworksRegistries.OUTLET.get()), new Scene("dismantle", new BlockPos(6, 0, -6), PipeworksRegistries.PIPE.get())); } @@ -89,7 +91,8 @@ private static void builds(GameTestHelper helper) { GalleryCheck.assertKeyBlocks(helper, rowOrigin(helper, row), scenes.subList(row * ROW, Math.min(scenes.size(), (row + 1) * ROW))); } - }, null); + }, () -> { + }); } /** The Outlet scene: its Pump fills the run, and the Outlet sets a water source down in its basin. */ @@ -104,7 +107,7 @@ private static void outletSetsWater(GameTestHelper helper) { /** A scene's sign, clicked, builds the scene again where it stands, leaving nothing loose: here the Outlet, taken away. */ private static void signReplays(GameTestHelper helper) { - var outlet = helper.absolutePos(ORIGIN).offset(15, 0, -6); + var outlet = helper.absolutePos(ORIGIN).offset(OUTLET); var sign = helper.absolutePos(ORIGIN).offset(SIGN); withPack(helper, 1, source -> { GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "outlet")); @@ -118,12 +121,13 @@ private static void signReplays(GameTestHelper helper) { var loose = helper.getLevel().getEntitiesOfClass(ItemEntity.class, AABB.of(plot)); helper.assertTrue(loose.isEmpty(), "building the scene again left items loose: " + loose.stream().map(ItemEntity::getItem).toList()); helper.assertTrue(helper.getLevel().getBlockState(sign).is(Blocks.OAK_SIGN), "building the scene again left no sign"); - }, null); + }, () -> { + }); } /** * Turns the pack on over the loaded plots of a gallery of {@code scenes}, runs {@code body} as an - * operator player standing where it is run, waits for {@code until} if any, then clears the plots + * operator player standing where it is run, waits until {@code until} passes, then clears the plots * and turns the pack off, passing or failing. */ private static void withPack(GameTestHelper helper, int scenes, Consumer body, Runnable until) { @@ -140,11 +144,7 @@ private static void withPack(GameTestHelper helper, int scenes, Consumer helper.assertTrue(area.intersectingChunks().allMatch(chunk -> helper.getLevel().hasChunk(chunk.x(), chunk.z())), "the gallery's plots are not loaded yet")) .thenExecute(() -> body.accept(source)) - .thenWaitUntil(() -> { - if (until != null) { - until.run(); - } - }) + .thenWaitUntil(until) .thenExecute(() -> off.set(clearAway(helper, area))) .thenWaitUntil(() -> helper.assertTrue(off.get() != null && off.get().isDone(), PACK + " is not off yet")) .thenSucceed(); diff --git a/src/main/resources/assets/pipeworks/lang/en_us.json b/src/main/resources/assets/pipeworks/lang/en_us.json index 7a97829..bb8e11a 100644 --- a/src/main/resources/assets/pipeworks/lang/en_us.json +++ b/src/main/resources/assets/pipeworks/lang/en_us.json @@ -18,9 +18,9 @@ "message.pipeworks.pump.no_source": "A Pump must touch a still source block.", "message.pipeworks.stretch.pipe_blocked": "Something a pipe can't replace stands in the way.", "pipeworks.gallery.breaking": "Breaking", - "pipeworks.gallery.breaking.1": "break a pipe:", - "pipeworks.gallery.breaking.2": "each side keeps", - "pipeworks.gallery.breaking.3": "its share", + "pipeworks.gallery.breaking.1": "break a pipe", + "pipeworks.gallery.breaking.2": "between tanks:", + "pipeworks.gallery.breaking.3": "split by size", "pipeworks.gallery.buckets": "Buckets", "pipeworks.gallery.buckets.1": "use a bucket on", "pipeworks.gallery.buckets.2": "a pipe or tank", @@ -34,9 +34,9 @@ "pipeworks.gallery.dismantle.2": "with a pickaxe;", "pipeworks.gallery.dismantle.3": "drag to lay", "pipeworks.gallery.one_fluid": "One Fluid", - "pipeworks.gallery.one_fluid.1": "a segment holds", - "pipeworks.gallery.one_fluid.2": "one fluid: a", - "pipeworks.gallery.one_fluid.3": "mix is refused", + "pipeworks.gallery.one_fluid.1": "one fluid each:", + "pipeworks.gallery.one_fluid.2": "a pipe in the", + "pipeworks.gallery.one_fluid.3": "gap is refused", "pipeworks.gallery.outlet": "Outlet", "pipeworks.gallery.outlet.1": "sets fluid down", "pipeworks.gallery.outlet.2": "a bucket at", diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction index 1ef06d9..13b0ff3 100644 --- a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/breaking.mcfunction @@ -1,5 +1,6 @@ -# A Pump on a water source filling a long run with a storage tank at each end, and a pickaxe in a chest. -# A pipe broken splits the run: the far tank keeps its share and stops rising while the near one fills. +# A Pump on a water source filling a long run, a storage tank near it and one at the far end, and a +# pickaxe in a chest. +# A pipe broken between the tanks splits the run: the far tank keeps its share and stops rising while the near one fills. function pipeworks:gallery/clear execute positioned ~5 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} setblock ~6 ~ ~-6 pipeworks:pump[facing=east] diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction index 9a6bf0c..120dfe7 100644 --- a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction @@ -1,4 +1,5 @@ -# An empty run of pipes and two storage tanks, and a chest of buckets to fill and empty it with. +# An empty run of pipes and a storage tank, whose glass rises a step for about each two buckets, and a +# chest of buckets to fill and empty it with. function pipeworks:gallery/clear setblock ~8 ~ ~-6 pipeworks:pipe setblock ~9 ~ ~-6 pipeworks:pipe @@ -11,6 +12,5 @@ setblock ~15 ~ ~-6 pipeworks:pipe setblock ~16 ~ ~-6 pipeworks:pipe setblock ~17 ~ ~-6 pipeworks:pipe setblock ~18 ~ ~-6 pipeworks:storage_tank -setblock ~18 ~1 ~-6 pipeworks:storage_tank -setblock ~13 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"minecraft:water_bucket",count:1},{Slot:1b,id:"minecraft:water_bucket",count:1},{Slot:2b,id:"minecraft:water_bucket",count:1},{Slot:3b,id:"minecraft:lava_bucket",count:1},{Slot:4b,id:"minecraft:bucket",count:1},{Slot:5b,id:"minecraft:bucket",count:1}]} +setblock ~13 ~ ~-3 minecraft:chest[facing=south]{Items:[{Slot:0b,id:"minecraft:water_bucket",count:1},{Slot:1b,id:"minecraft:water_bucket",count:1},{Slot:2b,id:"minecraft:water_bucket",count:1},{Slot:6b,id:"minecraft:water_bucket",count:1},{Slot:7b,id:"minecraft:water_bucket",count:1},{Slot:8b,id:"minecraft:water_bucket",count:1},{Slot:3b,id:"minecraft:lava_bucket",count:1},{Slot:4b,id:"minecraft:bucket",count:1},{Slot:5b,id:"minecraft:bucket",count:1}]} function pipeworks:gallery/sign {scene:"buckets"} From 2e8c715a29e397d102baed330827464929897eea Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 13:53:02 +0200 Subject: [PATCH 3/7] docs: say touching tanks share their contents, in the readme and the tank level sign Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- src/main/resources/assets/pipeworks/lang/en_us.json | 6 +++--- .../data/pipeworks/function/gallery/tank_level.mcfunction | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 91f8d36..2947add 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ To play Pipeworks beside every other FactoryWorks mod, install the [FactoryWorks ## Features -- **Pipes and a storage tank.** A pipe holds 100 mB and a tank 25,000 mB by default, Factorio's volumes; `pipeworks-server.toml` sets both, for the pipes and tanks placed afterwards. Pipes draw an arm toward each pipe, tank or port they link to, and toward any block beside them that holds fluid. +- **Pipes and a storage tank.** A pipe holds 100 mB and a tank 25,000 mB by default, Factorio's volumes; `pipeworks-server.toml` sets both, for the pipes and tanks placed afterwards. Pipes draw an arm toward each pipe, tank or port they link to, and toward any block beside them that holds fluid. Tanks that touch share their contents as one tank, their capacities added, as pipes do. - **Buckets.** A bucket, or any mod's item that holds fluid, used on a pipe or tank fills or empties its segment. A full one empties only if the segment has room for all of it and holds that fluid or none; an empty or part-full one fills from the segment. In creative the item is left as it is. - **One fluid per segment.** A pipe or tank that would join two segments holding different fluids is refused, with a message saying so. - **Breaking splits fairly.** Breaking a pipe, tank or port splits its segment by capacity, and the broken block takes its share of the fluid. Segments are saved with the world and keep their fluid while their chunks are unloaded. diff --git a/src/main/resources/assets/pipeworks/lang/en_us.json b/src/main/resources/assets/pipeworks/lang/en_us.json index bb8e11a..a128abf 100644 --- a/src/main/resources/assets/pipeworks/lang/en_us.json +++ b/src/main/resources/assets/pipeworks/lang/en_us.json @@ -52,9 +52,9 @@ "pipeworks.gallery.pump.2": "lava is taken", "pipeworks.gallery.pump.3": "whole", "pipeworks.gallery.tank_level": "Tank Level", - "pipeworks.gallery.tank_level.1": "the glass shows", - "pipeworks.gallery.tank_level.2": "how full it is;", - "pipeworks.gallery.tank_level.3": "Jade adds more", + "pipeworks.gallery.tank_level.1": "tanks that touch", + "pipeworks.gallery.tank_level.2": "share one level;", + "pipeworks.gallery.tank_level.3": "Jade: amounts", "screen.pipeworks.creative_pipe.fluid": "Click with a bucket to set the fluid, or with an empty cursor to clear it", "tooltip.pipeworks.jade.closed": "Closed: %s", "tooltip.pipeworks.jade.side.down": "down", diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction index 8297e38..6e93a25 100644 --- a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/tank_level.mcfunction @@ -1,4 +1,5 @@ -# A Pump on a water source filling a column of three storage tanks, whose glass rises step by step. +# A Pump on a water source filling a column of three storage tanks, which touch and so hold one segment +# between them: their glass rises together, step by step. # Jade, which the gallery may not need, adds the segment's amount and capacity. function pipeworks:gallery/clear execute positioned ~12 ~ ~-6 run function pipeworks:gallery/pool {fluid:"water"} From 33d680f99300936aceaeded0837f87aa0a6a0911 Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 14:04:33 +0200 Subject: [PATCH 4/7] fix: run each gallery test in a batch of its own, and check the pump scene The gallery tests build on the same plots, so in one batch the full build's clear wiped the Outlet scene before its water came. Each now runs alone, as Voidworks' do. Adds a test that the Pump scene leaves its water and takes its lava, and tidies GalleryTests from review. Co-Authored-By: Claude Opus 5.5 --- .../pipeworks/gametest/GalleryTests.java | 61 +++++++++++++------ .../gametest/PipeworksGameTests.java | 15 ++++- .../function/gallery/buckets.mcfunction | 3 +- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java index 8ef3a76..2569d88 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java @@ -32,9 +32,10 @@ * build running every command without failing, and each scene's key block where it stands. The scenes * stand in rows of three, each row 16 blocks north of the last, which keeps them near enough to where * the gallery is run for their pumps to tick; {@link GalleryCheck#builds} lays one long row, so the - * test runs its parts a row at a time. Beyond the build, the Outlet scene sets its water down, and a - * scene's sign, clicked, builds it again. Each test turns the pack on, which reloads the server's - * data, and off again, passing or failing, so they run in a batch of their own. + * test runs its parts a row at a time. Beyond the build, the Pump scene leaves its water and takes + * its lava, the Outlet scene sets its water down, and a scene's sign, clicked, builds it again. Each test turns the pack on, which reloads the server's + * data, and off again, passing or failing, and each builds where the others do, so each runs in a + * batch of its own. */ final class GalleryTests { @@ -48,6 +49,9 @@ final class GalleryTests { private static final BlockPos ORIGIN = new BlockPos(0, 1, 8); /** Where a scene's sign stands, from where the scene is run. */ private static final BlockPos SIGN = new BlockPos(14, 0, 0); + /** Where the Pump scene's water and lava sources stand, from where the scene is run. */ + private static final BlockPos PUMP_WATER = new BlockPos(6, 0, -6); + private static final BlockPos PUMP_LAVA = new BlockPos(22, 0, -6); /** Where the Outlet scene's outlet stands, from where the scene is run. */ private static final BlockPos OUTLET = new BlockPos(15, 0, -6); /** Where the Outlet scene's outlet sets its water down, from where the scene is run. */ @@ -56,7 +60,7 @@ final class GalleryTests { private GalleryTests() { } - /** The scenes in the order {@code build} lays them, each with a block it places. */ + /** The scenes in the order {@code build} lays them, each with a block it places, where its function places it. */ private static List scenes() { var tank = PipeworksRegistries.STORAGE_TANK.get(); var pump = PipeworksRegistries.PUMP.get(); @@ -73,17 +77,19 @@ private static List scenes() { } static void register(PipeworksGameTests.Registrar tests) { - tests.test("gallery_builds_a_scene_of_each_feature", GalleryCheck.TICKS, GalleryTests::builds); - tests.test("gallery_outlet_scene_sets_its_water_down", GalleryCheck.TICKS, GalleryTests::outletSetsWater); - tests.test("gallery_scenes_sign_clicked_builds_the_scene_again", GalleryCheck.TICKS, GalleryTests::signReplays); + tests.testOnItsOwn("gallery_builds_a_scene_of_each_feature", GalleryCheck.TICKS, GalleryTests::builds); + tests.testOnItsOwn("gallery_pump_scene_leaves_its_water_and_takes_its_lava", GalleryCheck.TICKS, GalleryTests::pumpTakesLava); + tests.testOnItsOwn("gallery_outlet_scene_sets_its_water_down", GalleryCheck.TICKS, GalleryTests::outletSetsWater); + tests.testOnItsOwn("gallery_scenes_sign_clicked_builds_the_scene_again", GalleryCheck.TICKS, GalleryTests::signReplays); } private static void builds(GameTestHelper helper) { var scenes = scenes(); + var functions = helper.getLevel().getServer().getFunctions(); withPack(helper, scenes.size(), source -> { GalleryCheck.assertLoads(helper, source, Pipeworks.MOD_ID); for (var scene : scenes) { - helper.assertTrue(helper.getLevel().getServer().getFunctions().get(GalleryPack.scene(Pipeworks.MOD_ID, scene.name())).isPresent(), + helper.assertTrue(functions.get(GalleryPack.scene(Pipeworks.MOD_ID, scene.name())).isPresent(), "there is no scene function " + scene.name()); } GalleryCheck.assertRuns(helper, source, GalleryPack.build(Pipeworks.MOD_ID)); @@ -91,10 +97,21 @@ private static void builds(GameTestHelper helper) { GalleryCheck.assertKeyBlocks(helper, rowOrigin(helper, row), scenes.subList(row * ROW, Math.min(scenes.size(), (row + 1) * ROW))); } - }, () -> { }); } + /** The Pump scene: one Pump leaves its water source in place, the other takes its lava source whole. */ + private static void pumpTakesLava(GameTestHelper helper) { + var level = helper.getLevel(); + var water = helper.absolutePos(ORIGIN).offset(PUMP_WATER); + var lava = helper.absolutePos(ORIGIN).offset(PUMP_LAVA); + withPack(helper, 1, source -> GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "pump")), + () -> { + helper.assertTrue(level.getFluidState(lava).isEmpty(), "the Pump has not taken its lava source yet"); + helper.assertTrue(level.getFluidState(water).isSourceOfType(Fluids.WATER), "the Pump took its water source"); + }); + } + /** The Outlet scene: its Pump fills the run, and the Outlet sets a water source down in its basin. */ private static void outletSetsWater(GameTestHelper helper) { var front = helper.absolutePos(ORIGIN).offset(OUTLET_FRONT); @@ -121,19 +138,25 @@ private static void signReplays(GameTestHelper helper) { var loose = helper.getLevel().getEntitiesOfClass(ItemEntity.class, AABB.of(plot)); helper.assertTrue(loose.isEmpty(), "building the scene again left items loose: " + loose.stream().map(ItemEntity::getItem).toList()); helper.assertTrue(helper.getLevel().getBlockState(sign).is(Blocks.OAK_SIGN), "building the scene again left no sign"); - }, () -> { + }); + } + + /** As {@link #withPack(GameTestHelper, int, Consumer, Runnable)}, waiting for nothing after {@code body}. */ + private static void withPack(GameTestHelper helper, int plots, Consumer body) { + withPack(helper, plots, body, () -> { }); } /** - * Turns the pack on over the loaded plots of a gallery of {@code scenes}, runs {@code body} as an - * operator player standing where it is run, waits until {@code until} passes, then clears the plots - * and turns the pack off, passing or failing. + * Turns the pack on over the first {@code plots} plots, as {@code build} lays them, loaded; runs + * {@code body} as an operator player standing where the gallery is run; waits until {@code until} + * passes; then clears the plots and turns the pack off, passing or failing. A single scene run + * from where the gallery is run stands on the first plot. */ - private static void withPack(GameTestHelper helper, int scenes, Consumer body, Runnable until) { + private static void withPack(GameTestHelper helper, int plots, Consumer body, Runnable until) { var server = helper.getLevel().getServer(); GalleryCheck.assertOfferedAndOff(helper, PACK); - var area = area(helper, scenes); + var area = area(helper, plots); area.intersectingChunks().forEach(chunk -> helper.getLevel().setChunkForced(chunk.x(), chunk.z(), true)); var source = GalleryCheck.operator(helper, helper.absolutePos(ORIGIN)); var on = GalleryCheck.select(server, PACK, true); @@ -155,12 +178,12 @@ private static BlockPos rowOrigin(GameTestHelper helper, int row) { return helper.absolutePos(ORIGIN).north(ROW_DEPTH * row); } - /** Every plot of a gallery's first {@code scenes} scenes, their floors included. */ - private static BoundingBox area(GameTestHelper helper, int scenes) { - int rows = Math.ceilDiv(scenes, ROW); + /** The first {@code plots} plots of the gallery, their floors included. */ + private static BoundingBox area(GameTestHelper helper, int plots) { + int rows = Math.ceilDiv(plots, ROW); return BoundingBox.encapsulatingBoxes(List.of( GalleryCheck.plot(helper.absolutePos(ORIGIN)), - GalleryCheck.strip(rowOrigin(helper, rows - 1), Math.min(scenes, ROW)))).orElseThrow(); + GalleryCheck.strip(rowOrigin(helper, rows - 1), Math.min(plots, ROW)))).orElseThrow(); } /** Air where the plots stood, their entities gone, and the pack off. The runner releases the forced chunks once the batch ends. */ diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java index 8da13b8..1198646 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java @@ -63,9 +63,7 @@ private static void registerTests(RegisterGameTestsEvent event) { // Registered rather than borrowed, since the event hands out no lookup for vanilla's. var environment = event.registerEnvironment(id("default"), new TestEnvironmentDefinition.AllOf(List.of())); var tests = new Registrar(event, environment); - // Turning the Gallery pack on reloads the server's data under every test in its batch (libworks ADR 0010). - var gallery = event.registerEnvironment(id("gallery"), new TestEnvironmentDefinition.AllOf(List.of())); - GalleryTests.register(new Registrar(event, gallery)); + GalleryTests.register(tests); LoadTests.register(tests); PlaysAloneTests.register(tests); SegmentTests.register(tests); @@ -96,5 +94,16 @@ void test(String name, int maxTicks, Consumer body) { CodeGameTest.define(id, body); event.registerTest(id, new CodeGameTest(id, new TestData<>(environment, PLATFORM, maxTicks, 0, true, Rotation.NONE))); } + + /** + * As {@link #test}, in a batch of its own: one that turns a pack on, which reloads the server's + * data under every test in its batch, or builds where another test would (libworks ADR 0010). + */ + void testOnItsOwn(String name, int maxTicks, Consumer body) { + var id = id(name); + var own = event.registerEnvironment(id, new TestEnvironmentDefinition.AllOf(List.of())); + CodeGameTest.define(id, body); + event.registerTest(id, new CodeGameTest(id, new TestData<>(own, PLATFORM, maxTicks, 0, true, Rotation.NONE))); + } } } diff --git a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction index 120dfe7..11c50a9 100644 --- a/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction +++ b/src/main/resources/gallery_pack/data/pipeworks/function/gallery/buckets.mcfunction @@ -1,5 +1,6 @@ # An empty run of pipes and a storage tank, whose glass rises a step for about each two buckets, and a -# chest of buckets to fill and empty it with. +# chest of buckets to fill and empty it with. Its one lava bucket goes in only while the run is empty, +# as the segment holds one fluid. function pipeworks:gallery/clear setblock ~8 ~ ~-6 pipeworks:pipe setblock ~9 ~ ~-6 pipeworks:pipe From 3d8aae9c812a0aa1edbbb48c102e385feb328e6a Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 14:39:02 +0200 Subject: [PATCH 5/7] fix: clear the gallery tests' plots with the pack's own clear The runner's clear sets blocks with no removal, which left the build test's pipes and tanks in the saved segments, so the Pump scene's lava run joined their water and its Pump never took the lava. Also checks the water Pump fills its tank, and tidies the tests from review. Co-Authored-By: Claude Opus 5.5 --- .../pipeworks/gametest/GalleryTests.java | 67 +++++++++++++------ .../gametest/PipeworksGameTests.java | 11 +-- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java index 2569d88..df1de68 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java @@ -13,6 +13,7 @@ import io.github._5thlayer.libworks.testkit.GalleryCheck.Scene; import io.github._5thlayer.pipeworks.Pipeworks; import io.github._5thlayer.pipeworks.PipeworksRegistries; +import io.github._5thlayer.pipeworks.api.FluidPorts; import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.BlockPos; import net.minecraft.gametest.framework.GameTestHelper; @@ -26,16 +27,17 @@ import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; /** * The Gallery pack (libworks ADR 0010), through libworks' {@link GalleryCheck}: offered and off, its * build running every command without failing, and each scene's key block where it stands. The scenes * stand in rows of three, each row 16 blocks north of the last, which keeps them near enough to where * the gallery is run for their pumps to tick; {@link GalleryCheck#builds} lays one long row, so the - * test runs its parts a row at a time. Beyond the build, the Pump scene leaves its water and takes - * its lava, the Outlet scene sets its water down, and a scene's sign, clicked, builds it again. Each test turns the pack on, which reloads the server's - * data, and off again, passing or failing, and each builds where the others do, so each runs in a - * batch of its own. + * test runs its parts a row at a time. Beyond the build, the Pump scene fills its tank and leaves + * its water while taking its lava, the Outlet scene sets its water down, and a scene's sign, clicked, + * builds it again. Each test turns the pack on, which reloads the server's data, and off again, + * passing or failing, and each builds where the others do, so each runs in a batch of its own. */ final class GalleryTests { @@ -49,18 +51,24 @@ final class GalleryTests { private static final BlockPos ORIGIN = new BlockPos(0, 1, 8); /** Where a scene's sign stands, from where the scene is run. */ private static final BlockPos SIGN = new BlockPos(14, 0, 0); - /** Where the Pump scene's water and lava sources stand, from where the scene is run. */ + /** Where the Pump scene's water source stands, from where the scene is run. */ private static final BlockPos PUMP_WATER = new BlockPos(6, 0, -6); + /** Where the Pump scene's water Pump fills its storage tank, from where the scene is run. */ + private static final BlockPos PUMP_WATER_TANK = new BlockPos(11, 0, -6); + /** Where the Pump scene's lava source stands, from where the scene is run. */ private static final BlockPos PUMP_LAVA = new BlockPos(22, 0, -6); /** Where the Outlet scene's outlet stands, from where the scene is run. */ private static final BlockPos OUTLET = new BlockPos(15, 0, -6); /** Where the Outlet scene's outlet sets its water down, from where the scene is run. */ private static final BlockPos OUTLET_FRONT = new BlockPos(16, 0, -6); + /** How many plots a test of one scene loads: the first, where the scene is run from where the gallery is. */ + private static final int ONE_SCENE = 1; + private GalleryTests() { } - /** The scenes in the order {@code build} lays them, each with a block it places, where its function places it. */ + /** The scenes in the order {@code build} lays them, each with a key block and where, from where the scene is run, its function places it. */ private static List scenes() { var tank = PipeworksRegistries.STORAGE_TANK.get(); var pump = PipeworksRegistries.PUMP.get(); @@ -100,22 +108,25 @@ private static void builds(GameTestHelper helper) { }); } - /** The Pump scene: one Pump leaves its water source in place, the other takes its lava source whole. */ + /** The Pump scene: one Pump fills its tank and leaves its water source in place, the other takes its lava source whole. */ private static void pumpTakesLava(GameTestHelper helper) { var level = helper.getLevel(); - var water = helper.absolutePos(ORIGIN).offset(PUMP_WATER); - var lava = helper.absolutePos(ORIGIN).offset(PUMP_LAVA); - withPack(helper, 1, source -> GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "pump")), + var water = at(helper, PUMP_WATER); + var tank = at(helper, PUMP_WATER_TANK); + var lava = at(helper, PUMP_LAVA); + withPack(helper, ONE_SCENE, source -> GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "pump")), () -> { helper.assertTrue(level.getFluidState(lava).isEmpty(), "the Pump has not taken its lava source yet"); - helper.assertTrue(level.getFluidState(water).isSourceOfType(Fluids.WATER), "the Pump took its water source"); + var drawn = FluidPorts.segment(level, tank); + helper.assertTrue(drawn != null && drawn.getAmountAsLong(0) > 0, "the water Pump has drawn nothing into its tank yet"); + helper.assertTrue(level.getFluidState(water).isSourceOfType(Fluids.WATER), "the water Pump took its source"); }); } /** The Outlet scene: its Pump fills the run, and the Outlet sets a water source down in its basin. */ private static void outletSetsWater(GameTestHelper helper) { - var front = helper.absolutePos(ORIGIN).offset(OUTLET_FRONT); - withPack(helper, 1, source -> GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "outlet")), + var front = at(helper, OUTLET_FRONT); + withPack(helper, ONE_SCENE, source -> GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "outlet")), () -> { var fluid = helper.getLevel().getFluidState(front); helper.assertTrue(fluid.isSource() && fluid.is(Fluids.WATER), "the Outlet has set no water down in its basin yet"); @@ -124,9 +135,9 @@ private static void outletSetsWater(GameTestHelper helper) { /** A scene's sign, clicked, builds the scene again where it stands, leaving nothing loose: here the Outlet, taken away. */ private static void signReplays(GameTestHelper helper) { - var outlet = helper.absolutePos(ORIGIN).offset(OUTLET); - var sign = helper.absolutePos(ORIGIN).offset(SIGN); - withPack(helper, 1, source -> { + var outlet = at(helper, OUTLET); + var sign = at(helper, SIGN); + withPack(helper, ONE_SCENE, source -> { GalleryCheck.assertRuns(helper, source, GalleryPack.scene(Pipeworks.MOD_ID, "outlet")); helper.getLevel().removeBlock(outlet, false); helper.assertTrue(helper.getLevel().getBlockEntity(sign) instanceof SignBlockEntity entity @@ -160,7 +171,7 @@ private static void withPack(GameTestHelper helper, int plots, Consumer helper.getLevel().setChunkForced(chunk.x(), chunk.z(), true)); var source = GalleryCheck.operator(helper, helper.absolutePos(ORIGIN)); var on = GalleryCheck.select(server, PACK, true); - helper.testInfo.addListener(new ClearAwayIfFailed(helper, area, on)); + helper.testInfo.addListener(new ClearAwayIfFailed(helper, plots, source, area, on)); var off = new AtomicReference>(); helper.startSequence() .thenWaitUntil(() -> helper.assertTrue(on.isDone(), PACK + " is not on yet")) @@ -168,11 +179,16 @@ private static void withPack(GameTestHelper helper, int plots, Consumer body.accept(source)) .thenWaitUntil(until) - .thenExecute(() -> off.set(clearAway(helper, area))) + .thenExecute(() -> off.set(clearAway(helper, plots, source, area))) .thenWaitUntil(() -> helper.assertTrue(off.get() != null && off.get().isDone(), PACK + " is not off yet")) .thenSucceed(); } + /** {@code offset}, from where the gallery is run, absolute. */ + private static BlockPos at(GameTestHelper helper, BlockPos offset) { + return helper.absolutePos(ORIGIN).offset(offset); + } + /** Where row {@code row} of the gallery is run from, absolute. */ private static BlockPos rowOrigin(GameTestHelper helper, int row) { return helper.absolutePos(ORIGIN).north(ROW_DEPTH * row); @@ -187,13 +203,22 @@ private static BoundingBox area(GameTestHelper helper, int plots) { } /** Air where the plots stood, their entities gone, and the pack off. The runner releases the forced chunks once the batch ends. */ - private static CompletableFuture clearAway(GameTestHelper helper, BoundingBox area) { + private static CompletableFuture clearAway(GameTestHelper helper, int plots, CommandSourceStack source, BoundingBox area) { + // The pack's own clear first, which removes each pipe, tank and port as a player's world would: + // the runner's clear sets blocks with no removal, which leaves their nodes in the saved segments + // for the next test's scene to join. + var commands = helper.getLevel().getServer().getCommands(); + for (int plot = 0; plot < plots; plot++) { + var from = Vec3.atBottomCenterOf(rowOrigin(helper, plot / ROW).east(GalleryCheck.PLOT_WIDTH * (plot % ROW))); + commands.performPrefixedCommand(source.withPosition(from), "function " + Pipeworks.MOD_ID + ":gallery/clear"); + } StructureUtils.clearSpaceForStructure(area, helper.getLevel()); return GalleryCheck.select(helper.getLevel().getServer(), PACK, false); } /** A failed test skips its own last steps, which would leave the pack on; it turns it off once turning it on is done. */ - private record ClearAwayIfFailed(GameTestHelper helper, BoundingBox area, CompletableFuture on) implements GameTestListener { + private record ClearAwayIfFailed(GameTestHelper helper, int plots, CommandSourceStack source, BoundingBox area, + CompletableFuture on) implements GameTestListener { @Override public void testStructureLoaded(GameTestInfo testInfo) { @@ -206,7 +231,7 @@ public void testPassed(GameTestInfo testInfo, GameTestRunner runner) { @Override public void testFailed(GameTestInfo testInfo, GameTestRunner runner) { var server = helper.getLevel().getServer(); - on.whenComplete((done, failed) -> server.execute(() -> clearAway(helper, area))); + on.whenComplete((done, failed) -> server.execute(() -> clearAway(helper, plots, source, area))); } @Override diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java index 1198646..52e6adc 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/PipeworksGameTests.java @@ -90,9 +90,7 @@ private static Identifier id(String path) { record Registrar(RegisterGameTestsEvent event, Holder> environment) { void test(String name, int maxTicks, Consumer body) { - var id = id(name); - CodeGameTest.define(id, body); - event.registerTest(id, new CodeGameTest(id, new TestData<>(environment, PLATFORM, maxTicks, 0, true, Rotation.NONE))); + register(id(name), environment, maxTicks, body); } /** @@ -101,9 +99,12 @@ void test(String name, int maxTicks, Consumer body) { */ void testOnItsOwn(String name, int maxTicks, Consumer body) { var id = id(name); - var own = event.registerEnvironment(id, new TestEnvironmentDefinition.AllOf(List.of())); + register(id, event.registerEnvironment(id, new TestEnvironmentDefinition.AllOf(List.of())), maxTicks, body); + } + + private void register(Identifier id, Holder> in, int maxTicks, Consumer body) { CodeGameTest.define(id, body); - event.registerTest(id, new CodeGameTest(id, new TestData<>(own, PLATFORM, maxTicks, 0, true, Rotation.NONE))); + event.registerTest(id, new CodeGameTest(id, new TestData<>(in, PLATFORM, maxTicks, 0, true, Rotation.NONE))); } } } From 8c8f63660c0728979e59c308c56dc1fac3d04cd2 Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 14:41:50 +0200 Subject: [PATCH 6/7] refactor: gather a gallery test's run into one record Co-Authored-By: Claude Opus 5.5 --- .../pipeworks/gametest/GalleryTests.java | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java index df1de68..12fe640 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java @@ -62,7 +62,7 @@ final class GalleryTests { /** Where the Outlet scene's outlet sets its water down, from where the scene is run. */ private static final BlockPos OUTLET_FRONT = new BlockPos(16, 0, -6); - /** How many plots a test of one scene loads: the first, where the scene is run from where the gallery is. */ + /** How many plots a test of one scene loads: a scene run alone, from where the gallery is run, fills only the first. */ private static final int ONE_SCENE = 1; private GalleryTests() { @@ -167,19 +167,19 @@ private static void withPack(GameTestHelper helper, int plots, Consumer body, Runnable until) { var server = helper.getLevel().getServer(); GalleryCheck.assertOfferedAndOff(helper, PACK); - var area = area(helper, plots); + var run = new Run(helper, plots, GalleryCheck.operator(helper, helper.absolutePos(ORIGIN))); + var area = run.area(); area.intersectingChunks().forEach(chunk -> helper.getLevel().setChunkForced(chunk.x(), chunk.z(), true)); - var source = GalleryCheck.operator(helper, helper.absolutePos(ORIGIN)); var on = GalleryCheck.select(server, PACK, true); - helper.testInfo.addListener(new ClearAwayIfFailed(helper, plots, source, area, on)); + helper.testInfo.addListener(new ClearAwayIfFailed(run, on)); var off = new AtomicReference>(); helper.startSequence() .thenWaitUntil(() -> helper.assertTrue(on.isDone(), PACK + " is not on yet")) .thenWaitUntil(() -> helper.assertTrue(area.intersectingChunks().allMatch(chunk -> helper.getLevel().hasChunk(chunk.x(), chunk.z())), "the gallery's plots are not loaded yet")) - .thenExecute(() -> body.accept(source)) + .thenExecute(() -> body.accept(run.source())) .thenWaitUntil(until) - .thenExecute(() -> off.set(clearAway(helper, plots, source, area))) + .thenExecute(() -> off.set(run.clearAway())) .thenWaitUntil(() -> helper.assertTrue(off.get() != null && off.get().isDone(), PACK + " is not off yet")) .thenSucceed(); } @@ -194,31 +194,39 @@ private static BlockPos rowOrigin(GameTestHelper helper, int row) { return helper.absolutePos(ORIGIN).north(ROW_DEPTH * row); } - /** The first {@code plots} plots of the gallery, their floors included. */ - private static BoundingBox area(GameTestHelper helper, int plots) { - int rows = Math.ceilDiv(plots, ROW); - return BoundingBox.encapsulatingBoxes(List.of( - GalleryCheck.plot(helper.absolutePos(ORIGIN)), - GalleryCheck.strip(rowOrigin(helper, rows - 1), Math.min(plots, ROW)))).orElseThrow(); + /** Where plot {@code plot} of the gallery, its first 0, is run from, absolute: as {@code build} lays them. */ + private static BlockPos plotOrigin(GameTestHelper helper, int plot) { + return rowOrigin(helper, plot / ROW).east(GalleryCheck.PLOT_WIDTH * (plot % ROW)); } - /** Air where the plots stood, their entities gone, and the pack off. The runner releases the forced chunks once the batch ends. */ - private static CompletableFuture clearAway(GameTestHelper helper, int plots, CommandSourceStack source, BoundingBox area) { - // The pack's own clear first, which removes each pipe, tank and port as a player's world would: - // the runner's clear sets blocks with no removal, which leaves their nodes in the saved segments - // for the next test's scene to join. - var commands = helper.getLevel().getServer().getCommands(); - for (int plot = 0; plot < plots; plot++) { - var from = Vec3.atBottomCenterOf(rowOrigin(helper, plot / ROW).east(GalleryCheck.PLOT_WIDTH * (plot % ROW))); - commands.performPrefixedCommand(source.withPosition(from), "function " + Pipeworks.MOD_ID + ":gallery/clear"); + /** A test's run of the gallery: its first {@code plots} plots, built by {@code source}, standing where the gallery is run. */ + private record Run(GameTestHelper helper, int plots, CommandSourceStack source) { + + /** The plots, their floors included. */ + BoundingBox area() { + int rows = Math.ceilDiv(plots, ROW); + return BoundingBox.encapsulatingBoxes(List.of( + GalleryCheck.plot(helper.absolutePos(ORIGIN)), + GalleryCheck.strip(rowOrigin(helper, rows - 1), Math.min(plots, ROW)))).orElseThrow(); + } + + /** Air where the plots stood, their entities gone, and the pack off. The runner releases the forced chunks once the batch ends. */ + CompletableFuture clearAway() { + // The pack's own clear first, which removes each pipe, tank and port as a player's world would: + // the runner's clear sets blocks with no removal, which leaves their nodes in the saved segments + // for the next test's scene to join. + var commands = helper.getLevel().getServer().getCommands(); + for (int plot = 0; plot < plots; plot++) { + var from = Vec3.atBottomCenterOf(plotOrigin(helper, plot)); + commands.performPrefixedCommand(source.withPosition(from), "function " + Pipeworks.MOD_ID + ":gallery/clear"); + } + StructureUtils.clearSpaceForStructure(area(), helper.getLevel()); + return GalleryCheck.select(helper.getLevel().getServer(), PACK, false); } - StructureUtils.clearSpaceForStructure(area, helper.getLevel()); - return GalleryCheck.select(helper.getLevel().getServer(), PACK, false); } - /** A failed test skips its own last steps, which would leave the pack on; it turns it off once turning it on is done. */ - private record ClearAwayIfFailed(GameTestHelper helper, int plots, CommandSourceStack source, BoundingBox area, - CompletableFuture on) implements GameTestListener { + /** A failed test skips its own last steps, which would leave the pack on; it clears away once turning it on is done. */ + private record ClearAwayIfFailed(Run run, CompletableFuture on) implements GameTestListener { @Override public void testStructureLoaded(GameTestInfo testInfo) { @@ -230,8 +238,8 @@ public void testPassed(GameTestInfo testInfo, GameTestRunner runner) { @Override public void testFailed(GameTestInfo testInfo, GameTestRunner runner) { - var server = helper.getLevel().getServer(); - on.whenComplete((done, failed) -> server.execute(() -> clearAway(helper, plots, source, area))); + var server = run.helper().getLevel().getServer(); + on.whenComplete((done, failed) -> server.execute(run::clearAway)); } @Override From 24c9cb616ea294f70eb913a07ea26c89319acdb1 Mon Sep 17 00:00:00 2001 From: Andrea D'Amico Date: Fri, 16 Oct 2026 14:43:01 +0200 Subject: [PATCH 7/7] docs: number the gallery tests' plots plainly Co-Authored-By: Claude Opus 5.5 --- .../io/github/_5thlayer/pipeworks/gametest/GalleryTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java index 12fe640..084fb86 100644 --- a/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java +++ b/src/main/java/io/github/_5thlayer/pipeworks/gametest/GalleryTests.java @@ -194,7 +194,7 @@ private static BlockPos rowOrigin(GameTestHelper helper, int row) { return helper.absolutePos(ORIGIN).north(ROW_DEPTH * row); } - /** Where plot {@code plot} of the gallery, its first 0, is run from, absolute: as {@code build} lays them. */ + /** Where plot {@code plot} of the gallery, numbered from 0 as {@code build} lays them, is run from, absolute. */ private static BlockPos plotOrigin(GameTestHelper helper, int plot) { return rowOrigin(helper, plot / ROW).east(GalleryCheck.PLOT_WIDTH * (plot % ROW)); }