From ab8b8e41dabba27c5cd95b6b316d1835ecf37564 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Thu, 24 Jun 2021 18:22:28 +0200 Subject: [PATCH] Thinking about Pipes - Survival mode players can now configure the fluid supplied by a creative fluid tank - Pondering for Tanks, Creative Tanks and Hose Pulleys --- .../fluids/actors/FluidDrainingBehaviour.java | 6 +- .../fluids/actors/FluidFillingBehaviour.java | 13 +- .../fluids/tank/FluidTankBlock.java | 2 +- .../create/foundation/fluid/FluidHelper.java | 5 +- .../foundation/ponder/SceneBuilder.java | 1 - .../ponder/content/PonderIndex.java | 8 +- .../ponder/content/fluid/FluidTankScenes.java | 220 ++++++++-- .../content/fluid/HosePulleyScenes.java | 387 +++++++++++++++--- .../textures/block/hose_pulley_magnet.png | Bin 257 -> 318 bytes .../resources/ponder/fluid_tank/sizes.nbt | Bin 0 -> 1502 bytes .../ponder/fluid_tank/sizes_creative.nbt | Bin 0 -> 1515 bytes .../ponder/fluid_tank/storage_creative.nbt | Bin 0 -> 937 bytes .../resources/ponder/hose_pulley/infinite.nbt | Bin 0 -> 1053 bytes .../resources/ponder/hose_pulley/intro.nbt | Bin 0 -> 994 bytes .../resources/ponder/hose_pulley/level.nbt | Bin 0 -> 1000 bytes 15 files changed, 553 insertions(+), 89 deletions(-) create mode 100644 src/main/resources/ponder/fluid_tank/sizes.nbt create mode 100644 src/main/resources/ponder/fluid_tank/sizes_creative.nbt create mode 100644 src/main/resources/ponder/fluid_tank/storage_creative.nbt create mode 100644 src/main/resources/ponder/hose_pulley/infinite.nbt create mode 100644 src/main/resources/ponder/hose_pulley/intro.nbt create mode 100644 src/main/resources/ponder/hose_pulley/level.nbt diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidDrainingBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidDrainingBehaviour.java index 7d49cd826..b1bbcb128 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidDrainingBehaviour.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidDrainingBehaviour.java @@ -96,7 +96,8 @@ public class FluidDrainingBehaviour extends FluidManipulationBehaviour { fluid = flowingFluid.getFluid(); else { affectedArea.expandTo(new MutableBoundingBox(currentPos, currentPos)); - world.setBlockState(currentPos, emptied, 2 | 16); + if (!tileEntity.isVirtual()) + world.setBlockState(currentPos, emptied, 2 | 16); queue.dequeue(); if (queue.isEmpty()) { isValid = checkValid(world, rootPos); @@ -136,7 +137,8 @@ public class FluidDrainingBehaviour extends FluidManipulationBehaviour { return true; } - world.setBlockState(currentPos, emptied, 2 | 16); + if (!tileEntity.isVirtual()) + world.setBlockState(currentPos, emptied, 2 | 16); affectedArea.expandTo(new MutableBoundingBox(currentPos, currentPos)); queue.dequeue(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidFillingBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidFillingBehaviour.java index a4b6fe0cd..0fbd08807 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidFillingBehaviour.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/FluidFillingBehaviour.java @@ -176,13 +176,16 @@ public class FluidFillingBehaviour extends FluidManipulationBehaviour { BlockState blockState = world.getBlockState(currentPos); if (blockState.contains(BlockStateProperties.WATERLOGGED) && fluid.isEquivalentTo(Fluids.WATER)) { - world.setBlockState(currentPos, - updatePostWaterlogging(blockState.with(BlockStateProperties.WATERLOGGED, true)), 2 | 16); + if (!tileEntity.isVirtual()) + world.setBlockState(currentPos, + updatePostWaterlogging(blockState.with(BlockStateProperties.WATERLOGGED, true)), + 2 | 16); } else { replaceBlock(world, currentPos, blockState); - world.setBlockState(currentPos, FluidHelper.convertToStill(fluid) - .getDefaultState() - .getBlockState(), 2 | 16); + if (!tileEntity.isVirtual()) + world.setBlockState(currentPos, FluidHelper.convertToStill(fluid) + .getDefaultState() + .getBlockState(), 2 | 16); } ITickList pendingFluidTicks = world.getPendingFluidTicks(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java index 2b7426d6a..3907fd89f 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java @@ -114,7 +114,7 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE s1 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s1, util.vector.of(2, -2, 2), 0); + scene.idle(10); + + scene.overlay.showText(60) + .text("Fluid Tanks can be combined to increase the total capacity") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.topOf(2, 1, 2)); + scene.idle(40); + + ElementLink s2 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s2, util.vector.of(2, -2, 3), 0); + scene.idle(5); + ElementLink s3 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s3, util.vector.of(3, -2, 3), 0); + scene.idle(5); + ElementLink s4 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s4, util.vector.of(3, -2, 2), 0); + scene.idle(10); + + scene.world.moveSection(s1, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s2, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s3, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s4, util.vector.of(0, -100, 0), 0); + + ElementLink d = scene.world.showIndependentSectionImmediately(single2); + scene.world.moveSection(d, util.vector.of(2, -1, 2), 0); + scene.effects.indicateSuccess(util.grid.at(2, 1, 2)); + scene.effects.indicateSuccess(util.grid.at(3, 1, 2)); + scene.effects.indicateSuccess(util.grid.at(2, 1, 3)); + scene.effects.indicateSuccess(util.grid.at(3, 1, 3)); + scene.world.hideIndependentSection(s1, Direction.DOWN); + scene.world.hideIndependentSection(s2, Direction.DOWN); + scene.world.hideIndependentSection(s3, Direction.DOWN); + scene.world.hideIndependentSection(s4, Direction.DOWN); + scene.idle(25); + + scene.overlay.showText(60) + .text("Their base square can be up to 3 blocks wide...") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.topOf(2, 1, 2)); + scene.idle(40); + + s1 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s1, util.vector.of(2, -2, 4), 0); + scene.idle(3); + s2 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s2, util.vector.of(3, -2, 4), 0); + scene.idle(3); + s3 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s3, util.vector.of(4, -2, 4), 0); + scene.idle(3); + s4 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s4, util.vector.of(4, -2, 3), 0); + scene.idle(3); + ElementLink s5 = scene.world.showIndependentSection(single, Direction.DOWN); + scene.world.moveSection(s5, util.vector.of(4, -2, 2), 0); + scene.idle(10); + + scene.world.moveSection(d, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s1, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s2, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s3, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s4, util.vector.of(0, -100, 0), 0); + scene.world.moveSection(s5, util.vector.of(0, -100, 0), 0); + + ElementLink t = scene.world.showIndependentSectionImmediately(single3); + scene.world.moveSection(t, util.vector.of(2, 0, 2), 0); + + for (int i = 2; i < 5; i++) + for (int j = 2; j < 5; j++) + scene.effects.indicateSuccess(util.grid.at(i, 1, j)); + + scene.world.hideIndependentSection(d, Direction.DOWN); + scene.world.hideIndependentSection(s1, Direction.DOWN); + scene.world.hideIndependentSection(s2, Direction.DOWN); + scene.world.hideIndependentSection(s3, Direction.DOWN); + scene.world.hideIndependentSection(s4, Direction.DOWN); + scene.world.hideIndependentSection(s5, Direction.DOWN); + scene.idle(25); + + scene.world.hideIndependentSection(t, Direction.DOWN); + scene.idle(10); + + Selection full1 = util.select.fromTo(5, 1, 0, 5, 6, 0); + Selection full2 = util.select.fromTo(4, 1, 1, 3, 6, 2); + Selection full3 = util.select.fromTo(0, 6, 5, 2, 1, 3); + + scene.world.showSection(full1, Direction.DOWN); + scene.idle(5); + scene.world.showSection(full2, Direction.DOWN); + scene.idle(5); + scene.world.showSection(full3, Direction.DOWN); + scene.idle(10); + + Vector3d blockSurface = util.vector.blockSurface(util.grid.at(3, 3, 1), Direction.WEST); + scene.overlay.showText(60) + .text("...and grow in height by more than 30 additional layers") + .attachKeyFrame() + .placeNearTarget() + .pointAt(blockSurface); + scene.idle(70); + + scene.overlay.showControls( + new InputWindowElement(util.vector.blockSurface(util.grid.at(3, 3, 1), Direction.NORTH), Pointing.RIGHT) + .rightClick() + .withWrench(), + 60); + scene.idle(7); + scene.world.modifyBlocks(full2, s -> s.with(FluidTankBlock.SHAPE, FluidTankBlock.Shape.PLAIN), false); + scene.idle(30); + + scene.overlay.showText(60) + .text("Using a Wrench, a tanks' window can be toggled") + .attachKeyFrame() + .placeNearTarget() + .pointAt(blockSurface); + scene.idle(50); } public static void creative(SceneBuilder scene, SceneBuildingUtil util) { scene.title("creative_fluid_tank", "Creative Fluid Tanks"); scene.configureBasePlate(0, 0, 5); - scene.world.showSection(util.select.layer(0), Direction.UP); + scene.showBasePlate(); scene.idle(5); - scene.world.showSection(util.select.layersFrom(1), Direction.DOWN); - /* - * Creative Fluid Tanks can be used to provide a bottomless supply of fluid - * - * Right-Click with a fluid containing item to configure it - * - * Pipe Networks can now endlessly draw the assigned fluid from this tank - * - * Any Fluids pipes push into a Creative Fluid Tank will be voided - */ + Selection largeCog = util.select.position(5, 0, 2); + Selection cTank = util.select.fromTo(3, 1, 1, 3, 2, 1); + Selection tank = util.select.fromTo(1, 1, 3, 1, 3, 3); + Selection pipes = util.select.fromTo(3, 1, 2, 2, 1, 3); + Selection cog = util.select.position(4, 1, 2); + BlockPos cTankPos = util.grid.at(3, 1, 1); + BlockPos pumpPos = util.grid.at(3, 1, 2); + + ElementLink cTankLink = scene.world.showIndependentSection(cTank, Direction.DOWN); + scene.world.moveSection(cTankLink, util.vector.of(-1, 0, 1), 0); + + scene.overlay.showText(70) + .text("Creative Fluid Tanks can be used to provide a bottomless supply of fluid") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.blockSurface(util.grid.at(2, 2, 2), Direction.WEST)); + scene.idle(80); + + ItemStack bucket = new ItemStack(Items.LAVA_BUCKET); + scene.overlay.showControls( + new InputWindowElement(util.vector.blockSurface(util.grid.at(2, 2, 2), Direction.NORTH), Pointing.RIGHT) + .rightClick() + .withItem(bucket), + 40); + scene.idle(7); + scene.world.modifyTileEntity(cTankPos, CreativeFluidTankTileEntity.class, + te -> ((CreativeSmartFluidTank) te.getTankInventory()) + .setContainedFluid(new FluidStack(Fluids.FLOWING_LAVA, 1000))); + scene.idle(5); + + scene.overlay.showText(50) + .text("Right-Click with a fluid containing item to configure it") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.blockSurface(util.grid.at(2, 2, 2), Direction.WEST)); + scene.idle(60); + scene.world.moveSection(cTankLink, util.vector.of(1, 0, -1), 6); + scene.idle(7); + scene.world.showSection(tank, Direction.DOWN); + scene.idle(5); + + scene.world.showSection(largeCog, Direction.UP); + scene.world.showSection(cog, Direction.NORTH); + scene.world.showSection(pipes, Direction.NORTH); + scene.world.multiplyKineticSpeed(util.select.everywhere(), -1); + scene.world.propagatePipeChange(pumpPos); + scene.effects.rotationDirectionIndicator(pumpPos); + scene.idle(40); + + scene.overlay.showText(70) + .text("Pipe Networks can now endlessly draw the assigned fluid from the tank") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.blockSurface(util.grid.at(3, 1, 2), Direction.WEST)); + scene.idle(120); + + scene.world.multiplyKineticSpeed(util.select.everywhere(), -1); + scene.world.propagatePipeChange(pumpPos); + scene.effects.rotationDirectionIndicator(pumpPos); + scene.idle(40); + + scene.overlay.showText(70) + .text("Any Fluids pushed back into a Creative Fluid Tank will be voided") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.blockSurface(util.grid.at(3, 1, 2), Direction.WEST)); + scene.idle(40); } diff --git a/src/main/java/com/simibubi/create/foundation/ponder/content/fluid/HosePulleyScenes.java b/src/main/java/com/simibubi/create/foundation/ponder/content/fluid/HosePulleyScenes.java index 92c7086d1..dc09e0e45 100644 --- a/src/main/java/com/simibubi/create/foundation/ponder/content/fluid/HosePulleyScenes.java +++ b/src/main/java/com/simibubi/create/foundation/ponder/content/fluid/HosePulleyScenes.java @@ -1,73 +1,358 @@ package com.simibubi.create.foundation.ponder.content.fluid; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import com.simibubi.create.content.contraptions.fluids.actors.HosePulleyFluidHandler; +import com.simibubi.create.content.contraptions.fluids.actors.HosePulleyTileEntity; +import com.simibubi.create.foundation.ponder.ElementLink; import com.simibubi.create.foundation.ponder.SceneBuilder; import com.simibubi.create.foundation.ponder.SceneBuildingUtil; +import com.simibubi.create.foundation.ponder.Selection; +import com.simibubi.create.foundation.ponder.content.PonderPalette; +import com.simibubi.create.foundation.ponder.elements.WorldSectionElement; +import net.minecraft.block.Blocks; +import net.minecraft.fluid.Fluids; import net.minecraft.util.Direction; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; public class HosePulleyScenes { - + public static void intro(SceneBuilder scene, SceneBuildingUtil util) { scene.title("hose_pulley", "Source Filling and Draining using Hose Pulleys"); - scene.configureBasePlate(0, 0, 5); - scene.world.showSection(util.select.layer(0), Direction.UP); + scene.configureBasePlate(0, 0, 3); + scene.setSceneOffsetY(-1); + scene.scaleSceneView(.9f); + scene.showBasePlate(); scene.idle(5); - scene.world.showSection(util.select.layersFrom(1), Direction.DOWN); - - /* - * Hose Pulleys can be used to fill or drain large bodies of Fluid - * - * With the Kinetic Input, the height of the pulleys' hose can be controlled - * - * The Pulley will retract when the input rotation is inverted - * - * Once the hose is in position, an attached pipe network can either provide fluid to the Hose Pulley... - * - * ...or pull from it, draining the pool instead - * - * Fill and Drain speed of the pulley depend entirely on the fluid networks' throughput - */ - + + Selection cogs = util.select.fromTo(3, 1, 2, 3, 2, 2); + Selection pipes = util.select.fromTo(3, 1, 1, 3, 5, 1) + .add(util.select.position(2, 5, 1)); + BlockPos hosePos = util.grid.at(1, 5, 1); + Selection hose = util.select.position(1, 5, 1); + Selection crank = util.select.position(0, 5, 1); + + ElementLink hoselink = scene.world.showIndependentSection(hose, Direction.UP); + scene.world.moveSection(hoselink, util.vector.of(0, -1, 0), 0); + scene.idle(10); + + Vector3d shaftInput = util.vector.blockSurface(hosePos.down(), Direction.WEST); + scene.overlay.showText(70) + .text("Hose Pulleys can be used to fill or drain large bodies of Fluid") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.topOf(hosePos.down())); + scene.idle(80); + + scene.overlay.showText(80) + .text("With the Kinetic Input, the height of the pulleys' hose can be controlled") + .attachKeyFrame() + .placeNearTarget() + .pointAt(shaftInput); + scene.idle(40); + + scene.world.showSectionAndMerge(crank, Direction.EAST, hoselink); + scene.idle(20); + + Selection kinetics = util.select.fromTo(1, 5, 1, 0, 5, 1); + scene.world.setKineticSpeed(kinetics, 32); + scene.idle(50); + + scene.world.setKineticSpeed(kinetics, 0); + scene.overlay.showText(80) + .text("The Pulley retracts while the input rotation is inverted") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.centerOf(hosePos.down(3))); + scene.idle(30); + + scene.world.setKineticSpeed(kinetics, -32); + scene.idle(16); + scene.world.setKineticSpeed(kinetics, 0); + scene.idle(10); + scene.rotateCameraY(70); + scene.idle(40); + + scene.overlay.showText(60) + .text("On the opposite side, pipes can be connected") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.blockSurface(hosePos.down(), Direction.EAST)); + scene.idle(70); + + scene.rotateCameraY(-70); + scene.idle(10); + + scene.world.showSectionAndMerge(cogs, Direction.NORTH, hoselink); + scene.world.showSectionAndMerge(pipes, Direction.WEST, hoselink); + scene.world.showSection(util.select.fromTo(0, 1, 0, 2, 2, 2), Direction.UP); + scene.idle(10); + + scene.overlay.showText(70) + .text("Attached pipe networks can either provide fluid to the hose...") + .attachKeyFrame() + .pointAt(util.vector.centerOf(util.grid.at(3, 1, 1))); + scene.idle(40); + + List blocks = new LinkedList<>(); + for (int y = 1; y < 3; y++) { + blocks.add(util.grid.at(1, y, 1)); + blocks.add(util.grid.at(0, y, 1)); + blocks.add(util.grid.at(1, y, 0)); + blocks.add(util.grid.at(2, y, 1)); + blocks.add(util.grid.at(1, y, 2)); + blocks.add(util.grid.at(0, y, 0)); + blocks.add(util.grid.at(2, y, 0)); + blocks.add(util.grid.at(2, y, 2)); + blocks.add(util.grid.at(0, y, 2)); + } + + for (BlockPos blockPos : blocks) { + scene.world.setBlock(blockPos, Blocks.WATER.getDefaultState(), false); + scene.idle(3); + } + + scene.world.multiplyKineticSpeed(util.select.fromTo(3, 1, 2, 3, 2, 1), -1); + scene.world.modifyTileEntity(util.grid.at(1, 5, 1), HosePulleyTileEntity.class, te -> te + .getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) + .ifPresent( + ifh -> ((HosePulleyFluidHandler) ifh).fill(new FluidStack(Fluids.WATER, 100), FluidAction.EXECUTE))); + scene.world.propagatePipeChange(util.grid.at(3, 2, 1)); + + scene.idle(40); + scene.world.setKineticSpeed(kinetics, 32); + scene.idle(16); + scene.world.setKineticSpeed(kinetics, 0); + scene.idle(5); + scene.overlay.showText(70) + .text("...or pull from it, draining the pool instead") + .attachKeyFrame() + .pointAt(util.vector.centerOf(util.grid.at(3, 1, 1))); + scene.idle(40); + + Collections.reverse(blocks); + for (BlockPos blockPos : blocks) { + scene.world.destroyBlock(blockPos); + scene.idle(3); + } + + scene.idle(20); + scene.overlay.showText(120) + .text("Fill and Drain speed of the pulley depends entirely on the fluid networks' throughput") + .placeNearTarget() + .colored(PonderPalette.MEDIUM) + .attachKeyFrame() + .pointAt(util.vector.centerOf(util.grid.at(3, 1, 1))); + scene.idle(40); + } - + public static void level(SceneBuilder scene, SceneBuildingUtil util) { scene.title("hose_pulley_level", "Fill and Drain level of Hose Pulleys"); - scene.configureBasePlate(0, 0, 5); - scene.world.showSection(util.select.layer(0), Direction.UP); + scene.configureBasePlate(0, 0, 3); + scene.setSceneOffsetY(-1.5f); + scene.scaleSceneView(.9f); + scene.showBasePlate(); + + List blocks = new LinkedList<>(); + for (int y = 1; y < 4; y++) { + blocks.add(util.grid.at(1, y, 1)); + blocks.add(util.grid.at(0, y, 1)); + blocks.add(util.grid.at(1, y, 0)); + blocks.add(util.grid.at(2, y, 1)); + blocks.add(util.grid.at(1, y, 2)); + blocks.add(util.grid.at(0, y, 0)); + blocks.add(util.grid.at(2, y, 0)); + blocks.add(util.grid.at(2, y, 2)); + blocks.add(util.grid.at(0, y, 2)); + } + + for (BlockPos blockPos : blocks) + scene.world.setBlock(blockPos, Blocks.WATER.getDefaultState(), false); scene.idle(5); - scene.world.showSection(util.select.layersFrom(1), Direction.DOWN); - - /* - * While fully retracted, the Hose Pulley cannot operate - * - * Draining runs from top to bottom - * - * The surface level will end up just below where the hose ends - * - * Filling runs from bottom to top - * - * The filled pool will not grow beyond the layer above the hose end - */ - + + Selection water = util.select.fromTo(2, 1, 0, 0, 4, 2); + scene.world.showSection(water, Direction.UP); + scene.idle(10); + + Selection cogs = util.select.fromTo(3, 1, 2, 3, 2, 2); + Selection pipes = util.select.fromTo(3, 1, 1, 3, 6, 1) + .add(util.select.position(2, 6, 1)); + BlockPos hosePos = util.grid.at(1, 6, 1); + Selection hose = util.select.position(1, 6, 1); + Selection crank = util.select.position(0, 6, 1); + + ElementLink hoselink = scene.world.showIndependentSection(hose, Direction.DOWN); + scene.world.moveSection(hoselink, util.vector.of(0, -1, 0), 0); + scene.idle(10); + + scene.world.showSectionAndMerge(crank, Direction.EAST, hoselink); + scene.idle(20); + + scene.overlay.showSelectionWithText(util.select.position(hosePos.down()), 50) + .text("While fully retracted, the Hose Pulley cannot operate") + .placeNearTarget() + .colored(PonderPalette.RED) + .attachKeyFrame() + .pointAt(util.vector.blockSurface(hosePos.down(), Direction.UP)); + scene.idle(55); + + Selection kinetics = util.select.fromTo(1, 6, 1, 0, 6, 1); + scene.world.setKineticSpeed(kinetics, 32); + scene.idle(50); + + scene.world.setKineticSpeed(kinetics, 0); + scene.overlay.showText(40) + .text("Draining runs from top to bottom") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.centerOf(hosePos.down(3))); + scene.idle(10); + + scene.world.showSectionAndMerge(cogs, Direction.NORTH, hoselink); + scene.world.showSectionAndMerge(pipes, Direction.WEST, hoselink); + scene.world.multiplyKineticSpeed(util.select.fromTo(3, 1, 2, 3, 2, 1), -1); + scene.world.modifyTileEntity(util.grid.at(1, 6, 1), HosePulleyTileEntity.class, te -> te + .getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) + .ifPresent( + ifh -> ((HosePulleyFluidHandler) ifh).fill(new FluidStack(Fluids.WATER, 100), FluidAction.EXECUTE))); + scene.world.propagatePipeChange(util.grid.at(3, 2, 1)); + + Vector3d surface = util.vector.topOf(1, 3, 1) + .subtract(0, 2 / 8f, 0); + AxisAlignedBB bb = new AxisAlignedBB(surface, surface).grow(1.5, 0, 1.5); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, bb, bb, 3); + scene.idle(3); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, bb, bb.expand(0, -2, 0), 70); + scene.idle(20); + + Collections.reverse(blocks); + int i = 0; + for (BlockPos blockPos : blocks) { + if (i++ == 18) + break; + scene.world.destroyBlock(blockPos); + scene.idle(3); + } + + scene.overlay.chaseBoundingBoxOutline(PonderPalette.WHITE, bb, bb.offset(0, -2, 0), 60); + scene.overlay.showText(60) + .text("The surface level will end up just below where the hose ends") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.of(0, 2 - 1 / 8f, 1.5f)); + scene.idle(30); + + scene.idle(10); + scene.world.multiplyKineticSpeed(util.select.fromTo(3, 1, 2, 3, 2, 1), -1); + scene.world.propagatePipeChange(util.grid.at(3, 2, 1)); + scene.idle(30); + scene.world.hideSection(water, Direction.SOUTH); + scene.idle(15); + for (BlockPos blockPos : blocks) + scene.world.destroyBlock(blockPos); + scene.world.showSection(water, Direction.UP); + scene.idle(15); + scene.world.setKineticSpeed(kinetics, -32); + scene.idle(16); + scene.world.setKineticSpeed(kinetics, 0); + + scene.overlay.showText(40) + .text("Filling runs from bottom to top") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.centerOf(hosePos.down(3))); + scene.idle(10); + + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, bb, bb.offset(0, -3 + 2 / 8f, 0), 3); + scene.idle(3); + scene.overlay.chaseBoundingBoxOutline(PonderPalette.MEDIUM, bb, bb.expand(0, -3 + 2 / 8f, 0), 120); + scene.idle(20); + + scene.world.setBlock(util.grid.at(1, 3, 1), Blocks.WATER.getDefaultState(), false); + scene.idle(3); + scene.world.setBlock(util.grid.at(1, 2, 1), Blocks.WATER.getDefaultState(), false); + scene.idle(3); + + Collections.reverse(blocks); + for (BlockPos blockPos : blocks) { + scene.world.setBlock(blockPos, Blocks.WATER.getDefaultState(), false); + scene.idle(3); + } + + scene.overlay.chaseBoundingBoxOutline(PonderPalette.WHITE, bb, bb, 100); + scene.overlay.showText(100) + .text("The filled pool will not grow beyond the layer above the hose end") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.of(0, 4 - 1 / 8f, 1.5f)); + scene.idle(80); } - + public static void infinite(SceneBuilder scene, SceneBuildingUtil util) { scene.title("hose_pulley_infinite", "Passively Filling and Draining large bodies of Fluid"); scene.configureBasePlate(0, 0, 5); - scene.world.showSection(util.select.layer(0), Direction.UP); + scene.setSceneOffsetY(-.5f); + scene.scaleSceneView(.9f); + scene.showBasePlate(); scene.idle(5); - scene.world.showSection(util.select.layersFrom(1), Direction.DOWN); - - /* - * When deploying the Hose Pulley into a large enough ocean... - * - * It will simply provide/dispose fluids without affecting the source - * - * Pipe networks can limitlessly pull or push fluids to and from such pulleys - * - * After such a state is reached, your goggles will display an indicator when looking at it - */ - + + Selection tank = util.select.fromTo(4, 1, 1, 4, 3, 1); + Selection pipes = util.select.fromTo(3, 1, 1, 2, 3, 2); + Selection kinetics = util.select.fromTo(5, 1, 2, 4, 2, 2) + .add(util.select.position(5, 0, 2)); + Selection hose = util.select.fromTo(1, 3, 2, 0, 3, 2); + BlockPos pumpPos = util.grid.at(3, 2, 2); + + scene.world.showSection(hose, Direction.UP); + scene.idle(5); + scene.world.showSection(tank, Direction.DOWN); + scene.idle(10); + scene.world.showSection(pipes, Direction.NORTH); + scene.idle(5); + scene.world.showSection(kinetics, Direction.DOWN); + scene.idle(10); + + scene.world.setKineticSpeed(hose, 32); + scene.idle(10); + + Vector3d entryPoint = util.vector.topOf(1, 0, 2); + scene.overlay.showText(60) + .text("When deploying the Hose Pulley into a large enough ocean...") + .attachKeyFrame() + .placeNearTarget() + .pointAt(entryPoint); + + scene.idle(40); + scene.world.setKineticSpeed(hose, 0); + scene.world.multiplyKineticSpeed(util.select.everywhere(), -1); + scene.world.propagatePipeChange(pumpPos); + scene.effects.rotationDirectionIndicator(pumpPos); + scene.idle(30); + + Selection pulleyPos = util.select.position(1, 3, 2); + scene.overlay.showSelectionWithText(pulleyPos, 60) + .text("It will provide/dispose fluids without affecting the source") + .attachKeyFrame() + .colored(PonderPalette.BLUE) + .placeNearTarget() + .pointAt(util.vector.topOf(util.grid.at(1, 3, 2))); + + scene.idle(80); + scene.overlay.showText(60) + .text("Pipe networks can limitlessly take fluids from/to such pulleys") + .attachKeyFrame() + .placeNearTarget() + .pointAt(util.vector.blockSurface(util.grid.at(3, 2, 2), Direction.WEST)); + scene.idle(40); } - + } diff --git a/src/main/resources/assets/create/textures/block/hose_pulley_magnet.png b/src/main/resources/assets/create/textures/block/hose_pulley_magnet.png index 3576785816684b5dc8124ec58f2c64beebf8970e..807b9df751b7db266bd3d73d6df96233d1045a5f 100644 GIT binary patch delta 275 zcmZo<+Q&3Og^RH`$lZxy-8q?;6BUK)Z328kT%FX_60EHl7#NK7G#soAbTm~CRzz>g z4Ls7F6yRwyEy!ziTHw4;&!y2`mnRoAxjD79wJlw`^z7NQKrNSp9@YXWk&+<4;QyF_ z!QcP6AW)vOz$3Dlfr0NZ2s0kfUy%Y7%=WBvaSYK2F75N?YcSwooxf0QO-S_K|FN#C z0v9_)7)aVVJm2}wv9ZdfFX`z5d8WAzmntOYm}Dn;)<|Ww9b3V6XM&FK^SQBXyAvPm zdt7?-;CFc&<{7s=jMuQw3Vao$$akdi>(j(PZz^Iqw=h23!r9U=xqKbaUItHBKbLh* G2~7aiJ9Q!e delta 229 zcmVE8Wo>fW2i()`KFf1t~GA<~xZBareCc$`HJOm^{00001bW%=J z06^y0W&i*HW=TXrR2b8Jj#&=XP;QoiT#WrqH2wopdCOn**tgX_THm6X| zV_;j>)Bwl68i?d6f+Lzi*&tOyHP~|pU)X8D487lYrTqMXSwv=B3@LROLl}c&18gAg f!_D}(@krSLW5fy?su+sV00000NkvXXu0mjf_O)4W diff --git a/src/main/resources/ponder/fluid_tank/sizes.nbt b/src/main/resources/ponder/fluid_tank/sizes.nbt new file mode 100644 index 0000000000000000000000000000000000000000..03d1a80d26eeb0ac0022015dcb92826cce02abba GIT binary patch literal 1502 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/fluid_tank/storage_creative.nbt b/src/main/resources/ponder/fluid_tank/storage_creative.nbt new file mode 100644 index 0000000000000000000000000000000000000000..5fc77449b86a1fd78504114a7e79813c1b4f7b32 GIT binary patch literal 937 zcmV;a16KSWiwFP!000000JT=zj?_jFEnnB;K_W=GiC3QU0c`Rxum}Z#R;z5nB`>31 zyLP6v9=GLgGr$Y^qlknLSn(mim#`^c+B10UmUkf|S?wNIpFVY}>)0W{1YF5qp#uQr z<KRB^>oLiwA3f0BskOolYMH326RQZH(M4tP>Ah0Th-7;7uW8i=t+;5Z|2 zTmz2P!?}y2W2_N4_6Qtj1deOKu}A2vV@x{h7?aLA#-y{3G3nga!+@h>Ogifrlg>KE zq_d7O>FkcsS;v@k)-fiXb&N@89rL-)Avo!Y^1wUJ5aV~Va@{wFbStYr{>WmAv;wes z4BtgL(rjPyRhHoVjHjr06oP*&iaet3x1JK>TNyQFD@=V(jI$F+cgh*Xi)1u(f^K00J8gzE%`QO-`)KCUwLlJM@VM3E}!DWTO|+a+|Gv_f##C>6Qs zQK?FiV=01tch*Qj&ynpuFCSGgHTNGH?u&#;Ij=Lwc!oXw*d^kQ)b~wC-}j^SwQG(y z8;;e$pX2=N3*Cynm7mw`gY*7AupsyipJOgDCIvhx3bov+%6VHAee6A$-ic$3>q9;J zs}%GKjX*o5BH!Jys`GhU=kFU0R~Ri>$|IJ{vtpHX`}Chqtv(D1wcTj`fIfv-ep|u-;RrY_{uKk_J|uadF(Wb+01@!&1~4C!m@30ktCQ8 zT49eN*&U@Pmt***!|*+f=<;2vyROo6DC|y4hgTExm;X;prz7T+V)inYpi)?V4_^zw zt?cZ_jh(HRT;V(tDbb)sI^x?+grq#?)kk%i@=-1_%**fX0H$l&Cr`v;fiZZeERjg- z*$XbgdeuVdc1T#hD0f<{mxxIh%acn+Zwh~1xqkqT5?PDsp9rPI3S5;J8;-lmjUwo< zM0MG|Xh8?xVPDSIa41I+_fN?L(uFoQ4Ljh*UC7!lHCFEi4RAZHe6vm>sO?x!>|&Fw zOZlhGY@jxM0**U8*R^I}a%_y;`sD9LZBicqy+m=ZfZjvdf$2S_*kk%u;36g0xe5OP L*rnJ|C=UPt08ZP> literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/hose_pulley/infinite.nbt b/src/main/resources/ponder/hose_pulley/infinite.nbt new file mode 100644 index 0000000000000000000000000000000000000000..614e4c007e75acb89c6ae3285674eb3eb8633f26 GIT binary patch literal 1053 zcmV+&1mgQ2iwFP!000000JT@!j@vd69bH$Jx7{}A4gG<>wvX9tfTkN1NgHez-6Xxd z7+i_A1j$-lMzrS{PsN~GX5s zIDQA`Hjc4zEO1;49M1yBAHdmsc5K`p8^^-NwXpFlZ2SQm$AYu58RBeghBzCWA6 zr&EzFi=0jt8KW{wVhFEzRVKLlonO)aZ@m77*ZX+=eZ5})i!qKas9f=K;e-1$1^DL8 zdR^QPAUY=nNiuo!L(S2@!Wn{hVStdbq(VHd8Q;z$=%|OM^N7J;Q6cfkr$k5`KbH!o zDyi~vzk92Fn|waj`AqXFOD9EE&=?_kCFgBN<_)Er8zH(wXeujnlk?|F~RL*tF{?aXbpj-COPBSpI_*u2g7#G;6 zM$h3Bn$6}?-FIav30uT4evX%O&Lr*t5sbc9uP(@v!U=-Wq$E>$N|%MaLA*yxUNPB~ zmfum~@PMj4J)mma?eQ%0a3K1b{y|GY(;Zd;j31>bO`DkCzE!ol1J9;NpXnkkX)-4) zOGrK`s%6ouRYg^;GSn1nzdttuZP(by>}*rx4h_4lA|2@>?a6Rh%4ZuX_bW73>MVJl z)3SFVbjZ;(eJ4A=zz=W!2y@m|K$Nad7#Tv_CnD84FQ)xI>8CW z79D35uN5w~4A+FXXF_m6afzY!Zg3f?QTYxt607{vbSkKd`I6*Sox*N>tl4u?&S>XI zbvlw5-`+q6wkMm7^>dx%EGI%twp(AviKI>B_}z#M?Bt!ZX=wLsQ~BU-ljq^#CXd&e zJRhRXQ9*JlB~|~ZP9pGY`FygKk3g1_`cD^_w_j}EuI8DflY}!QiIB+bvg8FV)jzff zf*iM<9GtH=t~^Mu>i_KgyRe%eskS57w{12RNt^Vl%b{Pqc%7kA?mVAb%~a8;k^ zBJd>N<``acDfzMq(F3X^Xw|oHiu0JwYR(3#=b&&Wl7p#Rv)ohHiN;CzY&EB--xi8V zt7%L-`p)d{@zdQljvW)-inm8{*-qls2DNvDzAts&UN2gb`^#lWNY?7q==Od0VEmX! X@)FOy8D}_gzl8q)WAVPMW)c7ZO~DU) literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/hose_pulley/intro.nbt b/src/main/resources/ponder/hose_pulley/intro.nbt new file mode 100644 index 0000000000000000000000000000000000000000..3ebb4e6e0f8534589a19d92d0a935be7cdd6b721 GIT binary patch literal 994 zcmV<810DPyiwFP!000000Nq#HjvGY~t+{!}<3Iw+0?G@Yz;hM}?Pih1ifFT9H{tSP zb!N(T%j53RbWfb+iBI7}5V80U6n=ppKuh&pY;R}ASP=6NOIEu*bvvi3da5IUKKP3N zh#ml#uTS5e`Q3*Qh31;00`uuZ?|CZYnTlw0*at6@s!>{fJP4F#8f%R<8D~Sr>5y@^ z*l}8X*>;>3UtM6@e$7YX^GYa*@b|6TzLwOzE$MT!;&!?0iR3 zztZ|Wtq*Da;pXP%HCgm8DPxt-=y5UULMpDWt3Z2SA)idOc^ZPt6L=8kNG^}cg}=bj zI4yWG(oD=E7+leEDut$8htMCIN9SygaF?uNoU^e$T99^5hEL|Q5M=n%W19o7g#)+7 z7eH{CAtt{q4KMf3>8C3Hc$y_7Nh*WOIG1VqjHLwrIvXF2j{34GM@$T z-bs>Ra>_Cm6X@Yjre@u<1mLq*H-G*4Fo3XmcD95KvitygmpVtKPM2?{(Q8SOS3{@* z`k)S|qA%kaM&K41^~r@XTI3isseW{9vg;31x!6joLxBs}pw?6+DN;2E+pe)$Rn3le z9m{SDV8_atzhllSLJn&}6rX1)j%E~t=5a(U%Oa1lqkjK@bu!;p;v6e+fraBy2M!0q zZ}0`?3KQ5Yu|Zj4mCxgUTVNGQA2nr+@stT3vviad^Q^mcmU}3j<$o-luAF|p%4thd zT&uWxdn~S_JrVo zVf(PbCKb||mZo@3Nig%ou7gDIyE$2&jK>NM*m3}<2w4NC_Pk>G1a-wiFqG8&PTMeN z(-__^+kF(IX@j?3kQKWh*X&MGrqpQNTrHEO4ZOc+;9L9t|Ljv6ctkP>8B0-XG`EyN z2!83{lMM$`u5lDgLE)29BXNH%p=kadl~)}y7+vHt!`$40LUJ@^l!4x&oYzC}w9LTQ zd4VBaj-=LdUTs%qx6IzQ@!pu;PGVAW*r*@c>g6zOYgVUmFlMQuKn5j+shmuxfto)9 zB?~=WZ-vY^w$-uT>`VYPs4|llq&M?!$I}{t&t7r`&h^U9-3FVSET)L*PFC7T>X@*) z_^rU&Xf_V$4!xSfMzQ7e+X;ckQf-s+a%Hu%>-UrLI?aa7)q~wqPPeu%nP$&ucIHx0 Q>pg(K0Tcs#@k|f^0LD=82><{9 literal 0 HcmV?d00001 diff --git a/src/main/resources/ponder/hose_pulley/level.nbt b/src/main/resources/ponder/hose_pulley/level.nbt new file mode 100644 index 0000000000000000000000000000000000000000..6c115dab4fe2020af1c3b7a3ab7b41c2cdfb9bb9 GIT binary patch literal 1000 zcmV9o}S$N}IGu(jqVYgubSJDC{^8YBX?++DR`j z!iF44gs5GDyNu#K^;7yGMJwbxisl#k1BE-wo0K)F5D3(VRDiHrac<|#>~J;!7=kO~ zpV0vT{dMVkpnng+N3LQOqXhjK!r(rtkVhSsbHW_P6#_Eu< zci6F7e3^Ev7GGU<>>YOO9=PllUtMD=UCoPncBSaZ%NM@D0YZxzbO4u;U0Ggc*{{!(xeF z;$)iSaWqkkF9H}{(sClWqFnnh9P3ACY>9B2tYVn4sXAPdc1DJemm=q6_=_Wx1E+-p zyTa#zcab7Suh*KFJ7@G$7JocTBa$SQ(M6bvBzew~90}mBW!Sf>u$MxKEP#VER4b7! zJoxB1iZD81DGLeo_!nKX_Gtw0y$j5Uvn1($UB(#B8IMDjOwxRrc9+h252dqy&(i72 z>0y)8mZaE5aSirZTwm?6xa^MN`e3_))6o?^SitW84Y2o)z7Np#uJ%s(Gg*u;Z<9m4 zEt*gye9Ah+)&tRI7W%C^?q4^vPylz1r&A0SM#mJ%I1|eqvLA2DUW!i1 z65FRWHX)Ikc9P%~B|#5NeJ3UFPu)77PNxzz*kZzgg8vP0>OYq(o1rRM@Wz7Lo7Fb0 znl$6WYU0iGvj7Ftr58FRV=}}YV6#uv5DDgj+pea z(neCp@Yuy~3D!ij)tWo>Dhg}GmcG{ufx{AIlJcst+IimhlkzIfn$6}xzm(I3^f6QH WIb924!RhEdfPVqQ%N(625dZ*HIq