diff --git a/patches/unapplied/server/Add-Alternate-Current-redstone-implementation.patch b/patches/server/Add-Alternate-Current-redstone-implementation.patch similarity index 99% rename from patches/unapplied/server/Add-Alternate-Current-redstone-implementation.patch rename to patches/server/Add-Alternate-Current-redstone-implementation.patch index 5f74f89ee9..2eea2ae430 100644 --- a/patches/unapplied/server/Add-Alternate-Current-redstone-implementation.patch +++ b/patches/server/Add-Alternate-Current-redstone-implementation.patch @@ -2068,7 +2068,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { @Override - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + protected 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 + // Paper start - optimize redstone - replace call to updatePowerStrength @@ -2098,7 +2098,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } @@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { @Override - public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { if (!world.isClientSide) { + // Paper start - optimize redstone (Alternate Current) + // Alternate Current handles breaking of redstone wires in the WireHandler. diff --git a/patches/unapplied/server/Collision-optimisations.patch b/patches/server/Collision-optimisations.patch similarity index 99% rename from patches/unapplied/server/Collision-optimisations.patch rename to patches/server/Collision-optimisations.patch index 8fd6e24ead..b732ddac57 100644 --- a/patches/unapplied/server/Collision-optimisations.patch +++ b/patches/server/Collision-optimisations.patch @@ -2176,8 +2176,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - optimise collisions - private Direction(int id, int idOpposite, int idHorizontal, String name, Direction.AxisDirection direction, Direction.Axis axis, Vec3i vector) { - this.data3d = id; + private Direction( + final int id, diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -2217,7 +2217,7 @@ diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/jav index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess float f = this.getBlockSpeedFactor(); this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f)); @@ -2265,7 +2265,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (this.remainingFireTicks <= 0) { this.setRemainingFireTicks(-this.getFireImmuneTicks()); } -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Vec3 collide(Vec3 movement) { @@ -2364,8 +2364,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List collisions) { -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - float f = this.dimensions.width * 0.8F; +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + float f = this.dimensions.width() * 0.8F; AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); - return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> { @@ -3028,7 +3028,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return shape.isFullBlock(); // Paper - optimise collisions } - public boolean propagatesSkylightDown(BlockState state, BlockGetter world, BlockPos pos) { + public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {} diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -3194,7 +3194,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - optimise collisions }); } - // Paper end + // Paper end - unfuck this diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java @@ -3308,22 +3308,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start - optimise collisions + // called with the shape of a VoxelShape, so we can expect the cache to exist + final io.papermc.paper.util.collisions.CachedShapeData cache = voxelSet.getOrCreateCachedShapeData(); - -- for (int i = 0; i < bitSetDiscreteVoxelShape.ySize; i++) { -- for (int j = 0; j < bitSetDiscreteVoxelShape.xSize; j++) { -- int k = -1; ++ + final int sizeX = cache.sizeX(); + final int sizeY = cache.sizeY(); + final int sizeZ = cache.sizeZ(); - -- for (int l = 0; l <= bitSetDiscreteVoxelShape.zSize; l++) { -- if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) { -- if (coalesce) { -- if (k == -1) { -- k = l; -- } -- } else { -- callback.consume(j, i, l, j + 1, i + 1, l + 1); ++ + int indexX; + int indexY = 0; + int indexZ; @@ -3353,10 +3342,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } else { + // same notes about loop order as the above + // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize()) -+ + +- for (int i = 0; i < bitSetDiscreteVoxelShape.ySize; i++) { +- for (int j = 0; j < bitSetDiscreteVoxelShape.xSize; j++) { +- int k = -1; + // only clone when we may write to it + bitset = bitset.clone(); -+ + +- for (int l = 0; l <= bitSetDiscreteVoxelShape.zSize; l++) { +- if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) { +- if (coalesce) { +- if (k == -1) { +- k = l; +- } +- } else { +- callback.consume(j, i, l, j + 1, i + 1, l + 1); + for (int y = 0; y < sizeY; ++y, indexY += incY) { + indexX = indexY; + for (int x = 0; x < sizeX; ++x, indexX += incX) { @@ -3365,6 +3365,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + if (firstSetZ == -1) { + break; ++ } ++ ++ int lastSetZ = io.papermc.paper.util.collisions.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex); ++ if (lastSetZ == -1) { ++ lastSetZ = endIndex; } - } else if (k != -1) { - int m = j; @@ -3375,14 +3380,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i); - m++; + -+ int lastSetZ = io.papermc.paper.util.collisions.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex); -+ if (lastSetZ == -1) { -+ lastSetZ = endIndex; - } - -- while (bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) { -- for (int o = j; o <= m; o++) { -- bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1); + io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ); + + // try to merge neighbouring on the X axis @@ -3393,8 +3390,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + ++endX; + io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd); -+ } -+ + } + +- while (bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) { +- for (int o = j; o <= m; o++) { +- bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1); + // try to merge neighbouring on the Y axis + + int endY; // exclusive diff --git a/patches/unapplied/server/Detail-more-information-in-watchdog-dumps.patch b/patches/server/Detail-more-information-in-watchdog-dumps.patch similarity index 96% rename from patches/unapplied/server/Detail-more-information-in-watchdog-dumps.patch rename to patches/server/Detail-more-information-in-watchdog-dumps.patch index ad606606b5..6d593205d6 100644 --- a/patches/unapplied/server/Detail-more-information-in-watchdog-dumps.patch +++ b/patches/server/Detail-more-information-in-watchdog-dumps.patch @@ -16,14 +16,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { + // Paper start - detailed watchdog information + net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); -+ try { // Paper end - detailed watchdog information ++ try { tickablepacketlistener.tick(); -+ } finally { // Paper start - detailed watchdog information ++ } finally { + net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); + } // Paper end - detailed watchdog information - } - // Paper end - Buffer joins to world + } // Paper end - Buffer joins to world } + diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java @@ -125,7 +125,7 @@ diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/jav index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return this.onGround; } @@ -169,7 +169,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (this.noPhysics) { this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); } else { -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.level().getProfiler().pop(); } } @@ -183,7 +183,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } private boolean isStateClimbable(BlockState state) { -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public void setDeltaMovement(Vec3 velocity) { @@ -193,7 +193,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public void addDeltaMovement(Vec3 velocity) { -@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } // Paper end - Fix MC-4 if (this.position.x != x || this.position.y != y || this.position.z != z) { diff --git a/patches/unapplied/server/Fix-entity-type-tags-suggestions-in-selectors.patch b/patches/server/Fix-entity-type-tags-suggestions-in-selectors.patch similarity index 100% rename from patches/unapplied/server/Fix-entity-type-tags-suggestions-in-selectors.patch rename to patches/server/Fix-entity-type-tags-suggestions-in-selectors.patch diff --git a/patches/unapplied/server/Fix-tripwire-disarming-not-working-as-intended.patch b/patches/server/Fix-tripwire-disarming-not-working-as-intended.patch similarity index 100% rename from patches/unapplied/server/Fix-tripwire-disarming-not-working-as-intended.patch rename to patches/server/Fix-tripwire-disarming-not-working-as-intended.patch diff --git a/patches/unapplied/server/Optimise-collision-checking-in-player-move-packet-ha.patch b/patches/server/Optimise-collision-checking-in-player-move-packet-ha.patch similarity index 99% rename from patches/unapplied/server/Optimise-collision-checking-in-player-move-packet-ha.patch rename to patches/server/Optimise-collision-checking-in-player-move-packet-ha.patch index 4891c51adf..974448e717 100644 --- a/patches/unapplied/server/Optimise-collision-checking-in-player-move-packet-ha.patch +++ b/patches/server/Optimise-collision-checking-in-player-move-packet-ha.patch @@ -116,9 +116,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (this.awaitingPositionFromClient != null) { return; // ... thanks Mojang for letting move calls teleport across dimensions. @@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } } + // Paper start - Add fail move event - boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); + // Paper start - optimise out extra getCubes + boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && movedWrongly; diff --git a/patches/unapplied/server/Optimize-Hoppers.patch b/patches/server/Optimize-Hoppers.patch similarity index 60% rename from patches/unapplied/server/Optimize-Hoppers.patch rename to patches/server/Optimize-Hoppers.patch index 23768f1a4a..ead8a91819 100644 --- a/patches/unapplied/server/Optimize-Hoppers.patch +++ b/patches/server/Optimize-Hoppers.patch @@ -60,12 +60,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers this.profiler.push(() -> { - return worldserver + " " + worldserver.dimension().location(); + String s = String.valueOf(worldserver); diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -0,0 +0,0 @@ public final class ItemStack { +@@ -0,0 +0,0 @@ public final class ItemStack implements DataComponentHolder { } public ItemStack copy() { @@ -79,11 +79,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - Perf: Optimize Hoppers return ItemStack.EMPTY; } else { -- ItemStack itemstack = new ItemStack(this.getItem(), this.count); -+ ItemStack itemstack = new ItemStack(originalItem ? this.item : this.getItem(), this.count); // Paper - Perf: Optimize Hoppers - - itemstack.setPopTime(this.getPopTime()); - if (this.tag != null) { + ItemStack itemstack = new ItemStack(this.getItem(), this.count, this.components.copy()); diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java @@ -157,10 +153,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 boolean flag = false; - if (!blockEntity.isEmpty()) { -+ int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers -+ -+ if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hopperss - flag = HopperBlockEntity.ejectItems(world, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit ++ final int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers ++ if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hoppers + flag = HopperBlockEntity.ejectItems(world, pos, blockEntity); } - if (!blockEntity.inventoryFull()) { @@ -368,181 +363,119 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); + // Paper end - Perf: Optimize Hoppers + - private static boolean ejectItems(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit - Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata); + private static boolean ejectItems(Level world, BlockPos pos, HopperBlockEntity blockEntity) { + Container iinventory = HopperBlockEntity.getAttachedContainer(world, pos, blockEntity); @@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (HopperBlockEntity.isFullContainer(iinventory1, enumdirection)) { + if (HopperBlockEntity.isFullContainer(iinventory, enumdirection)) { return false; } else { -- for (int i = 0; i < iinventory.getContainerSize(); ++i) { -- if (!iinventory.getItem(i).isEmpty()) { -- ItemStack itemstack = iinventory.getItem(i).copy(); -- // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection); +- for (int i = 0; i < blockEntity.getContainerSize(); ++i) { +- ItemStack itemstack = blockEntity.getItem(i); - +- if (!itemstack.isEmpty()) { +- int j = itemstack.getCount(); - // CraftBukkit start - Call event when pushing items into other inventories -- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(blockEntity.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot - - Inventory destinationInventory; - // Have to special case large chests as they work oddly -- if (iinventory1 instanceof CompoundContainer) { -- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1); -- } else if (iinventory1.getOwner() != null) { -- destinationInventory = iinventory1.getOwner().getInventory(); +- if (iinventory instanceof CompoundContainer) { +- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); +- } else if (iinventory.getOwner() != null) { +- destinationInventory = iinventory.getOwner().getInventory(); - } else { - destinationInventory = new CraftInventory(iinventory); - } - -- InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack, destinationInventory, true); - world.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { -- hopper.setItem(i, itemstack); -- hopper.setCooldown(world.spigotConfig.hopperTransfer); // Spigot +- blockEntity.setItem(i, itemstack); +- blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot - return false; - } -- int origCount = event.getItem().getAmount(); // Spigot -- ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); -+ // Paper start - replace logic; MAKE SURE TO CHECK FOR DIFFS ON UPDATES -+ return hopperPush(world, iinventory1, enumdirection, hopper); -+ // for (int i = 0; i < iinventory.getContainerSize(); ++i) { -+ // if (!iinventory.getItem(i).isEmpty()) { -+ // ItemStack itemstack = iinventory.getItem(i).copy(); -+ // // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection); -+ -+ // // CraftBukkit start - Call event when pushing items into other inventories -+ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot -+ -+ // Inventory destinationInventory; -+ // // Have to special case large chests as they work oddly -+ // if (iinventory1 instanceof CompoundContainer) { -+ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1); -+ // } else if (iinventory1.getOwner() != null) { -+ // destinationInventory = iinventory1.getOwner().getInventory(); -+ // } else { -+ // destinationInventory = new CraftInventory(iinventory); -+ // } -+ -+ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); -+ // world.getCraftServer().getPluginManager().callEvent(event); -+ // if (event.isCancelled()) { -+ // hopper.setItem(i, itemstack); -+ // hopper.setCooldown(world.spigotConfig.hopperTransfer); // Spigot -+ // return false; -+ // } -+ // int origCount = event.getItem().getAmount(); // Spigot -+ // ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); - // CraftBukkit end - +- ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); +- // CraftBukkit end +- - if (itemstack1.isEmpty()) { -- iinventory1.setChanged(); +- iinventory.setChanged(); - return true; - } -+ // if (itemstack1.isEmpty()) { -+ // iinventory1.setChanged(); -+ // return true; -+ // } - -- itemstack.shrink(origCount - itemstack1.getCount()); // Spigot -- iinventory.setItem(i, itemstack); +- +- itemstack.setCount(j); +- if (j == 1) { +- blockEntity.setItem(i, itemstack); +- } - } - } -+ // itemstack.shrink(origCount - itemstack1.getCount()); // Spigot -+ // iinventory.setItem(i, itemstack); -+ // } -+ // } - +- - return false; ++ // Paper start - Perf: Optimize Hoppers ++ return hopperPush(world, iinventory, enumdirection, blockEntity); ++ //for (int i = 0; i < blockEntity.getContainerSize(); ++i) { ++ // ItemStack itemstack = blockEntity.getItem(i); ++ ++ // if (!itemstack.isEmpty()) { ++ // int j = itemstack.getCount(); ++ // // CraftBukkit start - Call event when pushing items into other inventories ++ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(blockEntity.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot ++ ++ // Inventory destinationInventory; ++ // // Have to special case large chests as they work oddly ++ // if (iinventory instanceof CompoundContainer) { ++ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ // } else if (iinventory.getOwner() != null) { ++ // destinationInventory = iinventory.getOwner().getInventory(); ++ // } else { ++ // destinationInventory = new CraftInventory(iinventory); ++ // } ++ ++ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack, destinationInventory, true); ++ // world.getCraftServer().getPluginManager().callEvent(event); ++ // if (event.isCancelled()) { ++ // blockEntity.setItem(i, itemstack); ++ // blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot ++ // return false; ++ // } ++ // ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); ++ // // CraftBukkit end ++ ++ // if (itemstack1.isEmpty()) { ++ // iinventory.setChanged(); ++ // return true; ++ // } ++ ++ // itemstack.setCount(j); ++ // if (j == 1) { ++ // blockEntity.setItem(i, itemstack); ++ // } ++ // } ++ //} ++ + // return false; + // Paper end - Perf: Optimize Hoppers } } } @@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - - private static boolean isFullContainer(Container inventory, Direction direction) { -- return HopperBlockEntity.getSlots(inventory, direction).allMatch((i) -> { -- ItemStack itemstack = inventory.getItem(i); + return false; + } + } - -- return itemstack.getCount() >= itemstack.getMaxStackSize(); -- }); -+ // Paper start - Perf: Optimize Hoppers -+ if (inventory instanceof WorldlyContainer worldlyContainer) { -+ for (final int slot : worldlyContainer.getSlotsForFace(direction)) { -+ final ItemStack stack = inventory.getItem(slot); -+ if (stack.getCount() < stack.getMaxStackSize()) { -+ return false; -+ } -+ } -+ return true; -+ } else { -+ for (int slot = 0, max = inventory.getContainerSize(); slot < max; ++slot) { -+ final ItemStack stack = inventory.getItem(slot); -+ if (stack.getCount() < stack.getMaxStackSize()) { -+ return false; -+ } -+ } -+ return true; -+ } -+ // Paper end - Perf: Optimize Hoppers + return true; } - private static boolean isEmptyContainer(Container inv, Direction facing) { -- return HopperBlockEntity.getSlots(inv, facing).allMatch((i) -> { -- return inv.getItem(i).isEmpty(); -- }); -+ return allMatch(inv, facing, IS_EMPTY_TEST); // Paper - Perf: Optimize Hoppers - } - - public static boolean suckInItems(Level world, Hopper hopper) { -@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (iinventory != null) { - Direction enumdirection = Direction.DOWN; - -- return HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) ? false : HopperBlockEntity.getSlots(iinventory, enumdirection).anyMatch((i) -> { -- return HopperBlockEntity.a(hopper, iinventory, i, enumdirection, world); // Spigot -- }); -+ // Paper start - Perf: Optimize Hoppers -+ skipPullModeEventFire = skipHopperEvents; -+ // merge container isEmpty check and move logic into one loop -+ if (iinventory instanceof WorldlyContainer worldlyContainer) { -+ for (final int slot : worldlyContainer.getSlotsForFace(enumdirection)) { -+ ItemStack item = worldlyContainer.getItem(slot); -+ if (item.isEmpty() || !canTakeItemFromContainer(hopper, iinventory, item, slot, enumdirection)) { -+ continue; -+ } -+ if (hopperPull(world, hopper, iinventory, item, slot)) { -+ return true; -+ } -+ } -+ return false; -+ } else { -+ for (int slot = 0, max = iinventory.getContainerSize(); slot < max; ++slot) { -+ ItemStack item = iinventory.getItem(slot); -+ if (item.isEmpty() || !canTakeItemFromContainer(hopper, iinventory, item, slot, enumdirection)) { -+ continue; -+ } -+ if (hopperPull(world, hopper, iinventory, item, slot)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ // Paper end - Perf: Optimize Hoppers - } else { - Iterator iterator = HopperBlockEntity.getItemsAtAndAbove(world, hopper).iterator(); - @@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } } -+ @io.papermc.paper.annotation.DoNotUse // Paper - method unused as logic is inlined above - private static boolean a(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot ++ @io.papermc.paper.annotation.DoNotUse // Paper - Optimize hoppers + private static boolean tryTakeInItemFromSlot(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot ItemStack itemstack = iinventory.getItem(i); -- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { -- ItemStack itemstack1 = itemstack.copy(); -- // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); + if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { +- int j = itemstack.getCount(); - // CraftBukkit start - Call event on collection of items from inventories into the hopper - CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot - @@ -556,11 +489,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - sourceInventory = new CraftInventory(iinventory); - } - -- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false); - - Bukkit.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { -- iinventory.setItem(i, itemstack1); +- iinventory.setItem(i, itemstack); - - if (ihopper instanceof HopperBlockEntity) { - ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot @@ -568,58 +501,58 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - - return false; - } -- int origCount = event.getItem().getAmount(); // Spigot -- ItemStack itemstack2 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); +- ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); - // CraftBukkit end - -- if (itemstack2.isEmpty()) { +- if (itemstack1.isEmpty()) { - iinventory.setChanged(); - return true; - } - -- itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot -- iinventory.setItem(i, itemstack1); -+ // Paper start - Perf: Optimize Hoppers; replace pull logic; MAKE SURE TO CHECK FOR DIFFS WHEN UPDATING -+ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left unused incase reflective plugins +- itemstack.setCount(j); +- if (j == 1) { +- iinventory.setItem(i, itemstack); +- } ++ // Paper start - Perf: Optimize Hoppers + return hopperPull(world, ihopper, iinventory, itemstack, i); -+ // ItemStack itemstack1 = itemstack.copy(); -+ // // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); -+ // // CraftBukkit start - Call event on collection of items from inventories into the hopper -+ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot ++ // int j = itemstack.getCount(); ++ // // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot + -+ // Inventory sourceInventory; -+ // // Have to special case large chests as they work oddly -+ // if (iinventory instanceof CompoundContainer) { -+ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); -+ // } else if (iinventory.getOwner() != null) { -+ // sourceInventory = iinventory.getOwner().getInventory(); -+ // } else { -+ // sourceInventory = new CraftInventory(iinventory); -+ // } ++ // Inventory sourceInventory; ++ // // Have to special case large chests as they work oddly ++ // if (iinventory instanceof CompoundContainer) { ++ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ // } else if (iinventory.getOwner() != null) { ++ // sourceInventory = iinventory.getOwner().getInventory(); ++ // } else { ++ // sourceInventory = new CraftInventory(iinventory); ++ // } + -+ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); ++ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false); + -+ // Bukkit.getServer().getPluginManager().callEvent(event); -+ // if (event.isCancelled()) { -+ // iinventory.setItem(i, itemstack1); ++ // Bukkit.getServer().getPluginManager().callEvent(event); ++ // if (event.isCancelled()) { ++ // iinventory.setItem(i, itemstack); + -+ // if (ihopper instanceof HopperBlockEntity) { -+ // ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot -+ // } ++ // if (ihopper instanceof HopperBlockEntity) { ++ // ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot ++ // } + -+ // return false; -+ // } -+ // int origCount = event.getItem().getAmount(); // Spigot -+ // ItemStack itemstack2 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); -+ // // CraftBukkit end ++ // return false; ++ // } ++ // ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); ++ // // CraftBukkit end + -+ // if (itemstack2.isEmpty()) { -+ // iinventory.setChanged(); -+ // return true; -+ // } ++ // if (itemstack1.isEmpty()) { ++ // iinventory.setChanged(); ++ // return true; ++ // } + -+ // itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot -+ // iinventory.setItem(i, itemstack1); ++ // itemstack.setCount(j); ++ // if (j == 1) { ++ // iinventory.setItem(i, itemstack); ++ // } + // Paper end - Perf: Optimize Hoppers } @@ -651,74 +584,41 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 flag = true; } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { @@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - // CraftBukkit end - } - -+ // Paper start - Perf: Optimize Hoppers -+ static final AABB HOPPER_ITEM_SUCK_OVERALL = Hopper.SUCK.bounds(); -+ static final AABB[] HOPPER_ITEM_SUCK_INDIVIDUAL = Hopper.SUCK.toAabbs().toArray(new AABB[0]); -+ // Paper end - Perf: Optimize Hoppers -+ - public static List getItemsAtAndAbove(Level world, Hopper hopper) { -- return (List) hopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> { -- return world.getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(hopper.getLevelX() - 0.5D, hopper.getLevelY() - 0.5D, hopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream(); -- }).collect(Collectors.toList()); -+ // Paper start - Perf: Optimize Hoppers -+ // eliminate multiple getEntitiesOfClass() but maintain the voxelshape collision by moving -+ // the individual AABB checks into the predicate -+ final double shiftX = hopper.getLevelX() - 0.5D; -+ final double shiftY = hopper.getLevelY() - 0.5D; -+ final double shiftZ = hopper.getLevelZ() - 0.5D; -+ return world.getEntitiesOfClass(ItemEntity.class, HOPPER_ITEM_SUCK_OVERALL.move(shiftX, shiftY, shiftZ), (final Entity entity) -> { -+ if (!entity.isAlive()) { // EntitySelector.ENTITY_STILL_ALIVE -+ return false; -+ } -+ -+ for (final AABB aabb : HOPPER_ITEM_SUCK_INDIVIDUAL) { -+ if (aabb.move(shiftX, shiftY, shiftZ).intersects(entity.getBoundingBox())) { -+ return true; -+ } -+ } -+ -+ return false; -+ }); -+ // Paper end - Perf: Optimize Hoppers - } @Nullable public static Container getContainerAt(Level world, BlockPos pos) { -- return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); -+ return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); // Paper - Perf: Optimize Hoppers +- return HopperBlockEntity.getContainerAt(world, pos, world.getBlockState(pos), (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); ++ return HopperBlockEntity.getContainerAt(world, pos, world.getBlockState(pos), (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); } @Nullable - private static Container getContainerAt(Level world, double x, double y, double z) { + private static Container getContainerAt(Level world, BlockPos pos, BlockState state, double x, double y, double z) { + // Paper start - Perf: Optimize Hoppers -+ return HopperBlockEntity.getContainerAt(world, x, y, z, false); ++ return HopperBlockEntity.getContainerAt(world, pos, state, x, y, z, false); + } + @Nullable -+ private static Container getContainerAt(Level world, double x, double y, double z, final boolean optimizeEntities) { ++ private static Container getContainerAt(Level world, BlockPos pos, BlockState state, double x, double y, double z, boolean optimizeEntities) { + // Paper end - Perf: Optimize Hoppers - Object object = null; - BlockPos blockposition = BlockPos.containing(x, y, z); - if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( blockposition ) ) return null; // Spigot -@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } + Container iinventory = HopperBlockEntity.getBlockContainer(world, pos, state); + +- if (iinventory == null) { ++ if (iinventory == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers + iinventory = HopperBlockEntity.getEntityContainer(world, x, y, z); } -- if (object == null) { -- List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); -+ if (object == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !iblockdata.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers -+ List list = world.getEntitiesOfClass((Class)Container.class, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - Perf: Optimize Hoppers - - if (!list.isEmpty()) { - object = (Container) list.get(world.random.nextInt(list.size())); @@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static Container getEntityContainer(Level world, double x, double y, double z) { +- List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); ++ List list = world.getEntitiesOfClass((Class) Container.class, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - Perf: Optimize hoppers + + return !list.isEmpty() ? (Container) list.get(world.random.nextInt(list.size())) : null; } private static boolean canMergeItems(ItemStack first, ItemStack second) { -- return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameTags(first, second); -+ return first.getCount() < first.getMaxStackSize() && first.is(second.getItem()) && first.getDamageValue() == second.getDamageValue() && ((first.isEmpty() && second.isEmpty()) || java.util.Objects.equals(first.getTag(), second.getTag())); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! +- return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second); ++ return first.getCount() < first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! } @Override @@ -727,24 +627,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java @@ -0,0 +0,0 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - @Override - public boolean isEmpty() { - this.unpackLootTable(null); -- return this.getItems().stream().allMatch(ItemStack::isEmpty); -+ // Paper start - Perf: Optimize Hoppers -+ for (final ItemStack itemStack : this.getItems()) { -+ if (!itemStack.isEmpty()) { -+ return false; -+ } -+ } -+ return true; -+ // Paper end - Perf: Optimize Hoppers - } @Override public ItemStack getItem(int slot) { - this.unpackLootTable(null); + if (slot == 0) this.unpackLootTable(null); // Paper - Perf: Optimize Hoppers - return this.getItems().get(slot); + return super.getItem(slot); } diff --git a/patches/unapplied/server/Properly-resend-entities.patch b/patches/server/Properly-resend-entities.patch similarity index 54% rename from patches/unapplied/server/Properly-resend-entities.patch rename to patches/server/Properly-resend-entities.patch index 4e2336efe0..149b20ad49 100644 --- a/patches/unapplied/server/Properly-resend-entities.patch +++ b/patches/server/Properly-resend-entities.patch @@ -27,70 +27,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java @@ -0,0 +0,0 @@ public class SynchedEntityData { - // CraftBukkit start - public void refresh(ServerPlayer to) { - if (!this.isEmpty()) { -- List> list = this.getNonDefaultValues(); -+ List> list = this.packAll(); // Paper - Update EVERYTHING not just not default - - if (list != null) { -+ if (to.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); -+ } // Paper - } } } - // CraftBukkit end + +- private SynchedEntityData.DataItem getItem(EntityDataAccessor key) { ++ public SynchedEntityData.DataItem getItem(EntityDataAccessor key) { // Paper - public + return (SynchedEntityData.DataItem) this.itemsById[key.id()]; // CraftBukkit - decompile error + } + +@@ -0,0 +0,0 @@ public class SynchedEntityData { + } + } + + // Paper start + // We need to pack all as we cannot rely on "non default values" or "dirty" ones. + // Because these values can possibly be desynced on the client. + @Nullable -+ private List> packAll() { -+ if (this.isEmpty()) { -+ return null; -+ } -+ -+ List> list = new ArrayList<>(); -+ for (DataItem dataItem : this.itemsById.values()) { ++ public List> packAll() { ++ final List> list = new ArrayList<>(); ++ for (final DataItem dataItem : this.itemsById) { + list.add(dataItem.value()); + } + + return list; + } -+ -+ // This method should only be used if the data of an entity could have became desynced -+ // due to interactions on the client. -+ public void resendPossiblyDesyncedEntity(ServerPlayer player) { -+ if (this.entity.tracker == null) { -+ return; -+ } -+ -+ if (player.getBukkitEntity().canSee(entity.getBukkitEntity())) { -+ net.minecraft.server.level.ServerEntity serverEntity = this.entity.tracker.serverEntity; -+ -+ List> list = new ArrayList<>(); -+ serverEntity.sendPairingData(player, list::add); -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list)); -+ } -+ } -+ -+ // This method allows you to specifically resend certain data accessor keys to the client -+ public void resendPossiblyDesyncedDataValues(List> keys, ServerPlayer to) { -+ if (!to.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { -+ return; -+ } -+ List> values = new ArrayList<>(keys.size()); -+ for (EntityDataAccessor key : keys) { -+ SynchedEntityData.DataItem synchedValue = this.getItem(key); -+ values.add(synchedValue.value()); -+ } -+ -+ to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), values)); -+ } + // Paper end - ++ public static class DataItem { + final EntityDataAccessor accessor; diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -100,12 +65,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Paper end - extend Player Interact cancellation player.getBukkitEntity().updateInventory(); // SPIGOT-2867 + this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items - enuminteractionresult = (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; + return (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; } else if (this.gameModeForPlayer == GameType.SPECTATOR) { MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); @@ -0,0 +0,0 @@ public class ServerPlayerGameMode { - - return enuminteractionresult1; + } else { + return InteractionResult.PASS; } + // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response + else if (this.interactResult && this.interactResult != cancelledItem) { @@ -113,12 +78,53 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - Properly cancel usable items } - return enuminteractionresult; - // CraftBukkit end + } + diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -0,0 +0,0 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.Connection; + import net.minecraft.network.ConnectionProtocol; + import net.minecraft.network.TickablePacketListener; +-import net.minecraft.network.chat.ChatDecorator; + import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.LastSeenMessages; +@@ -0,0 +0,0 @@ import net.minecraft.network.protocol.PacketUtils; + import net.minecraft.network.protocol.common.ServerboundClientInformationPacket; + import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; + import net.minecraft.network.protocol.configuration.ConfigurationProtocols; +-import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; + import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket; + import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; +@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.entity.CrafterBlockEntity; + import net.minecraft.world.level.block.entity.JigsawBlockEntity; + import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.entity.StructureBlockEntity; +-import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; +@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.event.CraftEventFactory; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.craftbukkit.inventory.CraftItemType; +-import org.bukkit.craftbukkit.util.CraftChatMessage; + import org.bukkit.craftbukkit.util.CraftNamespacedKey; + import org.bukkit.craftbukkit.util.LazyPlayerSet; + import org.bukkit.craftbukkit.util.Waitable; +@@ -0,0 +0,0 @@ import org.bukkit.event.inventory.InventoryCreativeEvent; + import org.bukkit.event.inventory.InventoryType.SlotType; + import org.bukkit.event.inventory.SmithItemEvent; + import org.bukkit.event.player.AsyncPlayerChatEvent; +-import org.bukkit.event.player.PlayerAnimationEvent; +-import org.bukkit.event.player.PlayerAnimationType; + import org.bukkit.event.player.PlayerChatEvent; + import org.bukkit.event.player.PlayerCommandPreprocessEvent; + import org.bukkit.event.player.PlayerInteractAtEntityEvent; @@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } @@ -132,7 +138,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { - ServerGamePacketListenerImpl.this.send(new ClientboundAddEntityPacket(entity)); -+ entity.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it. ++ entity.getEntityData().resendPossiblyDesyncedEntityData(player); // Paper - The entire mob gets deleted, so resend it. ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); } @@ -144,11 +150,126 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now // CraftBukkit end -- player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn -+ //player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn Paper - THIS IS NOT NEEDED ANYMORE +- player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn ++ //player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // Paper - THIS IS NOT NEEDED ANYMORE this.sendLevelInfo(player, worldserver1); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -0,0 +0,0 @@ import com.google.common.collect.Lists; + import com.google.common.collect.Sets; + import com.google.common.collect.UnmodifiableIterator; + import com.mojang.logging.LogUtils; ++import io.papermc.paper.util.MCUtil; + import it.unimi.dsi.fastutil.objects.Object2DoubleArrayMap; + import it.unimi.dsi.fastutil.objects.Object2DoubleMap; + import java.util.Arrays; +@@ -0,0 +0,0 @@ import net.minecraft.network.syncher.SyncedDataHolder; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; +-import io.papermc.paper.util.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; +@@ -0,0 +0,0 @@ import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.scores.PlayerTeam; + import net.minecraft.world.scores.ScoreHolder; + import net.minecraft.world.scores.Team; +-import org.slf4j.Logger; + import org.bukkit.Bukkit; + import org.bukkit.Location; +-import org.bukkit.Server; + import org.bukkit.block.BlockFace; + import org.bukkit.command.CommandSender; +-import org.bukkit.craftbukkit.event.CraftPortalEvent; +-import org.bukkit.entity.Hanging; +-import org.bukkit.entity.LivingEntity; +-import org.bukkit.entity.Vehicle; +-import org.bukkit.event.entity.EntityCombustByEntityEvent; +-import org.bukkit.event.hanging.HangingBreakByEntityEvent; +-import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; +-import org.bukkit.event.vehicle.VehicleEnterEvent; +-import org.bukkit.event.vehicle.VehicleExitEvent; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.entity.CraftEntity; + import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.event.CraftPortalEvent; + import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Hanging; ++import org.bukkit.entity.LivingEntity; + import org.bukkit.entity.Pose; ++import org.bukkit.entity.Vehicle; + import org.bukkit.event.entity.EntityAirChangeEvent; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; + import org.bukkit.event.entity.EntityCombustEvent; + import org.bukkit.event.entity.EntityDismountEvent; + import org.bukkit.event.entity.EntityDropItemEvent; +@@ -0,0 +0,0 @@ import org.bukkit.event.entity.EntityMountEvent; + import org.bukkit.event.entity.EntityPortalEvent; + import org.bukkit.event.entity.EntityPoseChangeEvent; + import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.hanging.HangingBreakByEntityEvent; + import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; ++import org.bukkit.event.vehicle.VehicleEnterEvent; ++import org.bukkit.event.vehicle.VehicleExitEvent; + import org.bukkit.plugin.PluginManager; ++import org.slf4j.Logger; + // CraftBukkit end + + public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, CommandSource, ScoreHolder { +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + // CraftBukkit start + public void refreshEntityData(ServerPlayer to) { +- List> list = this.getEntityData().getNonDefaultValues(); ++ List> list = this.entityData.packAll(); // Paper - Update EVERYTHING not just not default + +- if (list != null) { ++ if (list != null && to.getBukkitEntity().canSee(this.getBukkitEntity())) { // Paper + to.connection.send(new ClientboundSetEntityDataPacket(this.getId(), list)); + } + } + // CraftBukkit end ++ // Paper start ++ // This method should only be used if the data of an entity could have become desynced ++ // due to interactions on the client. ++ public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) { ++ if (this.tracker == null) { ++ return; ++ } ++ ++ if (player.getBukkitEntity().canSee(this.getBukkitEntity())) { ++ final net.minecraft.server.level.ServerEntity serverEntity = this.tracker.serverEntity; ++ final List> list = new java.util.ArrayList<>(); ++ serverEntity.sendPairingData(player, list::add); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list)); ++ } ++ } ++ ++ // This method allows you to specifically resend certain data accessor keys to the client ++ public void resendPossiblyDesyncedDataValues(List> keys, ServerPlayer to) { ++ if (!to.getBukkitEntity().canSee(this.getBukkitEntity())) { ++ return; ++ } ++ ++ final List> values = new java.util.ArrayList<>(keys.size()); ++ for (final EntityDataAccessor key : keys) { ++ final SynchedEntityData.DataItem synchedValue = this.entityData.getItem(key); ++ values.add(synchedValue.value()); ++ } ++ ++ to.connection.send(new ClientboundSetEntityDataPacket(this.id, values)); ++ } ++ // Paper end + + public boolean equals(Object object) { + return object instanceof Entity ? ((Entity) object).id == this.id : false; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -174,8 +295,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (playerBucketFishEvent.isCancelled()) { ((ServerPlayer) player).containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket - ((ServerPlayer) player).connection.send(new ClientboundAddEntityPacket(entity)); // We need to play out these packets as the client assumes the fish is gone -- entity.getEntityData().refresh((ServerPlayer) player); // Need to send data such as the display name to client -+ entity.getEntityData().resendPossiblyDesyncedEntity((ServerPlayer) player); // Paper +- entity.refreshEntityData((ServerPlayer) player); // Need to send data such as the display name to client ++ entity.resendPossiblyDesyncedEntityData((ServerPlayer) player); // Paper return Optional.of(InteractionResult.FAIL); } entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F); @@ -190,7 +311,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - entityTracker.broadcast(this.getHandle().getAddEntityPacket()); + // Paper start, resend possibly desynced entity instead of add entity packet + for (ServerPlayerConnection playerConnection : entityTracker.seenBy) { -+ this.getHandle().getEntityData().resendPossiblyDesyncedEntity(playerConnection.getPlayer()); ++ this.getHandle().getEntityData().resendPossiblyDesyncedEntityData(playerConnection.getPlayer()); + } + // Paper end } diff --git a/patches/unapplied/server/Use-Velocity-compression-and-cipher-natives.patch b/patches/server/Use-Velocity-compression-and-cipher-natives.patch similarity index 85% rename from patches/unapplied/server/Use-Velocity-compression-and-cipher-natives.patch rename to patches/server/Use-Velocity-compression-and-cipher-natives.patch index 9d51f8829e..4aa6776869 100644 --- a/patches/unapplied/server/Use-Velocity-compression-and-cipher-natives.patch +++ b/patches/server/Use-Velocity-compression-and-cipher-natives.patch @@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -0,0 +0,0 @@ dependencies { - runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") - runtimeOnly("com.mysql:mysql-connector-j:8.2.0") + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files + implementation("commons-lang:commons-lang:2.6") runtimeOnly("com.lmax:disruptor:3.4.4") // Paper + // Paper start - Use Velocity cipher + implementation("com.velocitypowered:velocity-native:3.1.2-SNAPSHOT") { @@ -109,7 +109,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; private final Inflater inflater; -+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher ++ private com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher private int threshold; private boolean validateDecompressed; @@ -170,6 +170,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private void setupInflaterInput(ByteBuf buf) { ByteBuffer byteBuffer; if (buf.nioBufferCount() > 0) { +@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder { + } + } + +- public void setThreshold(int compressionThreshold, boolean rejectsBadPackets) { ++ public void setThreshold(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { // Paper - Use Velocity cipher + this.threshold = compressionThreshold; + this.validateDecompressed = rejectsBadPackets; + } diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/CompressionEncoder.java @@ -207,32 +216,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { + protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper - Use Velocity cipher int i = byteBuf.readableBytes(); - if (i < this.threshold) { - VarInt.write(byteBuf2, 0); - byteBuf2.writeBytes(byteBuf); - } else { -+ if (this.deflater != null) { // Paper - Use Velocity cipher - byte[] bs = new byte[i]; - byteBuf.readBytes(bs); - VarInt.write(byteBuf2, bs.length); + if (i > 8388608) { + throw new IllegalArgumentException("Packet too big (is " + i + ", should be less than 8388608)"); @@ -0,0 +0,0 @@ public class CompressionEncoder extends MessageToByteEncoder { - } + VarInt.write(byteBuf2, 0); + byteBuf2.writeBytes(byteBuf); + } else { ++ if (this.deflater != null) { // Paper - Use Velocity cipher + byte[] bs = new byte[i]; + byteBuf.readBytes(bs); + VarInt.write(byteBuf2, bs.length); +@@ -0,0 +0,0 @@ public class CompressionEncoder extends MessageToByteEncoder { + } - this.deflater.reset(); -+ // Paper start - Use Velocity cipher -+ return; -+ } -+ -+ VarInt.write(byteBuf2, i); -+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); -+ try { -+ this.compressor.deflate(compatibleIn, byteBuf2); -+ } finally { -+ compatibleIn.release(); -+ } -+ } -+ } + this.deflater.reset(); ++ // Paper start - Use Velocity cipher ++ return; ++ } + ++ VarInt.write(byteBuf2, i); ++ final ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); ++ try { ++ this.compressor.deflate(compatibleIn, byteBuf2); ++ } finally { ++ compatibleIn.release(); ++ } + } + } + } + + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{ + if (this.compressor != null) { @@ -244,7 +256,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // size the compressed size will ever be is the input size minus one. + // - Uncompressed + // This is fairly obvious - we will then have one more than the uncompressed size. -+ int initialBufferSize = msg.readableBytes() + 1; ++ final int initialBufferSize = msg.readableBytes() + 1; + return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize); + } + @@ -256,9 +268,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + if (this.compressor != null) { + this.compressor.close(); + // Paper end - Use Velocity cipher - } ++ } ++ } ++ + public int getThreshold() { + return this.threshold; } - diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/Connection.java @@ -301,18 +316,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { if (compressionThreshold >= 0) { + com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(io.papermc.paper.configuration.GlobalConfiguration.get().misc.compressionLevel.or(-1)); // Paper - Use Velocity cipher - if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { - ((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, rejectsBadPackets); - } else { -- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); -+ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper - Use Velocity cipher - } + ChannelHandler channelhandler = this.channel.pipeline().get("decompress"); - if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { - ((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold); + if (channelhandler instanceof CompressionDecoder) { + CompressionDecoder packetdecompressor = (CompressionDecoder) channelhandler; + +- packetdecompressor.setThreshold(compressionThreshold, rejectsBadPackets); ++ packetdecompressor.setThreshold(compressor, compressionThreshold, rejectsBadPackets); // Paper - Use Velocity cipher } else { -- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); -+ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - Use Velocity cipher + this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); + } +@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { + + packetcompressor.setThreshold(compressionThreshold); + } else { +- this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold)); ++ this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - Use Velocity cipher } this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners } else { @@ -331,7 +350,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { protected void initChannel(Channel channel) { - Connection.setInitialProtocolAttributes(channel); + try { diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java diff --git a/patches/unapplied/server/optimize-dirt-and-snow-spreading.patch b/patches/server/optimize-dirt-and-snow-spreading.patch similarity index 91% rename from patches/unapplied/server/optimize-dirt-and-snow-spreading.patch rename to patches/server/optimize-dirt-and-snow-spreading.patch index 00ebea1389..0b661fde2e 100644 --- a/patches/unapplied/server/optimize-dirt-and-snow-spreading.patch +++ b/patches/server/optimize-dirt-and-snow-spreading.patch @@ -40,16 +40,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - if (this instanceof GrassBlock && world.paperConfig().tickRates.grassSpread != 1 && (world.paperConfig().tickRates.grassSpread < 1 || (MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks + protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (this instanceof GrassBlock && world.paperConfig().tickRates.grassSpread != 1 && (world.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks - if (!SpreadingSnowyDirtBlock.canBeGrass(state, world, pos)) { + // Paper start - Perf: optimize dirt and snow spreading -+ net.minecraft.world.level.chunk.ChunkAccess cachedBlockChunk = world.getChunkIfLoaded(pos); ++ final net.minecraft.world.level.chunk.ChunkAccess cachedBlockChunk = world.getChunkIfLoaded(pos); + if (cachedBlockChunk == null) { // Is this needed? + return; + } + if (!SpreadingSnowyDirtBlock.canBeGrass(cachedBlockChunk, state, world, pos)) { -+ // Paper end - Perf: optimize dirt and snow spreading ++ // Paper end - Perf: optimize dirt and snow spreading // CraftBukkit start if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) { return;