diff --git a/patches/server/0084-Paper-Update-Alternate-Current-to-v1.9.patch b/patches/server/0084-Paper-Update-Alternate-Current-to-v1.9.patch new file mode 100644 index 0000000..31c80b2 --- /dev/null +++ b/patches/server/0084-Paper-Update-Alternate-Current-to-v1.9.patch @@ -0,0 +1,775 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 404Setup <153366651+404Setup@users.noreply.github.com> +Date: Wed, 4 Sep 2024 14:14:48 +0800 +Subject: [PATCH] Paper Update Alternate Current to v1.9 + +Source in https://github.com/PaperMC/Paper/commit/b483da4 + +diff --git a/src/main/java/alternate/current/wire/LevelHelper.java b/src/main/java/alternate/current/wire/LevelHelper.java +index 8b4697421d57f81ff1794c6f845258e10df91622..8196460fe91bc4d1b03ca214d4323276d1d19464 100644 +--- a/src/main/java/alternate/current/wire/LevelHelper.java ++++ b/src/main/java/alternate/current/wire/LevelHelper.java +@@ -11,7 +11,7 @@ import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.status.ChunkStatus; + import net.minecraft.world.level.chunk.LevelChunkSection; + +-public class LevelHelper { ++class LevelHelper { + + static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) { + BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(level, pos), prevPower, newPower); +diff --git a/src/main/java/alternate/current/wire/UpdateOrder.java b/src/main/java/alternate/current/wire/UpdateOrder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8dfe11c4321018b3ea2d95b39844cb00c1d8e77d +--- /dev/null ++++ b/src/main/java/alternate/current/wire/UpdateOrder.java +@@ -0,0 +1,389 @@ ++package alternate.current.wire; ++ ++import java.util.Locale; ++import java.util.function.Consumer; ++ ++import alternate.current.wire.WireHandler.Directions; ++import alternate.current.wire.WireHandler.NodeProvider; ++ ++public enum UpdateOrder { ++ ++ HORIZONTAL_FIRST_OUTWARD( ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be an extension of the Vanilla shape ++ * update order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, to reach it. Each step is 1 block, thus the position of a neighbor is ++ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), ++ * etc. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving outward. This means they are iterated over in 3 groups: direct ++ * neighbors first, then diagonal neighbors, and last are the far neighbors that ++ * are 2 blocks directly out. ++ *
++ * 4. The order within each group is determined using the following basic order: ++ * { front, back, right, left, down, up }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { west, east, north, south, down, up } - this is the order of ++ * shape updates. ++ */ ++ ++ int rightward = (forward + 1) & 0b11; ++ int backward = (forward + 2) & 0b11; ++ int leftward = (forward + 3) & 0b11; ++ int downward = Directions.DOWN; ++ int upward = Directions.UP; ++ ++ Node front = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // direct neighbors (6) ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ action.accept(below); ++ action.accept(above); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ action.accept(nodes.getNeighbor(front, downward)); ++ action.accept(nodes.getNeighbor(back, upward)); ++ action.accept(nodes.getNeighbor(front, upward)); ++ action.accept(nodes.getNeighbor(back, downward)); ++ action.accept(nodes.getNeighbor(right, downward)); ++ action.accept(nodes.getNeighbor(left, upward)); ++ action.accept(nodes.getNeighbor(right, upward)); ++ action.accept(nodes.getNeighbor(left, downward)); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ } ++ }, ++ HORIZONTAL_FIRST_INWARD( ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be an inversion of the above update ++ * order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, to reach it. Each step is 1 block, thus the position of a neighbor is ++ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), ++ * etc. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving inward. This means they are iterated over in 3 groups: neighbors that ++ * are 2 blocks directly out first, then diagonal neighbors, and last are direct ++ * neighbors. ++ *
++ * 4. The order within each group is determined using the following basic order: ++ * { front, back, right, left, down, up }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { west, east, north, south, down, up } - this is the order of ++ * shape updates. ++ */ ++ ++ int rightward = (forward + 1) & 0b11; ++ int backward = (forward + 2) & 0b11; ++ int leftward = (forward + 3) & 0b11; ++ int downward = Directions.DOWN; ++ int upward = Directions.UP; ++ ++ Node front = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ action.accept(nodes.getNeighbor(front, downward)); ++ action.accept(nodes.getNeighbor(back, upward)); ++ action.accept(nodes.getNeighbor(front, upward)); ++ action.accept(nodes.getNeighbor(back, downward)); ++ action.accept(nodes.getNeighbor(right, downward)); ++ action.accept(nodes.getNeighbor(left, upward)); ++ action.accept(nodes.getNeighbor(right, upward)); ++ action.accept(nodes.getNeighbor(left, downward)); ++ ++ ++ // direct neighbors (6) ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ action.accept(below); ++ action.accept(above); ++ } ++ }, ++ VERTICAL_FIRST_OUTWARD( ++ new int[][] { ++ new int[] { Directions.DOWN, Directions.UP, Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be the opposite of the Vanilla shape ++ * update order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, to reach it. Each step is 1 block, thus the position of a neighbor is ++ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), ++ * etc. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving outward. This means they are iterated over in 3 groups: direct ++ * neighbors first, then diagonal neighbors, and last are the far neighbors that ++ * are 2 blocks directly out. ++ *
++ * 4. The order within each group is determined using the following basic order: ++ * { down, up, front, back, right, left }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { down, up west, east, north, south } - this is the order of ++ * shape updates, with the vertical directions moved to the front. ++ */ ++ ++ int rightward = (forward + 1) & 0b11; ++ int backward = (forward + 2) & 0b11; ++ int leftward = (forward + 3) & 0b11; ++ int downward = Directions.DOWN; ++ int upward = Directions.UP; ++ ++ Node front = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // direct neighbors (6) ++ action.accept(below); ++ action.accept(above); ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(below, forward)); ++ action.accept(nodes.getNeighbor(above, backward)); ++ action.accept(nodes.getNeighbor(below, backward)); ++ action.accept(nodes.getNeighbor(above, forward)); ++ action.accept(nodes.getNeighbor(below, rightward)); ++ action.accept(nodes.getNeighbor(above, leftward)); ++ action.accept(nodes.getNeighbor(below, leftward)); ++ action.accept(nodes.getNeighbor(above, rightward)); ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ } ++ }, ++ VERTICAL_FIRST_INWARD( ++ new int[][] { ++ new int[] { Directions.DOWN, Directions.UP, Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be an inversion of the above update ++ * order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, to reach it. Each step is 1 block, thus the position of a neighbor is ++ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), ++ * etc. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving inward. This means they are iterated over in 3 groups: neighbors that ++ * are 2 blocks directly out first, then diagonal neighbors, and last are direct ++ * neighbors. ++ *
++ * 4. The order within each group is determined using the following basic order: ++ * { down, up, front, back, right, left }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { down, up west, east, north, south } - this is the order of ++ * shape updates, with the vertical directions moved to the front. ++ */ ++ ++ int rightward = (forward + 1) & 0b11; ++ int backward = (forward + 2) & 0b11; ++ int leftward = (forward + 3) & 0b11; ++ int downward = Directions.DOWN; ++ int upward = Directions.UP; ++ ++ Node front = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(below, forward)); ++ action.accept(nodes.getNeighbor(above, backward)); ++ action.accept(nodes.getNeighbor(below, backward)); ++ action.accept(nodes.getNeighbor(above, forward)); ++ action.accept(nodes.getNeighbor(below, rightward)); ++ action.accept(nodes.getNeighbor(above, leftward)); ++ action.accept(nodes.getNeighbor(below, leftward)); ++ action.accept(nodes.getNeighbor(above, rightward)); ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ ++ // direct neighbors (6) ++ action.accept(below); ++ action.accept(above); ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ } ++ }; ++ ++ private final int[][] directNeighbors; ++ private final int[][] cardinalNeighbors; ++ ++ private UpdateOrder(int[][] directNeighbors, int[][] cardinalNeighbors) { ++ this.directNeighbors = directNeighbors; ++ this.cardinalNeighbors = cardinalNeighbors; ++ } ++ ++ public String id() { ++ return name().toLowerCase(Locale.ENGLISH); ++ } ++ ++ public static UpdateOrder byId(String id) { ++ return valueOf(id.toUpperCase(Locale.ENGLISH)); ++ } ++ ++ public int[] directNeighbors(int forward) { ++ return directNeighbors[forward]; ++ } ++ ++ public int[] cardinalNeighbors(int forward) { ++ return cardinalNeighbors[forward]; ++ } ++ ++ /** ++ * Iterate over all neighboring nodes of the given source node. The iteration ++ * order is built from relative directions around the source, depending on the ++ * given 'forward' direction. This is an effort to eliminate any directional ++ * biases that would be emerge in rotationally symmetric circuits if the update ++ * order was built from absolute directions around the source. ++ *
++ * Each update order must include the source's direct neighbors, but further ++ * neighbors may not be included. ++ */ ++ public abstract void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action); ++ ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/wire/WireConnectionManager.java b/src/main/java/alternate/current/wire/WireConnectionManager.java +index 5a7209f05b549c222f6c9bc2af2a35790964947e..f03b313e58385d626490a9e64c9616fd08aa951e 100644 +--- a/src/main/java/alternate/current/wire/WireConnectionManager.java ++++ b/src/main/java/alternate/current/wire/WireConnectionManager.java +@@ -52,24 +52,22 @@ public class WireConnectionManager { + + if (neighbor.isWire()) { + add(neighbor.asWire(), iDir, true, true); ++ } else { ++ boolean sideIsConductor = neighbor.isConductor(); + +- continue; +- } +- +- boolean sideIsConductor = neighbor.isConductor(); ++ if (!sideIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.DOWN); + +- if (!sideIsConductor) { +- Node node = nodes.getNeighbor(neighbor, Directions.DOWN); +- +- if (node.isWire()) { +- add(node.asWire(), iDir, belowIsConductor, true); ++ if (node.isWire()) { ++ add(node.asWire(), iDir, belowIsConductor, true); ++ } + } +- } +- if (!aboveIsConductor) { +- Node node = nodes.getNeighbor(neighbor, Directions.UP); ++ if (!aboveIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.UP); + +- if (node.isWire()) { +- add(node.asWire(), iDir, true, sideIsConductor); ++ if (node.isWire()) { ++ add(node.asWire(), iDir, true, sideIsConductor); ++ } + } + } + } +@@ -126,8 +124,8 @@ public class WireConnectionManager { + * Iterate over all connections. Use this method if the iteration order is + * important. + */ +- void forEach(Consumer consumer, int iFlowDir) { +- for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) { ++ void forEach(Consumer consumer, UpdateOrder updateOrder, int iFlowDir) { ++ for (int iDir : updateOrder.cardinalNeighbors(iFlowDir)) { + for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) { + consumer.accept(c); + } +diff --git a/src/main/java/alternate/current/wire/WireHandler.java b/src/main/java/alternate/current/wire/WireHandler.java +index e943fdcbc15d5c17450659c2cd9e0be73ae06c0b..a31b2771fc530070ecbbd7adc224edcf745b950f 100644 +--- a/src/main/java/alternate/current/wire/WireHandler.java ++++ b/src/main/java/alternate/current/wire/WireHandler.java +@@ -15,6 +15,8 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.redstone.InstantNeighborUpdater; ++import net.minecraft.world.level.redstone.NeighborUpdater; + import net.minecraft.world.level.redstone.Redstone; + + /** +@@ -228,36 +230,9 @@ public class WireHandler { + -1, // 0b1111: west/north/east/south -> x + }; + /** +- * Update orders of all directions. Given that the index encodes the direction +- * that is to be considered 'forward', the resulting update order is +- * { front, back, right, left, down, up }. ++ * Update order of shape updates, matching that of Vanilla. + */ +- static final int[][] FULL_UPDATE_ORDERS = { +- { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, +- { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, +- { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, +- { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } +- }; +- /** +- * The default update order of all directions. It is equivalent to the order of +- * shape updates in vanilla Minecraft. +- */ +- static final int[] DEFAULT_FULL_UPDATE_ORDER = FULL_UPDATE_ORDERS[0]; +- /** +- * Update orders of cardinal directions. Given that the index encodes the +- * direction that is to be considered 'forward', the resulting update order is +- * { front, back, right, left }. +- */ +- static final int[][] CARDINAL_UPDATE_ORDERS = { +- { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, +- { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, +- { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, +- { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } +- }; +- /** +- * The default update order of all cardinal directions. +- */ +- static final int[] DEFAULT_CARDINAL_UPDATE_ORDER = CARDINAL_UPDATE_ORDERS[0]; ++ static final int[] SHAPE_UPDATE_ORDER = { Directions.WEST, Directions.EAST, Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }; + + private static final int POWER_MIN = Redstone.SIGNAL_MIN; + private static final int POWER_MAX = Redstone.SIGNAL_MAX; +@@ -275,6 +250,7 @@ public class WireHandler { + private final Queue search; + /** Queue of updates to wires and neighboring blocks. */ + private final Queue updates; ++ private final NeighborUpdater neighborUpdater; + + // Rather than creating new nodes every time a network is updated we keep + // a cache of nodes that can be re-used. +@@ -283,6 +259,8 @@ public class WireHandler { + + /** Is this WireHandler currently working through the update queue? */ + private boolean updating; ++ /** The update order currently in use. */ ++ private UpdateOrder updateOrder; + + public WireHandler(ServerLevel level) { + this.level = level; +@@ -291,6 +269,8 @@ public class WireHandler { + this.search = new SimpleQueue(); + this.updates = new PriorityQueue(); + ++ this.neighborUpdater = new InstantNeighborUpdater(this.level); ++ + this.nodeCache = new Node[16]; + this.fillNodeCache(0, 16); + } +@@ -429,80 +409,6 @@ public class WireHandler { + return neighbor; + } + +- /** +- * Iterate over all neighboring nodes of the given wire. The iteration order is +- * designed to be an extension of the default block update order, and is +- * determined as follows: +- *
+- * 1. The direction of power flow through the wire is to be considered +- * 'forward'. The iteration order depends on the neighbors' relative positions +- * to the wire. +- *
+- * 2. Each neighbor is identified by the step(s) you must take, starting at the +- * wire, to reach it. Each step is 1 block, thus the position of a neighbor is +- * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), +- * etc. +- *
+- * 3. Neighbors are iterated over in pairs that lie on opposite sides of the +- * wire. +- *
+- * 4. Neighbors are iterated over in order of their distance from the wire. This +- * means they are iterated over in 3 groups: direct neighbors first, then +- * diagonal neighbors, and last are the far neighbors that are 2 blocks directly +- * out. +- *
+- * 5. The order within each group is determined using the following basic order: +- * { front, back, right, left, down, up }. This order was chosen because it +- * converts to the following order of absolute directions when west is said to +- * be 'forward': { west, east, north, south, down, up } - this is the order of +- * shape updates. +- */ +- private void forEachNeighbor(WireNode wire, Consumer consumer) { +- int forward = wire.iFlowDir; +- int rightward = (forward + 1) & 0b11; +- int backward = (forward + 2) & 0b11; +- int leftward = (forward + 3) & 0b11; +- int downward = Directions.DOWN; +- int upward = Directions.UP; +- +- Node front = getNeighbor(wire, forward); +- Node right = getNeighbor(wire, rightward); +- Node back = getNeighbor(wire, backward); +- Node left = getNeighbor(wire, leftward); +- Node below = getNeighbor(wire, downward); +- Node above = getNeighbor(wire, upward); +- +- // direct neighbors (6) +- consumer.accept(front); +- consumer.accept(back); +- consumer.accept(right); +- consumer.accept(left); +- consumer.accept(below); +- consumer.accept(above); +- +- // diagonal neighbors (12) +- consumer.accept(getNeighbor(front, rightward)); +- consumer.accept(getNeighbor(back, leftward)); +- consumer.accept(getNeighbor(front, leftward)); +- consumer.accept(getNeighbor(back, rightward)); +- consumer.accept(getNeighbor(front, downward)); +- consumer.accept(getNeighbor(back, upward)); +- consumer.accept(getNeighbor(front, upward)); +- consumer.accept(getNeighbor(back, downward)); +- consumer.accept(getNeighbor(right, downward)); +- consumer.accept(getNeighbor(left, upward)); +- consumer.accept(getNeighbor(right, upward)); +- consumer.accept(getNeighbor(left, downward)); +- +- // far neighbors (6) +- consumer.accept(getNeighbor(front, forward)); +- consumer.accept(getNeighbor(back, backward)); +- consumer.accept(getNeighbor(right, rightward)); +- consumer.accept(getNeighbor(left, leftward)); +- consumer.accept(getNeighbor(below, downward)); +- consumer.accept(getNeighbor(above, upward)); +- } +- + /** + * This method should be called whenever a wire receives a block update. + */ +@@ -577,6 +483,8 @@ public class WireHandler { + node.invalid = true; + } + } ++ ++ updateOrder = UpdateOrder.values()[level.paperConfig().misc.alternateCurrentUpdateOrder.ordinal()]; + } + + /** +@@ -622,7 +530,7 @@ public class WireHandler { + return; + } + +- for (int iDir : FULL_UPDATE_ORDERS[wire.iFlowDir]) { ++ for (int iDir : updateOrder.directNeighbors(wire.iFlowDir)) { + Node neighbor = getNeighbor(wire, iDir); + + if (neighbor.isConductor() || neighbor.isSignalSource()) { +@@ -938,7 +846,7 @@ public class WireHandler { + if (needsUpdate(neighbor)) { + search(neighbor, false, connection.iDir); + } +- }, wire.iFlowDir); ++ }, updateOrder, wire.iFlowDir); + } + } + +@@ -1052,7 +960,7 @@ public class WireHandler { + if (neighbor.offerPower(power, iDir)) { + queueWire(neighbor); + } +- }, wire.iFlowDir); ++ }, updateOrder, wire.iFlowDir); + } + + /** +@@ -1062,10 +970,15 @@ public class WireHandler { + BlockPos wirePos = wire.pos; + BlockState wireState = wire.state; + +- for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { ++ for (int iDir : SHAPE_UPDATE_ORDER) { + Node neighbor = getNeighbor(wire, iDir); + +- if (!neighbor.isWire()) { ++ // Shape updates to redstone wire are very expensive, and should never happen ++ // as a result of power changes anyway, while shape updates to air do nothing. ++ // The current block state at this position *could* be wrong, but if you somehow ++ // manage to place a block where air used to be during the execution of a shape ++ // update I am very impressed and you deserve to have some broken behavior. ++ if (!neighbor.isWire() && !neighbor.state.isAir()) { + int iOpp = Directions.iOpposite(iDir); + Direction opp = Directions.ALL[iOpp]; + +@@ -1075,24 +988,14 @@ public class WireHandler { + } + + private void updateShape(Node node, Direction dir, BlockPos neighborPos, BlockState neighborState) { +- BlockPos pos = node.pos; +- BlockState state = level.getBlockState(pos); +- +- // Shape updates to redstone wire are very expensive, and should never happen +- // as a result of power changes anyway. +- if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { +- BlockState newState = state.updateShape(dir, neighborState, level, pos, neighborPos); +- Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS); +- } ++ neighborUpdater.shapeUpdate(dir, neighborState, node.pos, neighborPos, Block.UPDATE_CLIENTS, 512); + } + + /** + * Queue block updates to nodes around the given wire. + */ + private void queueNeighbors(WireNode wire) { +- forEachNeighbor(wire, neighbor -> { +- queueNeighbor(neighbor, wire); +- }); ++ updateOrder.forEachNeighbor(this::getNeighbor, wire, wire.iFlowDir, neighbor -> queueNeighbor(neighbor, wire)); + } + + /** +@@ -1100,7 +1003,20 @@ public class WireHandler { + */ + private void queueNeighbor(Node node, WireNode neighborWire) { + // Updates to wires are queued when power is transmitted. +- if (!node.isWire()) { ++ // While this check makes sure wires in the network are not given block ++ // updates, it also prevents block updates to wires in neighboring networks. ++ // While this should not make a difference in theory, in practice, it is ++ // possible to force a network into an invalid state without updating it, even ++ // if it is relatively obscure. ++ // While I was willing to make this compromise in return for some significant ++ // performance gains in certain setups, if you are not, you can add all the ++ // positions of the network to a set and filter out block updates to wires in ++ // the network that way. ++ // Block updates to air do nothing, so those are skipped as well. ++ // The current block state at this position *could* be wrong, but if you somehow ++ // manage to place a block where air used to be during the execution of a block ++ // update I am very impressed and you deserve to have some broken behavior. ++ if (!node.isWire() && !node.state.isAir()) { + node.neighborWire = neighborWire; + updates.offer(node); + } +@@ -1124,21 +1040,7 @@ public class WireHandler { + * Emit a block update to the given node. + */ + private void updateBlock(Node node, BlockPos neighborPos, Block neighborBlock) { +- BlockPos pos = node.pos; +- BlockState state = level.getBlockState(pos); +- +- // While this check makes sure wires in the network are not given block +- // updates, it also prevents block updates to wires in neighboring networks. +- // While this should not make a difference in theory, in practice, it is +- // possible to force a network into an invalid state without updating it, even +- // if it is relatively obscure. +- // While I was willing to make this compromise in return for some significant +- // performance gains in certain setups, if you are not, you can add all the +- // positions of the network to a set and filter out block updates to wires in +- // the network that way. +- if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { +- state.handleNeighborChanged(level, pos, neighborBlock, neighborPos, false); +- } ++ neighborUpdater.neighborChanged(node.pos, neighborBlock, neighborPos); + } + + @FunctionalInterface +diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +index efc91ff91827872c62b8bd060282549ccdcf67dd..01213aa099e59660496491a3849115fa0db15964 100644 +--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +@@ -537,6 +537,7 @@ public class WorldConfiguration extends ConfigurationPart { + public boolean updatePathfindingOnBlockUpdate = true; + public boolean showSignClickCommandFailureMsgsToPlayer = false; + public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA; ++ public AlternateCurrentUpdateOrder alternateCurrentUpdateOrder = AlternateCurrentUpdateOrder.HORIZONTAL_FIRST_OUTWARD; + public boolean disableEndCredits = false; + public float maxLeashDistance = 10f; + public boolean disableSprintInterruptionOnAttack = false; +@@ -546,5 +547,9 @@ public class WorldConfiguration extends ConfigurationPart { + public enum RedstoneImplementation { + VANILLA, EIGENCRAFT, ALTERNATE_CURRENT + } ++ ++ public enum AlternateCurrentUpdateOrder { ++ HORIZONTAL_FIRST_OUTWARD, HORIZONTAL_FIRST_INWARD, VERTICAL_FIRST_OUTWARD, VERTICAL_FIRST_INWARD ++ } + } + } diff --git a/patches/server/0084-lithium-precompute-shape-arrays.patch b/patches/server/0085-lithium-precompute-shape-arrays.patch similarity index 100% rename from patches/server/0084-lithium-precompute-shape-arrays.patch rename to patches/server/0085-lithium-precompute-shape-arrays.patch diff --git a/patches/server/0085-lithium-fast-powder-snow-check.patch b/patches/server/0086-lithium-fast-powder-snow-check.patch similarity index 100% rename from patches/server/0085-lithium-fast-powder-snow-check.patch rename to patches/server/0086-lithium-fast-powder-snow-check.patch diff --git a/patches/server/0086-vmp-TypeFilterableList.patch b/patches/server/0087-vmp-TypeFilterableList.patch similarity index 100% rename from patches/server/0086-vmp-TypeFilterableList.patch rename to patches/server/0087-vmp-TypeFilterableList.patch diff --git a/patches/server/0087-Slice-Improve-map-saving-performance.patch b/patches/server/0088-Slice-Improve-map-saving-performance.patch similarity index 100% rename from patches/server/0087-Slice-Improve-map-saving-performance.patch rename to patches/server/0088-Slice-Improve-map-saving-performance.patch diff --git a/patches/server/0088-Slice-noEntityCollisions-for-Entity.patch b/patches/server/0089-Slice-noEntityCollisions-for-Entity.patch similarity index 100% rename from patches/server/0088-Slice-noEntityCollisions-for-Entity.patch rename to patches/server/0089-Slice-noEntityCollisions-for-Entity.patch diff --git a/patches/server/0089-Akarin-Save-Json-list-asynchronously.patch b/patches/server/0090-Akarin-Save-Json-list-asynchronously.patch similarity index 100% rename from patches/server/0089-Akarin-Save-Json-list-asynchronously.patch rename to patches/server/0090-Akarin-Save-Json-list-asynchronously.patch diff --git a/patches/server/0090-Airplane-Remove-stream-in-PoiCompetitorScan.patch b/patches/server/0091-Airplane-Remove-stream-in-PoiCompetitorScan.patch similarity index 100% rename from patches/server/0090-Airplane-Remove-stream-in-PoiCompetitorScan.patch rename to patches/server/0091-Airplane-Remove-stream-in-PoiCompetitorScan.patch diff --git a/patches/server/0091-ScalableLux-Upstream-Starlight-Fix.patch b/patches/server/0092-ScalableLux-Upstream-Starlight-Fix.patch similarity index 100% rename from patches/server/0091-ScalableLux-Upstream-Starlight-Fix.patch rename to patches/server/0092-ScalableLux-Upstream-Starlight-Fix.patch