diff --git a/.github/workflows/localization.yml b/.github/workflows/localization.yml index ff318efe4..fd159edd4 100644 --- a/.github/workflows/localization.yml +++ b/.github/workflows/localization.yml @@ -4,15 +4,17 @@ name: Crowdin Action # Controls when the action will run. -on: +on: workflow_dispatch # Only run when started manually - workflow_dispatch: - inputs: - uploadTranslations: - description: "Set to true to upload (changed) translations to Crowdin" - type: boolean - required: true - default: false + + #: + # inputs: + # uploadTranslations: + # description: "Set to true to upload (changed) translations to Crowdin" + # type: boolean + # required: true + # default: false + #schedule: #- cron: '0 */6 * * *' # Every 6 hours - https://crontab.guru/#0_*/6_*_*_* @@ -30,7 +32,7 @@ jobs: # Upload sources to Crowdin upload_sources: true # Upload translations to Crowdin, only use true at initial run - upload_translations: ${{ github.event.inputs.uploadTranslations }} + upload_translations: false # Make pull request of Crowdin translations download_translations: true # To download translations to the specified version branch diff --git a/crowdin.yml b/crowdin.yml index 7ebdc939f..3f0119cba 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -11,6 +11,7 @@ "languages_mapping": { "locale_with_underscore": { "cs": "cs_cz", + "cy": "cy_gb", "da": "da_dk", "de": "de_de", "eo": "eo_uy", diff --git a/src/main/java/com/simibubi/create/content/contraptions/bearing/MechanicalBearingBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/bearing/MechanicalBearingBlockEntity.java index 6c06a7ec5..d0b8ef8a4 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/bearing/MechanicalBearingBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/bearing/MechanicalBearingBlockEntity.java @@ -352,4 +352,7 @@ public class MechanicalBearingBlockEntity extends GeneratingKineticBlockEntity angle = forcedAngle; } + public ControlledContraptionEntity getMovedContraption() { + return movedContraption; + } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/pulley/PulleyBlockEntity.java b/src/main/java/com/simibubi/create/content/contraptions/pulley/PulleyBlockEntity.java index c0a70d75f..2c425dd01 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/pulley/PulleyBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/pulley/PulleyBlockEntity.java @@ -163,7 +163,7 @@ public class PulleyBlockEntity extends LinearActuatorBlockEntity implements Thre } } } - + if (mirrorParent != null) removeRopes(); @@ -284,7 +284,7 @@ public class PulleyBlockEntity extends LinearActuatorBlockEntity implements Thre if (prevMirrorParent == null || !prevMirrorParent.equals(mirrorParent)) sharedMirrorContraption = null; } - + if (compound.contains("MirrorChildren")) mirrorChildren = NBTHelper.readCompoundList(compound.getList("MirrorChildren", Tag.TAG_COMPOUND), NbtUtils::readBlockPos); @@ -379,4 +379,8 @@ public class PulleyBlockEntity extends LinearActuatorBlockEntity implements Thre return 100; return 100 * getInterpolatedOffset(.5f) / distance; } + + public BlockPos getMirrorParent() { + return mirrorParent; + } } diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTestHelper.java b/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTestHelper.java index 426cf887d..2425641ad 100644 --- a/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTestHelper.java +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/CreateGameTestHelper.java @@ -3,7 +3,19 @@ package com.simibubi.create.infrastructure.gametest; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import com.simibubi.create.content.contraptions.Contraption; +import com.simibubi.create.content.contraptions.actors.contraptionControls.ContraptionControlsMovement; +import com.simibubi.create.content.contraptions.actors.contraptionControls.ContraptionControlsMovingInteraction; +import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.content.kinetics.gauge.SpeedGaugeBlockEntity; + +import com.simibubi.create.content.kinetics.gauge.StressGaugeBlockEntity; + +import net.minecraftforge.registries.ForgeRegistries; + +import org.apache.commons.lang3.tuple.MutablePair; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -26,6 +38,7 @@ import net.minecraft.core.Direction; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.gametest.framework.GameTestInfo; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.item.ItemEntity; @@ -39,6 +52,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.fluids.FluidStack; @@ -122,6 +136,34 @@ public class CreateGameTestHelper extends GameTestHelper { behavior.setValue(mode.ordinal()); } + public void assertSpeedometerSpeed(BlockPos speedometer, float value) { + SpeedGaugeBlockEntity be = getBlockEntity(AllBlockEntityTypes.SPEEDOMETER.get(), speedometer); + assertInRange(be.getSpeed(), value - 0.01, value + 0.01); + } + + public void assertStressometerCapacity(BlockPos stressometer, float value) { + StressGaugeBlockEntity be = getBlockEntity(AllBlockEntityTypes.STRESSOMETER.get(), stressometer); + assertInRange(be.getNetworkCapacity(), value - 0.01, value + 0.01); + } + + public void toggleActorsOfType(Contraption contraption, ItemLike item) { + AtomicBoolean toggled = new AtomicBoolean(false); + contraption.getInteractors().forEach((localPos, behavior) -> { + if (toggled.get() || !(behavior instanceof ContraptionControlsMovingInteraction controls)) + return; + MutablePair actor = contraption.getActorAt(localPos); + if (actor == null) + return; + ItemStack filter = ContraptionControlsMovement.getFilter(actor.right); + if (filter != null && filter.is(item.asItem())) { + controls.handlePlayerInteraction( + makeMockPlayer(), InteractionHand.MAIN_HAND, localPos, contraption.entity + ); + toggled.set(true); + } + }); + } + // block entities /** diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestContraptions.java b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestContraptions.java index 6fdba39a1..c38956676 100644 --- a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestContraptions.java +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestContraptions.java @@ -1,11 +1,20 @@ package com.simibubi.create.infrastructure.gametest.tests; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.AllEntityTypes; +import com.simibubi.create.content.contraptions.Contraption; +import com.simibubi.create.content.contraptions.bearing.MechanicalBearingBlockEntity; +import com.simibubi.create.content.contraptions.elevator.ElevatorPulleyBlockEntity; +import com.simibubi.create.content.kinetics.transmission.sequencer.SequencedGearshiftBlock; import com.simibubi.create.infrastructure.gametest.CreateGameTestHelper; import com.simibubi.create.infrastructure.gametest.GameTestGroup; import it.unimi.dsi.fastutil.objects.Object2LongMap; + import net.minecraft.core.BlockPos; import net.minecraft.gametest.framework.GameTest; import net.minecraft.world.entity.EntityType; @@ -13,6 +22,9 @@ import net.minecraft.world.entity.projectile.Arrow; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.CropBlock; +import net.minecraft.world.level.block.LeverBlock; +import net.minecraft.world.level.block.RedstoneLampBlock; import net.minecraftforge.fluids.FluidStack; @GameTestGroup(path = "contraptions") @@ -89,6 +101,128 @@ public class TestContraptions { helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, end)); } + @GameTest(template = "controls", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void controls(CreateGameTestHelper helper) { + BlockPos button = new BlockPos(5, 5, 4); + BlockPos gearshift = new BlockPos(4, 5, 4); + BlockPos bearingPos = new BlockPos(4, 4, 4); + AtomicInteger step = new AtomicInteger(1); + + List dirt = List.of(new BlockPos(4, 2, 6), new BlockPos(2, 2, 4), new BlockPos(4, 2, 2)); + List wheat = List.of(new BlockPos(4, 3, 7), new BlockPos(1, 3, 4), new BlockPos(4, 3, 1)); + + helper.pressButton(button); + helper.succeedWhen(() -> { + // wait for gearshift to reset + helper.assertBlockProperty(gearshift, SequencedGearshiftBlock.STATE, 0); + if (step.get() == 4) + return; // step 4: all done! + MechanicalBearingBlockEntity bearing = helper.getBlockEntity(AllBlockEntityTypes.MECHANICAL_BEARING.get(), bearingPos); + if (bearing.getMovedContraption() == null) + helper.fail("Contraption not assembled"); + Contraption contraption = bearing.getMovedContraption().getContraption(); + switch (step.get()) { + case 1 -> { // step 1: both should be active + helper.assertBlockPresent(Blocks.FARMLAND, dirt.get(0)); + helper.assertBlockProperty(wheat.get(0), CropBlock.AGE, 0); + // now disable harvester + helper.toggleActorsOfType(contraption, AllBlocks.MECHANICAL_HARVESTER.get()); + helper.pressButton(button); + step.incrementAndGet(); + helper.fail("Entering step 2"); + } + case 2 -> { // step 2: harvester disabled + helper.assertBlockPresent(Blocks.FARMLAND, dirt.get(1)); + helper.assertBlockProperty(wheat.get(1), CropBlock.AGE, 7); + // now disable plough + helper.toggleActorsOfType(contraption, AllBlocks.MECHANICAL_PLOUGH.get()); + helper.pressButton(button); + step.incrementAndGet(); + helper.fail("Entering step 3"); + } + case 3 -> { // step 3: both disabled + helper.assertBlockPresent(Blocks.DIRT, dirt.get(2)); + helper.assertBlockProperty(wheat.get(2), CropBlock.AGE, 7); + // successful! + helper.pressButton(button); + step.incrementAndGet(); + helper.fail("Entering step 4"); + } + } + }); + } + + @GameTest(template = "elevator") + public static void elevator(CreateGameTestHelper helper) { + BlockPos pulley = new BlockPos(5, 12, 3); + BlockPos secondaryPulley = new BlockPos(5, 12, 1); + BlockPos bottomLamp = new BlockPos(2, 3, 2); + BlockPos topLamp = new BlockPos(2, 12, 2); + BlockPos lever = new BlockPos(1, 11, 2); + BlockPos elevatorStart = new BlockPos(4, 2, 2); + BlockPos cowSpawn = new BlockPos(4, 4, 2); + BlockPos cowEnd = new BlockPos(4, 13, 2); + + helper.runAtTickTime(1, () -> helper.spawn(EntityType.COW, cowSpawn)); + helper.runAtTickTime( + 15, () -> helper.getBlockEntity(AllBlockEntityTypes.ELEVATOR_PULLEY.get(), pulley).clicked() + ); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(1); + if (!helper.getBlockState(lever).getValue(LeverBlock.POWERED)) { // step 1: check entity, lamps, and secondary, then move up + helper.getFirstEntity(AllEntityTypes.CONTROLLED_CONTRAPTION.get(), elevatorStart); // make sure entity exists + + helper.assertBlockProperty(topLamp, RedstoneLampBlock.LIT, false); + helper.assertBlockProperty(bottomLamp, RedstoneLampBlock.LIT, true); + + ElevatorPulleyBlockEntity secondary = helper.getBlockEntity(AllBlockEntityTypes.ELEVATOR_PULLEY.get(), secondaryPulley); + if (secondary.getMirrorParent() == null) + helper.fail("Secondary pulley has no parent"); + + helper.pullLever(lever); + helper.fail("Entering step 2"); + } else { // step 2: wait for top lamp and cow passenger + helper.assertBlockProperty(topLamp, RedstoneLampBlock.LIT, true); + helper.assertBlockProperty(bottomLamp, RedstoneLampBlock.LIT, false); + helper.assertEntityPresent(EntityType.COW, cowEnd); + // all done, disassemble + helper.getBlockEntity(AllBlockEntityTypes.ELEVATOR_PULLEY.get(), pulley).clicked(); + } + }); + } + + @GameTest(template = "roller_filling") + public static void rollerFilling(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(7, 6, 1); + BlockPos barrelEnd = new BlockPos(2, 5, 2); + List existing = BlockPos.betweenClosedStream(new BlockPos(1, 3, 2), new BlockPos(4, 2, 2)).toList(); + List filled = BlockPos.betweenClosedStream(new BlockPos(1, 2, 1), new BlockPos(4, 3, 3)) + .filter(pos -> !existing.contains(pos)).toList(); + List tracks = BlockPos.betweenClosedStream(new BlockPos(1, 4, 2), new BlockPos(4, 4, 2)).toList(); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(4); + existing.forEach(pos -> helper.assertBlockPresent(AllBlocks.RAILWAY_CASING.get(), pos)); + filled.forEach(pos -> helper.assertBlockPresent(AllBlocks.ANDESITE_CASING.get(), pos)); + tracks.forEach(pos -> helper.assertBlockPresent(AllBlocks.TRACK.get(), pos)); + helper.assertContainerEmpty(barrelEnd); + }); + } + + @GameTest(template = "roller_paving_and_clearing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void rollerPavingAndClearing(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(8, 5, 1); + List paved = BlockPos.betweenClosedStream(new BlockPos(1, 2, 1), new BlockPos(4, 2, 1)).toList(); + // block above will be cleared too, but will later be replaced by the contraption's barrel + BlockPos cleared = new BlockPos(2, 3, 1); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(9); + paved.forEach(pos -> helper.assertBlockPresent(AllBlocks.ANDESITE_CASING.get(), pos)); + helper.assertBlockPresent(Blocks.AIR, cleared); + }); + } + // FIXME: trains do not enjoy being loaded in structures // https://gist.github.com/TropheusJ/f2d0a7df48360d2e078d0987c115c6ef // @GameTest(template = "train_observer") diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestFluids.java b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestFluids.java index ee97c0bd8..ff4fc8413 100644 --- a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestFluids.java +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestFluids.java @@ -1,55 +1,60 @@ package com.simibubi.create.infrastructure.gametest.tests; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + import com.simibubi.create.AllBlockEntityTypes; import com.simibubi.create.content.fluids.hosePulley.HosePulleyFluidHandler; +import com.simibubi.create.content.fluids.pipes.valve.FluidValveBlock; import com.simibubi.create.content.kinetics.gauge.SpeedGaugeBlockEntity; import com.simibubi.create.content.kinetics.gauge.StressGaugeBlockEntity; +import com.simibubi.create.content.kinetics.waterwheel.WaterWheelBlockEntity; import com.simibubi.create.infrastructure.gametest.CreateGameTestHelper; import com.simibubi.create.infrastructure.gametest.GameTestGroup; import net.minecraft.core.BlockPos; import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestAssertException; +import net.minecraft.tags.BlockTags; import net.minecraft.util.Mth; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.RedStoneWireBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.RedstoneSide; +import net.minecraft.world.level.block.LeverBlock; +import net.minecraft.world.level.block.RedstoneLampBlock; import net.minecraft.world.level.material.Fluids; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.registries.ForgeRegistries; @GameTestGroup(path = "fluids") public class TestFluids { @GameTest(template = "hose_pulley_transfer", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) public static void hosePulleyTransfer(CreateGameTestHelper helper) { - // there was supposed to be redstone here built in, but it kept popping off, so put it there manually - BlockPos brokenRedstone = new BlockPos(4, 8, 3); - BlockState redstone = Blocks.REDSTONE_WIRE.defaultBlockState() - .setValue(RedStoneWireBlock.NORTH, RedstoneSide.NONE) - .setValue(RedStoneWireBlock.SOUTH, RedstoneSide.NONE) - .setValue(RedStoneWireBlock.EAST, RedstoneSide.UP) - .setValue(RedStoneWireBlock.WEST, RedstoneSide.SIDE) - .setValue(RedStoneWireBlock.POWER, 14); - helper.setBlock(brokenRedstone, redstone); - // pump - BlockPos lever = new BlockPos(6, 9, 3); + BlockPos lever = new BlockPos(7, 7, 5); helper.pullLever(lever); helper.succeedWhen(() -> { helper.assertSecondsPassed(15); // check filled - BlockPos filledLowerCorner = new BlockPos(8, 3, 2); - BlockPos filledUpperCorner = new BlockPos(10, 5, 4); + BlockPos filledLowerCorner = new BlockPos(2, 3, 2); + BlockPos filledUpperCorner = new BlockPos(4, 5, 4); BlockPos.betweenClosed(filledLowerCorner, filledUpperCorner) .forEach(pos -> helper.assertBlockPresent(Blocks.WATER, pos)); // check emptied - BlockPos emptiedLowerCorner = new BlockPos(2, 3, 2); - BlockPos emptiedUpperCorner = new BlockPos(4, 5, 4); + BlockPos emptiedLowerCorner = new BlockPos(8, 3, 2); + BlockPos emptiedUpperCorner = new BlockPos(10, 5, 4); BlockPos.betweenClosed(emptiedLowerCorner, emptiedUpperCorner) .forEach(pos -> helper.assertBlockPresent(Blocks.AIR, pos)); // check nothing left in pulley - BlockPos pulleyPos = new BlockPos(8, 7, 4); + BlockPos pulleyPos = new BlockPos(4, 7, 3); IFluidHandler storage = helper.fluidStorageAt(pulleyPos); if (storage instanceof HosePulleyFluidHandler hose) { IFluidHandler internalTank = hose.getInternalTank(); @@ -62,27 +67,27 @@ public class TestFluids { } @GameTest(template = "in_world_pumping_out") - public static void inWorldPumpingOutput(CreateGameTestHelper helper) { - BlockPos pumpPos = new BlockPos(3, 2, 2); - BlockPos waterPos = pumpPos.west(); - BlockPos basinPos = pumpPos.east(); - helper.flipBlock(pumpPos); + public static void inWorldPumpingOut(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(4, 3, 3); + BlockPos basin = new BlockPos(5, 2, 2); + BlockPos output = new BlockPos(2, 2, 2); + helper.pullLever(lever); helper.succeedWhen(() -> { - helper.assertBlockPresent(Blocks.WATER, waterPos); - helper.assertTankEmpty(basinPos); + helper.assertBlockPresent(Blocks.WATER, output); + helper.assertTankEmpty(basin); }); } @GameTest(template = "in_world_pumping_in") - public static void inWorldPumpingPickup(CreateGameTestHelper helper) { - BlockPos pumpPos = new BlockPos(3, 2, 2); - BlockPos basinPos = pumpPos.east(); - BlockPos waterPos = pumpPos.west(); + public static void inWorldPumpingIn(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(4, 3, 3); + BlockPos basin = new BlockPos(5, 2, 2); + BlockPos water = new BlockPos(2, 2, 2); FluidStack expectedResult = new FluidStack(Fluids.WATER, FluidType.BUCKET_VOLUME); - helper.flipBlock(pumpPos); + helper.pullLever(lever); helper.succeedWhen(() -> { - helper.assertBlockPresent(Blocks.AIR, waterPos); - helper.assertFluidPresent(expectedResult, basinPos); + helper.assertBlockPresent(Blocks.AIR, water); + helper.assertFluidPresent(expectedResult, basin); }); } @@ -147,4 +152,151 @@ public class TestFluids { } }); } + + @GameTest(template = "large_waterwheel", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void largeWaterwheel(CreateGameTestHelper helper) { + BlockPos wheel = new BlockPos(4, 3, 2); + BlockPos leftEnd = new BlockPos(6, 2, 2); + BlockPos rightEnd = new BlockPos(2, 2, 2); + List edges = List.of(new BlockPos(4, 5, 1), new BlockPos(4, 5, 3)); + BlockPos openLever = new BlockPos(3, 8, 1); + BlockPos leftLever = new BlockPos(5, 7, 1); + waterwheel(helper, wheel, 4, 512, leftEnd, rightEnd, edges, openLever, leftLever); + } + + @GameTest(template = "small_waterwheel", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void smallWaterwheel(CreateGameTestHelper helper) { + BlockPos wheel = new BlockPos(3, 2, 2); + BlockPos leftEnd = new BlockPos(4, 2, 2); + BlockPos rightEnd = new BlockPos(2, 2, 2); + List edges = List.of(new BlockPos(3, 3, 1), new BlockPos(3, 3, 3)); + BlockPos openLever = new BlockPos(2, 6, 1); + BlockPos leftLever = new BlockPos(4, 5, 1); + waterwheel(helper, wheel, 8, 256, leftEnd, rightEnd, edges, openLever, leftLever); + } + + private static void waterwheel(CreateGameTestHelper helper, + BlockPos wheel, float expectedRpm, float expectedSU, + BlockPos leftEnd, BlockPos rightEnd, List edges, + BlockPos openLever, BlockPos leftLever) { + BlockPos speedometer = wheel.north(); + BlockPos stressometer = wheel.south(); + helper.pullLever(openLever); + helper.succeedWhen(() -> { + // must always be true + edges.forEach(pos -> helper.assertBlockNotPresent(Blocks.WATER, pos)); + helper.assertBlockPresent(Blocks.WATER, rightEnd); + // first step: expect water on left end while flow is allowed + if (!helper.getBlockState(leftLever).getValue(LeverBlock.POWERED)) { + helper.assertBlockPresent(Blocks.WATER, leftEnd); + // water is present. both sides should cancel. + helper.assertSpeedometerSpeed(speedometer, 0); + helper.assertStressometerCapacity(stressometer, 0); + // success, pull the lever, enter step 2 + helper.powerLever(leftLever); + helper.fail("Entering step 2"); + } else { + // lever is pulled, flow should stop + helper.assertBlockNotPresent(Blocks.WATER, leftEnd); + // 1-sided flow, should be spinning + helper.assertSpeedometerSpeed(speedometer, expectedRpm); + helper.assertStressometerCapacity(stressometer, expectedSU); + } + }); + } + + @GameTest(template = "waterwheel_materials", timeoutTicks = CreateGameTestHelper.FIFTEEN_SECONDS) + public static void waterwheelMaterials(CreateGameTestHelper helper) { + List planks = ForgeRegistries.BLOCKS.tags().getTag(BlockTags.PLANKS).stream() + .map(ItemLike::asItem).collect(Collectors.toCollection(ArrayList::new)); + List chests = List.of(new BlockPos(6, 4, 2), new BlockPos(6, 4, 3)); + List deployers = chests.stream().map(pos -> pos.below(2)).toList(); + helper.runAfterDelay(3, () -> chests.forEach(chest -> + planks.forEach(plank -> ItemHandlerHelper.insertItem(helper.itemStorageAt(chest), new ItemStack(plank), false)) + )); + + BlockPos smallWheel = new BlockPos(4, 2, 2); + BlockPos largeWheel = new BlockPos(3, 3, 3); + BlockPos lever = new BlockPos(5, 3, 1); + helper.pullLever(lever); + + helper.succeedWhen(() -> { + Item plank = planks.get(0); + if (!(plank instanceof BlockItem blockItem)) + throw new GameTestAssertException(ForgeRegistries.ITEMS.getKey(plank) + " is not a BlockItem"); + Block block = blockItem.getBlock(); + + WaterWheelBlockEntity smallWheelBe = helper.getBlockEntity(AllBlockEntityTypes.WATER_WHEEL.get(), smallWheel); + if (!smallWheelBe.material.is(block)) + helper.fail("Small waterwheel has not consumed " + ForgeRegistries.ITEMS.getKey(plank)); + + WaterWheelBlockEntity largeWheelBe = helper.getBlockEntity(AllBlockEntityTypes.LARGE_WATER_WHEEL.get(), largeWheel); + if (!largeWheelBe.material.is(block)) + helper.fail("Large waterwheel has not consumed " + ForgeRegistries.ITEMS.getKey(plank)); + + // next item + planks.remove(0); + deployers.forEach(pos -> { + IItemHandler handler = helper.itemStorageAt(pos); + for (int i = 0; i < handler.getSlots(); i++) { + handler.extractItem(i, Integer.MAX_VALUE, false); + } + }); + if (!planks.isEmpty()) + helper.fail("Not all planks have been consumed"); + }); + } + + @GameTest(template = "smart_observer_pipes") + public static void smartObserverPipes(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(3, 3, 1); + BlockPos output = new BlockPos(3, 4, 4); + BlockPos tankOutput = new BlockPos(1, 2, 4); + FluidStack expected = new FluidStack(Fluids.WATER, 2 * FluidType.BUCKET_VOLUME); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertFluidPresent(expected, tankOutput); + helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, output); + }); + } + + @GameTest(template = "threshold_switch", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS) + public static void thresholdSwitch(CreateGameTestHelper helper) { + BlockPos leftHandle = new BlockPos(4, 2, 4); + BlockPos leftValve = new BlockPos(4, 2, 3); + BlockPos leftTank = new BlockPos(5, 2, 3); + + BlockPos rightHandle = new BlockPos(2, 2, 4); + BlockPos rightValve = new BlockPos(2, 2, 3); + BlockPos rightTank = new BlockPos(1, 2, 3); + + BlockPos drainHandle = new BlockPos(3, 3, 2); + BlockPos drainValve = new BlockPos(3, 3, 1); + BlockPos lamp = new BlockPos(1, 3, 1); + BlockPos tank = new BlockPos(2, 2, 1); + helper.succeedWhen(() -> { + if (!helper.getBlockState(leftValve).getValue(FluidValveBlock.ENABLED)) { // step 1 + helper.getBlockEntity(AllBlockEntityTypes.VALVE_HANDLE.get(), leftHandle) + .activate(false); // open the valve, fill 4 buckets + helper.fail("Entering step 2"); + } else if (!helper.getBlockState(rightValve).getValue(FluidValveBlock.ENABLED)) { // step 2 + helper.assertFluidPresent(FluidStack.EMPTY, leftTank); // wait for left tank to drain + helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, false); // should not be on yet + helper.getBlockEntity(AllBlockEntityTypes.VALVE_HANDLE.get(), rightHandle) + .activate(false); // fill another 4 buckets + helper.fail("Entering step 3"); + } else if (!helper.getBlockState(drainValve).getValue(FluidValveBlock.ENABLED)) { // step 3 + helper.assertFluidPresent(FluidStack.EMPTY, rightTank); // wait for right tank to drain + // 16 buckets inserted. tank full, lamp on. + helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, true); + // drain what's filled so far + helper.getBlockEntity(AllBlockEntityTypes.VALVE_HANDLE.get(), drainHandle) + .activate(false); // drain all 8 buckets + helper.fail("Entering step 4"); + } else { + helper.assertTankEmpty(tank); // wait for it to empty + helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, false); // should be off now + } + }); + } } diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestItems.java b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestItems.java index e28f4e369..e2b3dd865 100644 --- a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestItems.java +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestItems.java @@ -230,8 +230,35 @@ public class TestItems { }); } - @GameTest(template = "content_observer_counting") - public static void contentObserverCounting(CreateGameTestHelper helper) { + @GameTest(template = "smart_observer_belt_and_funnel", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void smartObserverBeltAndFunnel(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(6, 3, 2); + List targets = List.of( + new BlockPos(5, 2, 1), // belt + new BlockPos(2, 4, 6) // funnel + ); + List overflows = List.of( + new BlockPos(6, 2, 1), // belt + new BlockPos(1, 3, 6) // funnel + ); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(9); + targets.forEach(pos -> helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, pos)); + overflows.forEach(pos -> helper.assertBlockPresent(Blocks.AIR, pos)); + }); + } + + @GameTest(template = "smart_observer_chutes") + public static void smartObserverChutes(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(1, 5, 2); + BlockPos output = new BlockPos(1, 5, 3); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, output)); + } + + @GameTest(template = "smart_observer_counting") + public static void smartObserverCounting(CreateGameTestHelper helper) { BlockPos chest = new BlockPos(3, 2, 1); long totalChestItems = helper.getTotalItems(chest); BlockPos chestNixiePos = new BlockPos(2, 3, 1); @@ -254,6 +281,26 @@ public class TestItems { }); } + @GameTest(template = "smart_observer_filtered_storage") + public static void smartObserverFilteredStorage(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 3, 1); + BlockPos leftLamp = new BlockPos(3, 2, 3); + BlockPos rightLamp = new BlockPos(1, 2, 3); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertBlockProperty(leftLamp, RedstoneLampBlock.LIT, true); + helper.assertBlockProperty(rightLamp, RedstoneLampBlock.LIT, false); + }); + } + + @GameTest(template = "smart_observer_storage") + public static void smartObserverStorage(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(1, 3, 2); + BlockPos lamp = new BlockPos(1, 2, 3); + helper.pullLever(lever); + helper.succeedWhen(() -> helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, true)); + } + @GameTest(template = "depot_display", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) public static void depotDisplay(CreateGameTestHelper helper) { BlockPos displayPos = new BlockPos(5, 3, 1); @@ -284,8 +331,8 @@ public class TestItems { }); } - @GameTest(template = "stockpile_switch") - public static void stockpileSwitch(CreateGameTestHelper helper) { + @GameTest(template = "threshold_switch") + public static void thresholdSwitch(CreateGameTestHelper helper) { BlockPos chest = new BlockPos(1, 2, 1); BlockPos lamp = new BlockPos(2, 3, 1); helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, false); diff --git a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestMisc.java b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestMisc.java index f491f4924..c8bd01749 100644 --- a/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestMisc.java +++ b/src/main/java/com/simibubi/create/infrastructure/gametest/tests/TestMisc.java @@ -3,6 +3,7 @@ package com.simibubi.create.infrastructure.gametest.tests; import static com.simibubi.create.infrastructure.gametest.CreateGameTestHelper.FIFTEEN_SECONDS; import com.simibubi.create.AllBlockEntityTypes; +import com.simibubi.create.content.redstone.thresholdSwitch.ThresholdSwitchBlockEntity; import com.simibubi.create.content.schematics.SchematicExport; import com.simibubi.create.content.schematics.SchematicItem; import com.simibubi.create.content.schematics.cannon.SchematicannonBlockEntity; @@ -17,10 +18,14 @@ import net.minecraft.nbt.NbtUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.animal.Sheep; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.monster.Zombie; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.RedstoneLampBlock; @GameTestGroup(path = "misc") public class TestMisc { @@ -65,4 +70,47 @@ public class TestMisc { helper.assertItemEntityPresent(Items.WHITE_WOOL, sheepPos, 2); }); } + + @GameTest(template = "smart_observer_blocks") + public static void smartObserverBlocks(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(2, 2, 1); + BlockPos leftLamp = new BlockPos(3, 4, 3); + BlockPos rightLamp = new BlockPos(1, 4, 3); + helper.pullLever(lever); + helper.succeedWhen(() -> { + helper.assertBlockProperty(leftLamp, RedstoneLampBlock.LIT, true); + helper.assertBlockProperty(rightLamp, RedstoneLampBlock.LIT, false); + }); + } + + @GameTest(template = "threshold_switch_pulley") + public static void thresholdSwitchPulley(CreateGameTestHelper helper) { + BlockPos lever = new BlockPos(3, 7, 1); + BlockPos switchPos = new BlockPos(1, 6, 1); + helper.pullLever(lever); + helper.succeedWhen(() -> { + ThresholdSwitchBlockEntity switchBe = helper.getBlockEntity(AllBlockEntityTypes.THRESHOLD_SWITCH.get(), switchPos); + float level = switchBe.getStockLevel(); + if (level < 0 || level > 1) + helper.fail("Invalid level: " + level); + }); + } + + @GameTest(template = "netherite_backtank", timeoutTicks = CreateGameTestHelper.TEN_SECONDS) + public static void netheriteBacktank(CreateGameTestHelper helper) { + BlockPos lava = new BlockPos(2, 2, 3); + BlockPos zombieSpawn = lava.above(2); + BlockPos armorStandPos = new BlockPos(2, 2, 1); + helper.runAtTickTime(5, () -> { + Zombie zombie = helper.spawn(EntityType.ZOMBIE, zombieSpawn); + ArmorStand armorStand = helper.getFirstEntity(EntityType.ARMOR_STAND, armorStandPos); + for (EquipmentSlot slot : EquipmentSlot.values()) { + zombie.setItemSlot(slot, armorStand.getItemBySlot(slot).copy()); + } + }); + helper.succeedWhen(() -> { + helper.assertSecondsPassed(9); + helper.assertEntityPresent(EntityType.ZOMBIE, lava); + }); + } } diff --git a/src/main/resources/data/create/structures/gametest/contraptions/controls.nbt b/src/main/resources/data/create/structures/gametest/contraptions/controls.nbt new file mode 100644 index 000000000..c1b8456f9 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/controls.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/elevator.nbt b/src/main/resources/data/create/structures/gametest/contraptions/elevator.nbt new file mode 100644 index 000000000..6e962e5ca Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/elevator.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt b/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt index dead6de25..159761971 100644 Binary files a/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt and b/src/main/resources/data/create/structures/gametest/contraptions/mounted_item_extract.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/roller_filling.nbt b/src/main/resources/data/create/structures/gametest/contraptions/roller_filling.nbt new file mode 100644 index 000000000..862b26205 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/roller_filling.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/contraptions/roller_paving_and_clearing.nbt b/src/main/resources/data/create/structures/gametest/contraptions/roller_paving_and_clearing.nbt new file mode 100644 index 000000000..4ee3f9787 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/contraptions/roller_paving_and_clearing.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt b/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt index f42e51fe6..5a21ce411 100644 Binary files a/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt and b/src/main/resources/data/create/structures/gametest/fluids/hose_pulley_transfer.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt index fef3d5ff4..5098188f0 100644 Binary files a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt and b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_in.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt index cfa909345..911d2aaef 100644 Binary files a/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt and b/src/main/resources/data/create/structures/gametest/fluids/in_world_pumping_out.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/large_waterwheel.nbt b/src/main/resources/data/create/structures/gametest/fluids/large_waterwheel.nbt new file mode 100644 index 000000000..51263d53d Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/large_waterwheel.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/small_waterwheel.nbt b/src/main/resources/data/create/structures/gametest/fluids/small_waterwheel.nbt new file mode 100644 index 000000000..ea60331a7 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/small_waterwheel.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/smart_observer_pipes.nbt b/src/main/resources/data/create/structures/gametest/fluids/smart_observer_pipes.nbt new file mode 100644 index 000000000..7da76b1e4 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/smart_observer_pipes.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/threshold_switch.nbt b/src/main/resources/data/create/structures/gametest/fluids/threshold_switch.nbt new file mode 100644 index 000000000..ba193c371 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/threshold_switch.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/fluids/waterwheel_materials.nbt b/src/main/resources/data/create/structures/gametest/fluids/waterwheel_materials.nbt new file mode 100644 index 000000000..0cbebcc7f Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/fluids/waterwheel_materials.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt b/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt index 40e3c9614..29027a87a 100644 Binary files a/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt and b/src/main/resources/data/create/structures/gametest/items/attribute_filters.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt b/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt deleted file mode 100644 index 61719d373..000000000 Binary files a/src/main/resources/data/create/structures/gametest/items/content_observer_counting.nbt and /dev/null differ diff --git a/src/main/resources/data/create/structures/gametest/items/smart_observer_belt_and_funnel.nbt b/src/main/resources/data/create/structures/gametest/items/smart_observer_belt_and_funnel.nbt new file mode 100644 index 000000000..48a5e98d7 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/smart_observer_belt_and_funnel.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/smart_observer_chutes.nbt b/src/main/resources/data/create/structures/gametest/items/smart_observer_chutes.nbt new file mode 100644 index 000000000..0cf3dc3e1 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/smart_observer_chutes.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/smart_observer_counting.nbt b/src/main/resources/data/create/structures/gametest/items/smart_observer_counting.nbt new file mode 100644 index 000000000..8e8f1351d Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/smart_observer_counting.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/smart_observer_filtered_storage.nbt b/src/main/resources/data/create/structures/gametest/items/smart_observer_filtered_storage.nbt new file mode 100644 index 000000000..ce0fb9f36 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/smart_observer_filtered_storage.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/smart_observer_storage.nbt b/src/main/resources/data/create/structures/gametest/items/smart_observer_storage.nbt new file mode 100644 index 000000000..bd344d8da Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/items/smart_observer_storage.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt b/src/main/resources/data/create/structures/gametest/items/threshold_switch.nbt similarity index 100% rename from src/main/resources/data/create/structures/gametest/items/stockpile_switch.nbt rename to src/main/resources/data/create/structures/gametest/items/threshold_switch.nbt diff --git a/src/main/resources/data/create/structures/gametest/misc/netherite_backtank.nbt b/src/main/resources/data/create/structures/gametest/misc/netherite_backtank.nbt new file mode 100644 index 000000000..7f6a7ce57 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/misc/netherite_backtank.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/misc/smart_observer_blocks.nbt b/src/main/resources/data/create/structures/gametest/misc/smart_observer_blocks.nbt new file mode 100644 index 000000000..8881a6173 Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/misc/smart_observer_blocks.nbt differ diff --git a/src/main/resources/data/create/structures/gametest/misc/threshold_switch_pulley.nbt b/src/main/resources/data/create/structures/gametest/misc/threshold_switch_pulley.nbt new file mode 100644 index 000000000..a405da75c Binary files /dev/null and b/src/main/resources/data/create/structures/gametest/misc/threshold_switch_pulley.nbt differ