diff --git a/patches/server/0078-Remove-Eigencraft-redstone-algo.patch b/patches/server/0078-Remove-Eigencraft-redstone-algo.patch new file mode 100644 index 0000000..6a38d42 --- /dev/null +++ b/patches/server/0078-Remove-Eigencraft-redstone-algo.patch @@ -0,0 +1,1119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Sat, 18 Dec 2021 13:03:12 +0100 +Subject: [PATCH] Remove Eigencraft redstone algo + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +Reverts Eigencraft redstone from Paper in order for it to be replaced with alternate-current's redstone implementation + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index d71cd626bcbefc576f9c05b8885acc9fb2a33cd5..dbc9a4f494434bc8e938d8d149d74ee977f78483 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -77,16 +77,6 @@ public class PaperWorldConfig { + piglinsGuardChests = getBoolean("piglins-guard-chests", piglinsGuardChests); + } + +- public boolean useEigencraftRedstone = false; +- private void useEigencraftRedstone() { +- useEigencraftRedstone = this.getBoolean("use-faster-eigencraft-redstone", false); +- if (useEigencraftRedstone) { +- log("Using Eigencraft redstone algorithm by theosib."); +- } else { +- log("Using vanilla redstone algorithm."); +- } +- } +- + public boolean shouldRemoveDragon = false; + private void shouldRemoveDragon() { + shouldRemoveDragon = getBoolean("should-remove-dragon", shouldRemoveDragon); +diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +deleted file mode 100644 +index d4273df8124d9d6d4a122f5ecef6f3d011da5860..0000000000000000000000000000000000000000 +--- a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java ++++ /dev/null +@@ -1,913 +0,0 @@ +-package com.destroystokyo.paper.util; +- +-import java.util.List; +-import java.util.Map; +-import java.util.concurrent.ThreadLocalRandom; +-import net.minecraft.core.BlockPos; +-import net.minecraft.world.item.ItemStack; +-import net.minecraft.world.item.Items; +-import net.minecraft.world.level.Level; +-import net.minecraft.world.level.block.Block; +-import net.minecraft.world.level.block.RedStoneWireBlock; +-import net.minecraft.world.level.block.state.BlockState; +-import org.bukkit.event.block.BlockRedstoneEvent; +- +-import com.google.common.collect.Lists; +-import com.google.common.collect.Maps; +- +-/** +- * Used for the faster redstone algorithm. +- * Original author: theosib +- * Original license: MIT +- * +- * Ported to Paper and updated to 1.13 by egg82 +- */ +-public class RedstoneWireTurbo { +- /* +- * This is Helper class for BlockRedstoneWire. It implements a minimally-invasive +- * bolt-on accelerator that performs a breadth-first search through redstone wire blocks +- * in order to more efficiently and deterministically compute new redstone wire power levels +- * and determine the order in which other blocks should be updated. +- * +- * Features: +- * - Changes to BlockRedstoneWire are very limited, no other classes are affected, and the +- * choice between old and new redstone wire update algorithms is switchable on-line. +- * - The vanilla implementation relied on World.notifyNeighborsOfStateChange for redstone +- * wire blocks to communicate power level changes to each other, generating 36 block +- * updates per call. This improved implementation propagates power level changes directly +- * between redstone wire blocks. Redstone wire power levels are therefore computed more quickly, +- * and block updates are sent only to non-redstone blocks, many of which may perform an +- * action when informed of a change in redstone power level. (Note: Block updates are not +- * the same as state changes to redstone wire. Wire block states are updated as soon +- * as they are computed.) +- * - Of the 36 block updates generated by a call to World.notifyNeighborsOfStateChange, +- * 12 of them are obviously redundant (e.g. the west neighbor of the east neighbor). +- * These are eliminated. +- * - Updates to redstone wire and other connected blocks are propagated in a breath-first +- * manner, radiating out from the initial trigger (a block update to a redstone wire +- * from something other than redstone wire). +- * - Updates are scheduled both deterministically and in an intuitive order, addressing bug +- * MC-11193. +- * - All redstone behavior that used to be locational now works the same in all locations. +- * - All behaviors of redstone wire that used to be orientational now work the same in all +- * orientations, as long as orientation can be determined; random otherwise. Some other +- * redstone components still update directionally (e.g. switches), and this code can't +- * compensate for that. +- * - Information that is otherwise computed over and over again or which is expensive to +- * to compute is cached for faster lookup. This includes coordinates of block position +- * neighbors and block states that won't change behind our backs during the execution of +- * this search algorithm. +- * - Redundant block updates (both to redstone wire and to other blocks) are heavily +- * consolidated. For worst-case scenarios (depowering of redstone wire) this results +- * in a reduction of block updates by as much as 95% (factor of 1/21). Due to overheads, +- * empirical testing shows a speedup better than 10x. This addresses bug MC-81098. +- * +- * Extensive testing has been performed to ensure that existing redstone contraptions still +- * behave as expected. Results of early testing that identified undesirable behavior changes +- * were addressed. Additionally, real-time performance testing revealed compute inefficiencies +- * With earlier implementations of this accelerator. Some compatibility adjustments and +- * performance optimizations resulted in harmless increases in block updates above the +- * theoretical minimum. +- * +- * Only a single redstone machine was found to break: An instant dropper line hack that +- * relies on powered rails and quasi-connectivity but doesn't work in all directions. The +- * replacement is to lay redstone wire directly on top of the dropper line, which now works +- * reliably in any direction. +- * +- * There are numerous other optimization that can be made, but those will be provided later in +- * separate updates. This version is designed to be minimalistic. +- * +- * Many thanks to the following individuals for their help in testing this functionality: +- * - pokechu22, _MethodZz_, WARBEN, NarcolepticFrog, CommandHelper (nessie), ilmango, +- * OreoLamp, Xcom6000, tryashtar, RedCMD, Smokey95Dog, EDDxample, Rays Works, +- * Nodnam, BlockyPlays, Grumm, NeunEinser, HelVince. +- */ +- +- /* Reference to BlockRedstoneWire object, which uses this accelerator */ +- private final RedStoneWireBlock wire; +- +- /* +- * Implementation: +- * +- * RedstoneWire Blocks are updated in concentric rings or "layers" radiating out from the +- * initial block update that came from a call to BlockRedstoneWire.neighborChanged(). +- * All nodes put in Layer N are those with Manhattan distance N from the trigger +- * position, reachable through connected redstone wire blocks. +- * +- * Layer 0 represents the trigger block position that was input to neighborChanged. +- * Layer 1 contains the immediate neighbors of that position. +- * Layer N contains the neighbors of blocks in layer N-1, not including +- * those in previous layers. +- * +- * Layers enforce an update order that is a function of Manhattan distance +- * from the initial coordinates input to neighborChanged. The same +- * coordinates may appear in multiple layers, but redundant updates are minimized. +- * Block updates are sent layer-by-layer. If multiple of a block's neighbors experience +- * redstone wire changes before its layer is processed, then those updates will be merged. +- * If a block's update has been sent, but its neighboring redstone changes +- * after that, then another update will be sent. This preserves compatibility with +- * machines that rely on zero-tick behavior, except that the new functionality is non- +- * locational. +- * +- * Within each layer, updates are ordered left-to-right relative to the direction of +- * information flow. This makes the implementation non-orientational. Only when +- * this direction is ambiguous is randomness applied (intentionally). +- */ +- private List updateQueue0 = Lists.newArrayList(); +- private List updateQueue1 = Lists.newArrayList(); +- private List updateQueue2 = Lists.newArrayList(); +- +- public RedstoneWireTurbo(RedStoneWireBlock wire) { +- this.wire = wire; +- } +- +- /* +- * Compute neighbors of a block. When a redstone wire value changes, previously it called +- * World.notifyNeighborsOfStateChange. That lists immediately neighboring blocks in +- * west, east, down, up, north, south order. For each of those neighbors, their own +- * neighbors are updated in the same order. This generates 36 updates, but 12 of them are +- * redundant; for instance the west neighbor of a block's east neighbor. +- * +- * Note that this ordering is only used to create the initial list of neighbors. Once +- * the direction of signal flow is identified, the ordering of updates is completely +- * reorganized. +- */ +- public static BlockPos[] computeAllNeighbors(final BlockPos pos) { +- final int x = pos.getX(); +- final int y = pos.getY(); +- final int z = pos.getZ(); +- final BlockPos[] n = new BlockPos[24]; +- +- // Immediate neighbors, in the same order as +- // World.notifyNeighborsOfStateChange, etc.: +- // west, east, down, up, north, south +- n[0] = new BlockPos(x - 1, y, z); +- n[1] = new BlockPos(x + 1, y, z); +- n[2] = new BlockPos(x, y - 1, z); +- n[3] = new BlockPos(x, y + 1, z); +- n[4] = new BlockPos(x, y, z - 1); +- n[5] = new BlockPos(x, y, z + 1); +- +- // Neighbors of neighbors, in the same order, +- // except that duplicates are not included +- n[6] = new BlockPos(x - 2, y, z); +- n[7] = new BlockPos(x - 1, y - 1, z); +- n[8] = new BlockPos(x - 1, y + 1, z); +- n[9] = new BlockPos(x - 1, y, z - 1); +- n[10] = new BlockPos(x - 1, y, z + 1); +- n[11] = new BlockPos(x + 2, y, z); +- n[12] = new BlockPos(x + 1, y - 1, z); +- n[13] = new BlockPos(x + 1, y + 1, z); +- n[14] = new BlockPos(x + 1, y, z - 1); +- n[15] = new BlockPos(x + 1, y, z + 1); +- n[16] = new BlockPos(x, y - 2, z); +- n[17] = new BlockPos(x, y - 1, z - 1); +- n[18] = new BlockPos(x, y - 1, z + 1); +- n[19] = new BlockPos(x, y + 2, z); +- n[20] = new BlockPos(x, y + 1, z - 1); +- n[21] = new BlockPos(x, y + 1, z + 1); +- n[22] = new BlockPos(x, y, z - 2); +- n[23] = new BlockPos(x, y, z + 2); +- return n; +- } +- +- /* +- * We only want redstone wires to update redstone wires that are +- * immediately adjacent. Some more distant updates can result +- * in cross-talk that (a) wastes time and (b) can make the update +- * order unintuitive. Therefore (relative to the neighbor order +- * computed by computeAllNeighbors), updates are not scheduled +- * for redstone wire in those non-connecting positions. On the +- * other hand, updates will always be sent to *other* types of blocks +- * in any of the 24 neighboring positions. +- */ +- private static final boolean[] update_redstone = { +- true, true, false, false, true, true, // 0 to 5 +- false, true, true, false, false, false, // 6 to 11 +- true, true, false, false, false, true, // 12 to 17 +- true, false, true, true, false, false // 18 to 23 +- }; +- +- // Internal numbering for cardinal directions +- private static final int North = 0; +- private static final int East = 1; +- private static final int South = 2; +- private static final int West = 3; +- +- /* +- * These lookup tables completely remap neighbor positions into a left-to-right +- * ordering, based on the cardinal direction that is determined to be forward. +- * See below for more explanation. +- */ +- private static final int[] forward_is_north = {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}; +- private static final int[] forward_is_east = {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}; +- private static final int[] forward_is_south = {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}; +- private static final int[] forward_is_west = {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}; +- +- /* For any orientation, we end up with the update order defined below. This order is relative to any redstone wire block +- * that is itself having an update computed, and this center position is marked with C. +- * - The update position marked 0 is computed first, and the one marked 23 is last. +- * - Forward is determined by the local direction of information flow into position C from prior updates. +- * - The first updates are scheduled for the four positions below and above C. +- * - Then updates are scheduled for the four horizontal neighbors of C, followed by the positions below and above those neighbors. +- * - Finally, updates are scheduled for the remaining positions with Manhattan distance 2 from C (at the same Y coordinate). +- * - For a given horizontal distance from C, updates are scheduled starting from directly left and stepping clockwise to directly +- * right. The remaining positions behind C are scheduled counterclockwise so as to maintain the left-to-right ordering. +- * - If C is in layer N of the update schedule, then all 24 positions may be scheduled for layer N+1. For redstone wire, no +- * updates are scheduled for positions that cannot directly connect. Additionally, the four positions above and below C +- * are ALSO scheduled for layer N+2. +- * - This update order was selected after experimenting with a number of alternative schedules, based on its compatibility +- * with existing redstone designs and behaviors that were considered to be intuitive by various testers. WARBEN in particular +- * made some of the most challenging test cases, but the 3-tick clocks (made by RedCMD) were also challenging to fix, +- * along with the rail-based instant dropper line built by ilmango. Numerous others made test cases as well, including +- * NarcolepticFrog, nessie, and Pokechu22. +- * +- * - The forward direction is determined locally. So when there are branches in the redstone wire, the left one will get updated +- * before the right one. Each branch can have its own relative forward direction, resulting in the left side of a left branch +- * having priority over the right branch of a left branch, which has priority over the left branch of a right branch, followed +- * by the right branch of a right branch. And so forth. Since redstone power reduces to zero after a path distance of 15, +- * that imposes a practical limit on the branching. Note that the branching is not tracked explicitly -- relative forward +- * directions dictate relative sort order, which maintains the proper global ordering. This also makes it unnecessary to be +- * concerned about branches meeting up with each other. +- * +- * ^ +- * | +- * Forward +- * <-- Left Right --> +- * +- * 18 +- * 10 17 5 19 11 +- * 2 8 0 12 16 4 C 6 20 9 1 13 3 +- * 14 21 7 23 15 +- * Further 22 Further +- * Down Down Up Up +- * +- * Backward +- * | +- * V +- */ +- +- // This allows the above remapping tables to be looked up by cardial direction index +- private static final int[][] reordering = { forward_is_north, forward_is_east, forward_is_south, forward_is_west }; +- +- /* +- * Input: Array of UpdateNode objects in an order corresponding to the positions +- * computed by computeAllNeighbors above. +- * Output: Array of UpdateNode objects oriented using the above remapping tables +- * corresponding to the identified heading (direction of information flow). +- */ +- private static void orientNeighbors(final UpdateNode[] src, final UpdateNode[] dst, final int heading) { +- final int[] re = reordering[heading]; +- for (int i = 0; i < 24; i++) { +- dst[i] = src[re[i]]; +- } +- } +- +- /* +- * Structure to keep track of redstone wire blocks and +- * neighbors that will receive updates. +- */ +- private static class UpdateNode { +- public static enum Type { +- UNKNOWN, REDSTONE, OTHER +- } +- +- BlockState currentState; // Keep track of redstone wire value +- UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) +- BlockPos self; // UpdateNode's own position +- BlockPos parent; // Which block pos spawned/updated this node +- Type type = Type.UNKNOWN; // unknown, redstone wire, other type of block +- int layer; // Highest layer this node is scheduled in +- boolean visited; // To keep track of information flow direction, visited restone wire is marked +- int xbias, zbias; // Remembers directionality of ancestor nodes; helps eliminate directional ambiguities. +- } +- +- /* +- * Keep track of all block positions discovered during search and their current states. +- * We want to remember one entry for each position. +- */ +- private final Map nodeCache = Maps.newHashMap(); +- +- /* +- * For a newly created UpdateNode object, determine what type of block it is. +- */ +- private void identifyNode(final Level worldIn, final UpdateNode upd1) { +- final BlockPos pos = upd1.self; +- final BlockState oldState = worldIn.getBlockState(pos); +- upd1.currentState = oldState; +- +- // Some neighbors of redstone wire are other kinds of blocks. +- // These need to receive block updates to inform them that +- // redstone wire values have changed. +- final Block block = oldState.getBlock(); +- if (block != wire) { +- // Mark this block as not redstone wire and therefore +- // requiring updates +- upd1.type = UpdateNode.Type.OTHER; +- +- // Non-redstone blocks may propagate updates, but those updates +- // are not handled by this accelerator. Therefore, we do not +- // expand this position's neighbors. +- return; +- } +- +- // One job of BlockRedstoneWire.neighborChanged is to convert +- // redstone wires to items if the block beneath was removed. +- // With this accelerator, BlockRedstoneWire.neighborChanged +- // is only typically called for a single wire block, while +- // others are processed internally by the breadth first search +- // algorithm. To preserve this game behavior, this check must +- // be replicated here. +- if (!wire.canSurvive(null, worldIn, pos)) { +- // Pop off the redstone dust +- Block.popResource(worldIn, pos, new ItemStack(Items.REDSTONE)); // TODO +- worldIn.removeBlock(pos, false); +- +- // Mark this position as not being redstone wire +- upd1.type = UpdateNode.Type.OTHER; +- +- // Note: Sending updates to air blocks leads to an empty method. +- // Testing shows this to be faster than explicitly avoiding updates to +- // air blocks. +- return; +- } +- +- // If the above conditions fail, then this is a redstone wire block. +- upd1.type = UpdateNode.Type.REDSTONE; +- } +- +- /* +- * Given which redstone wire blocks have been visited and not visited +- * around the position currently being updated, compute the cardinal +- * direction that is "forward." +- * +- * rx is the forward direction along the West/East axis +- * rz is the forward direction along the North/South axis +- */ +- static private int computeHeading(final int rx, final int rz) { +- // rx and rz can only take on values -1, 0, and 1, so we can +- // compute a code number that allows us to use a single switch +- // to determine the heading. +- final int code = (rx + 1) + 3 * (rz + 1); +- switch (code) { +- case 0: { +- // Both rx and rz are -1 (northwest) +- // Randomly choose one to be forward. +- final int j = ThreadLocalRandom.current().nextInt(0, 1); +- return (j == 0) ? North : West; +- } +- case 1: { +- // rx=0, rz=-1 +- // Definitively North +- return North; +- } +- case 2: { +- // rx=1, rz=-1 (northeast) +- // Choose randomly between north and east +- final int j = ThreadLocalRandom.current().nextInt(0, 1); +- return (j == 0) ? North : East; +- } +- case 3: { +- // rx=-1, rz=0 +- // Definitively West +- return West; +- } +- case 4: { +- // rx=0, rz=0 +- // Heading is completely ambiguous. Choose +- // randomly among the four cardinal directions. +- return ThreadLocalRandom.current().nextInt(0, 4); +- } +- case 5: { +- // rx=1, rz=0 +- // Definitively East +- return East; +- } +- case 6: { +- // rx=-1, rz=1 (southwest) +- // Choose randomly between south and west +- final int j = ThreadLocalRandom.current().nextInt(0, 1); +- return (j == 0) ? South : West; +- } +- case 7: { +- // rx=0, rz=1 +- // Definitively South +- return South; +- } +- case 8: { +- // rx=1, rz=1 (southeast) +- // Choose randomly between south and east +- final int j = ThreadLocalRandom.current().nextInt(0, 1); +- return (j == 0) ? South : East; +- } +- } +- +- // We should never get here +- return ThreadLocalRandom.current().nextInt(0, 4); +- } +- +- // Select whether to use updateSurroundingRedstone from BlockRedstoneWire (old) +- // or this helper class (new) +- private static final boolean old_current_change = false; +- +- /* +- * Process a node whose neighboring redstone wire has experienced value changes. +- */ +- private void updateNode(final Level worldIn, final UpdateNode upd1, final int layer) { +- final BlockPos pos = upd1.self; +- +- // Mark this redstone wire as having been visited so that it can be used +- // to calculate direction of information flow. +- upd1.visited = true; +- +- // Look up the last known state. +- // Due to the way other redstone components are updated, we do not +- // have to worry about a state changing behind our backs. The rare +- // exception is handled by scheduleReentrantNeighborChanged. +- final BlockState oldState = upd1.currentState; +- +- // Ask the wire block to compute its power level from its neighbors. +- // This will also update the wire's power level and return a new +- // state if it has changed. When a wire power level is changed, +- // calculateCurrentChanges will immediately update the block state in the world +- // and return the same value here to be cached in the corresponding +- // UpdateNode object. +- BlockState newState; +- if (old_current_change) { +- newState = wire.calculateCurrentChanges(worldIn, pos, pos, oldState); +- } else { +- // Looking up block state is slow. This accelerator includes a version of +- // calculateCurrentChanges that uses cahed wire values for a +- // significant performance boost. +- newState = this.calculateCurrentChanges(worldIn, upd1); +- } +- +- // Only inform neighbors if the state has changed +- if (newState != oldState) { +- // Store the new state +- upd1.currentState = newState; +- +- // Inform neighbors of the change +- propagateChanges(worldIn, upd1, layer); +- } +- } +- +- /* +- * This identifies the neighboring positions of a new UpdateNode object, +- * determines their types, and links those to into the graph. Then based on +- * what nodes in the redstone wire graph have been visited, the neighbors +- * are reordered left-to-right relative to the direction of information flow. +- */ +- private void findNeighbors(final Level worldIn, final UpdateNode upd1) { +- final BlockPos pos = upd1.self; +- +- // Get the list of neighbor coordinates +- final BlockPos[] neighbors = computeAllNeighbors(pos); +- +- // Temporary array of neighbors in cardinal ordering +- final UpdateNode[] neighbor_nodes = new UpdateNode[24]; +- +- // Target array of neighbors sorted left-to-right +- upd1.neighbor_nodes = new UpdateNode[24]; +- +- for (int i=0; i<24; i++) { +- // Look up each neighbor in the node cache +- final BlockPos pos2 = neighbors[i]; +- UpdateNode upd2 = nodeCache.get(pos2); +- if (upd2 == null) { +- // If this is a previously unreached position, create +- // a new update node, add it to the cache, and identify what it is. +- upd2 = new UpdateNode(); +- upd2.self = pos2; +- upd2.parent = pos; +- nodeCache.put(pos2, upd2); +- identifyNode(worldIn, upd2); +- } +- +- // For non-redstone blocks, any of the 24 neighboring positions +- // should receive a block update. However, some block coordinates +- // may contain a redstone wire that does not directly connect to the +- // one being expanded. To avoid redundant calculations and confusing +- // cross-talk, those neighboring positions are not included. +- if (update_redstone[i] || upd2.type != UpdateNode.Type.REDSTONE) { +- neighbor_nodes[i] = upd2; +- } +- } +- +- // Determine the directions from which the redstone signal may have come from. This +- // checks for redstone wire at the same Y level and also Y+1 and Y-1, relative to the +- // block being expanded. +- final boolean fromWest = (neighbor_nodes[0].visited || neighbor_nodes[7].visited || neighbor_nodes[8].visited); +- final boolean fromEast = (neighbor_nodes[1].visited || neighbor_nodes[12].visited || neighbor_nodes[13].visited); +- final boolean fromNorth = (neighbor_nodes[4].visited || neighbor_nodes[17].visited || neighbor_nodes[20].visited); +- final boolean fromSouth = (neighbor_nodes[5].visited || neighbor_nodes[18].visited || neighbor_nodes[21].visited); +- +- int cx = 0, cz = 0; +- if (fromWest) cx += 1; +- if (fromEast) cx -= 1; +- if (fromNorth) cz += 1; +- if (fromSouth) cz -= 1; +- +- int heading; +- if (cx==0 && cz==0) { +- // If there is no clear direction, try to inherit the heading from ancestor nodes. +- heading = computeHeading(upd1.xbias, upd1.zbias); +- +- // Propagate that heading to descendant nodes. +- for (int i=0; i<24; i++) { +- final UpdateNode nn = neighbor_nodes[i]; +- if (nn != null) { +- nn.xbias = upd1.xbias; +- nn.zbias = upd1.zbias; +- } +- } +- } else { +- if (cx != 0 && cz != 0) { +- // If the heading is somewhat ambiguous, try to disambiguate based on +- // ancestor nodes. +- if (upd1.xbias != 0) cz = 0; +- if (upd1.zbias != 0) cx = 0; +- } +- heading = computeHeading(cx, cz); +- +- // Propagate that heading to descendant nodes. +- for (int i=0; i<24; i++) { +- final UpdateNode nn = neighbor_nodes[i]; +- if (nn != null) { +- nn.xbias = cx; +- nn.zbias = cz; +- } +- } +- } +- +- // Reorder neighboring UpdateNode objects according to the forward direction +- // determined above. +- orientNeighbors(neighbor_nodes, upd1.neighbor_nodes, heading); +- } +- +- /* +- * For any redstone wire block in layer N, inform neighbors to recompute their states +- * in layers N+1 and N+2; +- */ +- private void propagateChanges(final Level worldIn, final UpdateNode upd1, final int layer) { +- if (upd1.neighbor_nodes == null) { +- // If this node has not been expanded yet, find its neighbors +- findNeighbors(worldIn, upd1); +- } +- +- final BlockPos pos = upd1.self; +- +- // All neighbors may be scheduled for layer N+1 +- final int layer1 = layer + 1; +- +- // If the node being updated (upd1) has already been expanded, then merely +- // schedule updates to its neighbors. +- for (int i = 0; i < 24; i++) { +- final UpdateNode upd2 = upd1.neighbor_nodes[i]; +- +- // This test ensures that an UpdateNode is never scheduled to the same layer +- // more than once. Also, skip non-connecting redstone wire blocks +- if (upd2 != null && layer1 > upd2.layer) { +- upd2.layer = layer1; +- updateQueue1.add(upd2); +- +- // Keep track of which block updated this neighbor +- upd2.parent = pos; +- } +- } +- +- // Nodes above and below are scheduled ALSO for layer N+2 +- final int layer2 = layer + 2; +- +- // Repeat of the loop above, but only for the first four (above and below) neighbors +- // and for layer N+2; +- for (int i = 0; i < 4; i++) { +- final UpdateNode upd2 = upd1.neighbor_nodes[i]; +- if (upd2 != null && layer2 > upd2.layer) { +- upd2.layer = layer2; +- updateQueue2.add(upd2); +- upd2.parent = pos; +- } +- } +- } +- +- // The breadth-first search below will send block updates to blocks +- // that are not redstone wire. If one of those updates results in +- // a distant redstone wire getting an update, then this.neighborChanged +- // will get called. This would be a reentrant call, and +- // it is necessary to properly integrate those updates into the +- // on-going search through redstone wire. Thus, we make the layer +- // currently being processed visible at the object level. +- +- // The current layer being processed by the breadth-first search +- private int currentWalkLayer = 0; +- +- private void shiftQueue() { +- final List t = updateQueue0; +- t.clear(); +- updateQueue0 = updateQueue1; +- updateQueue1 = updateQueue2; +- updateQueue2 = t; +- } +- +- /* +- * Perform a breadth-first (layer by layer) traversal through redstone +- * wire blocks, propagating value changes to neighbors in an order +- * that is a function of distance from the initial call to +- * this.neighborChanged. +- */ +- private void breadthFirstWalk(final Level worldIn) { +- shiftQueue(); +- currentWalkLayer = 1; +- +- // Loop over all layers +- while (updateQueue0.size()>0 || updateQueue1.size()>0) { +- // Get the set of blocks in this layer +- final List thisLayer = updateQueue0; +- +- // Loop over all blocks in the layer. Recall that +- // this is a List, preserving the insertion order of +- // left-to-right based on direction of information flow. +- for (UpdateNode upd : thisLayer) { +- if (upd.type == UpdateNode.Type.REDSTONE) { +- // If the node is is redstone wire, +- // schedule updates to neighbors if its value +- // has changed. +- updateNode(worldIn, upd, currentWalkLayer); +- } else { +- // If this block is not redstone wire, send a block update. +- // Redstone wire blocks get state updates, but they don't +- // need block updates. Only non-redstone neighbors need updates. +- +- // World.neighborChanged is called from +- // World.notifyNeighborsOfStateChange, and +- // notifyNeighborsOfStateExcept. We don't use +- // World.notifyNeighborsOfStateChange here, since we are +- // already keeping track of all of the neighbor positions +- // that need to be updated. All on its own, handling neighbors +- // this way reduces block updates by 1/3 (24 instead of 36). +- worldIn.neighborChanged(upd.self, wire, upd.parent); +- } +- } +- +- // Move on to the next layer +- shiftQueue(); +- currentWalkLayer++; +- } +- +- currentWalkLayer = 0; +- } +- +- /* +- * Normally, when Minecraft is computing redstone wire power changes, and a wire power level +- * change sends a block update to a neighboring functional component (e.g. piston, repeater, etc.), +- * those updates are queued. Only once all redstone wire updates are complete will any component +- * action generate any further block updates to redstone wire. Instant repeater lines, for instance, +- * will process all wire updates for one redstone line, after which the pistons will zero-tick, +- * after which the next redstone line performs all of its updates. Thus, each wire is processed in its +- * own discrete wave. +- * +- * However, there are some corner cases where this pattern breaks, with a proof of concept discovered +- * by Rays Works, which works the same in vanilla. The scenario is as follows: +- * (1) A redstone wire is conducting a signal. +- * (2) Part-way through that wave of updates, a neighbor is updated that causes an update to a completely +- * separate redstone wire. +- * (3) This results in a call to BlockRedstoneWire.neighborChanged for that other wire, in the middle of +- * an already on-going propagation through the first wire. +- * +- * The vanilla code, being depth-first, would end up fully processing the second wire before going back +- * to finish processing the first one. (Although technically, vanilla has no special concept of "being +- * in the middle" of processing updates to a wire.) For the breadth-first algorithm, we give this +- * situation special handling, where the updates for the second wire are incorporated into the schedule +- * for the first wire, and then the callstack is allowed to unwind back to the on-going search loop in +- * order to continue processing both the first and second wire in the order of distance from the initial +- * trigger. +- */ +- private BlockState scheduleReentrantNeighborChanged(final Level worldIn, final BlockPos pos, final BlockState newState, final BlockPos source) { +- if (source != null) { +- // If the cause of the redstone wire update is known, we can use that to help determine +- // direction of information flow. +- UpdateNode src = nodeCache.get(source); +- if (src == null) { +- src = new UpdateNode(); +- src.self = source; +- src.parent = source; +- src.visited = true; +- identifyNode(worldIn, src); +- nodeCache.put(source, src); +- } +- } +- +- // Find or generate a node for the redstone block position receiving the update +- UpdateNode upd = nodeCache.get(pos); +- if (upd == null) { +- upd = new UpdateNode(); +- upd.self = pos; +- upd.parent = pos; +- upd.visited = true; +- identifyNode(worldIn, upd); +- nodeCache.put(pos, upd); +- } +- upd.currentState = newState; +- +- // Receiving this block update may mean something in the world changed. +- // Therefore we clear the cached block info about all neighbors of +- // the position receiving the update and then re-identify what they are. +- if (upd.neighbor_nodes != null) { +- for (int i=0; i<24; i++) { +- final UpdateNode upd2 = upd.neighbor_nodes[i]; +- if (upd2 == null) continue; +- upd2.type = UpdateNode.Type.UNKNOWN; +- upd2.currentState = null; +- identifyNode(worldIn, upd2); +- } +- } +- +- // The block at 'pos' is a redstone wire and has been updated already by calling +- // wire.calculateCurrentChanges, so we don't schedule that. However, we do need +- // to schedule its neighbors. By passing the current value of 'currentWalkLayer' to +- // propagateChanges, the neighbors of 'pos' are scheduled for layers currentWalkLayer+1 +- // and currentWalkLayer+2. +- propagateChanges(worldIn, upd, currentWalkLayer); +- +- // Return here. The call stack will unwind back to the first call to +- // updateSurroundingRedstone, whereupon the new updates just scheduled will +- // be propagated. This also facilitates elimination of superfluous and +- // redundant block updates. +- return newState; +- } +- +- /* +- * New version of pre-existing updateSurroundingRedstone, which is called from +- * wire.updateSurroundingRedstone, which is called from wire.neighborChanged and a +- * few other methods in BlockRedstoneWire. This sets off the breadth-first +- * walk through all redstone dust connected to the initial position triggered. +- */ +- public BlockState updateSurroundingRedstone(final Level worldIn, final BlockPos pos, final BlockState state, final BlockPos source) { +- // Check this block's neighbors and see if its power level needs to change +- // Use the calculateCurrentChanges method in BlockRedstoneWire since we have no +- // cached block states at this point. +- final BlockState newState = wire.calculateCurrentChanges(worldIn, pos, pos, state); +- +- // If no change, exit +- if (newState == state) { +- return state; +- } +- +- // Check to see if this update was received during an on-going breadth first search +- if (currentWalkLayer > 0 || nodeCache.size() > 0) { +- // As breadthFirstWalk progresses, it sends block updates to neighbors. Some of those +- // neighbors may affect the world so as to cause yet another redstone wire block to receive +- // an update. If that happens, we need to integrate those redstone wire updates into the +- // already on-going graph walk being performed by breadthFirstWalk. +- return scheduleReentrantNeighborChanged(worldIn, pos, newState, source); +- } +- // If there are no on-going walks through redstone wire, then start a new walk. +- +- // If the source of the block update to the redstone wire at 'pos' is known, we can use +- // that to help determine the direction of information flow. +- if (source != null) { +- final UpdateNode src = new UpdateNode(); +- src.self = source; +- src.parent = source; +- src.visited = true; +- nodeCache.put(source, src); +- identifyNode(worldIn, src); +- } +- +- // Create a node representing the block at 'pos', and then propagate updates +- // to its neighbors. As stated above, the call to wire.calculateCurrentChanges +- // already performs the update to the block at 'pos', so it is not added to the schedule. +- final UpdateNode upd = new UpdateNode(); +- upd.self = pos; +- upd.parent = source!=null ? source : pos; +- upd.currentState = newState; +- upd.type = UpdateNode.Type.REDSTONE; +- upd.visited = true; +- nodeCache.put(pos, upd); +- propagateChanges(worldIn, upd, 0); +- +- // Perform the walk over all directly reachable redstone wire blocks, propagating wire value +- // updates in a breadth first order out from the initial update received for the block at 'pos'. +- breadthFirstWalk(worldIn); +- +- // With the whole search completed, clear the list of all known blocks. +- // We do not want to keep around state information that may be changed by other code. +- // In theory, we could cache the neighbor block positions, but that is a separate +- // optimization. +- nodeCache.clear(); +- +- return newState; +- } +- +- // For any array of neighbors in an UpdateNode object, these are always +- // the indices of the four immediate neighbors at the same Y coordinate. +- private static final int[] rs_neighbors = {4, 5, 6, 7}; +- private static final int[] rs_neighbors_up = {9, 11, 13, 15}; +- private static final int[] rs_neighbors_dn = {8, 10, 12, 14}; +- +- /* +- * Updated calculateCurrentChanges that is optimized for speed and uses +- * the UpdateNode's neighbor array to find the redstone states of neighbors +- * that might power it. +- */ +- private BlockState calculateCurrentChanges(final Level worldIn, final UpdateNode upd) { +- BlockState state = upd.currentState; +- final int i = state.getValue(RedStoneWireBlock.POWER).intValue(); +- int j = 0; +- j = getMaxCurrentStrength(upd, j); +- int l = 0; +- +- wire.shouldSignal = false; +- // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, +- // and I'm not ready to try to replicate even more functionality from +- // elsewhere in Minecraft into this accelerator. So sadly, we must +- // suffer the performance hit of this very expensive call. If there +- // is consistency to what this call returns, we may be able to cache it. +- final int k = worldIn.getBestNeighborSignal(upd.self); +- wire.shouldSignal = true; +- +- // The variable 'k' holds the maximum redstone power value of any adjacent blocks. +- // If 'k' has the highest level of all neighbors, then the power level of this +- // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the +- // following loop can affect the power level of the wire. Therefore, the loop is +- // skipped if k is already 15. +- if (k < 15) { +- if (upd.neighbor_nodes == null) { +- // If this node's neighbors are not known, expand the node +- findNeighbors(worldIn, upd); +- } +- +- // These remain constant, so pull them out of the loop. +- // Regardless of which direction is forward, the UpdateNode for the +- // position directly above the node being calculated is always +- // at index 1. +- UpdateNode center_up = upd.neighbor_nodes[1]; +- boolean center_up_is_cube = center_up.currentState.isRedstoneConductor(worldIn, center_up.self); // TODO +- +- for (int m = 0; m < 4; m++) { +- // Get the neighbor array index of each of the four cardinal +- // neighbors. +- int n = rs_neighbors[m]; +- +- // Get the max redstone power level of each of the cardinal +- // neighbors +- UpdateNode neighbor = upd.neighbor_nodes[n]; +- l = getMaxCurrentStrength(neighbor, l); +- +- // Also check the positions above and below the cardinal +- // neighbors +- boolean neighbor_is_cube = neighbor.currentState.isRedstoneConductor(worldIn, neighbor.self); // TODO +- if (!neighbor_is_cube) { +- UpdateNode neighbor_down = upd.neighbor_nodes[rs_neighbors_dn[m]]; +- l = getMaxCurrentStrength(neighbor_down, l); +- } else +- if (!center_up_is_cube) { +- UpdateNode neighbor_up = upd.neighbor_nodes[rs_neighbors_up[m]]; +- l = getMaxCurrentStrength(neighbor_up, l); +- } +- } +- } +- +- // The new code sets this RedstoneWire block's power level to the highest neighbor +- // minus 1. This usually results in wire power levels dropping by 2 at a time. +- // This optimization alone has no impact on update order, only the number of updates. +- j = l - 1; +- +- // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will +- // always be in the range of 0 to 15, the following if will correct that. +- if (k > j) j = k; +- +- // egg82's amendment +- // Adding Bukkit's BlockRedstoneEvent - er.. event. +- if (i != j) { +- BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(upd.self.getX(), upd.self.getY(), upd.self.getZ()), i, j); +- worldIn.getCraftServer().getPluginManager().callEvent(event); +- j = event.getNewCurrent(); +- } +- +- if (i != j) { +- // If the power level has changed from its previous value, compute a new state +- // and set it in the world. +- // Possible optimization: Don't commit state changes to the world until they +- // need to be known by some nearby non-redstone-wire block. +- BlockPos pos = new BlockPos(upd.self.getX(), upd.self.getY(), upd.self.getZ()); +- if (wire.canSurvive(null, worldIn, pos)) { +- state = state.setValue(RedStoneWireBlock.POWER, Integer.valueOf(j)); +- worldIn.setBlock(upd.self, state, 2); +- } +- } +- +- return state; +- } +- +- /* +- * Optimized function to compute a redstone wire's power level based on cached +- * state. +- */ +- private static int getMaxCurrentStrength(final UpdateNode upd, final int strength) { +- if (upd.type != UpdateNode.Type.REDSTONE) return strength; +- final int i = upd.currentState.getValue(RedStoneWireBlock.POWER).intValue(); +- return i > strength ? i : strength; +- } +-} +diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +index ec89c162ad7de9d1647934efd02b2581b4689a6d..b9df4f319c29ee509d5939e9d06a6ebc319f508c 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -1,7 +1,5 @@ + package net.minecraft.world.level.block; + +-import com.destroystokyo.paper.PaperConfig; +-import com.destroystokyo.paper.util.RedstoneWireTurbo; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -257,121 +255,6 @@ public class RedStoneWireBlock extends Block { + return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); + } + +- // Paper start - Optimize redstone +- // The bulk of the new functionality is found in RedstoneWireTurbo.java +- RedstoneWireTurbo turbo = new RedstoneWireTurbo(this); +- +- /* +- * Modified version of pre-existing updateSurroundingRedstone, which is called from +- * this.neighborChanged and a few other methods in this class. +- * Note: Added 'source' argument so as to help determine direction of information flow +- */ +- private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, BlockPos source) { +- if (worldIn.paperConfig.useEigencraftRedstone) { +- turbo.updateSurroundingRedstone(worldIn, pos, state, source); +- return; +- } +- updatePowerStrength(worldIn, pos, state); +- } +- +- /* +- * Slightly modified method to compute redstone wire power levels from neighboring blocks. +- * Modifications cut the number of power level changes by about 45% from vanilla, and this +- * optimization synergizes well with the breadth-first search implemented in +- * RedstoneWireTurbo. +- * Note: RedstoneWireTurbo contains a faster version of this code. +- * Note: Made this public so that RedstoneWireTurbo can access it. +- */ +- public BlockState calculateCurrentChanges(Level worldIn, BlockPos pos1, BlockPos pos2, BlockState state) { +- BlockState iblockstate = state; +- int i = state.getValue(POWER); +- int j = 0; +- j = this.getPower(j, worldIn.getBlockState(pos2)); +- this.shouldSignal = false; +- int k = worldIn.getBestNeighborSignal(pos1); +- this.shouldSignal = true; +- +- if (!worldIn.paperConfig.useEigencraftRedstone) { +- // This code is totally redundant to if statements just below the loop. +- if (k > 0 && k > j - 1) { +- j = k; +- } +- } +- +- int l = 0; +- +- // The variable 'k' holds the maximum redstone power value of any adjacent blocks. +- // If 'k' has the highest level of all neighbors, then the power level of this +- // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the +- // following loop can affect the power level of the wire. Therefore, the loop is +- // skipped if k is already 15. +- if (!worldIn.paperConfig.useEigencraftRedstone || k < 15) { +- for (Direction enumfacing : Direction.Plane.HORIZONTAL) { +- BlockPos blockpos = pos1.relative(enumfacing); +- boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); +- +- if (flag) { +- l = this.getPower(l, worldIn.getBlockState(blockpos)); +- } +- +- if (worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && !worldIn.getBlockState(pos1.above()).isRedstoneConductor(worldIn, pos1)) { +- if (flag && pos1.getY() >= pos2.getY()) { +- l = this.getPower(l, worldIn.getBlockState(blockpos.above())); +- } +- } else if (!worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && flag && pos1.getY() <= pos2.getY()) { +- l = this.getPower(l, worldIn.getBlockState(blockpos.below())); +- } +- } +- } +- +- if (!worldIn.paperConfig.useEigencraftRedstone) { +- // The old code would decrement the wire value only by 1 at a time. +- if (l > j) { +- j = l - 1; +- } else if (j > 0) { +- --j; +- } else { +- j = 0; +- } +- +- if (k > j - 1) { +- j = k; +- } +- } else { +- // The new code sets this RedstoneWire block's power level to the highest neighbor +- // minus 1. This usually results in wire power levels dropping by 2 at a time. +- // This optimization alone has no impact on update order, only the number of updates. +- j = l - 1; +- +- // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will +- // always be in the range of 0 to 15, the following if will correct that. +- if (k > j) j = k; +- } +- +- if (i != j) { +- state = state.setValue(POWER, j); +- +- if (worldIn.getBlockState(pos1) == iblockstate) { +- worldIn.setBlock(pos1, state, 2); +- } +- +- // 1.16(.1?) dropped the need for blocks needing updates. +- // Whether this is necessary after all is to be seen. +-// if (!worldIn.paperConfig.useEigencraftRedstone) { +-// // The new search algorithm keeps track of blocks needing updates in its own data structures, +-// // so only add anything to blocksNeedingUpdate if we're using the vanilla update algorithm. +-// this.getBlocksNeedingUpdate().add(pos1); +-// +-// for (EnumDirection enumfacing1 : EnumDirection.values()) { +-// this.getBlocksNeedingUpdate().add(pos1.shift(enumfacing1)); +-// } +-// } +- } +- +- return state; +- } +- // Paper end +- + private void updatePowerStrength(Level world, BlockPos pos, BlockState state) { + int i = this.calculateTargetStrength(world, pos); + +@@ -441,7 +324,6 @@ public class RedStoneWireBlock extends Block { + return Math.max(i, j - 1); + } + +- private int getPower(int min, BlockState iblockdata) { return Math.max(min, getWireSignal(iblockdata)); } // Paper - Optimize redstone + private int getWireSignal(BlockState state) { + return state.is((Block) this) ? (Integer) state.getValue(RedStoneWireBlock.POWER) : 0; + } +@@ -464,7 +346,7 @@ public class RedStoneWireBlock extends Block { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock()) && !world.isClientSide) { +- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone ++ this.updatePowerStrength(world, pos, state); + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -491,7 +373,7 @@ public class RedStoneWireBlock extends Block { + world.updateNeighborsAt(pos.relative(enumdirection), this); + } + +- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone ++ this.updatePowerStrength(world, pos, state); + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -526,7 +408,7 @@ public class RedStoneWireBlock extends Block { + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { + if (!world.isClientSide) { + if (state.canSurvive(world, pos)) { +- this.updateSurroundingRedstone(world, pos, state, fromPos); // Paper - Optimize redstone ++ this.updatePowerStrength(world, pos, state); + } else { + dropResources(state, world, pos); + world.removeBlock(pos, false); diff --git a/patches/server/0079-Port-alternate-current.patch b/patches/server/0079-Port-alternate-current.patch new file mode 100644 index 0000000..d8e7b01 --- /dev/null +++ b/patches/server/0079-Port-alternate-current.patch @@ -0,0 +1,2647 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Sat, 18 Dec 2021 20:08:48 +0100 +Subject: [PATCH] Port alternate current + +Original code by SpaceWalkerRS, licensed under MIT +You can find the original code on https://github.com/SpaceWalkerRS/alternate-current + +diff --git a/src/main/java/alternate/current/interfaces/IBlock.java b/src/main/java/alternate/current/interfaces/IBlock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eb8fee04434bd4777758176e0131f51b05926376 +--- /dev/null ++++ b/src/main/java/alternate/current/interfaces/IBlock.java +@@ -0,0 +1,17 @@ ++package alternate.current.interfaces; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++ ++public interface IBlock { ++ ++ default boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return false; ++ } ++ ++ default boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return false; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/interfaces/IServerWorld.java b/src/main/java/alternate/current/interfaces/IServerWorld.java +new file mode 100644 +index 0000000000000000000000000000000000000000..404aa4baa72d928f460bca7826a04b8ee93c64fc +--- /dev/null ++++ b/src/main/java/alternate/current/interfaces/IServerWorld.java +@@ -0,0 +1,10 @@ ++package alternate.current.interfaces; ++ ++import alternate.current.redstone.WireBlock; ++import alternate.current.redstone.WorldAccess; ++ ++public interface IServerWorld { ++ ++ public WorldAccess getAccess(WireBlock wireBlock); ++ ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/Node.java b/src/main/java/alternate/current/redstone/Node.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3c139f67beaac9402a6add5b19613c4d40863f54 +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/Node.java +@@ -0,0 +1,98 @@ ++package alternate.current.redstone; ++ ++import java.util.Arrays; ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.block.state.BlockState; ++import alternate.current.redstone.WireHandler.Directions; ++ ++/** ++ * A Node represents a block in the world. It is tied to a ++ * specific wire block type so it can be identified as part of ++ * a wire network or as a neighbor of a wire network. It also ++ * holds a few other pieces of information that speed up the ++ * calculations in the WireHandler class. ++ * ++ * @author Space Walker ++ */ ++public class Node { ++ ++ // flags that encode the Node type ++ private static final int CONDUCTOR = 0b01; ++ private static final int REDSTONE = 0b10; ++ ++ public final WireBlock wireBlock; ++ public final WorldAccess world; ++ public final Node[] neighbors; ++ ++ public BlockPos pos; ++ public BlockState state; ++ public boolean invalid; ++ ++ private int flags; ++ ++ public Node(WireBlock wireBlock, WorldAccess world) { ++ this.wireBlock = wireBlock; ++ this.world = world; ++ this.neighbors = new Node[Directions.ALL.length]; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (o instanceof Node) { ++ Node node = (Node)o; ++ return world == node.world && pos.equals(node.pos); ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public int hashCode() { ++ return pos.hashCode(); ++ } ++ ++ public Node update(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ if (wireBlock.isOf(state)) { ++ throw new IllegalStateException("Cannot update a regular Node to a WireNode!"); ++ } ++ ++ if (clearNeighbors) { ++ Arrays.fill(neighbors, null); ++ } ++ ++ this.pos = pos.immutable(); ++ this.state = state; ++ this.invalid = false; ++ ++ this.flags = 0; ++ ++ if (this.world.isConductor(this.pos, this.state)) { ++ this.flags |= CONDUCTOR; ++ } ++ if (this.state.isSignalSource()) { ++ this.flags |= REDSTONE; ++ } ++ ++ return this; ++ } ++ ++ public boolean isOf(WireBlock wireBlock) { ++ return this.wireBlock == wireBlock; ++ } ++ ++ public boolean isWire() { ++ return false; ++ } ++ ++ public boolean isConductor() { ++ return (flags & CONDUCTOR) != 0; ++ } ++ ++ public boolean isRedstoneComponent() { ++ return (flags & REDSTONE) != 0; ++ } ++ ++ public WireNode asWire() { ++ throw new UnsupportedOperationException("Not a WireNode!"); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/PowerQueue.java b/src/main/java/alternate/current/redstone/PowerQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c519125dc8e5f30cef7f88dc26980e8586980c5a +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/PowerQueue.java +@@ -0,0 +1,91 @@ ++package alternate.current.redstone; ++ ++import java.util.AbstractQueue; ++import java.util.Iterator; ++import java.util.Queue; ++ ++import alternate.current.util.collection.SimpleQueue; ++ ++public class PowerQueue extends AbstractQueue { ++ ++ private final int minPower; ++ private final int maxPower; ++ private final Queue[] queues; ++ ++ private int size; ++ private int currentQueue; ++ ++ public PowerQueue(int minPower, int maxPower) { ++ this.minPower = minPower; ++ this.maxPower = maxPower; ++ this.queues = createQueues(this.maxPower - this.minPower + 1); ++ } ++ ++ private static Queue[] createQueues(int queueCount) { ++ @SuppressWarnings("unchecked") ++ Queue[] queues = new Queue[queueCount]; ++ ++ for (int index = 0; index < queueCount; index++) { ++ queues[index] = new SimpleQueue<>(); ++ } ++ ++ return queues; ++ } ++ ++ @Override ++ public boolean offer(WireNode wire) { ++ int queueIndex = wire.nextPower() - minPower; ++ queues[queueIndex].offer(wire); ++ size++; ++ ++ if (queueIndex > currentQueue) { ++ currentQueue = queueIndex; ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public WireNode poll() { ++ if (size == 0) { ++ return null; ++ } ++ ++ WireNode wire; ++ ++ do { ++ wire = queues[currentQueue].poll(); ++ } while (wire == null && currentQueue-- > 0); ++ ++ if (wire != null) { ++ size--; ++ } ++ ++ return wire; ++ } ++ ++ @Override ++ public WireNode peek() { ++ if (size == 0) { ++ return null; ++ } ++ ++ WireNode wire; ++ ++ do { ++ wire = queues[currentQueue].peek(); ++ } while (wire == null && currentQueue-- > 0); ++ ++ return wire; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/WireBlock.java b/src/main/java/alternate/current/redstone/WireBlock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..768408de7c9aff7c1842e15855dcf2209d405550 +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/WireBlock.java +@@ -0,0 +1,64 @@ ++package alternate.current.redstone; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.util.Mth; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.state.BlockState; ++ ++/** ++ * This interface should be implemented by each wire block type. ++ * While Vanilla only has one wire block type, they could add ++ * more in the future, and any mods that add more wire block ++ * types that wish to take advantage of Alternate Current's ++ * performance improvements should have those wire blocks ++ * implement this interface. ++ * ++ * @author Space Walker ++ */ ++public interface WireBlock { ++ ++ public default Block asBlock() { ++ return (Block)this; ++ } ++ ++ public default boolean isOf(BlockState state) { ++ return asBlock() == state.getBlock(); ++ } ++ ++ /** ++ * The lowest possible power level a wire can have. ++ */ ++ public int getMinPower(); ++ ++ /** ++ * The largest possible power level a wire can have. ++ */ ++ public int getMaxPower(); ++ ++ /** ++ * The drop in power level from one wire to the next. ++ */ ++ public int getPowerStep(); ++ ++ default int clampPower(int power) { ++ return Mth.clamp(power, getMinPower(), getMaxPower()); ++ } ++ ++ /** ++ * Return the power level of the given wire based on its ++ * location and block state. ++ */ ++ public int getPower(WorldAccess world, BlockPos pos, BlockState state); ++ ++ /** ++ * Return a block state that holds the given new power level. ++ */ ++ public BlockState updatePowerState(WorldAccess world, BlockPos pos, BlockState state, int power); ++ ++ /** ++ * Find the connections between the given WireNode and ++ * neighboring WireNodes. ++ */ ++ public void findWireConnections(WireNode wire, WireHandler.NodeProvider nodeProvider); ++ ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/WireConnection.java b/src/main/java/alternate/current/redstone/WireConnection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..03944c984072338093c46ea77abae1971414bf7c +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/WireConnection.java +@@ -0,0 +1,28 @@ ++package alternate.current.redstone; ++ ++/** ++ * This class represents a connection between some WireNode (the ++ * 'owner') and a neighboring WireNode. Two wires are considered ++ * to be connected if power can flow from one wire to the other ++ * (and/or vice versa). ++ * ++ * @author Space Walker ++ */ ++public class WireConnection { ++ ++ /** Position of the connected wire. */ ++ public final WireNode wire; ++ /** Cardinal direction to the connected wire. */ ++ public final int iDir; ++ /** True if the connected wire can provide power to the owner of the connection. */ ++ public final boolean in; ++ /** True if the connected wire can accept power from the owner of the connection. */ ++ public final boolean out; ++ ++ public WireConnection(WireNode wire, int iDir, boolean in, boolean out) { ++ this.wire = wire; ++ this.iDir = iDir; ++ this.in = in; ++ this.out = out; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/WireConnectionManager.java b/src/main/java/alternate/current/redstone/WireConnectionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..621cf923eb14cca047ae9219a7946e9b66f9641f +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/WireConnectionManager.java +@@ -0,0 +1,123 @@ ++package alternate.current.redstone; ++ ++import java.util.Arrays; ++import java.util.function.BiConsumer; ++ ++import alternate.current.redstone.WireHandler.Directions; ++ ++public class WireConnectionManager { ++ ++ /** ++ * The number of bits allocated to store the start indices ++ * of connections in any cardinal direction. ++ */ ++ private static final int BITS = 4; ++ private static final int MASK = (1 << BITS) - 1; ++ ++ /** The owner of these connections. */ ++ public final WireNode wire; ++ ++ /** All connections to other wires. */ ++ public WireConnection[] all; ++ ++ /** The total number of connections. */ ++ public int count; ++ /** The number of connections per cardinal direction. */ ++ private int indices; ++ ++ /** ++ * A 4 bit number that encodes which in direction(s) the owner ++ * has connections to other wires. ++ */ ++ private int flowTotal; ++ /** The direction of flow based connections to other wires. */ ++ public int flow; ++ ++ public WireConnectionManager(WireNode wire) { ++ this.wire = wire; ++ this.all = new WireConnection[Directions.HORIZONTAL.length]; ++ ++ this.count = 0; ++ this.indices = 0; ++ ++ this.flowTotal = 0; ++ this.flow = -1; ++ } ++ ++ public void set(BiConsumer setter) { ++ if (count > 0) { ++ clear(); ++ } ++ ++ for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) { ++ setIndex(iDir, count); ++ setter.accept(this::add, iDir); ++ } ++ ++ setIndex(Directions.HORIZONTAL.length, count); ++ } ++ ++ private void clear() { ++ Arrays.fill(all, null); ++ ++ count = 0; ++ indices = 0; ++ ++ flowTotal = 0; ++ flow = -1; ++ } ++ ++ private void add(WireNode wire, int iDir, boolean in, boolean out) { ++ addConnection(new WireConnection(wire, iDir, in, out)); ++ } ++ ++ private void addConnection(WireConnection connection) { ++ if (count == all.length) { ++ all = doubleSize(all); ++ } ++ ++ all[count++] = connection; ++ ++ flowTotal |= (1 << connection.iDir); ++ flow = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal]; ++ } ++ ++ /** ++ * Retrieve the start index of all connections in the given direction. ++ */ ++ public int start(int iDir) { ++ return getIndex(iDir); ++ } ++ ++ /** ++ * Retrieve the end index of all connections in the given direction. ++ */ ++ public int end(int iDir) { ++ return getIndex(iDir + 1); ++ } ++ ++ private void setIndex(int i, int index) { ++ indices |= (index & MASK) << (i * BITS); ++ } ++ ++ private int getIndex(int i) { ++ return (indices >> (i * BITS)) & MASK; ++ } ++ ++ private static WireConnection[] doubleSize(WireConnection[] array) { ++ WireConnection[] newArray = new WireConnection[array.length << 1]; ++ ++ for (int index = 0; index < array.length; index++) { ++ newArray[index] = array[index]; ++ } ++ ++ return newArray; ++ } ++ ++ @FunctionalInterface ++ public interface ConnectionConsumer { ++ ++ public void add(WireNode wire, int iDir, boolean in, boolean out); ++ ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/WireHandler.java b/src/main/java/alternate/current/redstone/WireHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e3d232bc23d9239f829f16351775b010b445462a +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/WireHandler.java +@@ -0,0 +1,1158 @@ ++package alternate.current.redstone; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Queue; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.world.level.block.state.BlockState; ++//import alternate.current.AlternateCurrentMod; ++import alternate.current.util.BlockUtil; ++//import alternate.current.util.profiler.Profiler; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++ ++/** ++ * This class handles power changes for redstone wire. The algorithm ++ * was designed with the following goals in mind: ++ *
++ * 1. Minimize the number of times a wire checks its surroundings to ++ * determine its power level. ++ *
++ * 2. Minimize the number of block and shape updates emitted. ++ *
++ * 3. Emit block and shape updates in a deterministic, non-locational ++ * order, fixing bug MC-11193. ++ * ++ *

++ * In Vanilla redstone wire is laggy because it fails on points 1 and 2. ++ * ++ *

++ * Redstone wire updates recursively and each wire calculates its power ++ * level in isolation rather than in the context of the network it is a ++ * part of. This means a wire in a grid can change its power level over ++ * half a dozen times before settling on its final value. This problem ++ * used to be worse in 1.14 and below, where a wire would only decrease ++ * its power level by 1 at a time. ++ * ++ *

++ * In addition to this, a wire emits 42 block updates and up to 22 shape ++ * updates each time it changes its power level. ++ * ++ *

++ * Of those 42 block updates, 6 are to itself, which are thus not only ++ * redundant, but a big source of lag, since those cause the wire to ++ * unnecessarily re-calculate its power level. A block only has 24 ++ * neighbors within a Manhattan distance of 2, meaning 12 of the remaining ++ * 36 block updates are duplicates and thus also redundant. ++ * ++ *

++ * Of the 22 shape updates, only 6 are strictly necessary. The other 16 ++ * are sent to blocks diagonally above and below. These are necessary ++ * if a wire changes its connections, but not when it changes its power ++ * level. ++ * ++ *

++ * Redstone wire in Vanilla also fails on point 3, though this is more of ++ * a quality-of-life issue than a lag issue. The recursive nature in which ++ * it updates, combined with the location-dependent order in which each ++ * wire updates its neighbors, makes the order in which neighbors of a ++ * wire network are updated incredibly inconsistent and seemingly random. ++ * ++ *

++ * Alternate Current fixes each of these problems as follows. ++ * ++ *

++ * 1. ++ * To make sure a wire calculates its power level as little as possible, ++ * we remove the recursive nature in which redstone wire updates in ++ * Vanilla. Instead, we build a network of connected wires, find those ++ * wires that receive redstone power from "outside" the network, and ++ * spread the power from there. This has a few advantages: ++ *
++ * - Each wire checks for power from non-wire components just once, and ++ * from nearby wires just twice. ++ *
++ * - Each wire only sets its power level in the world once. This is ++ * important, because calls to World.setBlockState are even more ++ * expensive than calls to World.getBlockState. ++ * ++ *

++ * 2. ++ * There are 2 obvious ways in which we can reduce the number of block ++ * and shape updates. ++ *
++ * - Get rid of the 18 redundant block updates and 16 redundant shape ++ * updates, so each wire only emits 24 block updates and 6 shape updates ++ * whenever it changes its power level. ++ *
++ * - Only emit block updates and shape updates once a wire reaches its ++ * final power level, rather than at each intermediary stage. ++ *
++ * For an individual wire, these two optimizations are the best you can ++ * do, but for an entire grid, you can do better! ++ * ++ *

++ * Since we calculate the power of the entire network, sending block and ++ * shape updates to the wires in it is redundant. Removing those updates ++ * can reduce the number of block and shape updates by up to 20%. ++ * ++ *

++ * 3. ++ * To make the order of block updates to neighbors of a network ++ * deterministic, the first thing we must do is to replace the location- ++ * dependent order in which a wire updates its neighbors. Instead, we ++ * base it on the direction of power flow. This part of the algorithm ++ * was heavily inspired by theosib's 'RedstoneWireTurbo', which you can ++ * read more about in theosib's comment on Mojira ++ * here ++ * or by checking out its implementation in carpet mod ++ * here. ++ * ++ *

++ * The idea is to determine the direction of power flow through a wire ++ * based on the power it receives from neighboring wires. For example, if ++ * the only power a wire receives is from a neighboring wire to its west, ++ * it can be said that the direction of power flow through the wire is east. ++ * ++ *

++ * We make the order of block updates to neighbors of a wire depend on what ++ * is determined to be the direction of power flow. This not only removes ++ * locationality entirely, it even removes directionality in a large ++ * number of cases. Unlike in 'RedstoneWireTurbo', however, I have decided ++ * to keep a directional element in ambiguous cases, rather than to ++ * introduce randomness, though this is trivial to change. ++ * ++ *

++ * While this change fixes the block update order of individual wires, ++ * we must still address the overall block update order of a network. This ++ * turns out to be a simple fix, because of a change we made earlier: we ++ * search through the network for wires that receive power from outside it, ++ * and spread the power from there. If we make each wire transmit its power ++ * to neighboring wires in an order dependent on the direction of power ++ * flow, we end up with a non-locational and largely non-directional wire ++ * update order. ++ * ++ * @author Space Walker ++ */ ++public class WireHandler { ++ ++ public static class Directions { ++ ++ public static final Direction[] ALL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.DOWN, Direction.UP }; ++ public static final Direction[] HORIZONTAL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH }; ++ ++ // Indices for the arrays above. ++ // The cardinal directions are ordered clockwise. This allows ++ // for conversion between relative and absolute directions ++ // ('left' 'right' vs 'east' 'west') with simple arithmetic: ++ // If some Direction index 'iDir' is considered 'forward', then ++ // '(iDir + 1) & 0b11' is 'right', '(iDir + 2) & 0b11' is 'backward', etc. ++ public static final int WEST = 0; ++ public static final int NORTH = 1; ++ public static final int EAST = 2; ++ public static final int SOUTH = 3; ++ public static final int DOWN = 4; ++ public static final int UP = 5; ++ ++ public static int iOpposite(int iDir) { ++ return iDir ^ (0b10 >>> (iDir >>> 2)); ++ } ++ ++ public static final int[][] EXCEPT = { ++ { NORTH, EAST , SOUTH, DOWN , UP }, ++ { WEST , EAST , SOUTH, DOWN , UP }, ++ { WEST , NORTH, SOUTH, DOWN , UP }, ++ { WEST , NORTH, EAST , DOWN , UP }, ++ { WEST , NORTH, EAST , SOUTH, UP }, ++ { WEST , NORTH, EAST , SOUTH, DOWN } ++ }; ++ } ++ ++ /** ++ * This conversion table takes in information about incoming flow, and ++ * outputs the determined outgoing flow. ++ * ++ *

++ * The input is a 4 bit number that encodes the incoming flow. Each bit ++ * represents a cardinal direction, and when it is 'on', there is flow ++ * in that direction. ++ * ++ *

++ * The output is a single Direction index, or -1 for ambiguous cases. ++ * ++ *

++ * The outgoing flow is determined as follows: ++ * ++ *

++ * If there is just 1 direction of incoming flow, that direction will ++ * be the direction of outgoing flow. ++ * ++ *

++ * If there are 2 directions of incoming flow, and these directions are ++ * not each other's opposites, the direction that is 'more clockwise' ++ * will be the direction of outgoing flow. More precisely, the ++ * direction that is 1 clockwise turn from the other is picked. ++ * ++ *

++ * If there are 3 directions of incoming flow, the two opposing ++ * directions cancel each other out, and the remaining direction will ++ * be the direction of outgoing flow. ++ * ++ *

++ * In all other cases, the flow is completely ambiguous. ++ */ ++ public static final int[] FLOW_IN_TO_FLOW_OUT = { ++ -1, // 0b0000: - -> x ++ 0 , // 0b0001: west -> west ++ 1 , // 0b0010: north -> north ++ 1 , // 0b0011: west/north -> north ++ 2 , // 0b0100: east -> east ++ -1, // 0b0101: west/east -> x ++ 2 , // 0b0110: north/east -> east ++ 1 , // 0b0111: west/north/east -> north ++ 3 , // 0b1000: south -> south ++ 0 , // 0b1001: west/south -> west ++ -1, // 0b1010: north/south -> x ++ 0 , // 0b1011: west/north/south -> west ++ 3 , // 0b1100: east/south -> south ++ 3 , // 0b1101: west/east/south -> south ++ 2 , // 0b1110: north/east/south -> east ++ -1, // 0b1111: west/north/east/south -> x ++ }; ++ /** ++ * Update order of cardinal directions. Given that the index is ++ * to be considered the direction that is 'forward', the resulting ++ * update order is { front, back, right, left }. ++ */ ++ private static final int[][] CARDINAL_UPDATE_ORDERS = { ++ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ { Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.WEST }, ++ { Directions.NORTH, Directions.SOUTH, Directions.WEST , Directions.EAST }, ++ { Directions.SOUTH, Directions.WEST , Directions.EAST , Directions.NORTH } ++ }; ++ /** ++ * The default update order of all directions. It is equivalent to ++ * the order of shape updates in vanilla Minecraft. ++ */ ++ private static final int[] DEFAULT_FULL_UPDATE_ORDER = { ++ Directions.WEST, ++ Directions.EAST, ++ Directions.NORTH, ++ Directions.SOUTH, ++ Directions.DOWN, ++ Directions.UP ++ }; ++ ++ /* ++ * While these fields are not strictly necessary, I opted to add ++ * them with "future proofing" in mind, and to avoid hard-coding ++ * certain constants. ++ * ++ * If Vanilla will ever multi-thread the ticking of dimensions, ++ * there should be only one WireHandler per dimension, in case ++ * redstone updates in both dimensions at the same time. There are ++ * already mods that add multi-threading as well. ++ * ++ * If Vanilla ever adds new redstone wire types that cannot interact ++ * with each other, there should be one WireHandler for each wire ++ * type, in case two networks of different types update each other. ++ */ ++ private final WireBlock wireBlock; ++ private final WorldAccess world; ++ private final int minPower; ++ private final int maxPower; ++ private final int powerStep; ++ ++ /** All the wires in the network. */ ++ private final List network; ++ /** Map of wires and neighboring blocks. */ ++ private final Long2ObjectMap nodes; ++ /** All the power changes that need to happen. */ ++ private final Queue powerChanges; ++ ++ private int rootCount; ++ // Rather than creating new nodes every time a network is updated ++ // we keep a cache of nodes that can be re-used. ++ private Node[] nodeCache; ++ private int nodeCount; ++ ++ private boolean updatingPower; ++ ++ public WireHandler(WireBlock wireBlock, WorldAccess world) { ++ this.wireBlock = wireBlock; ++ this.world = world; ++ this.minPower = this.wireBlock.getMinPower(); ++ this.maxPower = this.wireBlock.getMaxPower(); ++ this.powerStep = this.wireBlock.getPowerStep(); ++ ++ this.network = new ArrayList<>(); ++ this.nodes = new Long2ObjectOpenHashMap<>(); ++ this.powerChanges = new PowerQueue(this.minPower, this.maxPower); ++ ++ this.nodeCache = new Node[16]; ++ this.fillNodeCache(0, 16); ++ } ++ ++ private Node getOrAddNode(BlockPos pos) { ++ return nodes.compute(pos.asLong(), (key, node) -> { ++ if (node == null) { ++ // If there is not yet a node at this position, ++ // retrieve and update one from the cache. ++ return getNextNode(pos); ++ } ++ if (node.invalid) { ++ return revalidateNode(node); ++ } ++ ++ return node; ++ }); ++ } ++ ++ /** ++ * Retrieve the neighbor of a node in the given direction and ++ * create a link between the two nodes. ++ */ ++ private Node getNeighbor(Node node, int iDir) { ++ Node neighbor = node.neighbors[iDir]; ++ ++ if (neighbor == null || neighbor.invalid) { ++ Direction dir = Directions.ALL[iDir]; ++ BlockPos pos = node.pos.relative(dir); ++ ++ Node oldNeighbor = neighbor; ++ neighbor = getOrAddNode(pos); ++ ++ if (neighbor != oldNeighbor) { ++ int iOpp = Directions.iOpposite(iDir); ++ ++ node.neighbors[iDir] = neighbor; ++ neighbor.neighbors[iOpp] = node; ++ } ++ } ++ ++ return neighbor; ++ } ++ ++ private Node removeNode(BlockPos pos) { ++ return nodes.remove(pos.asLong()); ++ } ++ ++ private Node revalidateNode(Node node) { ++ node.invalid = false; ++ ++ if (node.isWire()) { ++ WireNode wire = node.asWire(); ++ ++ wire.prepared = false; ++ wire.inNetwork = false; ++ } else { ++ BlockPos pos = node.pos; ++ BlockState state = world.getBlockState(pos); ++ ++ node.update(pos, state, false); ++ } ++ ++ return node; ++ } ++ ++ /** ++ * Check the BlockState that occupies the given position. If it is ++ * a wire, then create a new WireNode. Otherwise, grab the next ++ * Node from the cache and update it. ++ */ ++ private Node getNextNode(BlockPos pos) { ++ BlockState state = world.getBlockState(pos); ++ ++ if (wireBlock.isOf(state)) { ++ return new WireNode(wireBlock, world, pos, state); ++ } ++ ++ return getNextNode().update(pos, state, true); ++ } ++ ++ /** ++ * Grab the first unused Node from the cache. If all of the cache ++ * is already in use, increase it in size first. ++ */ ++ private Node getNextNode() { ++ if (nodeCount == nodeCache.length) { ++ increaseNodeCache(); ++ } ++ ++ return nodeCache[nodeCount++]; ++ } ++ ++ private void increaseNodeCache() { ++ Node[] oldCache = nodeCache; ++ nodeCache = new Node[oldCache.length << 1]; ++ ++ for (int index = 0; index < oldCache.length; index++) { ++ nodeCache[index] = oldCache[index]; ++ } ++ ++ fillNodeCache(oldCache.length, nodeCache.length); ++ } ++ ++ private void fillNodeCache(int start, int end) { ++ for (int index = start; index < end; index++) { ++ nodeCache[index] = new Node(wireBlock, world); ++ } ++ } ++ ++ /** ++ * This method is called whenever a redstone wire receives a block ++ * update. ++ */ ++ public void onWireUpdated(BlockPos pos) { ++ invalidateNodes(); ++ findRoots(pos, true); ++ tryUpdatePower(); ++ } ++ ++ /** ++ * This method is called whenever a redstone wire is placed. ++ */ ++ public void onWireAdded(BlockPos pos) { ++ invalidateNodes(); ++ findRoots(pos, false); ++ tryUpdatePower(); ++ } ++ ++ /** ++ * This method is called whenever a redstone wire is broken. ++ */ ++ public void onWireRemoved(BlockPos pos) { ++ Node node = removeNode(pos); ++ WireNode wire; ++ ++ if (node == null || !node.isWire()) { ++ wire = new WireNode(wireBlock, world, pos, wireBlock.asBlock().defaultBlockState()); ++ } else { ++ wire = node.asWire(); ++ ++ // If this field is set to 'true', the removal of this ++ // wire was part of already ongoing power changes, so ++ // we can exit early here. ++ if (updatingPower && wire.shouldBreak) { ++ return; ++ } ++ } ++ ++ wire.invalid = true; ++ wire.removed = true; ++ ++ invalidateNodes(); ++ tryAddRoot(wire); ++ tryUpdatePower(); ++ } ++ ++ /** ++ * The nodes map is a snapshot of the state of the world. It ++ * becomes invalid when power changes are carried out, since ++ * the block and shape updates can lead to block changes. If ++ * these block changes cause the network to be updated again ++ * every node must be invalidated, and revalidated before it ++ * is used again. This ensures the power calculations of the ++ * network are accurate. ++ */ ++ private void invalidateNodes() { ++ if (updatingPower && !nodes.isEmpty()) { ++ Iterator> it = Long2ObjectMaps.fastIterator(nodes); ++ ++ while (it.hasNext()) { ++ Entry entry = it.next(); ++ Node node = entry.getValue(); ++ ++ node.invalid = true; ++ } ++ } ++ } ++ ++ /** ++ * Look for wires at and around the given position that are ++ * in an invalid state and require power changes. These wires ++ * are called 'roots' because it is only when these wires ++ * change power level that neighboring wires must adjust as ++ * well. ++ * ++ *

++ * While it is strictly only necessary to check the wire at ++ * the given position, if that wire is part of a network, it ++ * is beneficial to check its surroundings for other wires ++ * that require power changes. This is because a network can ++ * receive power at multiple points. Consider the following ++ * setup: ++ * ++ *

++ * (top-down view, W = wire, L = lever, _ = air/other) ++ *
{@code _ _ W _ _ } ++ *
{@code _ W W W _ } ++ *
{@code W W L W W } ++ *
{@code _ W W W _ } ++ *
{@code _ _ W _ _ } ++ * ++ *

++ * The lever powers four wires in the network at once. If this ++ * is identified correctly, the entire network can (un)power ++ * at once. While it is not practical to cover every possible ++ * situation where a network is (un)powered from multiple ++ * points at once, checking for common cases like the one ++ * described above is relatively straight-forward. ++ * ++ *

++ * While these extra checks can provide significant performance ++ * gains in some cases, in the majority of cases they will have ++ * little to no effect, but do require extra code modifications ++ * to all redstone power emitters. Removing these optimizations ++ * would limit code modifications to the RedstoneWireBlock and ++ * ServerWorld classes while leaving the performance mostly ++ * intact. ++ */ ++ private void findRoots(BlockPos pos, boolean checkNeighbors) { ++ Node node = getOrAddNode(pos); ++ ++ if (!node.isWire()) { ++ return; // we should never get here ++ } ++ ++ WireNode wire = node.asWire(); ++ tryAddRoot(wire); ++ ++ // If the wire at the given position is not in an invalid ++ // state or is not part of a larger network, we can exit ++ // early. ++ if (!checkNeighbors || !wire.inNetwork || wire.connections.count == 0) { ++ return; ++ } ++ ++ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (neighbor.isConductor()) { ++ // Redstone components can power multiple wires through ++ // solid blocks. ++ findRedstoneAround(neighbor, Directions.iOpposite(iDir)); ++ } else if (world.emitsWeakPowerTo(neighbor.pos, neighbor.state, Directions.ALL[iDir])) { ++ // Redstone components can also power multiple wires ++ // directly. ++ findRootsAroundRedstone(neighbor, Directions.iOpposite(iDir)); ++ } ++ } ++ } ++ ++ /** ++ * Find redstone components around the given node that can ++ * strongly power that node, and then search for wires that ++ * require power changes around those redstone components. ++ */ ++ private void findRedstoneAround(Node node, int except) { ++ for (int iDir : Directions.EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (world.emitsStrongPowerTo(neighbor.pos, neighbor.state, Directions.ALL[iDir])) { ++ findRootsAroundRedstone(neighbor, iDir); ++ } ++ } ++ } ++ ++ /** ++ * Find wires around the given redstone component that require ++ * power changes. ++ */ ++ private void findRootsAroundRedstone(Node node, int except) { ++ for (int iDir : Directions.EXCEPT[except]) { ++ // Directions are backwards in Minecraft, so we must check ++ // for power emitted in the opposite direction that we are ++ // interested in. ++ int iOpp = Directions.iOpposite(iDir); ++ Direction opp = Directions.ALL[iOpp]; ++ ++ boolean weak = world.emitsWeakPowerTo(node.pos, node.state, opp); ++ boolean strong = world.emitsStrongPowerTo(node.pos, node.state, opp); ++ ++ // If the redstone component does not emit any power in ++ // this direction, move on to the next direction. ++ if (!weak && !strong) { ++ continue; ++ } ++ ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (weak && neighbor.isWire()) { ++ tryAddRoot(neighbor.asWire()); ++ } else if (strong && neighbor.isConductor()) { ++ findRootsAround(neighbor, iOpp); ++ } ++ } ++ } ++ ++ /** ++ * Look for wires around the given node that require power ++ * changes. ++ */ ++ private void findRootsAround(Node node, int except) { ++ for (int iDir : Directions.EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isWire()) { ++ tryAddRoot(neighbor.asWire()); ++ } ++ } ++ } ++ ++ /** ++ * Check if the given wire is in an illegal state and needs ++ * power changes. ++ */ ++ private void tryAddRoot(WireNode wire) { ++ // We only want need to check each wire once ++ if (wire.prepared) { ++ return; ++ } ++ ++ prepareWire(wire); ++ findPower(wire, false); ++ ++ if (needsPowerChange(wire)) { ++ network.add(wire); ++ rootCount++; ++ ++ if (wire.connections.flow >= 0) { ++ wire.flowOut = wire.connections.flow; ++ } ++ ++ wire.inNetwork = true; ++ } ++ } ++ ++ /** ++ * Before a wire can be added to the network, it must be ++ * properly prepared. This method ++ *
++ * - checks if this wire should break. Rather than break ++ * the wire right away, its effects are integrated into ++ * the power calculations. ++ *
++ * - determines the 'external power' this wire receives ++ * (power from non-wire components). ++ *
++ * - finds connections this wire has to neighboring wires. ++ */ ++ private void prepareWire(WireNode wire) { ++ // Each wire only needs to be prepared once. ++ if (wire.prepared) { ++ return; ++ } ++ ++ wire.prepared = true; ++ wire.inNetwork = false; ++ ++ if (!wire.removed && !wire.shouldBreak && world.shouldBreak(wire.pos, wire.state)) { ++ wire.shouldBreak = true; ++ } ++ ++ wire.virtualPower = wire.externalPower = (wire.removed || wire.shouldBreak) ? minPower : getExternalPower(wire); ++ wireBlock.findWireConnections(wire, this::getNeighbor); ++ } ++ ++ private int getExternalPower(WireNode wire) { ++ int power = minPower; ++ ++ for (int iDir = 0; iDir < Directions.ALL.length; iDir++) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (neighbor.isWire()) { ++ continue; ++ } ++ ++ if (neighbor.isConductor()) { ++ power = Math.max(power, getStrongPowerTo(neighbor, Directions.iOpposite(iDir))); ++ } ++ if (neighbor.isRedstoneComponent()) { ++ power = Math.max(power, world.getWeakPowerFrom(neighbor.pos, neighbor.state, Directions.ALL[iDir])); ++ } ++ ++ if (power >= maxPower) { ++ return maxPower; ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Determine the strong power the given node receives from ++ * neighboring redstone components. ++ */ ++ private int getStrongPowerTo(Node node, int except) { ++ int power = minPower; ++ ++ for (int iDir : Directions.EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isRedstoneComponent()) { ++ power = Math.max(power, world.getStrongPowerFrom(neighbor.pos, neighbor.state, Directions.ALL[iDir])); ++ ++ if (power >= maxPower) { ++ return maxPower; ++ } ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Determine the power level the given wire receives from the ++ * blocks around it. Power from non-wire components has ++ * already been determined, so only power received from other ++ * wires needs to be checked. There are a few exceptions: ++ *
++ * - If the wire is removed or going to break, its power level ++ * should always be the minimum value. This is because it ++ * (effectively) no longer exists, so cannot provide any ++ * power to neighboring wires. ++ *
++ * - Power received from neighboring wires will never exceed ++ * {@code maxPower - powerStep}, so if the external power ++ * is already larger than or equal to that, there is no need ++ * to check for power from neighboring wires. ++ */ ++ private void findPower(WireNode wire, boolean ignoreNetwork) { ++ if (wire.removed || wire.shouldBreak || wire.externalPower >= (maxPower - powerStep)) { ++ return; ++ } ++ ++ // The virtual power is reset to the external power, so ++ // the flow information must be reset as well. ++ wire.virtualPower = wire.externalPower; ++ wire.flowIn = 0; ++ ++ findWirePower(wire, ignoreNetwork); ++ } ++ ++ /** ++ * Determine the power level the given wire receives from ++ * connected wires. ++ */ ++ private void findWirePower(WireNode wire, boolean ignoreNetwork) { ++ for (int c = 0; c < wire.connections.count; c++) { ++ WireConnection connection = wire.connections.all[c]; ++ ++ if (!connection.in) { ++ continue; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (!ignoreNetwork || !neighbor.inNetwork) { ++ int power = Math.max(minPower, neighbor.virtualPower - powerStep); ++ int iOpp = Directions.iOpposite(connection.iDir); ++ ++ wire.offerPower(power, iOpp); ++ } ++ } ++ } ++ ++ private boolean needsPowerChange(WireNode wire) { ++ return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower; ++ } ++ ++ private void tryUpdatePower() { ++ if (rootCount > 0 ) { ++ updatePower(); ++ } ++ if (!updatingPower) { ++ nodeCount = 0; ++ nodes.clear(); ++ } ++ } ++ ++ /** ++ * Propagate power changes through the network and notify ++ * neighboring blocks of these changes. ++ * ++ *

++ * Power changes are done in the following 3 steps. ++ * ++ *

++ * 1. Build up the network ++ *
++ * Collect all the wires around the roots that need to change ++ * their power levels. ++ * ++ *

++ * 2. Find powered wires ++ *
++ * Find those wires in the network that receive redstone power ++ * from outside the network. This can come in 2 forms: ++ *
++ * - Power from non-wire components (repeaters, torches, etc.). ++ *
++ * - Power from wires that are not in the network. ++ *
++ * These powered wires will then queue their power changes. ++ * ++ *

++ * 3. Let power flow ++ *
++ * Work through the queue of power changes. After each wire's ++ * power change, emit shape and block updates to neighboring ++ * blocks, then queue power changes for connected wires. ++ */ ++ private void updatePower() { ++ // The profiler keeps track of how long various parts of the ++ // algorithm take. It is only here for debugging purposes, ++ // and is commented out in production. ++// Profiler profiler = AlternateCurrentMod.createProfiler(); ++// profiler.start(); ++ ++ // Build a network of wires that need power changes. This ++ // includes the roots as well as any wires that will be ++ // affected by power changes to those roots. ++// profiler.push("build network"); ++ buildNetwork(); ++ ++ // Find those wires in the network that receive redstone power ++ // from outside it. Remember that the power changes for those ++ // wires are already queued here! ++// profiler.swap("find powered wires"); ++ findPoweredWires(); ++ ++ // Once the powered wires have been found, the network is ++ // no longer needed. In fact, it should be cleared before ++ // block and shape updates are emitted, in case a different ++ // network is updated that needs power changes. ++// profiler.swap("clear " + rootCount + " roots and network of " + network.size()); ++ rootCount = 0; ++ network.clear(); ++ ++ // Carry out the power changes and emit shape and block updates. ++// profiler.swap("let power flow"); ++ try { ++ letPowerFlow(); ++ } catch (Throwable t) { ++ // If anything goes wrong while carrying out power changes, ++ // this value must be reset to 'false', or the wire handler ++ // will be locked out of carrying out power changes until ++ // the world is reloaded. ++ updatingPower = false; ++ ++ throw t; ++ } finally { ++// profiler.pop(); ++// profiler.end(); ++ } ++ } ++ ++ /** ++ * Build up a network of wires that need power changes. This ++ * includes the roots that were already added and any wires ++ * powered by those roots that will need power changes as a ++ * result of power changes to the roots. ++ */ ++ private void buildNetwork() { ++ for (int index = 0; index < network.size(); index++) { ++ WireNode wire = network.get(index); ++ ++ // The order in which wires are added to the network ++ // can influence the order in which they update their ++ // power levels. ++ for (int iDir : CARDINAL_UPDATE_ORDERS[wire.flowOut]) { ++ int start = wire.connections.start(iDir); ++ int end = wire.connections.end(iDir); ++ ++ for (int c = start; c < end; c++) { ++ WireConnection connection = wire.connections.all[c]; ++ ++ if (!connection.out) { ++ continue; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (neighbor.inNetwork) { ++ continue; ++ } ++ ++ prepareWire(neighbor); ++ findPower(neighbor, false); ++ ++ if (needsPowerChange(neighbor)) { ++ addToNetwork(neighbor, iDir); ++ } ++ } ++ } ++ } ++ } ++ ++ /** ++ * Add the given wire to the network and set its outgoing flow ++ * to some backup value. This avoids directionality in redstone ++ * grids. ++ */ ++ private void addToNetwork(WireNode wire, int backupFlow) { ++ network.add(wire); ++ ++ wire.inNetwork = true; ++ // Normally the flow is not set until the power level is ++ // updated. However, in networks with multiple power ++ // sources the update order between them depends on which ++ // was discovered first. To make this less prone to ++ // directionality, each wire node is given a 'backup' flow. ++ // For roots, this is the determined flow of their ++ // connections. For non-roots this is the direction from ++ // which they were discovered. ++ wire.flowOut = backupFlow; ++ } ++ ++ /** ++ * Find those wires in the network that receive power from ++ * outside it, either from non-wire components or from wires ++ * that are not in the network, and queue the power changes for ++ * those wires. ++ */ ++ private void findPoweredWires() { ++ for (int index = 0; index < network.size(); index++) { ++ WireNode wire = network.get(index); ++ findPower(wire, true); ++ ++ if (index < rootCount || wire.removed || wire.shouldBreak || wire.virtualPower > minPower) { ++ queuePowerChange(wire); ++ } else { ++ // Wires that do not receive any power do not queue ++ // power changes until they are offered power from a ++ // neighboring wire. To ensure that they accept any ++ // power from neighboring wires and thus queue their ++ // power changes, their virtual power is set to below ++ // the minimum. ++ wire.virtualPower--; ++ } ++ } ++ } ++ ++ /** ++ * Queue the power change for the given wire. ++ */ ++ private void queuePowerChange(WireNode wire) { ++ if (needsPowerChange(wire)) { ++ powerChanges.add(wire); ++ } else { ++ findPowerFlow(wire); ++ transmitPower(wire); ++ } ++ } ++ ++ /** ++ * Use the information of incoming power flow to determine the ++ * direction of power flow through this wire. If that flow is ++ * ambiguous, try to use a flow direction based on connections ++ * to neighboring wires. If that is also ambiguous, use the ++ * backup value that was set when the wire was prepared. ++ */ ++ private void findPowerFlow(WireNode wire) { ++ int flow = FLOW_IN_TO_FLOW_OUT[wire.flowIn]; ++ ++ if (flow >= 0) { ++ wire.flowOut = flow; ++ } else if (wire.connections.flow >= 0) { ++ wire.flowOut = wire.connections.flow; ++ } ++ } ++ ++ /** ++ * Transmit power from the given wire to neighboring wires. ++ */ ++ private void transmitPower(WireNode wire) { ++ int nextPower = Math.max(minPower, wire.virtualPower - powerStep); ++ ++ for (int iDir : CARDINAL_UPDATE_ORDERS[wire.flowOut]) { ++ int start = wire.connections.start(iDir); ++ int end = wire.connections.end(iDir); ++ ++ for (int c = start; c < end; c++) { ++ WireConnection connection = wire.connections.all[c]; ++ ++ if (!connection.out) { ++ continue; ++ } ++ ++ WireNode connectedWire = connection.wire; ++ ++ if (connectedWire.offerPower(nextPower, iDir)) { ++ queuePowerChange(connectedWire); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Carry out power changes, setting the new power of each wire ++ * in the world, notifying neighbors of the power change, then ++ * queueing power changes of neighboring wires. ++ */ ++ private void letPowerFlow() { ++ // If an instantaneous update chain causes updates to another ++ // network (or the same network in another place), new power ++ // changes will be integrated into the already ongoing power ++ // queue, so we can exit early here. ++ if (updatingPower) { ++ return; ++ } ++ ++ updatingPower = true; ++ ++ while (!powerChanges.isEmpty()) { ++ WireNode wire = powerChanges.poll(); ++ ++ if (!needsPowerChange(wire)) { ++ continue; ++ } ++ ++ findPowerFlow(wire); ++ ++ if (wire.updateState()) { ++ // If the wire was removed, shape updates have already ++ // been emitted. ++ if (!wire.shouldBreak) { ++ updateNeighborShapes(wire); ++ } ++ ++ updateNeighborBlocks(wire); ++ } ++ ++ transmitPower(wire); ++ } ++ ++ updatingPower = false; ++ } ++ ++ /** ++ * Emit shape updates around the given wire. ++ */ ++ private void updateNeighborShapes(WireNode wire) { ++ BlockPos wirePos = wire.pos; ++ BlockState wireState = wire.state; ++ ++ for (Direction dir : BlockUtil.DIRECTIONS) { ++ updateNeighborShape(wirePos.relative(dir), dir.getOpposite(), wirePos, wireState); ++ } ++ } ++ ++ private void updateNeighborShape(BlockPos pos, Direction fromDir, BlockPos fromPos, BlockState fromState) { ++ BlockState state = world.getBlockState(pos); ++ ++ // Shape updates to redstone wire are very expensive, ++ // and should never happen as a result of power changes ++ // anyway. ++ if (!state.isAir() && !wireBlock.isOf(state)) { ++ world.updateNeighborShape(pos, state, fromDir, fromPos, fromState); ++ } ++ } ++ ++ /** ++ * Emit block updates around the given wire. The order in which neighbors ++ * are updated is determined as follows: ++ *
++ * 1. The direction of power flow through the wire is to be considered ++ * 'forward'. The order in which neighbors are updated depends on their ++ * 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 updated in pairs that lie on opposite sides of the wire. ++ *
++ * 4. Neighbors are updated in order of their distance from the wire. This ++ * means they are updated in 3 groups: direct neighbors are updated ++ * 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 updateNeighborBlocks(WireNode wire) { ++ int iDir = wire.flowOut; ++ ++ Direction forward = Directions.HORIZONTAL[ iDir ]; ++ Direction rightward = Directions.HORIZONTAL[(iDir + 1) & 0b11]; ++ Direction backward = Directions.HORIZONTAL[(iDir + 2) & 0b11]; ++ Direction leftward = Directions.HORIZONTAL[(iDir + 3) & 0b11]; ++ Direction downward = Direction.DOWN; ++ Direction upward = Direction.UP; ++ ++ BlockPos self = wire.pos; ++ BlockPos front = self.relative(forward); ++ BlockPos right = self.relative(rightward); ++ BlockPos back = self.relative(backward); ++ BlockPos left = self.relative(leftward); ++ BlockPos below = self.relative(downward); ++ BlockPos above = self.relative(upward); ++ ++ // direct neighbors (6) ++ updateNeighbor(front, self); ++ updateNeighbor(back, self); ++ updateNeighbor(right, self); ++ updateNeighbor(left, self); ++ updateNeighbor(below, self); ++ updateNeighbor(above, self); ++ ++ // diagonal neighbors (12) ++ updateNeighbor(front.relative(rightward), self); ++ updateNeighbor(back .relative(leftward), self); ++ updateNeighbor(front.relative(leftward), self); ++ updateNeighbor(back .relative(rightward), self); ++ updateNeighbor(front.relative(downward), self); ++ updateNeighbor(back .relative(upward), self); ++ updateNeighbor(front.relative(upward), self); ++ updateNeighbor(back .relative(downward), self); ++ updateNeighbor(right.relative(downward), self); ++ updateNeighbor(left .relative(upward), self); ++ updateNeighbor(right.relative(upward), self); ++ updateNeighbor(left .relative(downward), self); ++ ++ // far neighbors (6) ++ updateNeighbor(front.relative(forward), self); ++ updateNeighbor(back .relative(backward), self); ++ updateNeighbor(right.relative(rightward), self); ++ updateNeighbor(left .relative(leftward), self); ++ updateNeighbor(below.relative(downward), self); ++ updateNeighbor(above.relative(upward), self); ++ } ++ ++ private void updateNeighbor(BlockPos pos, BlockPos fromPos) { ++ BlockState state = world.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() && !wireBlock.isOf(state)) { ++ world.updateNeighborBlock(pos, state, fromPos, wireBlock.asBlock()); ++ } ++ } ++ ++ @FunctionalInterface ++ public interface NodeProvider { ++ ++ public Node getNeighbor(Node node, int iDir); ++ ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/WireNode.java b/src/main/java/alternate/current/redstone/WireNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..15db3329181a2677478e1841a7746be058e67285 +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/WireNode.java +@@ -0,0 +1,103 @@ ++package alternate.current.redstone; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.block.state.BlockState; ++ ++/** ++ * A WireNode is a Node that represents a redstone wire in the world. ++ * It stores all the information about the redstone wire that the ++ * WireHandler needs to calculate power changes. ++ * ++ * @author Space Walker ++ */ ++public class WireNode extends Node { ++ ++ public final WireConnectionManager connections; ++ ++ /** The power level this wire currently holds in the world. */ ++ public int currentPower; ++ /** ++ * While calculating power changes for a network, this field ++ * is used to keep track of the power level this wire should ++ * have. ++ */ ++ public int virtualPower; ++ /** The power level received from non-wire components. */ ++ public int externalPower; ++ /** ++ * A 4-bit number that keeps track of the power flow of the ++ * wires that give this wire its power level. ++ */ ++ public int flowIn; ++ /** The direction of power flow, based on the incoming flow. */ ++ public int flowOut; ++ public boolean removed; ++ public boolean shouldBreak; ++ public boolean prepared; ++ public boolean inNetwork; ++ ++ public WireNode(WireBlock wireBlock, WorldAccess world, BlockPos pos, BlockState state) { ++ super(wireBlock, world); ++ ++ this.pos = pos.immutable(); ++ this.state = state; ++ ++ this.connections = new WireConnectionManager(this); ++ ++ this.virtualPower = this.currentPower = this.wireBlock.getPower(this.world, this.pos, this.state); ++ } ++ ++ @Override ++ public Node update(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ throw new UnsupportedOperationException("Cannot update a WireNode!"); ++ } ++ ++ @Override ++ public boolean isWire() { ++ return true; ++ } ++ ++ @Override ++ public WireNode asWire() { ++ return this; ++ } ++ ++ public int nextPower() { ++ return wireBlock.clampPower(virtualPower); ++ } ++ ++ public boolean offerPower(int power, int iDir) { ++ if (removed || shouldBreak) { ++ return false; ++ } ++ if (power == virtualPower) { ++ flowIn |= (1 << iDir); ++ return false; ++ } ++ if (power > virtualPower) { ++ virtualPower = power; ++ flowIn = (1 << iDir); ++ ++ return true; ++ } ++ ++ return false; ++ } ++ ++ public boolean updateState() { ++ if (removed) { ++ return true; ++ } ++ ++ state = world.getBlockState(pos); ++ ++ if (shouldBreak) { ++ return world.breakBlock(pos, state); ++ } ++ ++ currentPower = wireBlock.clampPower(virtualPower); ++ state = wireBlock.updatePowerState(world, pos, state, currentPower); ++ ++ return world.setWireState(pos, state); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/redstone/WorldAccess.java b/src/main/java/alternate/current/redstone/WorldAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..21fade488dd3ce2c6c5b23d397137b22ad4954f1 +--- /dev/null ++++ b/src/main/java/alternate/current/redstone/WorldAccess.java +@@ -0,0 +1,140 @@ ++package alternate.current.redstone; ++ ++import alternate.current.interfaces.IBlock; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++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.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++public class WorldAccess { ++ ++ private final WireBlock wireBlock; ++ private final ServerLevel world; ++ private final WireHandler wireHandler; ++ ++ public WorldAccess(WireBlock wireBlock, ServerLevel world) { ++ this.wireBlock = wireBlock; ++ this.world = world; ++ this.wireHandler = new WireHandler(this.wireBlock, this); ++ } ++ ++ public WireHandler getWireHandler() { ++ return wireHandler; ++ } ++ ++ /** ++ * A slightly optimized version of World.getBlockState. ++ */ ++ public BlockState getBlockState(BlockPos pos) { ++ int y = pos.getY(); ++ ++ if (y < world.getMinBuildHeight() || y >= world.getMaxBuildHeight()) { ++ return Blocks.VOID_AIR.defaultBlockState(); ++ } ++ ++ int x = pos.getX(); ++ int z = pos.getZ(); ++ int index = world.getSectionIndex(y); ++ ++ ChunkAccess chunk = world.getChunk(x >> 4, z >> 4, ChunkStatus.FULL, true); ++ LevelChunkSection section = chunk.getSections()[index]; ++ ++ if (section == null) { ++ return Blocks.AIR.defaultBlockState(); ++ } ++ ++ return section.getBlockState(x & 15, y & 15, z & 15); ++ } ++ ++ /** ++ * An optimized version of World.setBlockState. Since this method is ++ * only used to update redstone wire block states, lighting checks, ++ * height map updates, and block entity updates are omitted. ++ */ ++ public boolean setWireState(BlockPos pos, BlockState state) { ++ if (!wireBlock.isOf(state)) { ++ return false; ++ } ++ ++ int y = pos.getY(); ++ ++ if (y < world.getMinBuildHeight() || y >= world.getMaxBuildHeight()) { ++ return false; ++ } ++ ++ int x = pos.getX(); ++ int z = pos.getZ(); ++ int index = world.getSectionIndex(y); ++ ++ ChunkAccess chunk = world.getChunk(x >> 4, z >> 4, ChunkStatus.FULL, true); ++ LevelChunkSection section = chunk.getSections()[index]; ++ ++ if (section == null) { ++ return false; // we should never get here ++ } ++ ++ BlockState prevState = section.setBlockState(x & 15, y & 15, z & 15, state); ++ ++ if (state == prevState) { ++ return false; ++ } ++ ++ // notify clients of the BlockState change ++ world.getChunkSource().blockChanged(pos); ++ // mark the chunk for saving ++ chunk.setUnsaved(true); ++ ++ return true; ++ } ++ ++ public boolean breakBlock(BlockPos pos, BlockState state) { ++ Block.dropResources(state, world, pos); ++ return world.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); ++ } ++ ++ public void updateNeighborShape(BlockPos pos, BlockState state, Direction fromDir, BlockPos fromPos, BlockState fromState) { ++ BlockState newState = state.updateShape(fromDir, fromState, world, pos, fromPos); ++ Block.updateOrDestroy(state, newState, world, pos, Block.UPDATE_CLIENTS); ++ } ++ ++ public void updateNeighborBlock(BlockPos pos, BlockPos fromPos, Block fromBlock) { ++ getBlockState(pos).neighborChanged(world, pos, fromBlock, fromPos, false); ++ } ++ ++ public void updateNeighborBlock(BlockPos pos, BlockState state, BlockPos fromPos, Block fromBlock) { ++ state.neighborChanged(world, pos, fromBlock, fromPos, false); ++ } ++ ++ public boolean isConductor(BlockPos pos) { ++ return getBlockState(pos).isRedstoneConductor(world, pos); ++ } ++ ++ public boolean isConductor(BlockPos pos, BlockState state) { ++ return state.isRedstoneConductor(world, pos); ++ } ++ ++ public boolean emitsWeakPowerTo(BlockPos pos, BlockState state, Direction dir) { ++ return ((IBlock)state.getBlock()).emitsWeakPowerTo(world, pos, state, dir); ++ } ++ ++ public boolean emitsStrongPowerTo(BlockPos pos, BlockState state, Direction dir) { ++ return ((IBlock)state.getBlock()).emitsStrongPowerTo(world, pos, state, dir); ++ } ++ ++ public int getWeakPowerFrom(BlockPos pos, BlockState state, Direction dir) { ++ return state.getSignal(world, pos, dir); ++ } ++ ++ public int getStrongPowerFrom(BlockPos pos, BlockState state, Direction dir) { ++ return state.getDirectSignal(world, pos, dir); ++ } ++ ++ public boolean shouldBreak(BlockPos pos, BlockState state) { ++ return !state.canSurvive(world, pos); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/util/BlockUtil.java b/src/main/java/alternate/current/util/BlockUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1fa03724ac39cb131a2261e97c98a5961d8cf7c0 +--- /dev/null ++++ b/src/main/java/alternate/current/util/BlockUtil.java +@@ -0,0 +1,14 @@ ++package alternate.current.util; ++ ++import net.minecraft.core.Direction; ++import net.minecraft.world.level.block.state.BlockBehaviour; ++ ++public abstract class BlockUtil extends BlockBehaviour { ++ ++ /** Directions in the order in which they are used for emitting shape updates. */ ++ public static final Direction[] DIRECTIONS = BlockBehaviour.UPDATE_SHAPE_ORDER; ++ ++ private BlockUtil(Properties settings) { ++ super(settings); ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/alternate/current/util/collection/SimpleQueue.java b/src/main/java/alternate/current/util/collection/SimpleQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5d58615073c2b2018a3625544e7e702cdf0db8fe +--- /dev/null ++++ b/src/main/java/alternate/current/util/collection/SimpleQueue.java +@@ -0,0 +1,105 @@ ++package alternate.current.util.collection; ++ ++import java.util.AbstractQueue; ++import java.util.Iterator; ++ ++public class SimpleQueue extends AbstractQueue { ++ ++ private static final int MINIMUM_CAPACITY = 16; ++ ++ private Object[] queue; ++ private int size; ++ private int head; ++ private int tail; ++ ++ public SimpleQueue() { ++ this(MINIMUM_CAPACITY); ++ } ++ ++ public SimpleQueue(int initialCapacity) { ++ if (initialCapacity <= 0) { ++ throw new IllegalArgumentException("The value of initialCapacity must be greater than 0!"); ++ } ++ ++ this.queue = new Object[initialCapacity]; ++ } ++ ++ @Override ++ public boolean offer(E e) { ++ if (e == null) { ++ throw new NullPointerException(); ++ } ++ ++ if (size == queue.length) { ++ resize(size << 1); ++ } ++ ++ queue[tail] = e; ++ tail = incr(tail); ++ size++; ++ ++ return true; ++ } ++ ++ @Override ++ public E poll() { ++ @SuppressWarnings("unchecked") ++ E e = (E)queue[head]; ++ ++ if (e != null) { ++ queue[head] = null; ++ head = incr(head); ++ size--; ++ ++ if (queue.length > MINIMUM_CAPACITY && size == (queue.length >> 2)) { ++ resize(size << 1); ++ } ++ } ++ ++ return e; ++ } ++ ++ @Override ++ public E peek() { ++ @SuppressWarnings("unchecked") ++ E e = (E)queue[head]; ++ return e; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++ ++ private void resize(int newSize) { ++ Object[] oldQueue = queue; ++ queue = new Object[newSize]; ++ ++ int i = 0; ++ ++ if (head < tail) { ++ for (int j = head; j < tail; ) { ++ queue[i++] = oldQueue[j++]; ++ } ++ } else { ++ for (int j = head; j < oldQueue.length; ) { ++ queue[i++] = oldQueue[j++]; ++ } ++ for (int j = 0; j < tail; ) { ++ queue[i++] = oldQueue[j++]; ++ } ++ } ++ ++ head = 0; ++ tail = size; ++ } ++ ++ private int incr(int i) { ++ return ++i < queue.length ? i : 0; ++ } ++} +\ No newline at end of file +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 6d0369bb8215eae6ea852c36597e88ebe880ff34..5717c9362bffdd5b6b3eb4ca2efd1e13ee0d1329 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -161,8 +161,14 @@ import org.bukkit.event.weather.LightningStrikeEvent; + import org.bukkit.event.world.TimeSkipEvent; + // CraftBukkit end + import it.unimi.dsi.fastutil.ints.IntArrayList; // Paper ++import alternate.current.redstone.WireBlock; // Jettpack ++import alternate.current.redstone.WorldAccess; // Jettpack ++import alternate.current.interfaces.IServerWorld; // Jettpack ++import java.util.Map; // Jettpack + +-public class ServerLevel extends Level implements WorldGenLevel { ++public class ServerLevel extends Level implements WorldGenLevel, IServerWorld { ++ ++ private final Map access = new java.util.HashMap<>(); // Jettpack + + public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0); + private static final int MIN_RAIN_DELAY_TIME = 12000; +@@ -225,6 +231,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + return convertable.dimensionType; + } + ++ // Jettpack start - port alternate-current ++ public WorldAccess getAccess(WireBlock wireBlock) { ++ return access.computeIfAbsent(wireBlock, key -> new WorldAccess(wireBlock, (ServerLevel)(Object)this)); ++ } ++ // Jettpack end ++ + // Paper start + public final boolean areChunksLoadedForMove(AABB axisalignedbb) { + // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override +diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +index 2036006b934ba1f27da606320b4c456af019a361..ccfeb3a295cc2c70544a5a8910daaf846ae15b87 100644 +--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -18,8 +18,21 @@ import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit ++import alternate.current.interfaces.IBlock; // Jettpack + +-public abstract class BasePressurePlateBlock extends Block { ++public abstract class BasePressurePlateBlock extends Block implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.UP; ++ } ++ // Jettpack end + + protected static final VoxelShape PRESSED_AABB = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 0.5D, 15.0D); + protected static final VoxelShape AABB = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 1.0D, 15.0D); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 230848eb7e78fb67d5a679f402e3057f0064f1eb..198245f3fb6f08bb0928d6d2a0e4bb3b29fba9ec 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -62,8 +62,9 @@ import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class Block extends BlockBehaviour implements ItemLike { ++public class Block extends BlockBehaviour implements ItemLike, IBlock { + + protected static final Logger LOGGER = LogManager.getLogger(); + public static final IdMapper BLOCK_STATE_REGISTRY = new IdMapper<>(); +diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +index b7f37475192bf79252482314080c9ba08e9aefdb..043b1b6b6d8e90812d3c9c61577a6dc35c125713 100644 +--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +@@ -30,8 +30,21 @@ import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.event.block.BlockRedstoneEvent; + import org.bukkit.event.entity.EntityInteractEvent; + // CraftBukkit end ++import alternate.current.interfaces.IBlock; + +-public abstract class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { ++public abstract class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return getConnectedDirection(state) == dir; ++ } ++ // Jettpack end + + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; + private static final int PRESSED_DEPTH = 1; +diff --git a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java +index 40b0380aa6fd052bf6376a15939c08e603f2f60c..d212774a4e7c578683394fb4a6c90ce5ce875711 100644 +--- a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java +@@ -23,8 +23,16 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class DaylightDetectorBlock extends BaseEntityBlock { ++public class DaylightDetectorBlock extends BaseEntityBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Jettpack end + + public static final IntegerProperty POWER = BlockStateProperties.POWER; + public static final BooleanProperty INVERTED = BlockStateProperties.INVERTED; +diff --git a/src/main/java/net/minecraft/world/level/block/DiodeBlock.java b/src/main/java/net/minecraft/world/level/block/DiodeBlock.java +index 14db3ab0eaa3b3a259366a6ca73026bf07ddfed8..204f40efa14463532066cce8d3a03fc671a797ec 100644 +--- a/src/main/java/net/minecraft/world/level/block/DiodeBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DiodeBlock.java +@@ -19,8 +19,21 @@ import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.ticks.TickPriority; + import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit ++import alternate.current.interfaces.IBlock; // Jettpack + +-public abstract class DiodeBlock extends HorizontalDirectionalBlock { ++public abstract class DiodeBlock extends HorizontalDirectionalBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.HORIZONTAL_FACING) == dir; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.HORIZONTAL_FACING) == dir; ++ } ++ // Jettpack end + + protected static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D); + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; +diff --git a/src/main/java/net/minecraft/world/level/block/LecternBlock.java b/src/main/java/net/minecraft/world/level/block/LecternBlock.java +index 4d19c5f4653831a7c02e691e0585bf45d1d0ee64..d5c16d62d1b084d409742897718eea41bd6cc911 100644 +--- a/src/main/java/net/minecraft/world/level/block/LecternBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LecternBlock.java +@@ -35,8 +35,21 @@ import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class LecternBlock extends BaseEntityBlock { ++public class LecternBlock extends BaseEntityBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.UP; ++ } ++ // Jettpack end + + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; +diff --git a/src/main/java/net/minecraft/world/level/block/LeverBlock.java b/src/main/java/net/minecraft/world/level/block/LeverBlock.java +index 1093dc8595e42a90e74e19f74965f5be07a1d6cf..fda932c58fe58c31f7520aa88d7a041823555af9 100644 +--- a/src/main/java/net/minecraft/world/level/block/LeverBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LeverBlock.java +@@ -23,8 +23,21 @@ import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock { ++public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return getConnectedDirection(state) == dir; ++ } ++ // Jettpack end + + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; + protected static final int DEPTH = 6; +diff --git a/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java b/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java +index b7605dda79c4d907f5822a0ded694b080e08dae5..50b500fa1f88d5fb02e3b0f4f6a8d3647f5faed7 100644 +--- a/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java +@@ -35,8 +35,21 @@ import net.minecraft.world.phys.Vec3; + import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.event.block.BlockRedstoneEvent; + // CraftBukkit end ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBlock { ++public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBlock, IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.FACING) == dir; ++ } ++ // Jettpack end + + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; +diff --git a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +index 4a34a08a1d46e4d3020644a51d9e30a36a18791a..07b86f906c4e8dd4612487889ef3a653c8f92d42 100644 +--- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +@@ -14,8 +14,21 @@ import net.minecraft.world.level.block.state.StateDefinition; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.BooleanProperty; + import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class ObserverBlock extends DirectionalBlock { ++public class ObserverBlock extends DirectionalBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.FACING) == dir; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.FACING) == dir; ++ } ++ // Jettpack end + + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; + +diff --git a/src/main/java/net/minecraft/world/level/block/PoweredBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredBlock.java +index 0afffc33f3be221a28c62115f493808aeffb1bd8..3cc839ca36fabf0c6cb459fb3b00b9c8c688055a 100644 +--- a/src/main/java/net/minecraft/world/level/block/PoweredBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PoweredBlock.java +@@ -5,12 +5,20 @@ import net.minecraft.core.Direction; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class PoweredBlock extends Block { ++public class PoweredBlock extends Block implements IBlock { + public PoweredBlock(BlockBehaviour.Properties settings) { + super(settings); + } + ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(net.minecraft.world.level.Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Jettpack end ++ + @Override + public boolean isSignalSource(BlockState state) { + return true; +diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +index b9df4f319c29ee509d5939e9d06a6ebc319f508c..da29b421f6a10d1e72e24199dabe5fa7df4d0fe7 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -38,7 +38,81 @@ import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit + +-public class RedStoneWireBlock extends Block { ++// JettPack start ++import alternate.current.redstone.Node; ++import alternate.current.redstone.WireBlock; ++import alternate.current.redstone.WireHandler; ++import alternate.current.redstone.WireNode; ++import alternate.current.redstone.WorldAccess; ++import alternate.current.interfaces.IServerWorld; ++import alternate.current.redstone.WireHandler.NodeProvider; ++// JettPackend ++ ++public class RedStoneWireBlock extends Block implements WireBlock { // Jettpack ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public int getMinPower() { ++ return 0; ++ } ++ ++ @Override ++ public int getMaxPower() { ++ return 15; ++ } ++ ++ @Override ++ public int getPowerStep() { ++ return 1; ++ } ++ ++ @Override ++ public int getPower(WorldAccess world, BlockPos pos, BlockState state) { ++ return state.getValue(BlockStateProperties.POWER); ++ } ++ ++ @Override ++ public BlockState updatePowerState(WorldAccess world, BlockPos pos, BlockState state, int power) { ++ return state.setValue(BlockStateProperties.POWER, power); ++ } ++ ++ @Override ++ public Block asBlock() { ++ return (Block)this; ++ } ++ ++ @Override ++ public void findWireConnections(WireNode wire, NodeProvider nodes) { ++ boolean belowIsConductor = nodes.getNeighbor(wire, WireHandler.Directions.DOWN).isConductor(); ++ boolean aboveIsConductor = nodes.getNeighbor(wire, WireHandler.Directions.UP).isConductor(); ++ ++ wire.connections.set((connections, iDir) -> { ++ Node neighbor = nodes.getNeighbor(wire, iDir); ++ ++ if (neighbor.isWire()) { ++ connections.add(neighbor.asWire(), iDir, true, true); ++ return; ++ } ++ ++ boolean sideIsConductor = neighbor.isConductor(); ++ ++ if (!sideIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, WireHandler.Directions.DOWN); ++ ++ if (node.isWire()) { ++ connections.add(node.asWire(), iDir, true, belowIsConductor); ++ } ++ } ++ if (!aboveIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, WireHandler.Directions.UP); ++ ++ if (node.isWire()) { ++ connections.add(node.asWire(), iDir, sideIsConductor, true); ++ } ++ } ++ }); ++ } ++ // Jettpack end + + public static final EnumProperty NORTH = BlockStateProperties.NORTH_REDSTONE; + public static final EnumProperty EAST = BlockStateProperties.EAST_REDSTONE; +@@ -256,6 +330,7 @@ public class RedStoneWireBlock extends Block { + } + + private void updatePowerStrength(Level world, BlockPos pos, BlockState state) { ++ if (wtf.etil.mirai.MiraiConfig.alternateCurrent) return; // JettPack - port alternate-current + int i = this.calculateTargetStrength(world, pos); + + // CraftBukkit start +@@ -346,7 +421,19 @@ public class RedStoneWireBlock extends Block { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock()) && !world.isClientSide) { +- this.updatePowerStrength(world, pos, state); ++ // Jettpack start - port alternate-current ++ if (wtf.etil.mirai.MiraiConfig.alternateCurrent) { ++ ((IServerWorld)world).getAccess(this).getWireHandler().onWireAdded(pos); ++ BlockState newState = world.getBlockState(pos); ++ ++ if (newState != state) { ++ newState.updateNeighbourShapes(world, pos, Block.UPDATE_CLIENTS); ++ newState.updateIndirectNeighbourShapes(world, pos, Block.UPDATE_CLIENTS); ++ } ++ } else { ++ this.updatePowerStrength(world, pos, state); ++ } ++ // Jettpack end + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -373,7 +460,13 @@ public class RedStoneWireBlock extends Block { + world.updateNeighborsAt(pos.relative(enumdirection), this); + } + +- this.updatePowerStrength(world, pos, state); ++ // JettPack start - port alternate-current ++ if (wtf.etil.mirai.MiraiConfig.alternateCurrent) { ++ ((IServerWorld)world).getAccess(this).getWireHandler().onWireRemoved(pos); // Jettpack ++ } else { ++ this.updatePowerStrength(world, pos, state); ++ } ++ // JettPack end + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -406,7 +499,10 @@ public class RedStoneWireBlock extends Block { + + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { +- if (!world.isClientSide) { ++ // Jettpack start - port alternate-current ++ if (wtf.etil.mirai.MiraiConfig.alternateCurrent) { ++ ((IServerWorld)world).getAccess(this).getWireHandler().onWireUpdated(pos); ++ } else { + if (state.canSurvive(world, pos)) { + this.updatePowerStrength(world, pos, state); + } else { +@@ -415,6 +511,7 @@ public class RedStoneWireBlock extends Block { + } + + } ++ // Jettpack end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +index 93a2dade36a08a17eae34181f087114eca706872..cbe51e65f3d5ea396a4fb44a084fd709d13cb8d1 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +@@ -17,8 +17,21 @@ import net.minecraft.world.level.block.state.StateDefinition; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.BooleanProperty; + import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit ++import alternate.current.interfaces.IBlock; // Jettpack + +-public class RedstoneTorchBlock extends TorchBlock { ++public class RedstoneTorchBlock extends TorchBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return dir != Direction.UP; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.DOWN; ++ } ++ // Jettpack end + + public static final BooleanProperty LIT = BlockStateProperties.LIT; + // Paper - Move the mapped list to World +diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java +index 5cf0ae04059533385a19f7b07909a67b57350c09..7d68d809eb355eaa43ac878c4300668ee1580bf4 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java +@@ -16,8 +16,18 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty; + import net.minecraft.world.level.block.state.properties.DirectionProperty; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import alternate.current.interfaces.IBlock; // Jettpack ++import net.minecraft.world.level.block.state.properties.BlockStateProperties; // Jettpack ++ ++public class RedstoneWallTorchBlock extends RedstoneTorchBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.HORIZONTAL_FACING) != dir; ++ } ++ // Jettpack end + +-public class RedstoneWallTorchBlock extends RedstoneTorchBlock { + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; + public static final BooleanProperty LIT = RedstoneTorchBlock.LIT; + +diff --git a/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java b/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java +index 33bca696c1ae0a63055eea5d2e05551458da50b4..83e45119ae820d7dcc3bd3eb095c4bedb7f70cc0 100644 +--- a/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java +@@ -41,9 +41,17 @@ import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.craftbukkit.block.CraftBlock; + import org.bukkit.event.block.BlockRedstoneEvent; + // CraftBukkit end ++import alternate.current.interfaces.IBlock; // Jettpack + + public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterloggedBlock { + ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Jettpack end ++ + public static final int ACTIVE_TICKS = 40; + public static final int COOLDOWN_TICKS = 1; + public static final Object2IntMap VIBRATION_STRENGTH_FOR_EVENT = Object2IntMaps.unmodifiable((Object2IntMap) Util.make(new Object2IntOpenHashMap(), (object2intopenhashmap) -> { +diff --git a/src/main/java/net/minecraft/world/level/block/TargetBlock.java b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +index d609c60c1650a5b7f860154e0a4f4c6d84fa63fc..1cf7b68259a0b48c260af339ac14a41d428f131d 100644 +--- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +@@ -21,8 +21,17 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.IntegerProperty; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; ++import alternate.current.interfaces.IBlock; // Jettpack ++ ++public class TargetBlock extends Block implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Jettpack end + +-public class TargetBlock extends Block { + private static final IntegerProperty OUTPUT_POWER = BlockStateProperties.POWER; + private static final int ACTIVATION_TICKS_ARROWS = 20; + private static final int ACTIVATION_TICKS_OTHER = 8; +diff --git a/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java b/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java +index 184c70cd2954f4904518c3fee2a377d9c4e81cc3..cdc1ea044d5fa13af48f9b87e5e8a10013f401f7 100644 +--- a/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java +@@ -13,8 +13,23 @@ import net.minecraft.world.level.block.entity.ChestBlockEntity; + import net.minecraft.world.level.block.entity.TrappedChestBlockEntity; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; ++import alternate.current.interfaces.IBlock; // Jettpack ++import net.minecraft.world.level.Level; // Jettpack ++ ++public class TrappedChestBlock extends ChestBlock implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.UP; ++ } ++ // Jettpack end + +-public class TrappedChestBlock extends ChestBlock { + public TrappedChestBlock(BlockBehaviour.Properties settings) { + super(settings, () -> { + return BlockEntityType.TRAPPED_CHEST; +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +index 02a3e1ced592784b9c66927c76376c7ab413367d..7070dabb09347b98570b7460da712a3b7e621bf7 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -26,8 +26,22 @@ import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit ++import alternate.current.interfaces.IBlock; // Jettpack ++import net.minecraft.world.level.block.state.properties.BlockStateProperties; // Jettpack + +-public class TripWireHookBlock extends Block { ++public class TripWireHookBlock extends Block implements IBlock { ++ ++ // Jettpack start - port alternate-current ++ @Override ++ public boolean emitsWeakPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean emitsStrongPowerTo(Level world, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(BlockStateProperties.HORIZONTAL_FACING) == dir; ++ } ++ // Jettpack end + + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; +diff --git a/src/main/java/wtf/etil/mirai/MiraiConfig.java b/src/main/java/wtf/etil/mirai/MiraiConfig.java +index 58fedd73583f5c8d1c3d3971d994156f67ae082c..b5e0254f043e2d25bcbb98885558c117e2c72c44 100644 +--- a/src/main/java/wtf/etil/mirai/MiraiConfig.java ++++ b/src/main/java/wtf/etil/mirai/MiraiConfig.java +@@ -242,4 +242,9 @@ public class MiraiConfig { + signAllowColors = getBoolean("sign.allow-colors", signAllowColors); + } + ++ public static boolean alternateCurrent = true; ++ private static void redstoneAC() { ++ alternateCurrent = getBoolean("enable-alternate-current", alternateCurrent); ++ } ++ + } +\ No newline at end of file