From eb626e11765f6d197d9e6dcffe06ee364b3a712d Mon Sep 17 00:00:00 2001 From: Aikar Date: Wed, 13 May 2020 23:01:26 -0400 Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed This fixes exploits that let players destroy bedrock by Pistons, explosions and Mushrooom/Tree generation. These blocks are designed to not be broken except by creative players/commands. So protect them from a multitude of methods of destroying them. A config is provided if you rather let players use these exploits, and let them destroy the worlds End Portals and get on top of the nether easy. --- .../minecraft/world/level/Level.java.patch | 28 +++++---- .../world/level/ServerExplosion.java.patch | 21 +++---- .../world/level/block/Block.java.patch | 41 +++++++++---- .../block/piston/PistonBaseBlock.java.patch | 45 +++++++++++--- .../block/state/BlockBehaviour.java.patch | 59 +++++++++++++++---- .../level/portal/PortalForcer.java.patch | 14 +++++ 6 files changed, 156 insertions(+), 52 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch index 23f0897bb6..f389e6b3f6 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch @@ -344,7 +344,7 @@ + return chunk == null ? null : chunk.getFluidState(blockposition); + } + - @Override ++ @Override + public final boolean hasChunkAt(BlockPos pos) { + return getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4) != null; // Paper - Perf: Optimize Level.hasChunkAt(BlockPosition)Z + } @@ -365,18 +365,22 @@ + return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null; + } + -+ @Override + @Override public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { + // Paper end ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create); if (ichunkaccess == null && create) { -@@ -207,6 +444,18 @@ +@@ -207,6 +444,22 @@ @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ BlockState type = getBlockState(pos); ++ if (!type.isDestroyable()) return false; ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + CraftBlockState blockstate = this.capturedBlockStates.get(pos); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); @@ -390,7 +394,7 @@ if (this.isOutsideBuildHeight(pos)) { return false; } else if (!this.isClientSide && this.isDebug()) { -@@ -214,45 +463,126 @@ +@@ -214,45 +467,126 @@ } else { LevelChunk chunk = this.getChunkAt(pos); Block block = state.getBlock(); @@ -532,7 +536,7 @@ public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {} @Override -@@ -270,15 +600,33 @@ +@@ -270,15 +604,33 @@ return false; } else { FluidState fluid = this.getFluidState(pos); @@ -569,7 +573,7 @@ } boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth); -@@ -340,10 +688,18 @@ +@@ -340,10 +692,18 @@ @Override public BlockState getBlockState(BlockPos pos) { @@ -589,7 +593,7 @@ return chunk.getBlockState(pos); } -@@ -446,34 +802,53 @@ +@@ -446,34 +806,53 @@ this.pendingBlockEntityTickers.clear(); } @@ -641,18 +645,18 @@ + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + // Paper end - Prevent block entity and entity crashes } - } ++ } + // Paper start - Option to prevent armor stands from doing entity lookups + @Override + public boolean noCollision(@Nullable Entity entity, AABB box) { + if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; + return LevelAccessor.super.noCollision(entity, box); -+ } + } + // Paper end - Option to prevent armor stands from doing entity lookups public boolean shouldTickDeath(Entity entity) { return true; -@@ -510,13 +885,32 @@ +@@ -510,13 +889,32 @@ @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { @@ -686,7 +690,7 @@ this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity); } } -@@ -643,7 +1037,7 @@ +@@ -643,7 +1041,7 @@ for (int k = 0; k < j; ++k) { EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; @@ -695,7 +699,7 @@ if (t0 != null && predicate.test(t0)) { result.add(t0); -@@ -912,7 +1306,7 @@ +@@ -912,7 +1310,7 @@ public static enum ExplosionInteraction implements StringRepresentable { diff --git a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch index 36deb65d8a..e0f12942e5 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch @@ -53,16 +53,17 @@ } private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { -@@ -135,7 +150,7 @@ +@@ -135,7 +150,8 @@ for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { BlockPos blockposition = BlockPos.containing(d4, d5, d6); BlockState iblockdata = this.level.getBlockState(blockposition); - FluidState fluid = this.level.getFluidState(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions if (!this.level.isInWorldBounds(blockposition)) { break; -@@ -149,6 +164,15 @@ +@@ -149,6 +165,15 @@ if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { set.add(blockposition); @@ -78,7 +79,7 @@ } d4 += d0 * 0.30000001192092896D; -@@ -171,7 +195,7 @@ +@@ -171,7 +196,7 @@ int l = Mth.floor(this.center.y + (double) f + 1.0D); int i1 = Mth.floor(this.center.z - (double) f - 1.0D); int j1 = Mth.floor(this.center.z + (double) f + 1.0D); @@ -87,7 +88,7 @@ Iterator iterator = list.iterator(); while (iterator.hasNext()) { -@@ -192,10 +216,38 @@ +@@ -192,10 +217,38 @@ d3 /= d4; boolean flag = this.damageCalculator.shouldDamageEntity(this, entity); float f1 = this.damageCalculator.getKnockbackMultiplier(entity); @@ -128,7 +129,7 @@ } double d5 = (1.0D - d0) * (double) f2 * (double) f1; -@@ -204,7 +256,7 @@ +@@ -204,7 +257,7 @@ if (entity instanceof LivingEntity) { LivingEntity entityliving = (LivingEntity) entity; @@ -137,7 +138,7 @@ } else { d6 = d5; } -@@ -214,11 +266,19 @@ +@@ -214,11 +267,19 @@ d3 *= d6; Vec3 vec3d = new Vec3(d1, d2, d3); @@ -158,7 +159,7 @@ this.hitPlayers.put(entityhuman, vec3d); } } -@@ -235,10 +295,62 @@ +@@ -235,10 +296,62 @@ List list1 = new ArrayList(); Util.shuffle(positions, this.level.random); @@ -221,7 +222,7 @@ this.level.getBlockState(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> { ServerExplosion.addOrAppendStack(list1, itemstack, blockposition1); -@@ -262,13 +374,22 @@ +@@ -262,13 +375,22 @@ BlockPos blockposition = (BlockPos) iterator.next(); if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockposition).isAir() && this.level.getBlockState(blockposition.below()).isSolidRender()) { @@ -245,7 +246,7 @@ this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center); List list = this.calculateExplodedPositions(); -@@ -288,6 +409,7 @@ +@@ -288,6 +410,7 @@ } private static void addOrAppendStack(List droppedItemsOut, ItemStack item, BlockPos pos) { @@ -253,7 +254,7 @@ Iterator iterator = droppedItemsOut.iterator(); do { -@@ -372,4 +494,85 @@ +@@ -372,4 +495,85 @@ } } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch index 8b81b9c548..c1060afc42 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch @@ -1,11 +1,30 @@ --- a/net/minecraft/world/level/block/Block.java +++ b/net/minecraft/world/level/block/Block.java -@@ -292,15 +292,41 @@ - }); - state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); - } -+ +@@ -88,6 +88,21 @@ + public static final int UPDATE_LIMIT = 512; + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || ++ this != Blocks.BARRIER && ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_GATEWAY && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.STRUCTURE_BLOCK && ++ this != Blocks.JIGSAW; + } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + @Nullable + private Item item; + private static final int CACHE_SIZE = 256; +@@ -295,12 +310,38 @@ + + } + // Paper start - Add BlockBreakBlockEvent + public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) { @@ -25,9 +44,9 @@ + block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping + } + return true; - } ++ } + // Paper end - Add BlockBreakBlockEvent - ++ public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { + // Paper start - Properly handle xp dropping + dropResources(state, world, pos, blockEntity, entity, tool, true); @@ -43,7 +62,7 @@ } } -@@ -340,7 +366,13 @@ +@@ -340,7 +381,13 @@ ItemEntity entityitem = (ItemEntity) itemEntitySupplier.get(); entityitem.setDefaultPickUpDelay(); @@ -58,7 +77,7 @@ return; } } -@@ -348,8 +380,13 @@ +@@ -348,8 +395,13 @@ } public void popExperience(ServerLevel world, BlockPos pos, int size) { @@ -73,7 +92,7 @@ } } -@@ -367,10 +404,18 @@ +@@ -367,10 +419,18 @@ return this.defaultBlockState(); } @@ -94,7 +113,7 @@ } public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} -@@ -490,15 +535,35 @@ +@@ -490,15 +550,35 @@ return this.builtInRegistryHolder; } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch index 2bd5e17a27..d269336ba6 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch @@ -33,7 +33,20 @@ world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); } -@@ -229,6 +248,13 @@ +@@ -197,6 +216,12 @@ + @Override + protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true); + + if (!world.isClientSide) { +@@ -229,8 +254,15 @@ BlockState iblockdata2 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); @@ -45,9 +58,12 @@ + } + // Paper end - Fix sticky pistons and BlockPistonRetractEvent world.setBlock(pos, iblockdata2, 20); - world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change world.blockUpdated(pos, iblockdata2.getBlock()); -@@ -255,6 +281,13 @@ + iblockdata2.updateNeighbourShapes(world, pos, 2); + if (this.isSticky) { +@@ -255,11 +287,25 @@ if (type == 1 && !iblockdata3.isAir() && PistonBaseBlock.isPushable(iblockdata3, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata3.getPistonPushReaction() == PushReaction.NORMAL || iblockdata3.is(Blocks.PISTON) || iblockdata3.is(Blocks.STICKY_PISTON))) { this.moveBlocks(world, pos, enumdirection, false); } else { @@ -61,18 +77,31 @@ world.removeBlock(pos.relative(enumdirection), false); } } -@@ -335,7 +368,49 @@ + } else { +- world.removeBlock(pos.relative(enumdirection), false); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks ++ BlockPos headPos = pos.relative(enumdirection); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.removeBlock(headPos, false); ++ } else { ++ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + } + + world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +@@ -335,7 +381,49 @@ BlockState[] aiblockdata = new BlockState[list.size() + list2.size()]; Direction enumdirection1 = extend ? dir : dir.getOpposite(); int i = 0; + // CraftBukkit start + final org.bukkit.block.Block bblock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); - ++ + final List moved = pistonextendschecker.getToPush(); + final List broken = pistonextendschecker.getToDestroy(); + + List blocks = new AbstractList() { -+ + + @Override + public int size() { + return moved.size() + broken.size(); @@ -111,7 +140,7 @@ BlockPos blockposition3; int j; BlockState iblockdata1; -@@ -345,7 +420,7 @@ +@@ -345,7 +433,7 @@ iblockdata1 = world.getBlockState(blockposition3); BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null; @@ -120,7 +149,7 @@ world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); if (!iblockdata1.is(BlockTags.FIRE)) { -@@ -358,13 +433,25 @@ +@@ -358,13 +446,25 @@ BlockState iblockdata2; for (j = list.size() - 1; j >= 0; --j) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch index f250348594..56a6b42a76 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch @@ -37,6 +37,15 @@ if (state.hasBlockEntity() && !state.is(newState.getBlock())) { world.removeBlockEntity(pos); } +@@ -166,7 +178,7 @@ + } + + protected void onExplosionHit(BlockState state, ServerLevel world, BlockPos pos, Explosion explosion, BiConsumer stackMerger) { +- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { ++ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed + Block block = state.getBlock(); + boolean flag = explosion.getIndirectSourceEntity() instanceof Player; + @@ -174,8 +186,10 @@ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; LootParams.Builder lootparams_a = (new LootParams.Builder(world)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity).withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity()); @@ -50,6 +59,15 @@ } state.spawnAfterBreak(world, pos, ItemStack.EMPTY, flag); +@@ -243,7 +257,7 @@ + } + + protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) { +- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); ++ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + protected boolean canBeReplaced(BlockState state, Fluid fluid) { @@ -851,7 +865,15 @@ this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles; this.instrument = blockbase_info.instrument; @@ -81,7 +99,20 @@ this.legacySolid = this.calculateSolid(); this.occlusionShape = this.canOcclude ? ((Block) this.owner).getOcclusionShape(this.asState()) : Shapes.empty(); -@@ -945,19 +969,19 @@ +@@ -925,6 +949,12 @@ + return this.legacySolid; + } + ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed ++ + public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType type) { + return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type); + } +@@ -945,19 +975,19 @@ return this.occlusionShape; } @@ -106,7 +137,15 @@ return this.isAir; } -@@ -1035,7 +1059,7 @@ +@@ -1028,14 +1058,14 @@ + } + + public PushReaction getPistonPushReaction() { +- return this.pushReaction; ++ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + public boolean isSolidRender() { return this.solidRender; } @@ -115,24 +154,22 @@ return this.canOcclude; } -@@ -1125,9 +1149,15 @@ +@@ -1125,7 +1155,13 @@ } public void onPlace(Level world, BlockPos pos, BlockState state, boolean notify) { - this.getBlock().onPlace(this.asState(), world, pos, state, notify); + // CraftBukkit start + this.onPlace(world, pos, state, notify, null); - } - ++ } ++ + public void onPlace(Level world, BlockPos blockposition, BlockState iblockdata, boolean flag, @Nullable UseOnContext context) { + this.getBlock().onPlace(this.asState(), world, blockposition, iblockdata, flag, context); + // CraftBukkit end -+ } -+ - public void onRemove(Level world, BlockPos pos, BlockState state, boolean moved) { - this.getBlock().onRemove(this.asState(), world, pos, state, moved); } -@@ -1154,6 +1184,7 @@ + + public void onRemove(Level world, BlockPos pos, BlockState state, boolean moved) { +@@ -1154,6 +1190,7 @@ public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience); @@ -140,7 +177,7 @@ } public List getDrops(LootParams.Builder builder) { -@@ -1250,11 +1281,11 @@ +@@ -1250,11 +1287,11 @@ return this.getBlock().builtInRegistryHolder().is(key); } diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch index d2424a498a..2bcc6db1ba 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch @@ -133,3 +133,17 @@ return Optional.of(new BlockUtil.FoundRectangle(blockposition1.immutable(), 2, 3)); } +@@ -178,6 +207,13 @@ + for (int j = -1; j < 3; ++j) { + for (int k = -1; k < 4; ++k) { + temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { ++ if (!this.level.getBlockState(temp).isDestroyable()) { ++ return false; ++ } ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + if (k < 0 && !this.level.getBlockState(temp).isSolid()) { + return false; + }