diff --git a/patches/minecraft/net/minecraft/core/Direction.java.patch b/patches/minecraft/net/minecraft/core/Direction.java.patch index d0dabc7787..1213925f35 100644 --- a/patches/minecraft/net/minecraft/core/Direction.java.patch +++ b/patches/minecraft/net/minecraft/core/Direction.java.patch @@ -1,12 +1,17 @@ --- a/net/minecraft/core/Direction.java +++ b/net/minecraft/core/Direction.java -@@ -49,6 +_,10 @@ +@@ -49,6 +_,15 @@ private final Vec3i normal; private final Vec3 normalVec3; private static final Direction[] VALUES = values(); + private static final List VALUES_VIEW = java.util.Collections.unmodifiableList(Arrays.asList(VALUES)); + public static final List valuesView() { + return VALUES_VIEW; ++ } ++ // In vanilla updates are notified only on the horizontal plane, in Forge we also notify up and down ++ private static final List UPDATE_ORDER = java.util.Collections.unmodifiableList(Stream.concat(Plane.HORIZONTAL.stream(), Plane.VERTICAL.stream()).toList()); ++ public static final List getUpdateOrder() { ++ return UPDATE_ORDER; + } private static final Direction[] BY_3D_DATA = Arrays.stream(VALUES) .sorted(Comparator.comparingInt(p_235687_ -> p_235687_.data3d)) diff --git a/patches/minecraft/net/minecraft/gametest/framework/GameTestInfo.java.patch b/patches/minecraft/net/minecraft/gametest/framework/GameTestInfo.java.patch new file mode 100644 index 0000000000..3ba28ba53f --- /dev/null +++ b/patches/minecraft/net/minecraft/gametest/framework/GameTestInfo.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/gametest/framework/GameTestInfo.java ++++ b/net/minecraft/gametest/framework/GameTestInfo.java +@@ -265,7 +_,7 @@ + BlockPos blockpos = this.getOrCalculateNorthwestCorner(); + this.structureBlockEntity = StructureUtils.prepareTestStructure(this, blockpos, this.getRotation(), this.level); + this.structureBlockPos = this.structureBlockEntity.getBlockPos(); +- StructureUtils.addCommandBlockAndButtonToStartTest(this.structureBlockPos, new BlockPos(1, 0, -1), this.getRotation(), this.level); ++ StructureUtils.addCommandBlockAndButtonToStartTest(this.structureBlockPos, new BlockPos(1, 0, -1), Rotation.NONE, this.level); // Forge: The control blocks are always in the North West Corner + StructureUtils.encaseStructure(this.getStructureBounds(), this.level, !this.testFunction.skyAccess()); + this.listeners.forEach(p_127630_ -> p_127630_.testStructureLoaded(this)); + return this; diff --git a/patches/minecraft/net/minecraft/gametest/framework/ReportGameListener.java.patch b/patches/minecraft/net/minecraft/gametest/framework/ReportGameListener.java.patch new file mode 100644 index 0000000000..9cc37009ef --- /dev/null +++ b/patches/minecraft/net/minecraft/gametest/framework/ReportGameListener.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/gametest/framework/ReportGameListener.java ++++ b/net/minecraft/gametest/framework/ReportGameListener.java +@@ -151,7 +_,7 @@ + private static BlockPos getBeaconPos(GameTestInfo p_344999_) { + BlockPos blockpos = p_344999_.getStructureBlockPos(); + BlockPos blockpos1 = new BlockPos(-1, -2, -1); +- return StructureTemplate.transform(blockpos.offset(blockpos1), Mirror.NONE, p_344999_.getRotation(), blockpos); ++ return blockpos.offset(blockpos1); // Forge: The control blocks are always in the North West Corner + } + + private static void updateBeaconGlass(GameTestInfo p_343978_, Block p_344076_) { +@@ -167,8 +_,8 @@ + ServerLevel serverlevel = p_177739_.getLevel(); + BlockPos blockpos = p_177739_.getStructureBlockPos(); + BlockPos blockpos1 = new BlockPos(-1, 0, -1); +- BlockPos blockpos2 = StructureTemplate.transform(blockpos.offset(blockpos1), Mirror.NONE, p_177739_.getRotation(), blockpos); +- serverlevel.setBlockAndUpdate(blockpos2, Blocks.LECTERN.defaultBlockState().rotate(p_177739_.getRotation())); ++ BlockPos blockpos2 = blockpos.offset(blockpos1); // Forge: The control blocks are always in the North West Corner ++ serverlevel.setBlockAndUpdate(blockpos2, Blocks.LECTERN.defaultBlockState()); + BlockState blockstate = serverlevel.getBlockState(blockpos2); + ItemStack itemstack = createBook(p_177739_.getTestName(), p_177739_.isRequired(), p_177740_); + LecternBlock.tryPlaceBook(null, serverlevel, blockpos2, blockstate, itemstack); diff --git a/patches/minecraft/net/minecraft/gametest/framework/StructureUtils.java.patch b/patches/minecraft/net/minecraft/gametest/framework/StructureUtils.java.patch new file mode 100644 index 0000000000..b1757f2e2b --- /dev/null +++ b/patches/minecraft/net/minecraft/gametest/framework/StructureUtils.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/gametest/framework/StructureUtils.java ++++ b/net/minecraft/gametest/framework/StructureUtils.java +@@ -132,10 +_,16 @@ + .orElseThrow(() -> new IllegalStateException("Missing test structure: " + p_311701_.getStructureName())) + .getSize(); + BoundingBox boundingbox = getStructureBoundingBox(p_311042_, vec3i, p_310584_); +- BlockPos blockpos = getStartCorner(p_311701_, p_311042_, p_310584_, p_312330_); ++ BlockPos blockpos = getStartCorner(p_311701_, p_311042_, Rotation.NONE, p_312330_); // Forge: The control blocks are always in the North West Corner + forceLoadChunks(boundingbox, p_312330_); + clearSpaceForStructure(boundingbox, p_312330_); +- return createStructureBlock(p_311701_, blockpos.below(), p_310584_, p_312330_); ++ var entity = createStructureBlock(p_311701_, blockpos.below(), p_310584_, p_312330_); ++ //Forge: We need to offset the structure so that it will load within bounds. ++ if (p_310584_ != Rotation.NONE) { ++ var rotated = StructureTemplate.getZeroPositionWithTransform(BlockPos.ZERO, Mirror.NONE, p_310584_, entity.getStructureSize().getX(), entity.getStructureSize().getZ()); ++ entity.setStructurePos(rotated.offset(new BlockPos(0, 1, 0))); ++ } ++ return entity; + } + + public static void encaseStructure(AABB p_330422_, ServerLevel p_331249_, boolean p_328180_) { diff --git a/patches/minecraft/net/minecraft/gametest/framework/TestCommand.java.patch b/patches/minecraft/net/minecraft/gametest/framework/TestCommand.java.patch new file mode 100644 index 0000000000..a4a6ffc7f7 --- /dev/null +++ b/patches/minecraft/net/minecraft/gametest/framework/TestCommand.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/gametest/framework/TestCommand.java ++++ b/net/minecraft/gametest/framework/TestCommand.java +@@ -313,7 +_,11 @@ + return Optional.empty(); + } else { + TestFunction testfunction = optional.get(); +- GameTestInfo gametestinfo = new GameTestInfo(testfunction, structureblockentity.getRotation(), p_328153_, p_330368_); ++ // Forge: The rotation is stored in the structure block, and added in the GameTestInfo constructor. ++ // So reverse it to find the manually specified rotation so the test runs the same every time. ++ var steps = StructureUtils.getRotationStepsForRotation(structureblockentity.getRotation()) - StructureUtils.getRotationStepsForRotation(testfunction.rotation()); ++ if (steps < 0) steps += 4; ++ GameTestInfo gametestinfo = new GameTestInfo(testfunction, StructureUtils.getRotationForRotationSteps(steps), p_328153_, p_330368_); + gametestinfo.setStructureBlockPos(p_332856_); + return !verifyStructureExists(p_328153_, gametestinfo.getStructureName()) ? Optional.empty() : Optional.of(gametestinfo); + } diff --git a/patches/minecraft/net/minecraft/world/level/Level.java.patch b/patches/minecraft/net/minecraft/world/level/Level.java.patch index 2b967f294a..7acf8f6e9a 100644 --- a/patches/minecraft/net/minecraft/world/level/Level.java.patch +++ b/patches/minecraft/net/minecraft/world/level/Level.java.patch @@ -157,12 +157,12 @@ T t = p_261885_.tryCast(enderdragonpart); if (t != null && p_261688_.test(t)) { p_262071_.add(t); -@@ -919,16 +_,15 @@ +@@ -919,17 +_,16 @@ public abstract Scoreboard getScoreboard(); public void updateNeighbourForOutputSignal(BlockPos p_46718_, Block p_46719_) { - for (Direction direction : Direction.Plane.HORIZONTAL) { -+ for (Direction direction : Direction.valuesView()) { ++ for (Direction direction : Direction.getUpdateOrder()) { BlockPos blockpos = p_46718_.relative(direction); if (this.hasChunkAt(blockpos)) { BlockState blockstate = this.getBlockState(blockpos); @@ -174,10 +174,12 @@ blockpos = blockpos.relative(direction); blockstate = this.getBlockState(blockpos); - if (blockstate.is(Blocks.COMPARATOR)) { +- this.neighborChanged(blockstate, blockpos, p_46719_, null, false); + if (blockstate.getWeakChanges(this, blockpos)) { - this.neighborChanged(blockstate, blockpos, p_46719_, null, false); ++ blockstate.onNeighborChange(this, blockpos, p_46718_); } } + } @@ -1031,6 +_,20 @@ } diff --git a/src/test/java/net/minecraftforge/debug/gameplay/redstone/UpdateOrder.java b/src/test/java/net/minecraftforge/debug/gameplay/redstone/UpdateOrder.java new file mode 100644 index 0000000000..65e66ec431 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/redstone/UpdateOrder.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.gameplay.redstone; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +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.entity.BarrelBlockEntity; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.test.BaseTestMod; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.gametest.GameTestHolder; + +/** + * Redstone update orders are important in many contraptions as there are pistons and other things that could conflict with each other. + * These tests verify just that, two pistons pushing the same block. + * + * Partial verification/fix for https://github.com/MinecraftForge/MinecraftForge/issues/9973 + */ +@Mod(UpdateOrder.MODID) +@GameTestHolder("forge." + UpdateOrder.MODID) +public class UpdateOrder extends BaseTestMod { + public static final String MODID = "update_order"; + private static final int PISTON_DELAY = 4; + + public UpdateOrder(FMLJavaModLoadingContext context) { + super(context); + } + + @GameTest(template = "update_order:update_order", rotationSteps = 0) + public static void south(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 0); + helper.assertBlockPresent(Blocks.CHEST, chestPos); + var chest = helper.getBlockEntity(chestPos); + chest.setItem(0, new ItemStack(Items.DIRT)); + + helper.runAfterDelay(PISTON_DELAY, () -> { + var expectedPos = new BlockPos(2, 1, 3); + helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos); + helper.succeed(); + }); + } + + @GameTest(template = "update_order:update_order", rotationSteps = 1) + public static void west(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 0); + helper.assertBlockPresent(Blocks.CHEST, chestPos); + var chest = helper.getBlockEntity(chestPos); + chest.setItem(0, new ItemStack(Items.DIRT)); + + helper.runAfterDelay(PISTON_DELAY, () -> { + var expectedPos = new BlockPos(2, 1, 3); + helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos); + helper.succeed(); + }); + } + + @GameTest(template = "update_order:update_order", rotationSteps = 2) + public static void north(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 0); + helper.assertBlockPresent(Blocks.CHEST, chestPos); + var chest = helper.getBlockEntity(chestPos); + chest.setItem(0, new ItemStack(Items.DIRT)); + + helper.runAfterDelay(4, () -> { + var expectedPos = new BlockPos(3, 1, 2); + helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos); + helper.succeed(); + }); + } + + @GameTest(template = "update_order:update_order", rotationSteps = 3) + public static void east(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 0); + helper.assertBlockPresent(Blocks.CHEST, chestPos); + var chest = helper.getBlockEntity(chestPos); + chest.setItem(0, new ItemStack(Items.DIRT)); + + helper.runAfterDelay(PISTON_DELAY, () -> { + var expectedPos = new BlockPos(2, 1, 3); + helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos); + helper.succeed(); + }); + } + + @GameTest(template = "update_order:upward_update") + public static void up(GameTestHelper helper) { + var chestPos = new BlockPos(0, 1, 2); + var expectedPos = new BlockPos(2, 1, 0); + var unexpectedPos = new BlockPos(0, 4, 0); + helper.assertBlockPresent(Blocks.BARREL, chestPos); + helper.assertBlockPresent(Blocks.AIR, expectedPos); + helper.assertBlockPresent(Blocks.AIR, unexpectedPos); + var chest = helper.getBlockEntity(chestPos); + chest.setItem(0, new ItemStack(Items.DIRT)); + + helper.runAfterDelay(10, () -> { // There are a lot of things happening, give it a few ticks + helper.assertBlockNotPresent(Blocks.WHITE_WOOL, unexpectedPos); + helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos); + helper.succeed(); + }); + } +} \ No newline at end of file diff --git a/src/test/resources/update_order/data/update_order/structure/update_order.nbt b/src/test/resources/update_order/data/update_order/structure/update_order.nbt new file mode 100644 index 0000000000..7a10f54b94 Binary files /dev/null and b/src/test/resources/update_order/data/update_order/structure/update_order.nbt differ diff --git a/src/test/resources/update_order/data/update_order/structure/upward_update.nbt b/src/test/resources/update_order/data/update_order/structure/upward_update.nbt new file mode 100644 index 0000000000..f192fa0084 Binary files /dev/null and b/src/test/resources/update_order/data/update_order/structure/upward_update.nbt differ