diff --git a/lophine-server/luminol-patches/features/0006-Carpet-features.patch b/lophine-server/luminol-patches/features/0006-Carpet-features.patch new file mode 100644 index 0000000..405b42a --- /dev/null +++ b/lophine-server/luminol-patches/features/0006-Carpet-features.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bacteriawa +Date: Sun, 22 Mar 2026 01:39:17 +0800 +Subject: [PATCH] Carpet features + + +diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java +index f5fb4e210c07f32e52eb8c1ba166d1571a6778c7..e990c147e1b632ef5a33e4f3c37b5cd608040ac7 100644 +--- a/src/main/java/me/earthme/luminol/config/ConfigManager.java ++++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java +@@ -25,6 +25,13 @@ public class ConfigManager { + public static void initConfigs() { + configfiles.put("luminol", ConfigsInstance.of("luminol", "me.earthme.luminol.config.modules")); + configfiles.put("lophine", ConfigsInstance.of("lophine", "fun.bm.lophine.config.modules")); // add lophine global config ++ configfiles.put("lophine_carpet", ConfigsInstance.of( ++ new java.io.File("lophine_config"), ++ "lophine_carpet", ++ "lophine_carpet_config.toml", ++ "lophinecarpetconfig", ++ "fun.bm.lophine.carpet.config.modules" ++ )); // add lophine carpet config + preLoad(); + } + diff --git a/lophine-server/minecraft-patches/features/0034-Leaves-Catch-update-suppression-crash.patch b/lophine-server/minecraft-patches/features/0034-Leaves-Catch-update-suppression-crash.patch index a9cba66..5928055 100644 --- a/lophine-server/minecraft-patches/features/0034-Leaves-Catch-update-suppression-crash.patch +++ b/lophine-server/minecraft-patches/features/0034-Leaves-Catch-update-suppression-crash.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Bacteriawa -Date: Sat, 7 Feb 2026 23:57:50 +0800 +Date: Tue, 2 Jun 2026 19:55:06 +0800 Subject: [PATCH] Leaves: Catch update suppression crash Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com> diff --git a/lophine-server/minecraft-patches/features/0035-Leaves-CCE-update-suppression.patch b/lophine-server/minecraft-patches/features/0035-Leaves-CCE-update-suppression.patch index a4e377e..f7d627d 100644 --- a/lophine-server/minecraft-patches/features/0035-Leaves-CCE-update-suppression.patch +++ b/lophine-server/minecraft-patches/features/0035-Leaves-CCE-update-suppression.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Bacteriawa -Date: Sat, 15 Nov 2025 01:12:39 +0800 +Date: Tue, 2 Jun 2026 20:06:33 +0800 Subject: [PATCH] Leaves: CCE update suppression Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com> diff --git a/lophine-server/minecraft-patches/features/0036-Leaves-Redstone-ignore-upwards-update.patch b/lophine-server/minecraft-patches/features/0036-Leaves-Redstone-ignore-upwards-update.patch index 2e005c5..c1067f2 100644 --- a/lophine-server/minecraft-patches/features/0036-Leaves-Redstone-ignore-upwards-update.patch +++ b/lophine-server/minecraft-patches/features/0036-Leaves-Redstone-ignore-upwards-update.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Bacteriawa -Date: Wed, 14 Jun 2023 12:07:07 +0800 +Date: Tue, 2 Jun 2026 20:15:44 +0800 Subject: [PATCH] Leaves: Redstone ignore upwards update Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com> diff --git a/lophine-server/minecraft-patches/features/0037-Instant-Block-Updater.patch b/lophine-server/minecraft-patches/features/0037-Instant-Block-Updater.patch index 8c4d1b3..465af7a 100644 --- a/lophine-server/minecraft-patches/features/0037-Instant-Block-Updater.patch +++ b/lophine-server/minecraft-patches/features/0037-Instant-Block-Updater.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Helvetica Volubi -Date: Mon, 17 Nov 2025 18:41:08 +0800 +From: Bacteriawa +Date: Tue, 2 Jun 2026 20:32:47 +0800 Subject: [PATCH] Instant Block Updater diff --git a/lophine-server/minecraft-patches/features/0038-Revert-TrapDoorBlock-changes-form-Paper.patch b/lophine-server/minecraft-patches/features/0038-Revert-TrapDoorBlock-changes-form-Paper.patch index f3b0a76..e398f6d 100644 --- a/lophine-server/minecraft-patches/features/0038-Revert-TrapDoorBlock-changes-form-Paper.patch +++ b/lophine-server/minecraft-patches/features/0038-Revert-TrapDoorBlock-changes-form-Paper.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Helvetica Volubi -Date: Tue, 18 Nov 2025 00:24:32 +0800 +From: Bacteriawa +Date: Tue, 2 Jun 2026 20:37:27 +0800 Subject: [PATCH] Revert TrapDoorBlock changes form Paper diff --git a/lophine-server/minecraft-patches/features/0039-Leaves-Prevent-loss-of-item-drops-due-to-update-supp.patch b/lophine-server/minecraft-patches/features/0039-Leaves-Prevent-loss-of-item-drops-due-to-update-supp.patch index 81abc43..b99add7 100644 --- a/lophine-server/minecraft-patches/features/0039-Leaves-Prevent-loss-of-item-drops-due-to-update-supp.patch +++ b/lophine-server/minecraft-patches/features/0039-Leaves-Prevent-loss-of-item-drops-due-to-update-supp.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Helvetica Volubi -Date: Tue, 18 Nov 2025 00:35:13 +0800 +From: Bacteriawa +Date: Tue, 2 Jun 2026 20:42:45 +0800 Subject: [PATCH] Leaves: Prevent loss of item drops due to update suppression when breaking blocks diff --git a/lophine-server/minecraft-patches/features/0040-Leaves-Do-not-reset-placed-block-on-exception-Do-not.patch b/lophine-server/minecraft-patches/features/0040-Leaves-Do-not-reset-placed-block-on-exception-Do-not.patch index 1f357e9..90962a7 100644 --- a/lophine-server/minecraft-patches/features/0040-Leaves-Do-not-reset-placed-block-on-exception-Do-not.patch +++ b/lophine-server/minecraft-patches/features/0040-Leaves-Do-not-reset-placed-block-on-exception-Do-not.patch @@ -1,6 +1,6 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Helvetica Volubi -Date: Tue, 18 Nov 2025 23:26:27 +0800 +From: Bacteriawa +Date: Tue, 2 Jun 2026 20:50:57 +0800 Subject: [PATCH] Leaves: Do not reset placed block on exception & Do not prevent block entity and entity crash at LevelChunk diff --git a/lophine-server/minecraft-patches/features/0041-Leaves-Old-Block-remove-behaviour.patch b/lophine-server/minecraft-patches/features/0041-Leaves-Old-Block-remove-behaviour.patch index e5fb9ac..a2f1896 100644 --- a/lophine-server/minecraft-patches/features/0041-Leaves-Old-Block-remove-behaviour.patch +++ b/lophine-server/minecraft-patches/features/0041-Leaves-Old-Block-remove-behaviour.patch @@ -8,14 +8,15 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/net/minecraft/world/Containers.java b/net/minecraft/world/Containers.java -index 1311d4936f84652c5ffc51544f99cb98fc293906..b4cbe16c9660f3e88a1177817979b1f6a59fc485 100644 +index 1311d4936f84652c5ffc51544f99cb98fc293906..3a62831838a60776159dcb1ca783d3f00e6cd35e 100644 --- a/net/minecraft/world/Containers.java +++ b/net/minecraft/world/Containers.java -@@ -51,4 +51,15 @@ public class Containers { +@@ -51,4 +51,16 @@ public class Containers { public static void updateNeighboursAfterDestroy(final BlockState state, final Level level, final BlockPos pos) { level.updateNeighbourForOutputSignal(pos, state.getBlock()); } + ++ + // Leaves start - behaviour 1.21.1- + public static void dropContentsOnDestroy(BlockState state, BlockState newState, Level level, BlockPos pos) { + if (!state.is(newState.getBlock())) { @@ -28,10 +29,10 @@ index 1311d4936f84652c5ffc51544f99cb98fc293906..b4cbe16c9660f3e88a1177817979b1f6 + // Leaves end - behaviour 1.21.1- } diff --git a/net/minecraft/world/level/block/AbstractFurnaceBlock.java b/net/minecraft/world/level/block/AbstractFurnaceBlock.java -index 98561dcc465ca9d5394e58b05ad3e8dc59773ebc..16b9f7eaccbe63aa13fcc89571e0b9c95ff9fd25 100644 +index 98561dcc465ca9d5394e58b05ad3e8dc59773ebc..72e3d6c1ce3389972df21d5ff6e4d74bcf2ad300 100644 --- a/net/minecraft/world/level/block/AbstractFurnaceBlock.java +++ b/net/minecraft/world/level/block/AbstractFurnaceBlock.java -@@ -53,6 +53,26 @@ public abstract class AbstractFurnaceBlock extends BaseEntityBlock { +@@ -53,6 +53,27 @@ public abstract class AbstractFurnaceBlock extends BaseEntityBlock { return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite()); } @@ -54,15 +55,16 @@ index 98561dcc465ca9d5394e58b05ad3e8dc59773ebc..16b9f7eaccbe63aa13fcc89571e0b9c9 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/BarrelBlock.java b/net/minecraft/world/level/block/BarrelBlock.java -index 5332fd32fa7a0d3fc3be1b0a7c2ca5dee8e402c6..0a77f67da73856edd2c45c8a9c6962cc653d7176 100644 +index 5332fd32fa7a0d3fc3be1b0a7c2ca5dee8e402c6..37667a8e512bdba385c563e950d8c5d5cdd18832 100644 --- a/net/minecraft/world/level/block/BarrelBlock.java +++ b/net/minecraft/world/level/block/BarrelBlock.java -@@ -51,6 +51,14 @@ public class BarrelBlock extends BaseEntityBlock { +@@ -51,6 +51,15 @@ public class BarrelBlock extends BaseEntityBlock { return InteractionResult.SUCCESS; } @@ -73,15 +75,16 @@ index 5332fd32fa7a0d3fc3be1b0a7c2ca5dee8e402c6..0a77f67da73856edd2c45c8a9c6962cc + super.onRemove(state, level, pos, newState, isMoving); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/BasePressurePlateBlock.java b/net/minecraft/world/level/block/BasePressurePlateBlock.java -index c2fbf44e93e44c36423465d406b5e4d3db029a80..e22cade1f4258f9660f1f5ac3a330432cdf326d1 100644 +index c2fbf44e93e44c36423465d406b5e4d3db029a80..a28a453f139295834c77aa908027c74c8dfd7aba 100644 --- a/net/minecraft/world/level/block/BasePressurePlateBlock.java +++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java -@@ -133,6 +133,19 @@ public abstract class BasePressurePlateBlock extends Block { +@@ -133,6 +133,20 @@ public abstract class BasePressurePlateBlock extends Block { } } @@ -97,15 +100,16 @@ index c2fbf44e93e44c36423465d406b5e4d3db029a80..e22cade1f4258f9660f1f5ac3a330432 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston && this.getSignalForState(state) > 0) { diff --git a/net/minecraft/world/level/block/BaseRailBlock.java b/net/minecraft/world/level/block/BaseRailBlock.java -index bc79a7c2cb18237dad15432ae7e1238125108696..ac1db4712de6d998fe6b992dd75ed5f7fd1a0338 100644 +index bc79a7c2cb18237dad15432ae7e1238125108696..5ef16955c69f0e0fecc772067a0fb006e6413011 100644 --- a/net/minecraft/world/level/block/BaseRailBlock.java +++ b/net/minecraft/world/level/block/BaseRailBlock.java -@@ -123,6 +123,23 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl +@@ -123,6 +123,24 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl } } @@ -125,19 +129,19 @@ index bc79a7c2cb18237dad15432ae7e1238125108696..ac1db4712de6d998fe6b992dd75ed5f7 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston) { diff --git a/net/minecraft/world/level/block/BrewingStandBlock.java b/net/minecraft/world/level/block/BrewingStandBlock.java -index aea12aad3270b966eed7779eaf77d26052f8a21f..751792a388202a14be69fd68f891198bcea71193 100644 +index aea12aad3270b966eed7779eaf77d26052f8a21f..6c39edf98f33df437880a77450557082ea9b0f6c 100644 --- a/net/minecraft/world/level/block/BrewingStandBlock.java +++ b/net/minecraft/world/level/block/BrewingStandBlock.java -@@ -79,7 +79,13 @@ public class BrewingStandBlock extends BaseEntityBlock { - double z = pos.getZ() + 0.4 + random.nextFloat() * 0.2; +@@ -80,6 +80,15 @@ public class BrewingStandBlock extends BaseEntityBlock { level.addParticle(ParticleTypes.SMOKE, x, y, z, 0.0, 0.0, 0.0); } -- + + // Leaves start - behaviour 1.21.1- + @Override + protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { @@ -145,14 +149,16 @@ index aea12aad3270b966eed7779eaf77d26052f8a21f..751792a388202a14be69fd68f891198b + super.onRemove(state, level, pos, newState, isMoving); + } + // Leaves end - behaviour 1.21.1- ++ ++ @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/ButtonBlock.java b/net/minecraft/world/level/block/ButtonBlock.java -index 86f4dcd959bb83247c6d2c5e947c79230aed3189..e6c2630e33ac23b31a01ef8b1c5c10b72a854251 100644 +index 86f4dcd959bb83247c6d2c5e947c79230aed3189..1b0223f1d533d1a8db5497ab9104052bc799f691 100644 --- a/net/minecraft/world/level/block/ButtonBlock.java +++ b/net/minecraft/world/level/block/ButtonBlock.java -@@ -131,6 +131,19 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { +@@ -131,6 +131,20 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { return pressed ? this.type.buttonClickOn() : this.type.buttonClickOff(); } @@ -168,15 +174,16 @@ index 86f4dcd959bb83247c6d2c5e947c79230aed3189..e6c2630e33ac23b31a01ef8b1c5c10b7 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston && state.getValue(POWERED)) { diff --git a/net/minecraft/world/level/block/CampfireBlock.java b/net/minecraft/world/level/block/CampfireBlock.java -index 8b706b34914326c039b761387f25e488f30fe6c8..d83754699a181c6abb97e9492186dc96d922b325 100644 +index 8b706b34914326c039b761387f25e488f30fe6c8..b816594fcd83d26037734ec051f2c5d7f7dbde9f 100644 --- a/net/minecraft/world/level/block/CampfireBlock.java +++ b/net/minecraft/world/level/block/CampfireBlock.java -@@ -111,6 +111,20 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB +@@ -111,6 +111,21 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB return InteractionResult.TRY_WITH_EMPTY_HAND; } @@ -193,15 +200,16 @@ index 8b706b34914326c039b761387f25e488f30fe6c8..d83754699a181c6abb97e9492186dc96 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void entityInside( final BlockState state, diff --git a/net/minecraft/world/level/block/ChestBlock.java b/net/minecraft/world/level/block/ChestBlock.java -index d3f0680d59ec87990b54dc3dbce27706bfadf5f1..85d6c53c06245d5692d27650fc5b5f98f4760dfc 100644 +index d3f0680d59ec87990b54dc3dbce27706bfadf5f1..b5595d87339e4d313c5691a06c2b1b3d1b40b6de 100644 --- a/net/minecraft/world/level/block/ChestBlock.java +++ b/net/minecraft/world/level/block/ChestBlock.java -@@ -257,6 +257,14 @@ public class ChestBlock extends AbstractChestBlock implements +@@ -257,6 +257,15 @@ public class ChestBlock extends AbstractChestBlock implements return this.chestCanConnectTo(state) && state.getValue(TYPE) == ChestType.SINGLE ? state.getValue(FACING) : null; } @@ -212,15 +220,16 @@ index d3f0680d59ec87990b54dc3dbce27706bfadf5f1..85d6c53c06245d5692d27650fc5b5f98 + super.onRemove(state, level, pos, newState, isMoving); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/ChiseledBookShelfBlock.java b/net/minecraft/world/level/block/ChiseledBookShelfBlock.java -index 0682acaae12650549eea3c291d8cde7bb17766bd..9dc7edb546eafc60c05f9004205f1ff329c6a46b 100644 +index 0682acaae12650549eea3c291d8cde7bb17766bd..ac12ef0c99c82d771e5275ec7ced7f76c76f5938 100644 --- a/net/minecraft/world/level/block/ChiseledBookShelfBlock.java +++ b/net/minecraft/world/level/block/ChiseledBookShelfBlock.java -@@ -168,6 +168,27 @@ public class ChiseledBookShelfBlock extends BaseEntityBlock implements Selectabl +@@ -168,6 +168,28 @@ public class ChiseledBookShelfBlock extends BaseEntityBlock implements Selectabl SLOT_OCCUPIED_PROPERTIES.forEach(property -> builder.add(property)); } @@ -244,15 +253,16 @@ index 0682acaae12650549eea3c291d8cde7bb17766bd..9dc7edb546eafc60c05f9004205f1ff3 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/CrafterBlock.java b/net/minecraft/world/level/block/CrafterBlock.java -index 2b97fd6af1c4956eb173b8c66c2a3efc8772a798..8f7258e3cb01ae55fd153b051506b54c2db91782 100644 +index 2b97fd6af1c4956eb173b8c66c2a3efc8772a798..90326fe95673edbf5ed8c1a5d9020735fdd4d53e 100644 --- a/net/minecraft/world/level/block/CrafterBlock.java +++ b/net/minecraft/world/level/block/CrafterBlock.java -@@ -130,6 +130,14 @@ public class CrafterBlock extends BaseEntityBlock { +@@ -130,6 +130,15 @@ public class CrafterBlock extends BaseEntityBlock { } } @@ -263,15 +273,16 @@ index 2b97fd6af1c4956eb173b8c66c2a3efc8772a798..8f7258e3cb01ae55fd153b051506b54c + super.onRemove(state, level, pos, newState, movedByPiston); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/CreakingHeartBlock.java b/net/minecraft/world/level/block/CreakingHeartBlock.java -index 3dd5f8c20ad68e0bd6222c8ba1687df0b2ad3b0d..746f140ea7522db879b8af1922be54395741c9f2 100644 +index 3dd5f8c20ad68e0bd6222c8ba1687df0b2ad3b0d..b2e1e78cd70e20dd1c17ef0199059fc2b4e513b5 100644 --- a/net/minecraft/world/level/block/CreakingHeartBlock.java +++ b/net/minecraft/world/level/block/CreakingHeartBlock.java -@@ -152,6 +152,16 @@ public class CreakingHeartBlock extends BaseEntityBlock { +@@ -152,6 +152,17 @@ public class CreakingHeartBlock extends BaseEntityBlock { builder.add(AXIS, STATE, NATURAL); } @@ -284,15 +295,16 @@ index 3dd5f8c20ad68e0bd6222c8ba1687df0b2ad3b0d..746f140ea7522db879b8af1922be5439 + super.onRemove(state, level, pos, newState, movedByPiston); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/DecoratedPotBlock.java b/net/minecraft/world/level/block/DecoratedPotBlock.java -index 3ce41ce3f9d2bf7a79e4b9811e043a69e0dedb13..237d0f609a9343ea067fe0bbe4a80dc9a61d8eff 100644 +index 3ce41ce3f9d2bf7a79e4b9811e043a69e0dedb13..2856b4081cddb38bac5c1d7199649a7531ef077f 100644 --- a/net/minecraft/world/level/block/DecoratedPotBlock.java +++ b/net/minecraft/world/level/block/DecoratedPotBlock.java -@@ -172,6 +172,14 @@ public class DecoratedPotBlock extends BaseEntityBlock implements SimpleWaterlog +@@ -172,6 +172,15 @@ public class DecoratedPotBlock extends BaseEntityBlock implements SimpleWaterlog return new DecoratedPotBlockEntity(worldPosition, blockState); } @@ -303,15 +315,16 @@ index 3ce41ce3f9d2bf7a79e4b9811e043a69e0dedb13..237d0f609a9343ea067fe0bbe4a80dc9 + super.onRemove(state, level, pos, newState, movedByPiston); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/DiodeBlock.java b/net/minecraft/world/level/block/DiodeBlock.java -index 3457a1fa8f5efc4bf173bf155165ab5e5ae9f303..fc1861cffed656ecb0f58e5aef11f85cc40c7785 100644 +index 3457a1fa8f5efc4bf173bf155165ab5e5ae9f303..1342607e3bf28ca085f51ade22efaf34f19da170 100644 --- a/net/minecraft/world/level/block/DiodeBlock.java +++ b/net/minecraft/world/level/block/DiodeBlock.java -@@ -180,6 +180,16 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { +@@ -180,6 +180,17 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { if (me.earthme.luminol.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this instanceof ComparatorBlock && !oldState.is(Blocks.COMPARATOR)) org.leavesmc.leaves.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracking.notifyNearbyBlockEntitiesAboutNewComparator(level, pos); // Leaves - Lithium Sleeping Block Entity } @@ -324,15 +337,16 @@ index 3457a1fa8f5efc4bf173bf155165ab5e5ae9f303..fc1861cffed656ecb0f58e5aef11f85c + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston) { diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java -index dca954bd076fa917512af91e0b93d449eb5cac6d..6e52b01c497357ebd51f319b9d9e3eec2e52a069 100644 +index dca954bd076fa917512af91e0b93d449eb5cac6d..7f880ba7cdd08be392dd4fe5a06fffbf6962318b 100644 --- a/net/minecraft/world/level/block/DispenserBlock.java +++ b/net/minecraft/world/level/block/DispenserBlock.java -@@ -159,6 +159,14 @@ public class DispenserBlock extends BaseEntityBlock { +@@ -159,6 +159,15 @@ public class DispenserBlock extends BaseEntityBlock { return this.defaultBlockState().setValue(FACING, context.getNearestLookingDirection().getOpposite()); } @@ -343,15 +357,16 @@ index dca954bd076fa917512af91e0b93d449eb5cac6d..6e52b01c497357ebd51f319b9d9e3eec + super.onRemove(state, level, pos, newState, isMoving); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/HopperBlock.java b/net/minecraft/world/level/block/HopperBlock.java -index 88606b84ea3a33ffbb0cfa6e073eb8f59d7188d0..413be2cbaf2ca89974f3f6242d6a3499e70fb018 100644 +index 88606b84ea3a33ffbb0cfa6e073eb8f59d7188d0..c3ccebd7f27fd4d313cbcd616d58180a70f5f574 100644 --- a/net/minecraft/world/level/block/HopperBlock.java +++ b/net/minecraft/world/level/block/HopperBlock.java -@@ -139,6 +139,14 @@ public class HopperBlock extends BaseEntityBlock implements org.leavesmc.leaves. +@@ -139,6 +139,15 @@ public class HopperBlock extends BaseEntityBlock implements org.leavesmc.leaves. } } @@ -362,15 +377,16 @@ index 88606b84ea3a33ffbb0cfa6e073eb8f59d7188d0..413be2cbaf2ca89974f3f6242d6a3499 + super.onRemove(state, level, pos, newState, isMoving); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/JukeboxBlock.java b/net/minecraft/world/level/block/JukeboxBlock.java -index a9e46a66ffc868c3b4ef2bb6257f23a20f12b1a6..8f32082ccc49b50d9a1d1048841342ce3817a36f 100644 +index a9e46a66ffc868c3b4ef2bb6257f23a20f12b1a6..cee96a825f0e0e740ede8df0bf6dc5256b9fb8d3 100644 --- a/net/minecraft/world/level/block/JukeboxBlock.java +++ b/net/minecraft/world/level/block/JukeboxBlock.java -@@ -82,6 +82,19 @@ public class JukeboxBlock extends BaseEntityBlock { +@@ -82,6 +82,20 @@ public class JukeboxBlock extends BaseEntityBlock { } } @@ -386,15 +402,16 @@ index a9e46a66ffc868c3b4ef2bb6257f23a20f12b1a6..8f32082ccc49b50d9a1d1048841342ce + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/LecternBlock.java b/net/minecraft/world/level/block/LecternBlock.java -index 75660fb19c25444e35dbbe58cd20f9b3d3f7ad32..7d4ce2d8018e7be813e32a462ee4c33872f9bb4a 100644 +index 75660fb19c25444e35dbbe58cd20f9b3d3f7ad32..196051c80baa489f43065a11a61f22a8c61251e7 100644 --- a/net/minecraft/world/level/block/LecternBlock.java +++ b/net/minecraft/world/level/block/LecternBlock.java -@@ -200,6 +200,36 @@ public class LecternBlock extends BaseEntityBlock { +@@ -200,6 +200,37 @@ public class LecternBlock extends BaseEntityBlock { changePowered(level, pos, state, false); } @@ -427,15 +444,16 @@ index 75660fb19c25444e35dbbe58cd20f9b3d3f7ad32..7d4ce2d8018e7be813e32a462ee4c338 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (state.getValue(POWERED)) { diff --git a/net/minecraft/world/level/block/LeverBlock.java b/net/minecraft/world/level/block/LeverBlock.java -index 341960ef67a689f660d0dcf61aaf5bf52bdc9074..341acc65fb2ffa45d9ad90fa15cf4e972e68a95e 100644 +index 341960ef67a689f660d0dcf61aaf5bf52bdc9074..6fc372e782f2a34a34a9ef1faed41ace6db5ec1b 100644 --- a/net/minecraft/world/level/block/LeverBlock.java +++ b/net/minecraft/world/level/block/LeverBlock.java -@@ -127,6 +127,19 @@ public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock { +@@ -127,6 +127,20 @@ public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock { } } @@ -451,15 +469,16 @@ index 341960ef67a689f660d0dcf61aaf5bf52bdc9074..341acc65fb2ffa45d9ad90fa15cf4e97 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston && state.getValue(POWERED)) { diff --git a/net/minecraft/world/level/block/LightningRodBlock.java b/net/minecraft/world/level/block/LightningRodBlock.java -index 47ca45518df306cc4368983eefb89294e56b015a..c440a04864fae43f94185e02bb8c3c33abf01a5c 100644 +index 47ca45518df306cc4368983eefb89294e56b015a..31be8864aa28eff804b507efdab614b35e3fea6f 100644 --- a/net/minecraft/world/level/block/LightningRodBlock.java +++ b/net/minecraft/world/level/block/LightningRodBlock.java -@@ -123,6 +123,18 @@ public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBloc +@@ -123,6 +123,19 @@ public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBloc } } @@ -474,15 +493,16 @@ index 47ca45518df306cc4368983eefb89294e56b015a..c440a04864fae43f94185e02bb8c3c33 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (state.getValue(POWERED)) { diff --git a/net/minecraft/world/level/block/ObserverBlock.java b/net/minecraft/world/level/block/ObserverBlock.java -index 6a3413ca875ce78f48cd8d3ce2cfe7798be1a19e..c492a770cedf976949434d700c943fcd8d4b4199 100644 +index 6a3413ca875ce78f48cd8d3ce2cfe7798be1a19e..be572eff561ffdd961a9602eaa6136761c4d48a4 100644 --- a/net/minecraft/world/level/block/ObserverBlock.java +++ b/net/minecraft/world/level/block/ObserverBlock.java -@@ -128,6 +128,17 @@ public class ObserverBlock extends DirectionalBlock { +@@ -128,6 +128,18 @@ public class ObserverBlock extends DirectionalBlock { } } @@ -496,15 +516,16 @@ index 6a3413ca875ce78f48cd8d3ce2cfe7798be1a19e..c492a770cedf976949434d700c943fcd + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (state.getValue(POWERED) && level.getBlockTicks().hasScheduledTick(pos, this)) { diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java -index 66a05f099a263b0c9220f1416f3cf1221422cc20..eb7b8d971185764230d575af1f54537b48526548 100644 +index 66a05f099a263b0c9220f1416f3cf1221422cc20..1de86856f4a27346d7003ca9eb24a36074a2a4a3 100644 --- a/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -378,6 +378,27 @@ public class RedStoneWireBlock extends Block { +@@ -378,6 +378,28 @@ public class RedStoneWireBlock extends Block { } } @@ -528,15 +549,16 @@ index 66a05f099a263b0c9220f1416f3cf1221422cc20..eb7b8d971185764230d575af1f54537b + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston) { diff --git a/net/minecraft/world/level/block/RedstoneTorchBlock.java b/net/minecraft/world/level/block/RedstoneTorchBlock.java -index 0d0b5443c5c56825e88fd64c02c993eca6044d45..23137a24794fbe1536e1c47bc783ed211fc1042f 100644 +index 0d0b5443c5c56825e88fd64c02c993eca6044d45..e2e5552008547fa1782b0a5ed3129162e94647c9 100644 --- a/net/minecraft/world/level/block/RedstoneTorchBlock.java +++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java -@@ -54,6 +54,15 @@ public class RedstoneTorchBlock extends BaseTorchBlock { +@@ -54,6 +54,16 @@ public class RedstoneTorchBlock extends BaseTorchBlock { } } @@ -548,15 +570,16 @@ index 0d0b5443c5c56825e88fd64c02c993eca6044d45..23137a24794fbe1536e1c47bc783ed21 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (!movedByPiston) { diff --git a/net/minecraft/world/level/block/SculkSensorBlock.java b/net/minecraft/world/level/block/SculkSensorBlock.java -index 2bbce50d9dd0ca65f9e3bc90c8de098473039bc5..94ec05d2c6338464f4912cc1c919165c080f8cde 100644 +index 2bbce50d9dd0ca65f9e3bc90c8de098473039bc5..9a9fcb593c4b5f2bb0c57f9268dfdc9eb21d38ea 100644 --- a/net/minecraft/world/level/block/SculkSensorBlock.java +++ b/net/minecraft/world/level/block/SculkSensorBlock.java -@@ -129,6 +129,18 @@ public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterlogg +@@ -129,6 +129,19 @@ public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterlogg } } @@ -571,15 +594,16 @@ index 2bbce50d9dd0ca65f9e3bc90c8de098473039bc5..94ec05d2c6338464f4912cc1c919165c + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { if (getPhase(state) == SculkSensorPhase.ACTIVE) { diff --git a/net/minecraft/world/level/block/SculkShriekerBlock.java b/net/minecraft/world/level/block/SculkShriekerBlock.java -index d7e5ec23c64b60c47ab6b54cbf282e88c3a4a045..6858d2721a7e5bad21756bacd90255e3c817f931 100644 +index d7e5ec23c64b60c47ab6b54cbf282e88c3a4a045..037aa3592a963bbb142465d31040f60e95cb146e 100644 --- a/net/minecraft/world/level/block/SculkShriekerBlock.java +++ b/net/minecraft/world/level/block/SculkShriekerBlock.java -@@ -68,6 +68,16 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo +@@ -68,6 +68,17 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo super.stepOn(level, pos, onState, entity); } @@ -592,15 +616,16 @@ index d7e5ec23c64b60c47ab6b54cbf282e88c3a4a045..6858d2721a7e5bad21756bacd90255e3 + super.onRemove(state, level, pos, newState, movedByPiston); + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void tick(final BlockState state, final ServerLevel level, final BlockPos pos, final RandomSource random) { if (state.getValue(SHRIEKING)) { diff --git a/net/minecraft/world/level/block/ShulkerBoxBlock.java b/net/minecraft/world/level/block/ShulkerBoxBlock.java -index ffa76dc1338c6c892a42b2148f9f01c933660b98..2957008ce8fc8c39a7c13637dad42784f230f575 100644 +index ffa76dc1338c6c892a42b2148f9f01c933660b98..7bf2cde59c112f4babb87476585207dacebef341 100644 --- a/net/minecraft/world/level/block/ShulkerBoxBlock.java +++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java -@@ -150,6 +150,19 @@ public class ShulkerBoxBlock extends BaseEntityBlock { +@@ -150,6 +150,20 @@ public class ShulkerBoxBlock extends BaseEntityBlock { // Paper end - re-set loot table if it was cleared } @@ -616,39 +641,38 @@ index ffa76dc1338c6c892a42b2148f9f01c933660b98..2957008ce8fc8c39a7c13637dad42784 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); diff --git a/net/minecraft/world/level/block/TripWireBlock.java b/net/minecraft/world/level/block/TripWireBlock.java -index 04e5995409a3532947dfab1c8ef05bd546a6f66d..961995694172658716309e2695f9a034fe058b06 100644 +index 04e5995409a3532947dfab1c8ef05bd546a6f66d..6bf811477a27bbbeeeba6c9d768d33fc677ea5e7 100644 --- a/net/minecraft/world/level/block/TripWireBlock.java +++ b/net/minecraft/world/level/block/TripWireBlock.java -@@ -107,9 +107,18 @@ public class TripWireBlock extends Block { +@@ -107,6 +107,17 @@ public class TripWireBlock extends Block { } } + // Leaves start - behaviour 1.21.1- - @Override -- protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { ++ @Override + protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { - if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating + if (!isMoving && !state.is(newState.getBlock())) { + this.updateSource(level, pos, state.setValue(POWERED, Boolean.TRUE)); + } + } + // Leaves end - behaviour 1.21.1- + -+ @Override -+ protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { - if (!movedByPiston) { - this.updateSource(level, pos, state.setValue(POWERED, true)); - } ++ + @Override + protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { + if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating diff --git a/net/minecraft/world/level/block/TripWireHookBlock.java b/net/minecraft/world/level/block/TripWireHookBlock.java -index fd808353181e05f2fc527a391ce7bb990f61b4c7..e070b2781ce15c754a678eb76de4c0248b0ddec7 100644 +index fd808353181e05f2fc527a391ce7bb990f61b4c7..836eae0c3e0891a3e3309b684f15d65f891215cf 100644 --- a/net/minecraft/world/level/block/TripWireHookBlock.java +++ b/net/minecraft/world/level/block/TripWireHookBlock.java -@@ -248,6 +248,25 @@ public class TripWireHookBlock extends Block { +@@ -248,6 +248,26 @@ public class TripWireHookBlock extends Block { level.updateNeighborsAt(pos.relative(front), block, orientation); } @@ -670,6 +694,7 @@ index fd808353181e05f2fc527a391ce7bb990f61b4c7..e070b2781ce15c754a678eb76de4c024 + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { @@ -688,10 +713,10 @@ index ac58bbed01edaf5dfe4839c89649ca865c339c8a..caead1012ff3b5b5f78c24a3baf20b7d @Deprecated diff --git a/net/minecraft/world/level/block/piston/MovingPistonBlock.java b/net/minecraft/world/level/block/piston/MovingPistonBlock.java -index 2b6ad989e1e1424d0283bae39e5dae33eba44923..c72a14a8c3d9fe4430cfde671f29688c696f983a 100644 +index 2b6ad989e1e1424d0283bae39e5dae33eba44923..0f47fb528422d591bd03434a089ea69c7f27bf8c 100644 --- a/net/minecraft/world/level/block/piston/MovingPistonBlock.java +++ b/net/minecraft/world/level/block/piston/MovingPistonBlock.java -@@ -70,6 +70,18 @@ public class MovingPistonBlock extends BaseEntityBlock { +@@ -70,6 +70,19 @@ public class MovingPistonBlock extends BaseEntityBlock { return createTickerHelper(type, BlockEntityType.PISTON, PistonMovingBlockEntity::tick); } @@ -706,15 +731,16 @@ index 2b6ad989e1e1424d0283bae39e5dae33eba44923..c72a14a8c3d9fe4430cfde671f29688c + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override public void destroy(final LevelAccessor level, final BlockPos pos, final BlockState state) { BlockPos relative = pos.relative(state.getValue(FACING).getOpposite()); diff --git a/net/minecraft/world/level/block/piston/PistonHeadBlock.java b/net/minecraft/world/level/block/piston/PistonHeadBlock.java -index f8caa0b3c21766dbad73c524c1680346e4257188..343660d3e5fbdc25aba84a5cacbc367d40533848 100644 +index f8caa0b3c21766dbad73c524c1680346e4257188..b658d602953ade618d89c02745eb46be5d0175dd 100644 --- a/net/minecraft/world/level/block/piston/PistonHeadBlock.java +++ b/net/minecraft/world/level/block/piston/PistonHeadBlock.java -@@ -78,6 +78,19 @@ public class PistonHeadBlock extends DirectionalBlock { +@@ -78,6 +78,20 @@ public class PistonHeadBlock extends DirectionalBlock { return super.playerWillDestroy(level, pos, state, player); } @@ -730,15 +756,16 @@ index f8caa0b3c21766dbad73c524c1680346e4257188..343660d3e5fbdc25aba84a5cacbc367d + } + } + // Leaves end - behaviour 1.21.1- ++ + @Override protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { BlockPos basePos = pos.relative(state.getValue(FACING).getOpposite()); diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java -index 0fbdf810c785c8a596d461e7651361fdedbb6217..66f3fbe18451644be939b22342fc22592005d252 100644 +index 0fbdf810c785c8a596d461e7651361fdedbb6217..ff1471f40b73582df68efbf2996a854c6aa37d65 100644 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -174,6 +174,15 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea +@@ -174,6 +174,16 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot } @@ -750,11 +777,12 @@ index 0fbdf810c785c8a596d461e7651361fdedbb6217..66f3fbe18451644be939b22342fc2259 + } + } + // Leaves end - behaviour 1.21.1- ++ + protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { } -@@ -889,6 +898,12 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea +@@ -889,6 +899,13 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea // CraftBukkit end } @@ -763,6 +791,7 @@ index 0fbdf810c785c8a596d461e7651361fdedbb6217..66f3fbe18451644be939b22342fc2259 + this.getBlock().onRemove(this.asState(), level, pos, newState, movedByPiston); + } + // Leaves end - behaviour 1.21.1- ++ + public void affectNeighborsAfterRemoval(final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { this.getBlock().affectNeighborsAfterRemoval(this.asState(), level, pos, movedByPiston); diff --git a/lophine-server/minecraft-patches/features/0042-Carpet-features.patch b/lophine-server/minecraft-patches/features/0042-Carpet-features.patch new file mode 100644 index 0000000..c799647 --- /dev/null +++ b/lophine-server/minecraft-patches/features/0042-Carpet-features.patch @@ -0,0 +1,1145 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bacteriawa +Date: Sun, 22 Mar 2026 01:39:15 +0800 +Subject: [PATCH] Carpet features + + +diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java +index 96423a1a429f4fb1c975ad10726fe0e1ff70a6ad..fbeff565c204372846b63fac61f9e0e310522a67 100644 +--- a/io/papermc/paper/threadedregions/RegionizedServer.java ++++ b/io/papermc/paper/threadedregions/RegionizedServer.java +@@ -337,6 +337,7 @@ public final class RegionizedServer { + } + this.randomWalk(); + */ ++ final long tickStartNanos = System.nanoTime(); + ++this.tickCount; + // Luminol start - Add a config to enable tick command + if (fun.bm.lophine.config.modules.experiment.CommandConfig.tick) { +@@ -378,6 +379,8 @@ public final class RegionizedServer { + } + // Luminol end - Add a config to enable tick command + ++ fun.bm.lophine.protocol.tiscm.TISCMProtocol.broadcastMsptSample(this.tickCount, System.nanoTime() - tickStartNanos); ++ + // tick connections + this.tickConnections(); + +diff --git a/net/minecraft/commands/arguments/blocks/BlockInput.java b/net/minecraft/commands/arguments/blocks/BlockInput.java +index a4b38651340aff56bb3e3ffd85dea0da538a7532..ed0800548e008142ec94875b0de4c4a8ed3e9d26 100644 +--- a/net/minecraft/commands/arguments/blocks/BlockInput.java ++++ b/net/minecraft/commands/arguments/blocks/BlockInput.java +@@ -65,7 +65,9 @@ public class BlockInput implements Predicate { + } + + public boolean place(final ServerLevel level, final BlockPos pos, @Block.UpdateFlags final int update) { +- BlockState state = (update & Block.UPDATE_KNOWN_SHAPE) != 0 ? this.state : Block.updateFromNeighbourShapes(this.state, level, pos); ++ BlockState state = (update & Block.UPDATE_KNOWN_SHAPE) != 0 || fun.bm.lophine.carpet.InteractionUpdateCompatHelper.shouldSkipUpdates() ++ ? this.state ++ : Block.updateFromNeighbourShapes(this.state, level, pos); + if (state.isAir()) { + state = this.state; + } +diff --git a/net/minecraft/core/Direction.java b/net/minecraft/core/Direction.java +index 240fb1346b721e4aaf8517b01fa205760cee4bbd..327d6c7e0f18a88f8c9b9cfc9a972266db03842d 100644 +--- a/net/minecraft/core/Direction.java ++++ b/net/minecraft/core/Direction.java +@@ -136,7 +136,7 @@ public enum Direction implements StringRepresentable, ca.spottedleaf.moonrise.pa + + public static Direction[] orderedByNearest(final Entity entity) { + float pitch = entity.getViewXRot(1.0F) * Mth.DEG_TO_RAD; +- float yaw = -entity.getViewYRot(1.0F) * Mth.DEG_TO_RAD; ++ float yaw = -(fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.placementRotationFix ? entity.getYRot(1.0F) : entity.getViewYRot(1.0F)) * Mth.DEG_TO_RAD; + float pitchSin = Mth.sin(pitch); + float pitchCos = Mth.cos(pitch); + float yawSin = Mth.sin(yaw); +diff --git a/net/minecraft/network/chat/SignedMessageValidator.java b/net/minecraft/network/chat/SignedMessageValidator.java +index 5cb693319169dbd95d87ffdc450bf60bab4021aa..4095c0a6bf24a734945cbf3011271e1a9d0c39f7 100644 +--- a/net/minecraft/network/chat/SignedMessageValidator.java ++++ b/net/minecraft/network/chat/SignedMessageValidator.java +@@ -31,7 +31,9 @@ public interface SignedMessageValidator { + private boolean validateChain(final PlayerChatMessage message) { + if (message.equals(this.lastMessage)) { + return true; +- } else if (this.lastMessage != null && !message.link().isDescendantOf(this.lastMessage.link())) { ++ } else if (this.lastMessage != null ++ && !fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.yeetOutOfOrderChatKick ++ && !message.link().isDescendantOf(this.lastMessage.link())) { + LOGGER.error( + "Received out-of-order chat message from {}: expected index > {} for session {}, but was {} for session {}", + message.sender(), +diff --git a/net/minecraft/server/commands/TickCommand.java b/net/minecraft/server/commands/TickCommand.java +index c3dcfb633877ef230ab48a455ad4f5a1d601ba48..445d55fc0bfe6c8998b983f131c263ad16b71266 100644 +--- a/net/minecraft/server/commands/TickCommand.java ++++ b/net/minecraft/server/commands/TickCommand.java +@@ -20,7 +20,7 @@ public class TickCommand { + public static void register(final CommandDispatcher dispatcher) { + dispatcher.register( + Commands.literal("tick") +- .requires(Commands.hasPermission(Commands.LEVEL_ADMINS)) ++ .requires(Commands.hasPermission(tickCommandPermissionCheck())) + .then(Commands.literal("query").executes(c -> tickQuery(c.getSource()))) + .then( + Commands.literal("rate") +@@ -106,8 +106,12 @@ public class TickCommand { + return 1; + } + +- private static int setFreeze(final CommandSourceStack source, final boolean freeze) { ++ private static int setFreeze(final CommandSourceStack source, boolean freeze) { + ServerTickRateManager manager = source.getServer().tickRateManager(); ++ if (freeze && fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.tickFreezeCommandToggleable && manager.isFrozen()) { ++ freeze = false; ++ } ++ + if (freeze) { + if (manager.isSprinting()) { + manager.stopSprinting(); +@@ -163,4 +167,8 @@ public class TickCommand { + return 0; + } + } ++ ++ private static net.minecraft.server.permissions.PermissionCheck tickCommandPermissionCheck() { ++ return new net.minecraft.server.permissions.PermissionCheck.Require(new net.minecraft.server.permissions.Permission.HasCommandLevel(net.minecraft.server.permissions.PermissionLevel.byId(fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.normalizedTickCommandPermission()))); ++ } + } +diff --git a/net/minecraft/server/dedicated/DedicatedPlayerList.java b/net/minecraft/server/dedicated/DedicatedPlayerList.java +index 1a2b57a100c997b39f8f25ad3da121e04700d5d2..be345feecfa5b4e27a7b8a88ab5a5e5b18f23cf0 100644 +--- a/net/minecraft/server/dedicated/DedicatedPlayerList.java ++++ b/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -14,7 +14,7 @@ public class DedicatedPlayerList extends PlayerList { + + public DedicatedPlayerList(final DedicatedServer server, final LayeredRegistryAccess registries, final PlayerDataStorage playerDataStorage) { + super(server, registries, playerDataStorage, server.notificationManager()); +- this.setViewDistance(server.viewDistance()); ++ this.setViewDistance(Math.max(2, fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.viewDistance)); + this.setSimulationDistance(server.simulationDistance()); + // Paper start - fix converting txt to json file; moved from constructor + } +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 91ec7f6b751159f2935165268166f44d27579426..6e529edc0be2a090f1068384e59817f6a05ba1f9 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -259,6 +259,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + private int requestedViewDistance = 2; + public String language = null; // Paper - default to null + public java.util.Locale adventure$locale = java.util.Locale.US; // Paper ++ private @Nullable ClientInformation lastKnownClientInformation; + private @Nullable Vec3 startingToFallPosition; + private @Nullable Vec3 enteredNetherPosition; + private @Nullable Vec3 enteredLavaOnVehiclePosition; +@@ -2828,6 +2829,9 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + this.setShoulderEntityRight(oldPlayer.getShoulderEntityRight()); + this.setLastDeathLocation(oldPlayer.getLastDeathLocation()); + this.waypointIcon().copyFrom(oldPlayer.waypointIcon()); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.clientSettingsLostOnRespawnFix && oldPlayer.lastKnownClientInformation != null) { ++ this.updateOptions(oldPlayer.lastKnownClientInformation); ++ } + } + + private void transferInventoryXpAndScore(final Player oldPlayer) { +@@ -3111,6 +3115,9 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + this.particleStatus = information.particleStatus(); + this.getEntityData().set(DATA_PLAYER_MODE_CUSTOMISATION, (byte)information.modelCustomisation()); + this.getEntityData().set(DATA_PLAYER_MAIN_HAND, information.mainHand()); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.clientSettingsLostOnRespawnFix) { ++ this.lastKnownClientInformation = information; ++ } + } + + public ClientInformation clientInformation() { +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 57aff87cc7be0cb7136689412490cd399eff15eb..5a902f36f9157694be4ee65b7687c43f215ae041 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2099,7 +2099,13 @@ public class ServerGamePacketListenerImpl + this.player.gameMode.capturedBlockEntity = false; + this.player.gameMode.captureSentBlockEntities = true; + // Paper end - Send block entities after destroy prediction +- this.player.gameMode.handleBlockBreakAction(pos, action, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.interactionUpdates) { ++ this.player.gameMode.handleBlockBreakAction(pos, action, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); ++ } else { ++ fun.bm.lophine.carpet.InteractionUpdateCompatHelper.runWithSuppressedUpdates( ++ () -> this.player.gameMode.handleBlockBreakAction(pos, action, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()) ++ ); ++ } + this.ackBlockChangesUpTo(packet.getSequence()); + // Paper start - Send block entities after destroy prediction + this.player.gameMode.captureSentBlockEntities = false; +@@ -2202,7 +2208,9 @@ public class ServerGamePacketListenerImpl + if (!me.earthme.luminol.config.modules.fixes.ItemMultitaskConfig.enabled) { // Luminol - Add item multitask config + this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 + } // Luminol - Add item multitask config +- InteractionResult interactionResult = this.player.gameMode.useItemOn(this.player, level, itemStack, hand, blockHit); ++ InteractionResult interactionResult = this.runInteractionWithConfiguredUpdates( ++ () -> this.player.gameMode.useItemOn(this.player, level, itemStack, hand, blockHit) ++ ); + if (interactionResult.consumesAction()) { + CriteriaTriggers.ANY_BLOCK_USE.trigger(this.player, blockHit.getBlockPos(), itemStack); + } +@@ -2307,7 +2315,8 @@ public class ServerGamePacketListenerImpl + } + // CraftBukkit end + +- if (this.player.gameMode.useItem(this.player, level, itemStack, hand) instanceof InteractionResult.Success success ++ ItemStack itemStackToUse = itemStack; ++ if (this.runInteractionWithConfiguredUpdates(() -> this.player.gameMode.useItem(this.player, level, itemStackToUse, hand)) instanceof InteractionResult.Success success + && success.swingSource() == InteractionResult.SwingSource.SERVER) { + this.player.swing(hand, true); + } +@@ -2315,6 +2324,14 @@ public class ServerGamePacketListenerImpl + } + } + ++ private T runInteractionWithConfiguredUpdates(final java.util.function.Supplier action) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.interactionUpdates) { ++ return action.get(); ++ } ++ ++ return fun.bm.lophine.carpet.InteractionUpdateCompatHelper.supplyWithSuppressedUpdates(action); ++ } ++ + @Override + public void handleTeleportToEntityPacket(final ServerboundTeleportToEntityPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); +@@ -2695,6 +2712,8 @@ public class ServerGamePacketListenerImpl + if (true) throw new UnsupportedOperationException(); // Folia - region threading + final String conversationInput = rawMessage; + this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput)); ++ } else if (fun.bm.lophine.carpet.CarpetCalculatorCompatHelper.handleChat(this.player, rawMessage)) { ++ return; + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check + this.send(new ClientboundSystemChatPacket(Component.translatable("chat.cannotSend").withStyle(ChatFormatting.RED), false)); + } else { +@@ -2707,6 +2726,9 @@ public class ServerGamePacketListenerImpl + + // Spigot start - spam exclusions + private void detectRateSpam(String message) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.antiSpamDisabled) { ++ return; ++ } + if (me.earthme.luminol.config.modules.misc.PaperPacketLimiterConfig.forceDisable) return; // Leaves - disable + // CraftBukkit start - replaced with thread safe throttle + if (org.spigotmc.SpigotConfig.enableSpamExclusions) { +@@ -3642,8 +3664,10 @@ public class ServerGamePacketListenerImpl + if (org.leavesmc.leaves.util.ItemOverstackUtils.hasOverstackingItem()) this.player.containerMenu.sendSingleSlot(packet.slotNum(), itemStack); // Leaves - item over-stack util - force send carried item + if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes. + } else if (drop && validData) { +- if (this.dropSpamThrottler.isUnderThreshold()) { +- this.dropSpamThrottler.increment(); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.antiSpamDisabled || this.dropSpamThrottler.isUnderThreshold()) { ++ if (!fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.antiSpamDisabled) { ++ this.dropSpamThrottler.increment(); ++ } + this.player.drop(itemStack, true); + } else { + LOGGER.warn("Player {} was dropping items too fast in creative mode, ignoring.", this.player.getPlainTextName()); +diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java +index 2312803297321f66c9057bc54ce56dd17ecb6f1f..5327a5e33147cde1b0b26083c14433d9e6e5b575 100644 +--- a/net/minecraft/world/entity/ExperienceOrb.java ++++ b/net/minecraft/world/entity/ExperienceOrb.java +@@ -351,8 +351,23 @@ public class ExperienceOrb extends Entity { + @Override + public void playerTouch(final Player player) { + if (player instanceof ServerPlayer serverPlayer) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.xpNoCooldown) { ++ player.takeXpDelay = 0; ++ } ++ + if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent +- player.takeXpDelay = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(player, 2, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entity.takeXpDelay = 2; ++ int newCooldown = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(player, 2, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entity.takeXpDelay = 2; ++ player.takeXpDelay = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.xpNoCooldown ? 0 : newCooldown; ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.xpNoCooldown) { ++ while (this.count > 1) { ++ int remaining = this.repairPlayerItems(serverPlayer, this.getValue()); ++ if (remaining > 0) { ++ player.giveExperiencePoints(org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerExpChangeEvent(player, this, remaining).getAmount()); // CraftBukkit - remaining -> event.getAmount() // Paper - supply experience orb ++ } ++ this.count--; ++ } ++ } ++ + player.take(this, 1); + int remaining = this.repairPlayerItems(serverPlayer, this.getValue()); + if (remaining > 0) { +@@ -368,6 +383,10 @@ public class ExperienceOrb extends Entity { + } + + private int repairPlayerItems(final ServerPlayer player, final int amount) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.powerfulExpMending) { ++ return this.repairAllPlayerItems(player, amount); ++ } ++ + Optional selected = EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); + if (selected.isPresent()) { + ItemStack itemStack = selected.get().itemStack(); +@@ -397,6 +416,76 @@ public class ExperienceOrb extends Entity { + } + } + ++ private int repairAllPlayerItems(ServerPlayer player, int value) { ++ List candidates = new java.util.ArrayList<>(); ++ net.minecraft.world.entity.player.Inventory inventory = player.getInventory(); ++ ++ for (int slotIndex = 0; slotIndex < inventory.getNonEquipmentItems().size(); slotIndex++) { ++ ItemStack stack = inventory.getNonEquipmentItems().get(slotIndex); ++ EquipmentSlot slot = slotIndex == inventory.getSelectedSlot() ? EquipmentSlot.MAINHAND : null; ++ candidates.add(new MendingCandidate(stack, slot)); ++ } ++ ++ candidates.add(new MendingCandidate(player.getItemBySlot(EquipmentSlot.HEAD), EquipmentSlot.HEAD)); ++ candidates.add(new MendingCandidate(player.getItemBySlot(EquipmentSlot.CHEST), EquipmentSlot.CHEST)); ++ candidates.add(new MendingCandidate(player.getItemBySlot(EquipmentSlot.LEGS), EquipmentSlot.LEGS)); ++ candidates.add(new MendingCandidate(player.getItemBySlot(EquipmentSlot.FEET), EquipmentSlot.FEET)); ++ candidates.add(new MendingCandidate(player.getItemBySlot(EquipmentSlot.OFFHAND), EquipmentSlot.OFFHAND)); ++ ++ java.util.Collections.shuffle(candidates, new java.util.Random(player.getRandom().nextLong())); ++ ++ int remaining = value; ++ for (MendingCandidate candidate : candidates) { ++ if (remaining <= 0) { ++ break; ++ } ++ ++ ItemStack stack = candidate.stack(); ++ if (!this.canRepairWithXp(player, stack)) { ++ continue; ++ } ++ ++ int durabilityToRepair = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.level(), stack, remaining); ++ int repairedAmount = Math.min(durabilityToRepair, stack.getDamageValue()); ++ if (repairedAmount <= 0) { ++ continue; ++ } ++ ++ org.bukkit.event.player.PlayerItemMendEvent event = candidate.slot() != null ++ ? org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent( ++ player, this, stack, candidate.slot(), repairedAmount, repairedAmount * remaining / durabilityToRepair ++ ) ++ : this.callInventoryItemMendEvent(player, stack, repairedAmount); ++ repairedAmount = event.getRepairAmount(); ++ if (event.isCancelled() || repairedAmount <= 0) { ++ continue; ++ } ++ ++ stack.setDamageValue(stack.getDamageValue() - repairedAmount); ++ remaining -= repairedAmount * remaining / durabilityToRepair; ++ } ++ ++ return remaining; ++ } ++ ++ private boolean canRepairWithXp(ServerPlayer player, ItemStack stack) { ++ return !stack.isEmpty() && stack.isDamaged() && EnchantmentHelper.modifyDurabilityToRepairFromXp(player.level(), stack, 1) > 1; ++ } ++ ++ private org.bukkit.event.player.PlayerItemMendEvent callInventoryItemMendEvent(ServerPlayer player, ItemStack stack, int repairAmount) { ++ org.bukkit.event.player.PlayerItemMendEvent event = new org.bukkit.event.player.PlayerItemMendEvent( ++ player.getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), ++ (org.bukkit.entity.ExperienceOrb)this.getBukkitEntity(), ++ repairAmount ++ ); ++ player.level().getCraftServer().getPluginManager().callEvent(event); ++ return event; ++ } ++ ++ private record MendingCandidate(ItemStack stack, @Nullable EquipmentSlot slot) { ++ } ++ + public int getValue() { + return this.entityData.get(DATA_VALUE); + } +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 167855ad90d0c168a6d49f078ee3409ab84d4bf3..15defe6c740ab7fb3bccc4fb421c0411a4e6a646 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -601,7 +601,8 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + public boolean broadcastedDeath = false; // Folia - region threading + protected void tickDeath() { + this.deathTime++; +- if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) { ++ int removalDelay = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.entityInstantDeathRemoval ? 1 : 20; ++ if (this.deathTime >= removalDelay && !this.level().isClientSide() && !this.isRemoved()) { + this.level().broadcastEntityEvent(this, EntityEvent.POOF); this.broadcastedDeath = true; // Folia - region threading - death has been broadcasted + if (!(this instanceof ServerPlayer)) this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause // Folia - region threading - don't remove, we want the tick scheduler to be running + if ((this instanceof ServerPlayer)) this.unRide(); // Folia - region threading - unmount player when dead +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index 42aff39d537aa6b138bc4a76304d49f423212acd..475e5abeeb29fce0f8ae0abc127f2023300ca186 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -70,8 +70,12 @@ public class PrimedTnt extends Entity implements TraceableEntity { + this(EntityType.TNT, level); + this.setPos(x, y, z); + double rot = this.getRandom().nextDouble() * (float) (Math.PI * 2); // Paper - Don't use level random in entity constructors +- this.setDeltaMovement(-Math.sin(rot) * 0.02, 0.2F, -Math.cos(rot) * 0.02); +- this.setFuse(80); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.tntPrimerMomentumRemoved) { ++ this.setDeltaMovement(0.0, 0.20000000298023224, 0.0); ++ } else { ++ this.setDeltaMovement(-Math.sin(rot) * 0.02, 0.2F, -Math.cos(rot) * 0.02); ++ } ++ this.setFuse(getConfiguredFuseTime()); + this.xo = x; + this.yo = y; + this.zo = z; +@@ -80,7 +84,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + + @Override + protected void defineSynchedData(final SynchedEntityData.Builder entityData) { +- entityData.define(DATA_FUSE_ID, 80); ++ entityData.define(DATA_FUSE_ID, getConfiguredFuseTime()); + entityData.define(DATA_BLOCK_STATE_ID, DEFAULT_BLOCK_STATE); + } + +@@ -177,7 +181,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + + @Override + protected void readAdditionalSaveData(final ValueInput input) { +- this.setFuse(input.getShortOr("fuse", (short)80)); ++ this.setFuse(input.getShortOr("fuse", (short)getConfiguredFuseTime())); + this.setBlockState(input.read("block_state", BlockState.CODEC).orElse(DEFAULT_BLOCK_STATE)); + this.explosionPower = Mth.clamp(input.getFloatOr("explosion_power", 4.0F), 0.0F, 128.0F); + this.owner = EntityReference.read(input, "owner"); +@@ -200,6 +204,10 @@ public class PrimedTnt extends Entity implements TraceableEntity { + this.entityData.set(DATA_FUSE_ID, time); + } + ++ private static int getConfiguredFuseTime() { ++ return fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.normalizedTntFuseDuration(); ++ } ++ + public int getFuse() { + return this.entityData.get(DATA_FUSE_ID); + } +diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java +index 55a7cac408614d0efdece87a5380bd1fb7406200..0e0fc81e72b1f4950b9072d4b05d6b4b41fee04a 100644 +--- a/net/minecraft/world/entity/monster/EnderMan.java ++++ b/net/minecraft/world/entity/monster/EnderMan.java +@@ -635,7 +635,7 @@ public class EnderMan extends Monster implements NeutralMob { + Vec3 to = new Vec3(xt + 0.5, yt + 0.5, zt + 0.5); + BlockHitResult result = level.clip(new ClipContext(from, to, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman)); + boolean reachable = result.getBlockPos().equals(pos); +- if (blockState.is(BlockTags.ENDERMAN_HOLDABLE) && reachable) { ++ if (canPickupBlock(blockState) && reachable) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, pos, blockState.getFluidState().createLegacyBlock())) { // Paper - Place event + level.removeBlock(pos, false); + level.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(this.enderman, blockState)); +@@ -643,5 +643,13 @@ public class EnderMan extends Monster implements NeutralMob { + } // CraftBukkit + } + } ++ ++ private static boolean canPickupBlock(BlockState state) { ++ if (!fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.sensibleEnderman) { ++ return state.is(BlockTags.ENDERMAN_HOLDABLE); ++ } ++ ++ return state.is(Blocks.MELON) || state.is(Blocks.PUMPKIN); ++ } + } + } +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index 7d514537ee38f9b9366692bc969ff503d883aa16..da0c648cafec4cb4fe1bbb31037204b180fcf4ce 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -555,7 +555,13 @@ public abstract class Player extends Avatar implements ContainerUser { + } + + if (!orbs.isEmpty()) { +- this.touch(Util.getRandom(orbs, this.random)); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.xpNoCooldown) { ++ for (Entity orb : orbs) { ++ this.touch(orb); ++ } ++ } else { ++ this.touch(Util.getRandom(orbs, this.random)); ++ } + } + } + +@@ -1066,6 +1072,26 @@ public abstract class Player extends Avatar implements ContainerUser { + + if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. + // Paper end - PlayerAttackEntityEvent ++ if (this.level() instanceof ServerLevel serverLevel ++ && fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.creativeOneHitKill ++ && this.isCreative() ++ && net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity)) { ++ if (this.isShiftKeyDown()) { ++ for (Entity nearbyEntity : this.level().getEntities( ++ this, ++ entity.getBoundingBox().inflate(2.0, 0.5, 2.0), ++ candidate -> candidate.isAttackable() && net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(candidate) ++ )) { ++ this.creativeInstantKill(serverLevel, nearbyEntity); ++ } ++ this.playServerSideSound(SoundEvents.PLAYER_ATTACK_SWEEP); ++ } else { ++ this.creativeInstantKill(serverLevel, entity); ++ this.playServerSideSound(SoundEvents.PLAYER_ATTACK_CRIT); ++ } ++ return; ++ } ++ + float baseDamage = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE); + ItemStack attackingItemStack = this.getWeaponItem(); + DamageSource damageSource = this.createAttackSource(attackingItemStack); final DamageSource dmgSourceFinal = damageSource; // Paper - damage events +@@ -1123,6 +1149,16 @@ public abstract class Player extends Avatar implements ContainerUser { + } + } + ++ private void creativeInstantKill(final ServerLevel level, final Entity target) { ++ if (target instanceof EnderDragonPart enderDragonPart) { ++ java.util.Arrays.stream(enderDragonPart.parentMob.getSubEntities()).forEach(part -> part.kill(level)); ++ enderDragonPart.parentMob.kill(level); ++ return; ++ } ++ ++ target.kill(level); ++ } ++ + private void playServerSideSound(final SoundEvent sound) { + sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), sound, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } +diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java +index ca3f31a72060f797115787302d32a019fa8243e0..8d13dab5dcc3a500f9e4faf8168f6d25697ea99f 100644 +--- a/net/minecraft/world/item/BlockItem.java ++++ b/net/minecraft/world/item/BlockItem.java +@@ -147,8 +147,12 @@ public class BlockItem extends Item { + // CraftBukkit start + Level world = context.getLevel(); // Paper - Cancel hit for vanished players + CollisionContext collisionContext = player == null ? CollisionContext.empty() : CollisionContext.placementContext(player); // Leaves - creative no clip ++ boolean ignoreEntityCollision = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.blockPlacementIgnoreEntity && player != null && player.isCreative(); + boolean canBuild = (!this.mustSurvive() || stateForPlacement.canSurvive(world, context.getClickedPos())) +- && ((fun.bm.lophine.config.modules.function.CreativeFlyNoClipConfig.enabled && context.getPlayer() != null) ? context.getPlayer().canSpectatingPlace(world, stateForPlacement, context.getClickedPos(), collisionContext) : world.checkEntityCollision(stateForPlacement, player, collisionContext, context.getClickedPos(), true)); // Paper - Cancel hit for vanished players // Leaves - creative no clip ++ && (ignoreEntityCollision ++ || ((fun.bm.lophine.config.modules.function.CreativeFlyNoClipConfig.enabled && context.getPlayer() != null) ++ ? context.getPlayer().canSpectatingPlace(world, stateForPlacement, context.getClickedPos(), collisionContext) ++ : world.checkEntityCollision(stateForPlacement, player, collisionContext, context.getClickedPos(), true))); // Paper - Cancel hit for vanished players // Leaves - creative no clip + org.bukkit.entity.Player bukkitPlayer = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + + org.bukkit.event.block.BlockCanBuildEvent event = new org.bukkit.event.block.BlockCanBuildEvent( +diff --git a/net/minecraft/world/item/ServerItemCooldowns.java b/net/minecraft/world/item/ServerItemCooldowns.java +index cf66ce7cf737b2abd45c76218db55c8d0fc109d4..4da0768c14918d13b7137daa74142a544cf857cb 100644 +--- a/net/minecraft/world/item/ServerItemCooldowns.java ++++ b/net/minecraft/world/item/ServerItemCooldowns.java +@@ -22,6 +22,9 @@ public class ServerItemCooldowns extends ItemCooldowns { + + @Override + public void addCooldown(ItemStack item, int duration) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.creativeNoItemCooldown && this.player.isCreative()) { ++ return; ++ } + final Identifier cooldownGroup = this.getCooldownGroup(item); + final io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent( + this.player.getBukkitEntity(), +diff --git a/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java +index 298908003d5422b8b58de631145fad381e8294c1..b825035f937863c4d485e9534e7b6780788f20de 100644 +--- a/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/net/minecraft/world/item/crafting/RecipeManager.java +@@ -28,6 +28,8 @@ import net.minecraft.server.packs.resources.ResourceManager; + import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; + import net.minecraft.server.packs.resources.SimplePreparableReloadListener; + import net.minecraft.util.profiling.ProfilerFiller; ++import net.minecraft.world.item.ItemStackTemplate; ++import net.minecraft.world.item.Items; + import net.minecraft.world.flag.FeatureFlagSet; + import net.minecraft.world.item.crafting.display.RecipeDisplay; + import net.minecraft.world.item.crafting.display.RecipeDisplayEntry; +@@ -55,6 +57,9 @@ public class RecipeManager extends SimplePreparableReloadListener imp + forSingleInput(RecipeType.CAMPFIRE_COOKING) + ); + private static final FileToIdConverter RECIPE_LISTER = FileToIdConverter.registry(Registries.RECIPE); ++ private static final ResourceKey> BETTER_CRAFTABLE_BONE_BLOCK_RECIPE = compatRecipeKey("better_craftable_bone_block"); ++ private static final ResourceKey> BETTER_CRAFTABLE_DISPENSER_SHAPED_RECIPE = compatRecipeKey("better_craftable_dispenser_shaped"); ++ private static final ResourceKey> BETTER_CRAFTABLE_DISPENSER_SHAPELESS_RECIPE = compatRecipeKey("better_craftable_dispenser_shapeless"); + private final HolderLookup.Provider registries; + public RecipeMap recipes = RecipeMap.EMPTY; + private Map, RecipePropertySet> propertySets = Map.of(); +@@ -83,10 +88,73 @@ public class RecipeManager extends SimplePreparableReloadListener imp + + @Override + protected void apply(final RecipeMap recipes, final ResourceManager manager, final ProfilerFiller profiler) { ++ this.injectCompatRecipes(recipes); + this.recipes = recipes; + LOGGER.info("Loaded {} recipes", recipes.values().size()); + } + ++ private void injectCompatRecipes(final RecipeMap recipeMap) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.betterCraftableBoneBlock) { ++ addCompatRecipe(recipeMap, createBetterCraftableBoneBlockRecipe()); ++ } ++ ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.betterCraftableDispenser) { ++ addCompatRecipe(recipeMap, createBetterCraftableDispenserShapedRecipe()); ++ addCompatRecipe(recipeMap, createBetterCraftableDispenserShapelessRecipe()); ++ } ++ } ++ ++ private static void addCompatRecipe(final RecipeMap recipeMap, final RecipeHolder recipeHolder) { ++ if (recipeMap.byKey(recipeHolder.id()) == null) { ++ recipeMap.addRecipe(recipeHolder); ++ } ++ } ++ ++ private static RecipeHolder createBetterCraftableBoneBlockRecipe() { ++ return new RecipeHolder<>( ++ BETTER_CRAFTABLE_BONE_BLOCK_RECIPE, ++ new ShapedRecipe( ++ new Recipe.CommonInfo(true), ++ new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.BUILDING, "carpet_ams_better_bone_block"), ++ ShapedRecipePattern.of(Map.of('#', Ingredient.of(Items.BONE)), "###", "###", "###"), ++ new ItemStackTemplate(Items.BONE_BLOCK, 3) ++ ) ++ ); ++ } ++ ++ private static RecipeHolder createBetterCraftableDispenserShapedRecipe() { ++ return new RecipeHolder<>( ++ BETTER_CRAFTABLE_DISPENSER_SHAPED_RECIPE, ++ new ShapedRecipe( ++ new Recipe.CommonInfo(true), ++ new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.REDSTONE, "carpet_ams_better_dispenser"), ++ ShapedRecipePattern.of( ++ Map.of('S', Ingredient.of(Items.STICK), 'D', Ingredient.of(Items.DROPPER), 'X', Ingredient.of(Items.STRING)), ++ " SX", ++ "SDX", ++ " SX" ++ ), ++ new ItemStackTemplate(Items.DISPENSER) ++ ) ++ ); ++ } ++ ++ private static RecipeHolder createBetterCraftableDispenserShapelessRecipe() { ++ return new RecipeHolder<>( ++ BETTER_CRAFTABLE_DISPENSER_SHAPELESS_RECIPE, ++ new ShapelessRecipe( ++ new Recipe.CommonInfo(true), ++ new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.REDSTONE, "carpet_ams_better_dispenser"), ++ new ItemStackTemplate(Items.DISPENSER), ++ List.of(Ingredient.of(Items.BOW), Ingredient.of(Items.DROPPER)) ++ ) ++ ); ++ } ++ ++ private static ResourceKey> compatRecipeKey(final String path) { ++ return ResourceKey.create(Registries.RECIPE, Identifier.fromNamespaceAndPath("lophine", path)); ++ } ++ + // CraftBukkit start + public void addRecipe(RecipeHolder holder) { + org.spigotmc.AsyncCatcher.catchOp("Recipe Add"); // Spigot +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index e06e48dd9c997ac1dc6e404aa0bc590c851e1ba8..aba2f23951a79d32238dcf948a9232d43726a43d 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -1176,6 +1176,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + BlockState state = newState; + BlockState blockState = oldState; + BlockState blockState1 = currentState; ++ boolean skipInteractionUpdates = fun.bm.lophine.carpet.InteractionUpdateCompatHelper.shouldSkipUpdates(); ++ if (skipInteractionUpdates) { ++ flags &= ~Block.UPDATE_NEIGHBORS; ++ } + if (blockState1 == state) { + if (blockState != blockState1) { + this.setBlocksDirty(pos, blockState, blockState1); +@@ -1192,7 +1196,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + } + +- if ((flags & Block.UPDATE_KNOWN_SHAPE) == 0 && recursionLeft > 0) { ++ if (!skipInteractionUpdates && (flags & Block.UPDATE_KNOWN_SHAPE) == 0 && recursionLeft > 0) { + int i = flags & ~(Block.UPDATE_SUPPRESS_DROPS | Block.UPDATE_NEIGHBORS); + + // CraftBukkit start +diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java +index cd20d10ee4cd2fe98131c9a93367ae133f96b8d4..b9443bd4a7ee46fa9faea9f7db6352e4c6a6bd0f 100644 +--- a/net/minecraft/world/level/NaturalSpawner.java ++++ b/net/minecraft/world/level/NaturalSpawner.java +@@ -292,6 +292,10 @@ public final class NaturalSpawner { + Player nearestPlayer = level.getNearestPlayer(xx, yStart, zz, -1.0, false); + if (nearestPlayer != null) { + double nearestPlayerDistanceSqr = nearestPlayer.distanceToSqr(xx, yStart, zz); ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.lagFreeSpawning) { ++ nearestPlayerDistanceSqr = fun.bm.lophine.carpet.LagFreeSpawningCompatHelper.limitDistanceSq(mobCategory, nearestPlayerDistanceSqr); ++ } ++ + if (level.isLoadedAndInBounds(pos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, pos, nearestPlayerDistanceSqr)) { // Paper - don't load chunks for mob spawn + if (currentSpawnData == null) { + Optional nextSpawnData = getRandomSpawnMobAt( +@@ -332,6 +336,10 @@ public final class NaturalSpawner { + // SPIGOT-7045: Give ocelot babies back their special spawn reason. Note: This is the only modification required as ocelots count as monsters which means they only spawn during normal chunk ticking and do not spawn during chunk generation as starter mobs. + level.addFreshEntityWithPassengers(mob, (mob instanceof net.minecraft.world.entity.animal.feline.Ocelot && !((org.bukkit.entity.Ageable) mob.getBukkitEntity()).isAdult()) ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OCELOT_BABY : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); + if (!mob.isRemoved()) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.lagFreeSpawning) { ++ fun.bm.lophine.carpet.LagFreeSpawningCompatHelper.markSpawned(level, mob.getType()); ++ } ++ + clusterSize++; + groupSize++; + spawnCallback.run(mob, chunk); +@@ -411,11 +419,20 @@ public final class NaturalSpawner { + && canSpawnMobAt(level, structureManager, generator, mobCategory, currentSpawnData, pos) + && SpawnPlacements.isSpawnPositionOk(type, level, pos) + && SpawnPlacements.checkSpawnRules(type, level, EntitySpawnReason.NATURAL, pos, level.random) +- && level.noCollision(type.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5)); ++ && (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.lagFreeSpawning ++ ? fun.bm.lophine.carpet.LagFreeSpawningCompatHelper.hasNoCollision(level, type.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5)) ++ : level.noCollision(type.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5))); + return success ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL; // Paper - PreCreatureSpawnEvent + } + + private static @Nullable Mob getMobForSpawn(final ServerLevel level, final EntityType type) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.lagFreeSpawning) { ++ Mob cachedMob = fun.bm.lophine.carpet.LagFreeSpawningCompatHelper.getOrCreateMob(level, type); ++ if (cachedMob != null) { ++ return cachedMob; ++ } ++ } ++ + try { + if (type.create(level, EntitySpawnReason.NATURAL) instanceof Mob mob) { + return mob; +@@ -536,7 +553,9 @@ public final class NaturalSpawner { + float width = spawnerData.type().getWidth(); + double fx = Mth.clamp((double)x, (double)xo + width, xo + 16.0 - width); + double fz = Mth.clamp((double)z, (double)zo + width, zo + 16.0 - width); +- if (!level.noCollision(spawnerData.type().getSpawnAABB(fx, pos.getY(), fz)) ++ if (!(fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.lagFreeSpawning ++ ? fun.bm.lophine.carpet.LagFreeSpawningCompatHelper.hasNoCollision(level.getLevel(), spawnerData.type().getSpawnAABB(fx, pos.getY(), fz)) ++ : level.noCollision(spawnerData.type().getSpawnAABB(fx, pos.getY(), fz))) + || !SpawnPlacements.checkSpawnRules( + spawnerData.type(), + level, +@@ -568,6 +587,9 @@ public final class NaturalSpawner { + level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, groupSpawnData + ); + level.addFreshEntityWithPassengers(mob, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.lagFreeSpawning) { ++ fun.bm.lophine.carpet.LagFreeSpawningCompatHelper.markSpawned(level.getLevel(), mob.getType()); ++ } + success = true; + } + } +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index 9fc97e8b01f66ada6af1fe76f4e27c9bb1a5dcfc..3d5bb6a0893db1cac5cff61e4a3607de9536c9a3 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -371,6 +371,10 @@ public class ServerExplosion implements Explosion { + } + + private List calculateExplodedPositions() { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.explosionNoBlockDamage) { ++ return List.of(); ++ } ++ + // Paper start - collision optimisations + final ObjectArrayList ret = new ObjectArrayList<>(); + +diff --git a/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/net/minecraft/world/level/block/CarvedPumpkinBlock.java +index a63dddd01ced6ba23067a113d871f2351bb690fc..6f67b18005ad9b877d7f680ff9b4afc7abed62d1 100644 +--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java ++++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java +@@ -59,7 +59,8 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { + public boolean canSpawnGolem(final LevelReader level, final BlockPos topPos) { + return this.getOrCreateSnowGolemBase().find(level, topPos) != null + || this.getOrCreateIronGolemBase().find(level, topPos) != null +- || this.getOrCreateCopperGolemBase().find(level, topPos) != null; ++ || this.getOrCreateCopperGolemBase().find(level, topPos) != null ++ || this.canSpawnShulkerGolem(level, topPos); + } + + private void trySpawnGolem(final Level level, final BlockPos topPos) { +@@ -92,6 +93,8 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { + copperGolem.spawn(this.getWeatherStateFromPattern(copperGolemMatch)); + } + } ++ ++ this.trySpawnShulkerGolem(level, topPos); + } + + private WeatheringCopper.WeatherState getWeatherStateFromPattern(final BlockPattern.BlockPatternMatch copperGolemMatch) { +@@ -159,6 +162,42 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { + builder.add(FACING); + } + ++ private boolean canSpawnShulkerGolem(LevelReader level, BlockPos pos) { ++ return fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.shulkerGolem ++ && PUMPKINS_PREDICATE.test(level.getBlockState(pos)) ++ && level.getBlockState(pos.below()).is(BlockTags.SHULKER_BOXES); ++ } ++ ++ private void trySpawnShulkerGolem(Level level, BlockPos pos) { ++ if (!this.canSpawnShulkerGolem(level, pos)) { ++ return; ++ } ++ ++ net.minecraft.world.entity.monster.Shulker shulker = EntityType.SHULKER.create(level, EntitySpawnReason.TRIGGERED); ++ if (shulker == null) { ++ return; ++ } ++ ++ BlockPos bodyPos = pos.below(); ++ BlockState headState = level.getBlockState(pos); ++ BlockState bodyState = level.getBlockState(bodyPos); ++ shulker.snapTo(bodyPos.getX() + 0.5, bodyPos.getY(), bodyPos.getZ() + 0.5, 0.0F, 0.0F); ++ if (!level.addFreshEntity(shulker, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_IRONGOLEM)) { ++ return; ++ } ++ ++ level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); ++ level.setBlock(bodyPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); ++ level.levelEvent(LevelEvent.PARTICLES_DESTROY_BLOCK, pos, Block.getId(headState)); ++ level.levelEvent(LevelEvent.PARTICLES_DESTROY_BLOCK, bodyPos, Block.getId(bodyState)); ++ level.updateNeighborsAt(pos, Blocks.AIR); ++ level.updateNeighborsAt(bodyPos, Blocks.AIR); ++ ++ for (ServerPlayer serverPlayer : level.getEntitiesOfClass(ServerPlayer.class, shulker.getBoundingBox().inflate(5.0))) { ++ CriteriaTriggers.SUMMONED_ENTITY.trigger(serverPlayer, shulker); ++ } ++ } ++ + private BlockPattern getOrCreateSnowGolemBase() { + if (this.snowGolemBase == null) { + this.snowGolemBase = BlockPatternBuilder.start() +diff --git a/net/minecraft/world/level/block/ChestBlock.java b/net/minecraft/world/level/block/ChestBlock.java +index b5595d87339e4d313c5691a06c2b1b3d1b40b6de..cceb2e8170bf56c6d34520e69968ea6568a5878e 100644 +--- a/net/minecraft/world/level/block/ChestBlock.java ++++ b/net/minecraft/world/level/block/ChestBlock.java +@@ -276,7 +276,8 @@ public class ChestBlock extends AbstractChestBlock implements + final BlockState state, final Level level, final BlockPos pos, final Player player, final BlockHitResult hitResult + ) { + if (level instanceof ServerLevel serverLevel) { +- MenuProvider menuProvider = this.getMenuProvider(state, level, pos); ++ boolean ignoreObstructions = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.creativeOpenContainerForcibly && player.isCreative(); ++ MenuProvider menuProvider = this.getMenuProvider(state, level, pos, ignoreObstructions); + if (menuProvider != null && player.openMenu(menuProvider).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(this.getOpenChestStat()); + PiglinAi.angerNearbyPiglins(serverLevel, player, true); +diff --git a/net/minecraft/world/level/block/EnderChestBlock.java b/net/minecraft/world/level/block/EnderChestBlock.java +index a9f511a549681a76a962855c26b9af5980f36df7..26a5330b53f3381896e64c0b65147d69f6369773 100644 +--- a/net/minecraft/world/level/block/EnderChestBlock.java ++++ b/net/minecraft/world/level/block/EnderChestBlock.java +@@ -82,7 +82,8 @@ public class EnderChestBlock extends AbstractChestBlock i + PlayerEnderChestContainer container = player.getEnderChestInventory(); + if (container != null && level.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChest) { + BlockPos above = pos.above(); +- if (level.getBlockState(above).isRedstoneConductor(level, above)) { // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic ++ boolean canOpenForcibly = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.creativeOpenContainerForcibly && player.isCreative(); ++ if (!canOpenForcibly && level.getBlockState(above).isRedstoneConductor(level, above)) { // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic + return InteractionResult.SUCCESS; + } else { + // Paper start - Fix InventoryOpenEvent cancellation - moved up; +diff --git a/net/minecraft/world/level/block/FarmlandBlock.java b/net/minecraft/world/level/block/FarmlandBlock.java +index ef55faf83e39c8dfd6bbe33c4943ed88bc473715..3d083f390f4dffc43e1a7f09fad87b05f296e6b8 100644 +--- a/net/minecraft/world/level/block/FarmlandBlock.java ++++ b/net/minecraft/world/level/block/FarmlandBlock.java +@@ -132,7 +132,9 @@ public class FarmlandBlock extends Block { + return; + } + // CraftBukkit end +- turnToDirt(entity, state, level, pos); ++ if (!fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.farmlandTrampledDisabled) { ++ turnToDirt(entity, state, level, pos); ++ } + } + + // super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved up +diff --git a/net/minecraft/world/level/block/ObserverBlock.java b/net/minecraft/world/level/block/ObserverBlock.java +index be572eff561ffdd961a9602eaa6136761c4d48a4..f11f0669bfbd5bb037cd17298563694316597772 100644 +--- a/net/minecraft/world/level/block/ObserverBlock.java ++++ b/net/minecraft/world/level/block/ObserverBlock.java +@@ -89,6 +89,10 @@ public class ObserverBlock extends DirectionalBlock { + } + + private void startSignal(final LevelReader level, final ScheduledTickAccess ticks, final BlockPos pos) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.observerNoDetection) { ++ return; ++ } ++ + if (!level.isClientSide() && !ticks.getBlockTicks().hasScheduledTick(pos, this)) { + ticks.scheduleTick(pos, this, 2); + } +diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java +index 1de86856f4a27346d7003ca9eb24a36074a2a4a3..e8291bb18e24a2a1772a91404403f902e301107a 100644 +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -288,7 +288,8 @@ public class RedStoneWireBlock extends Block { + * Note: Added 'source' argument so as to help determine direction of information flow + */ + private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) { +- if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { ++ if (!fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.fastRedstoneDust ++ && worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { + // since 24w33a the source pos is no longer given, but instead an Orientation parameter + // when this is not null, it can be used to find the source pos, which the turbo uses + // to find the direction of information flow +@@ -363,7 +364,8 @@ public class RedStoneWireBlock extends Block { + protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { + if (!oldState.is(state.getBlock()) && !level.isClientSide()) { + // Paper start - optimize redstone - replace call to updatePowerStrength +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.fastRedstoneDust ++ || level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + level.getWireHandler().onWireAdded(pos, state); // Alternate Current + } else { + this.updateSurroundingRedstone(level, pos, state, null, true); // Vanilla/Eigencraft +@@ -388,7 +390,8 @@ public class RedStoneWireBlock extends Block { + } + + // Paper start - optimize redstone - replace call to updatePowerStrength +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.fastRedstoneDust ++ || level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + level.getWireHandler().onWireRemoved(pos, state); // Alternate Current + } else { + this.updateSurroundingRedstone(level, pos, state, null, false); // Vanilla/Eigencraft +@@ -408,7 +411,8 @@ public class RedStoneWireBlock extends Block { + } + + // Paper start - optimize redstone - replace call to updatePowerStrength +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.fastRedstoneDust ++ || level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + level.getWireHandler().onWireRemoved(pos, state); // Alternate Current + } else { + this.updateSurroundingRedstone(level, pos, state, null, false); // Vanilla/Eigencraft +@@ -440,7 +444,8 @@ public class RedStoneWireBlock extends Block { + if (!level.isClientSide()) { + // Paper start - optimize redstone (Alternate Current) + // Alternate Current handles breaking of redstone wires in the WireHandler. +- if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.fastRedstoneDust ++ || level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + level.getWireHandler().onWireUpdated(pos, state, orientation); + } else + // Paper end - optimize redstone (Alternate Current) +diff --git a/net/minecraft/world/level/block/ShulkerBoxBlock.java b/net/minecraft/world/level/block/ShulkerBoxBlock.java +index 7bf2cde59c112f4babb87476585207dacebef341..fac01e0398f1042b6bc680e967c101063a3023cd 100644 +--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java ++++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java +@@ -74,9 +74,10 @@ public class ShulkerBoxBlock extends BaseEntityBlock { + protected InteractionResult useWithoutItem( + final BlockState state, final Level level, final BlockPos pos, final Player player, final BlockHitResult hitResult + ) { ++ boolean canOpenForcibly = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.creativeOpenContainerForcibly && player.isCreative(); + if (level instanceof ServerLevel serverLevel + && level.getBlockEntity(pos) instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity +- && canOpen(state, level, pos, shulkerBoxBlockEntity) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check ++ && (canOpen(state, level, pos, shulkerBoxBlockEntity) || canOpenForcibly) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check + && player.openMenu(shulkerBoxBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation + player.awardStat(Stats.OPEN_SHULKER_BOX); + PiglinAi.angerNearbyPiglins(serverLevel, player, true); +diff --git a/net/minecraft/world/level/block/TntBlock.java b/net/minecraft/world/level/block/TntBlock.java +index 58142c276c976b16100e14ee9adb547f74e4cb41..7a6a05813216d0a0dcc1f4a546b51c021b07714a 100644 +--- a/net/minecraft/world/level/block/TntBlock.java ++++ b/net/minecraft/world/level/block/TntBlock.java +@@ -47,7 +47,8 @@ public class TntBlock extends Block { + @Override + protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { + if (!oldState.is(state.getBlock())) { +- if (level.hasNeighborSignal(pos) && prime(level, pos, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.REDSTONE, null, null))) { // CraftBukkit - TNTPrimeEvent ++ if (shouldPrimeFromRedstone(level, pos) ++ && prime(level, pos, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.REDSTONE, null, null))) { // CraftBukkit - TNTPrimeEvent + level.removeBlock(pos, false); + } + } +@@ -57,7 +58,9 @@ public class TntBlock extends Block { + protected void neighborChanged( + final BlockState state, final Level level, final BlockPos pos, final Block block, final @Nullable Orientation orientation, final boolean movedByPiston + ) { +- if (level.hasNeighborSignal(pos) && prime(level, pos, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.REDSTONE, null, null))) { // CraftBukkit - TNTPrimeEvent ++ if (!fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.tntIgnoreRedstoneSignal ++ && level.hasNeighborSignal(pos) ++ && prime(level, pos, () -> org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.REDSTONE, null, null))) { // CraftBukkit - TNTPrimeEvent + level.removeBlock(pos, false); + } + } +@@ -76,7 +79,7 @@ public class TntBlock extends Block { + if (level.getGameRules().get(GameRules.TNT_EXPLODES)) { + PrimedTnt primed = new PrimedTnt(level, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, explosion.getIndirectSourceEntity()); + int fuse = primed.getFuse(); +- primed.setFuse((short)(level.getRandom().nextInt(fuse / 4) + fuse / 8)); ++ primed.setFuse(level.getRandom().nextInt(Math.max(fuse / 4, 1)) + fuse / 8); + level.addFreshEntity(primed); + } + } +@@ -156,4 +159,10 @@ public class TntBlock extends Block { + protected void createBlockStateDefinition(final StateDefinition.Builder builder) { + builder.add(UNSTABLE); + } ++ ++ private static boolean shouldPrimeFromRedstone(final Level level, final BlockPos pos) { ++ return !fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.tntDoNotUpdate ++ && !fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.tntIgnoreRedstoneSignal ++ && level.hasNeighborSignal(pos); ++ } + } +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index aa782c6441cdce3eb39aefb28d40f3b1970d4e62..3e4fae7a5ae7f8a26bf78727e81c4a87e8fed332 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -296,6 +296,8 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { + io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading + worldData.skipPushModeEventFire = worldData.skipHopperEvents; // Folia - region threading ++ boolean noItemCost = fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.hopperNoItemCost ++ && org.leavesmc.leaves.util.WoolUtils.getWoolColorAtPosition(level, hopper.getBlockPos().above()) != null; + boolean foundItem = false; + for (int i = 0; i < hopper.getContainerSize(); ++i) { + final ItemStack item = hopper.getItem(i); +@@ -321,6 +323,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); + final int remainingItemCount = remainingItem.getCount(); + if (remainingItemCount != movedItemCount) { ++ if (noItemCost) { ++ origItemStack.setCount(originalItemCount); ++ hopper.setItem(i, origItemStack); ++ destination.setChanged(); ++ return true; ++ } ++ + origItemStack = origItemStack.copy(true); + origItemStack.setCount(originalItemCount); + if (!origItemStack.isEmpty()) { +diff --git a/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 5ae07ece1046dda464f284c1dd13b2780f831e02..a4d8ed890fc6bea1e961df814c00d22ae147734f 100644 +--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -379,7 +379,7 @@ public class PistonBaseBlock extends DirectionalBlock { + + for (int i = toPush.size() - 1; i >= 0; i--) { + // Paper start - fix a variety of piston desync dupes +- boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; ++ boolean allowDesync = !fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.tntDupingFix; + BlockPos oldPos = toPush.get(i); + BlockPos pos = oldPos; + BlockState blockState = allowDesync ? level.getBlockState(oldPos) : null; +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index ff1471f40b73582df68efbf2996a854c6aa37d65..668f59f94efb34490bdd7d6aac62fb37dd64a485 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -848,6 +848,10 @@ public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.lea + } + + public Vec3 getOffset(final BlockPos pos) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.bambooModelNoOffset && (this.getBlock() == Blocks.BAMBOO || this.getBlock() == Blocks.BAMBOO_SAPLING)) { ++ return Vec3.ZERO; ++ } ++ + BlockBehaviour.OffsetFunction function = this.offsetFunction; + return function != null ? function.evaluate(this.asState(), pos) : Vec3.ZERO; + } +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 6b5ad391eda92cdbb47ab78a4c27d7e6a3b098a8..ecf24df6c7c8f3576634e017b0bfd22163f729a2 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -408,10 +408,11 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot + boolean blockChanged = !oldState.is(newBlock); + boolean movedByPiston = (flags & Block.UPDATE_MOVE_BY_PISTON) != 0; + boolean sideEffects = (flags & Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS) == 0; ++ boolean skipInteractionUpdates = fun.bm.lophine.carpet.InteractionUpdateCompatHelper.shouldSkipUpdates(); + // Leaves start - behaviour 1.21.1- + if (!fun.bm.lophine.config.modules.experiment.RedStoneConfig.oldBlockRemoveBehaviour) { + if (blockChanged && oldState.hasBlockEntity() && !state.shouldChangedStateKeepBlockEntity(oldState)) { +- if (!this.level.isClientSide() && sideEffects) { ++ if (!this.level.isClientSide() && sideEffects && !skipInteractionUpdates) { + BlockEntity blockEntity = this.level.getBlockEntity(pos); + if (blockEntity != null) { + blockEntity.preRemoveSideEffects(pos, oldState); +@@ -423,6 +424,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot + + if ((blockChanged || newBlock instanceof BaseRailBlock) + && this.level instanceof ServerLevel serverLevel ++ && !skipInteractionUpdates + && ((flags & Block.UPDATE_NEIGHBORS) != 0 || movedByPiston)) { + oldState.affectNeighborsAfterRemoval(serverLevel, pos, movedByPiston); + } +@@ -434,7 +436,10 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot + if (!section.getBlockState(localX, localY, localZ).is(newBlock)) { + return null; + } else { +- if (!this.level.isClientSide() && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0 && (!this.level.getCurrentWorldData().captureBlockStates || newBlock instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. // Folia - region threading ++ if (!this.level.isClientSide() ++ && !skipInteractionUpdates ++ && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0 ++ && (!this.level.getCurrentWorldData().captureBlockStates || newBlock instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. // Folia - region threading + state.onPlace(this.level, pos, oldState, movedByPiston); + } + +diff --git a/net/minecraft/world/level/dimension/end/DragonRespawnStage.java b/net/minecraft/world/level/dimension/end/DragonRespawnStage.java +index e02f5432d90866a06c48a58895908f7178ef1051..5573df701aef5e1fb77d1ff57c1459c142dad638 100644 +--- a/net/minecraft/world/level/dimension/end/DragonRespawnStage.java ++++ b/net/minecraft/world/level/dimension/end/DragonRespawnStage.java +@@ -55,16 +55,17 @@ public enum DragonRespawnStage implements StringRepresentable { + respawnCrystal.setBeamTarget(new BlockPos(spike.getCenterX(), spike.getHeight() + 1, spike.getCenterZ())); + } + } else { +- int radius = 10; ++ if (!fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.preventEndSpikeRespawn) { ++ for (BlockPos pos : BlockPos.betweenClosed( ++ new BlockPos(spike.getCenterX() - 10, spike.getHeight() - 10, spike.getCenterZ() - 10), ++ new BlockPos(spike.getCenterX() + 10, spike.getHeight() + 10, spike.getCenterZ() + 10) ++ )) { ++ level.removeBlock(pos, false); ++ } + +- for (BlockPos pos : BlockPos.betweenClosed( +- new BlockPos(spike.getCenterX() - 10, spike.getHeight() - 10, spike.getCenterZ() - 10), +- new BlockPos(spike.getCenterX() + 10, spike.getHeight() + 10, spike.getCenterZ() + 10) +- )) { +- level.removeBlock(pos, false); ++ level.explode(null, spike.getCenterX() + 0.5F, spike.getHeight(), spike.getCenterZ() + 0.5F, 5.0F, Level.ExplosionInteraction.BLOCK); + } + +- level.explode(null, spike.getCenterX() + 0.5F, spike.getHeight(), spike.getCenterZ() + 0.5F, 5.0F, Level.ExplosionInteraction.BLOCK); + EndSpikeConfiguration configuration = new EndSpikeConfiguration(true, ImmutableList.of(spike), new BlockPos(0, 128, 0)); + Feature.END_SPIKE + .place( +diff --git a/net/minecraft/world/level/levelgen/feature/EndSpikeFeature.java b/net/minecraft/world/level/levelgen/feature/EndSpikeFeature.java +index 3fdf80e32bdcaab4daac8f1c7329a2bc7b2bd625..284cec8401eb75076b2d80581059f489aa4022f9 100644 +--- a/net/minecraft/world/level/levelgen/feature/EndSpikeFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/EndSpikeFeature.java +@@ -68,6 +68,10 @@ public class EndSpikeFeature extends Feature { + private void placeSpike( + final ServerLevelAccessor level, final RandomSource random, final EndSpikeConfiguration config, final EndSpikeFeature.EndSpike spike + ) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.preventEndSpikeRespawn && config.getCrystalBeamTarget() != null) { ++ return; ++ } ++ + int radius = spike.getRadius(); + + for (BlockPos pos : BlockPos.betweenClosed( +diff --git a/net/minecraft/world/level/redstone/NeighborUpdater.java b/net/minecraft/world/level/redstone/NeighborUpdater.java +index 44731e230869f0df058f6a426277befbb0d559e4..a74b13bdd9145e3c54340aa959ee138c044ee433 100644 +--- a/net/minecraft/world/level/redstone/NeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/NeighborUpdater.java +@@ -42,6 +42,10 @@ public interface NeighborUpdater { + @Block.UpdateFlags final int updateFlags, + final int updateLimit + ) { ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.totallyNoBlockUpdate) { ++ return; ++ } ++ + BlockState currentState = level.getBlockState(pos); + if ((updateFlags & Block.UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE) == 0 || !currentState.is(Blocks.REDSTONE_WIRE)) { + try { +@@ -72,6 +76,10 @@ public interface NeighborUpdater { + + static void executeUpdate(Level level, BlockState state, BlockPos pos, Block changedBlock, @Nullable Orientation orientation, boolean movedByPiston, BlockPos sourcePos) { + // Paper end - Add source block to BlockPhysicsEvent ++ if (fun.bm.lophine.carpet.config.modules.GeneralCompatConfig.totallyNoBlockUpdate) { ++ return; ++ } ++ + try { + // CraftBukkit start + org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), state.asBlockData(), org.bukkit.craftbukkit.block.CraftBlock.at(level, sourcePos)); // Paper - Add source block to BlockPhysicsEvent diff --git a/lophine-server/src/main/java/fun/bm/lophine/protocol/CarpetLoggerProtocol.java b/lophine-server/src/main/java/fun/bm/lophine/protocol/CarpetLoggerProtocol.java new file mode 100644 index 0000000..b71f9cf --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/protocol/CarpetLoggerProtocol.java @@ -0,0 +1,302 @@ +package fun.bm.lophine.protocol; + +import fun.bm.lophine.carpet.config.modules.GeneralCompatConfig; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.threadedregions.RegionizedWorldData; +import io.papermc.paper.threadedregions.TickRegionScheduler; +import net.kyori.adventure.text.Component; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.protocol.game.ClientboundTabListPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerTickRateManager; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.NaturalSpawner; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; +import org.leavesmc.leaves.util.HopperCounter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +@LeavesProtocol.Register(namespace = "carpet") +public class CarpetLoggerProtocol implements LeavesProtocol { + private static final Logger LOGGER = LoggerFactory.getLogger("CarpetLoggerProtocol"); + private static final Map> PLAYER_SUBSCRIPTIONS = new ConcurrentHashMap<>(); + private static volatile Map configuredSubscriptions = null; + + public static void refreshConfiguredDefaults(boolean initial) { + Map defaults = parseConfiguredDefaults(GeneralCompatConfig.defaultLoggers); + configuredSubscriptions = defaults; + if (defaults == null || defaults.isEmpty()) { + PLAYER_SUBSCRIPTIONS.clear(); + if (initial) return; + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + clearHud(player); + } + return; + } + PLAYER_SUBSCRIPTIONS.replaceAll((playerName, ignored) -> defaults); + } + + public static String serializeConfiguredDefaults(List configuredLoggers) { + List serialized = new ArrayList<>(); + if (configuredLoggers != null) { + for (String entry : configuredLoggers) { + if (entry == null) { + continue; + } + String trimmed = entry.trim(); + if (!trimmed.isEmpty()) { + serialized.add(trimmed); + } + } + } + return serialized.isEmpty() ? "none" : String.join(",", serialized); + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerJoin(ServerPlayer player) { + if (configuredSubscriptions != null && !configuredSubscriptions.isEmpty()) { + PLAYER_SUBSCRIPTIONS.putIfAbsent(player.getScoreboardName(), configuredSubscriptions); + } + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLeave(ServerPlayer player) { + clearHud(player); + } + + @ProtocolHandler.Ticker(tickerId = "hud") + public static void onHudTick() { + if (PLAYER_SUBSCRIPTIONS.isEmpty()) { + return; + } + MinecraftServer server = MinecraftServer.getServer(); + for (ServerPlayer player : server.getPlayerList().getPlayers()) { + Map subscriptions = PLAYER_SUBSCRIPTIONS.get(player.getScoreboardName()); + if (subscriptions == null || subscriptions.isEmpty()) { + continue; + } + player.getBukkitEntity().taskScheduler.schedule((LivingEntity livingEntity) -> { + if (livingEntity instanceof ServerPlayer scheduledPlayer) { + sendHud(server, scheduledPlayer, subscriptions); + } + }, null, 1L); + } + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public int tickerInterval(String tickerID) { + return "hud".equals(tickerID) ? 20 : 1; + } + + private static void sendHud(MinecraftServer server, ServerPlayer player, Map subscriptions) { + List lines = new ArrayList<>(); + subscriptions.forEach((loggerName, option) -> { + switch (loggerName) { + case "tps" -> lines.add(buildTpsLine(server)); + case "mobcaps" -> { + net.minecraft.network.chat.Component line = buildMobcapsLine(player, option); + if (line != null) { + lines.add(line); + } + } + case "counter" -> lines.addAll(buildCounterLines(server, option)); + default -> { + } + } + }); + + MutableComponent footer = net.minecraft.network.chat.Component.empty(); + for (int i = 0; i < lines.size(); i++) { + if (i > 0) { + footer.append(net.minecraft.network.chat.Component.literal("\n")); + } + footer.append(lines.get(i)); + } + player.connection.send(new ClientboundTabListPacket(net.minecraft.network.chat.Component.empty(), footer)); + } + + private static void clearHud(ServerPlayer player) { + player.connection.send(new ClientboundTabListPacket(net.minecraft.network.chat.Component.empty(), net.minecraft.network.chat.Component.empty())); + } + + private static net.minecraft.network.chat.Component buildTpsLine(MinecraftServer server) { + ServerTickRateManager tickManager = server.tickRateManager(); + ca.spottedleaf.moonrise.common.time.TickData.TickReportData tickData = TickRegionScheduler.getCurrentRegion().getData().getRegionSchedulingHandle().getTickReport5s(System.nanoTime()); + final double tps = tickData.tpsData().segmentAll().average(); + final double mspt = tickData.timePerTickData().segmentAll().average() / 1.0E6; + + ChatFormatting color = heatmapColor(mspt, tickManager.millisecondsPerTick()); + return net.minecraft.network.chat.Component.empty() + .append(net.minecraft.network.chat.Component.literal("TPS: ").withStyle(ChatFormatting.GRAY)) + .append(net.minecraft.network.chat.Component.literal(String.format(Locale.US, "%.1f", tps)).withStyle(color)) + .append(net.minecraft.network.chat.Component.literal(" MSPT: ").withStyle(ChatFormatting.GRAY)) + .append(net.minecraft.network.chat.Component.literal(String.format(Locale.US, "%.1f", mspt)).withStyle(color)); + } + + private static net.minecraft.network.chat.Component buildMobcapsLine(ServerPlayer player, String option) { + final ServerLevel level = resolveMobcapsLevel(player, option); + if (level == null) { + return null; + } + + final RegionizedWorldData data = level.getCurrentWorldData(); + if (data == null) { + return null; + } + + final NaturalSpawner.SpawnState spawnState = data.lastSpawnState; + if (spawnState == null) { + return net.minecraft.network.chat.Component.literal("Mobcaps: unavailable").withStyle(ChatFormatting.DARK_GRAY); + } + + int chunks = spawnState.getSpawnableChunkCount(); + var counts = spawnState.getMobCategoryCounts(); + MutableComponent line = net.minecraft.network.chat.Component.literal("Mobcaps").withStyle(ChatFormatting.GRAY); + for (MobCategory category : MobCategory.values()) { + if (category == MobCategory.MISC) { + continue; + } + int current = counts.getOrDefault(category, 0); + int limit = NaturalSpawner.globalLimitForCategory(level, category, chunks); + line.append(net.minecraft.network.chat.Component.literal(" " + shortName(category) + " ").withStyle(ChatFormatting.DARK_GRAY)); + line.append(net.minecraft.network.chat.Component.literal(current + "/" + limit).withStyle(categoryColor(current, limit))); + } + return line; + } + + private static List buildCounterLines(MinecraftServer server, String option) { + List lines = new ArrayList<>(); + String colors = option == null || option.isBlank() ? "white" : option; + for (String rawColor : colors.split(",")) { + String colorName = rawColor.trim(); + if (colorName.isEmpty()) { + continue; + } + DyeColor color = DyeColor.byName(colorName, null); + if (color == null) { + continue; + } + HopperCounter counter = HopperCounter.getCounter(color); + if (counter == null) { + continue; + } + for (Component component : counter.format(server, false)) { + lines.add(PaperAdventure.asVanilla(component)); + } + } + return lines; + } + + private static ServerLevel resolveMobcapsLevel(ServerPlayer player, String option) { + if (option == null || option.isBlank() || option.equalsIgnoreCase("dynamic")) { + return player.level(); + } + return switch (option.toLowerCase(Locale.ROOT)) { + case "overworld" -> player.level().dimension() == Level.OVERWORLD ? player.level() : null; + case "nether" -> player.level().dimension() == Level.NETHER ? player.level() : null; + case "end" -> player.level().dimension() == Level.END ? player.level() : null; + default -> player.level(); + }; + } + + private static ChatFormatting heatmapColor(double actual, double reference) { + if (actual > reference) { + return ChatFormatting.LIGHT_PURPLE; + } + if (actual > 0.8D * reference) { + return ChatFormatting.RED; + } + if (actual > 0.5D * reference) { + return ChatFormatting.YELLOW; + } + if (actual >= 0.0D) { + return ChatFormatting.DARK_GREEN; + } + return ChatFormatting.GRAY; + } + + private static ChatFormatting categoryColor(int current, int limit) { + if (limit <= 0) { + return ChatFormatting.DARK_GRAY; + } + double ratio = current / (double) limit; + if (ratio >= 1.0D) { + return ChatFormatting.RED; + } + if (ratio >= 0.8D) { + return ChatFormatting.YELLOW; + } + return ChatFormatting.GREEN; + } + + private static String shortName(MobCategory category) { + return switch (category) { + case MONSTER -> "M"; + case CREATURE -> "C"; + case AMBIENT -> "A"; + case AXOLOTLS -> "Ax"; + case UNDERGROUND_WATER_CREATURE -> "UWC"; + case WATER_CREATURE -> "WC"; + case WATER_AMBIENT -> "WA"; + case MISC -> "X"; + }; + } + + private static Map parseConfiguredDefaults(List configuredLoggers) { + LinkedHashMap subscriptions = new LinkedHashMap<>(); + if (configuredLoggers == null) { + return null; + } + for (String entry : configuredLoggers) { + if (entry == null) { + continue; + } + for (String chunk : entry.split(",")) { + String token = chunk.trim(); + if (token.isEmpty() || token.equalsIgnoreCase("none")) { + continue; + } + String[] parts = token.split("\\s+", 2); + String loggerName = parts[0].toLowerCase(Locale.ROOT); + if (!isSupported(loggerName)) { + LOGGER.debug("Ignoring unsupported Carpet default logger '{}'", loggerName); + continue; + } + String option = parts.length == 1 ? defaultOption(loggerName) : parts[1].trim(); + if (option.isEmpty()) { + option = defaultOption(loggerName); + } + subscriptions.put(loggerName, option); + } + } + return subscriptions.isEmpty() ? null : Map.copyOf(subscriptions); + } + + private static boolean isSupported(String loggerName) { + return Arrays.asList("tps", "mobcaps", "counter").contains(loggerName); + } + + private static @NotNull String defaultOption(String loggerName) { + return switch (loggerName) { + case "mobcaps" -> "dynamic"; + case "counter" -> "white"; + default -> ""; + }; + } +} diff --git a/lophine-server/src/main/java/fun/bm/lophine/protocol/tiscm/TISCMProtocol.java b/lophine-server/src/main/java/fun/bm/lophine/protocol/tiscm/TISCMProtocol.java new file mode 100644 index 0000000..96f7996 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/protocol/tiscm/TISCMProtocol.java @@ -0,0 +1,201 @@ +package fun.bm.lophine.protocol.tiscm; + +import com.mojang.logging.LogUtils; +import fun.bm.lophine.carpet.config.modules.GeneralCompatConfig; +import io.papermc.paper.ServerBuildInfo; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Contract; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +import org.leavesmc.leaves.protocol.core.ProtocolHandler; +import org.leavesmc.leaves.protocol.core.ProtocolUtils; +import org.slf4j.Logger; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +@LeavesProtocol.Register(namespace = TISCMProtocol.PROTOCOL_ID) +public class TISCMProtocol implements LeavesProtocol { + private static final Logger LOGGER = LogUtils.getLogger(); + + public static final String PROTOCOL_ID = "tiscm"; + private static final String PLATFORM_NAME = "Lophine"; + private static final String PLATFORM_VERSION = ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE); + private static final Map> CLIENT_SUPPORTED_PACKETS = new ConcurrentHashMap<>(); + + @Contract("_ -> new") + public static Identifier id(String path) { + return Identifier.fromNamespaceAndPath(PROTOCOL_ID, path); + } + + public static void broadcastMsptSample(long tickCounter, long nanosecond) { + if (!GeneralCompatConfig.tiscmNetworkProtocol || !GeneralCompatConfig.syncServerMsptMetricsData) { + return; + } + + CompoundTag nbt = new CompoundTag(); + nbt.putInt("version", 2); + nbt.putLong("millisecond", nanosecond / 1_000_000L); + nbt.putLong("nanosecond", nanosecond); + nbt.putString("type", "tick_server_method"); + + TISCMPayload payload = new TISCMPayload(S2CPacket.MSPT_METRICS_SAMPLE.id, nbt); + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + if (supports(player, S2CPacket.MSPT_METRICS_SAMPLE)) { + ProtocolUtils.sendPayloadPacket(player, payload); + } + } + } + + @ProtocolHandler.PayloadReceiver(payload = TISCMPayload.class) + public static void handlePayload(ServerPlayer player, TISCMPayload payload) { + if (!GeneralCompatConfig.tiscmNetworkProtocol) { + return; + } + + Optional packetType = C2SPacket.fromId(payload.packetId()); + if (packetType.isEmpty()) { + return; + } + + switch (packetType.get()) { + case HI -> handleHi(player, payload.nbt()); + case SUPPORTED_S2C_PACKETS -> handleSupportedS2CPackets(player, payload.nbt()); + default -> { + } + } + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLeave(ServerPlayer player) { + CLIENT_SUPPORTED_PACKETS.remove(player.getStringUUID()); + } + + private static void handleHi(ServerPlayer player, CompoundTag payload) { + String platformName = payload.getString("platform_name").orElse("Unknown"); + String platformVersion = payload.getString("platform_version").orElse("Unknown"); + LOGGER.info("Player {} connected with TISCM protocol support ({} @ {})", player.getScoreboardName(), platformName, platformVersion); + + send(player, S2CPacket.HELLO, nbt -> { + nbt.putString("platform_name", PLATFORM_NAME); + nbt.putString("platform_version", PLATFORM_VERSION); + }); + send(player, S2CPacket.SUPPORTED_C2S_PACKETS, nbt -> nbt.put("supported_c2s_packets", stringList(List.of( + C2SPacket.HI.id, + C2SPacket.SUPPORTED_S2C_PACKETS.id + )))); + } + + private static void handleSupportedS2CPackets(ServerPlayer player, CompoundTag payload) { + EnumSet packets = EnumSet.noneOf(S2CPacket.class); + ListTag listTag = payload.getListOrEmpty("supported_s2c_packets"); + for (int i = 0; i < listTag.size(); i++) { + S2CPacket.fromId(listTag.getString(i).orElse("")).ifPresent(packets::add); + } + CLIENT_SUPPORTED_PACKETS.put(player.getStringUUID(), packets); + } + + private static void send(ServerPlayer player, S2CPacket packet, PayloadBuilder builder) { + if (!supports(player, packet)) { + return; + } + + CompoundTag nbt = new CompoundTag(); + builder.accept(nbt); + ProtocolUtils.sendPayloadPacket(player, new TISCMPayload(packet.id, nbt)); + } + + private static boolean supports(ServerPlayer player, S2CPacket packet) { + if (packet.handshake) { + return true; + } + + EnumSet packets = CLIENT_SUPPORTED_PACKETS.get(player.getStringUUID()); + return packets != null && packets.contains(packet); + } + + private static ListTag stringList(List values) { + ListTag list = new ListTag(); + for (String value : values) { + list.add(StringTag.valueOf(value)); + } + return list; + } + + @Override + public boolean isActive() { + return GeneralCompatConfig.tiscmNetworkProtocol; + } + + private interface PayloadBuilder { + void accept(CompoundTag nbt); + } + + public enum C2SPacket { + HI("hi", true), + SUPPORTED_S2C_PACKETS("supported_s2c_packets", true), + SPEED_TEST_UPLOAD_PAYLOAD("speed_test_upload_payload", false), + SPEED_TEST_PING("speed_test_ping", false); + + private static final Map BY_ID = Arrays.stream(values()).collect(Collectors.toMap(packet -> packet.id, packet -> packet)); + + private final String id; + private final boolean handshake; + + C2SPacket(String id, boolean handshake) { + this.id = id; + this.handshake = handshake; + } + + public static Optional fromId(String id) { + return Optional.ofNullable(BY_ID.get(id)); + } + } + + public enum S2CPacket { + HELLO("hello", true), + SUPPORTED_C2S_PACKETS("supported_c2s_packets", true), + MSPT_METRICS_SAMPLE("mspt_metrics_sample", false), + SPEED_TEST_DOWNLOAD_PAYLOAD("speed_test_download_payload", false), + SPEED_TEST_UPLOAD_REQUEST("speed_test_upload_request", false), + SPEED_TEST_PING("speed_test_ping", false), + SPEED_TEST_ABORT("speed_test_abort", false); + + private static final Map BY_ID = Arrays.stream(values()).collect(Collectors.toMap(packet -> packet.id, packet -> packet)); + + private final String id; + private final boolean handshake; + + S2CPacket(String id, boolean handshake) { + this.id = id; + this.handshake = handshake; + } + + public static Optional fromId(String id) { + return Optional.ofNullable(BY_ID.get(id)); + } + } + + public record TISCMPayload(String packetId, CompoundTag nbt) implements LeavesCustomPayload { + @ID + private static final Identifier NETWORK_ID = TISCMProtocol.id("network/v1"); + + @Codec + private static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, + TISCMPayload::packetId, + ByteBufCodecs.COMPOUND_TAG, + TISCMPayload::nbt, + TISCMPayload::new + ); + } +}