feat: add Leaves' Creative Fly No Clip feature

fix #16
This commit is contained in:
Helvetica Volubi
2026-03-09 00:13:08 +08:00
parent b22e9e588b
commit 85d2fa3372
3 changed files with 287 additions and 0 deletions
@@ -0,0 +1,148 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Sun, 8 Mar 2026 23:56:06 +0800
Subject: [PATCH] Leaves: Creative fly no clip
Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com>
As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/da12a58e8d3bd3cd13b52a60676d5a1e03acd30a/leaves-server/minecraft-patches/features/0049-Creative-fly-no-clip.patch)
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java
index f2ec0bd8cca3c08d558790537e17a8a2f95f1953..29d904b02a2c7b3d25261ca26c7c0479b00999ef 100644
--- a/net/minecraft/world/entity/ExperienceOrb.java
+++ b/net/minecraft/world/entity/ExperienceOrb.java
@@ -199,7 +199,7 @@ public class ExperienceOrb extends Entity {
Player prevTarget = this.followingPlayer; // CraftBukkit - store old target
if (this.followingPlayer == null || this.followingPlayer.isSpectator() || this.followingPlayer.distanceToSqr(this) > 64.0) {
Player nearestPlayer = this.level().getNearestPlayer(this, 8.0);
- if (nearestPlayer != null && !nearestPlayer.isSpectator() && !nearestPlayer.isDeadOrDying()) {
+ if (nearestPlayer != null && !nearestPlayer.isCreativeFlyOrSpectator() && !nearestPlayer.isDeadOrDying()) { // Leaves - creative no clip
this.followingPlayer = nearestPlayer;
} else {
this.followingPlayer = null;
diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java
index 29ca31b05ec5636243b13fee8b3399d5e21808b4..8966953ee80a1527c9c4cc4d0a7a3bdb2197a2da 100644
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -285,8 +285,8 @@ public abstract class Player extends Avatar implements ContainerUser {
if (this.cullTask != null) this.cullTask.requestCullSignal(); // Luminol - Ray tracing entity tracker
// Luminol end
- this.noPhysics = this.isSpectator();
- if (this.isSpectator() || this.isPassenger()) {
+ this.noPhysics = this.isCreativeFlyOrSpectator(); // Leaves - creative no clip
+ if (this.isCreativeFlyOrSpectator() || this.isPassenger()) { // Leaves - creative no clip
this.setOnGround(false);
}
@@ -415,7 +415,7 @@ public abstract class Player extends Avatar implements ContainerUser {
if (this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.SWIMMING)) {
Pose desiredPose = this.getDesiredPose();
Pose pose;
- if (this.isSpectator() || this.isPassenger() || this.canPlayerFitWithinBlocksAndEntitiesWhen(desiredPose)) {
+ if (this.isCreativeFlyOrSpectator() || this.isPassenger() || this.canPlayerFitWithinBlocksAndEntitiesWhen(desiredPose)) { // Leaves - creative no clip
pose = desiredPose;
} else if (this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.CROUCHING)) {
pose = Pose.CROUCHING;
@@ -544,7 +544,7 @@ public abstract class Player extends Avatar implements ContainerUser {
this.updateSwingTime();
this.yHeadRot = this.getYRot();
this.setSpeed((float)this.getAttributeValue(Attributes.MOVEMENT_SPEED));
- if (this.getHealth() > 0.0F && !this.isSpectator()) {
+ if (this.getHealth() > 0.0F && !this.isCreativeFlyOrSpectator()) { // Leaves - creative no clip
AABB aabb;
if (this.isPassenger() && !this.getVehicle().isRemoved()) {
aabb = this.getBoundingBox().minmax(this.getVehicle().getBoundingBox()).inflate(1.0, 0.0, 1.0);
@@ -1877,9 +1877,29 @@ public abstract class Player extends Avatar implements ContainerUser {
return this.gameMode() == GameType.SPECTATOR;
}
+ // Leaves start - creative no clip
+ public boolean isCreativeFlyOrSpectator() {
+ return isSpectator() || (fun.bm.lophine.config.modules.function.CreativeFlyNoClipConfig.enabled && isCreative() && getAbilities().flying);
+ }
+
+ public boolean canSpectatingPlace(net.minecraft.world.level.LevelReader world, BlockState state, BlockPos pos, net.minecraft.world.phys.shapes.CollisionContext context) {
+ if (this.isCreativeFlyOrSpectator()) {
+ net.minecraft.world.phys.shapes.VoxelShape voxelShape = state.getCollisionShape(world, pos, context);
+ return voxelShape.isEmpty() || world.isUnobstructed(this, voxelShape.move(pos.getX(), pos.getY(), pos.getZ()));
+ } else {
+ return world.isUnobstructed(state, pos, context);
+ }
+ }
+
+ @Override
+ public boolean isCollidable(boolean ignoreClimbing) {
+ return !isCreativeFlyOrSpectator() && super.isCollidable(ignoreClimbing);
+ }
+ // Leaves end - creative no clip
+
@Override
public boolean canBeHitByProjectile() {
- return !this.isSpectator() && super.canBeHitByProjectile();
+ return !this.isCreativeFlyOrSpectator() && super.canBeHitByProjectile(); // Leaves - creative no clip
}
@Override
diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java
index 4bb36d2965785fadf628a9bfac4fa2aa305aaf65..44786e4f77285f1fbd4643f560105e410035afeb 100644
--- a/net/minecraft/world/item/BlockItem.java
+++ b/net/minecraft/world/item/BlockItem.java
@@ -170,8 +170,9 @@ public class BlockItem extends Item {
protected boolean canPlace(BlockPlaceContext context, BlockState state) {
Player player = context.getPlayer();
// CraftBukkit start
+ CollisionContext collisionContext = player == null ? CollisionContext.empty() : CollisionContext.placementContext(player);
Level world = context.getLevel(); // Paper - Cancel hit for vanished players
- boolean canBuild = (!this.mustSurvive() || state.canSurvive(world, context.getClickedPos())) && world.checkEntityCollision(state, player, CollisionContext.placementContext(player), context.getClickedPos(), true); // Paper - Cancel hit for vanished players
+ boolean canBuild = (!this.mustSurvive() || state.canSurvive(world, context.getClickedPos())) && ((fun.bm.lophine.config.modules.function.CreativeFlyNoClipConfig.enabled && context.getPlayer() != null) ? context.getPlayer().canSpectatingPlace(world, state, context.getClickedPos(), collisionContext) : world.checkEntityCollision(state, 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/StandingAndWallBlockItem.java b/net/minecraft/world/item/StandingAndWallBlockItem.java
index be685383b8e7c8111e976fc560f16d43d6545a34..6a2219b45eae0ff6fc48de9a6e6cabf2ec60cb07 100644
--- a/net/minecraft/world/item/StandingAndWallBlockItem.java
+++ b/net/minecraft/world/item/StandingAndWallBlockItem.java
@@ -44,7 +44,7 @@ public class StandingAndWallBlockItem extends BlockItem {
// return blockState != null && level.isUnobstructed(blockState, clickedPos, CollisionContext.empty()) ? blockState : null;
// CraftBukkit start
if (blockState != null) {
- boolean defaultReturn = level.isUnobstructed(blockState, clickedPos, CollisionContext.empty());
+ boolean defaultReturn = (fun.bm.lophine.config.modules.function.CreativeFlyNoClipConfig.enabled && context.getPlayer() != null) ? context.getPlayer().canSpectatingPlace(level, blockState, clickedPos, CollisionContext.empty()) : level.isUnobstructed(blockState, clickedPos, CollisionContext.empty()); // Leaves - creative no clip
org.bukkit.entity.Player player = (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer serverPlayer) ? serverPlayer.getBukkitEntity() : null;
org.bukkit.event.block.BlockCanBuildEvent event = new org.bukkit.event.block.BlockCanBuildEvent(org.bukkit.craftbukkit.block.CraftBlock.at(context.getLevel(), clickedPos), player, org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockState), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent
diff --git a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
index 17110035f4b0f144c29b8ff8bf7d0d39efc8103a..1b7f0bfa66e632678164ac64e03855e75bab1305 100644
--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
@@ -153,7 +153,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl
List<Entity> entities = level.getEntities(null, progressDeltaAabb);
if (!entities.isEmpty()) {
for (Entity entity : entities) {
- if (entity.getPistonPushReaction() != PushReaction.IGNORE) {
+ if (entity.getPistonPushReaction() != PushReaction.IGNORE && !(entity instanceof net.minecraft.world.entity.player.Player player && player.isCreativeFlyOrSpectator())) { // Leaves - creative no clip
entity.move(
MoverType.SHULKER_BOX,
new Vec3(
diff --git a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
index 1d60054146f970ec24054d2c76a27e2d8620ec67..a5469971734a3f4f08d676426821fbf059c526dc 100644
--- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
+++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
@@ -161,7 +161,7 @@ public class PistonMovingBlockEntity extends BlockEntity {
d3 = movementDirection.getStepZ();
}
- entity.setDeltaMovement(d1, d2, d3);
+ if (!(entity instanceof net.minecraft.world.entity.player.Player player) || !player.isCreativeFlyOrSpectator()) entity.setDeltaMovement(d1, d2, d3); // Leaves - creative no clip
// Paper - EAR items stuck in slime pushed by a piston
entity.activatedTick = Math.max(entity.activatedTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 10); // Folia - region threading
entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 10); // Folia - region threading
@@ -197,6 +197,7 @@ public class PistonMovingBlockEntity extends BlockEntity {
}
private static void moveEntityByPiston(Direction noClipDirection, Entity entity, double progress, Direction direction) {
+ if (entity instanceof net.minecraft.world.entity.player.Player player && player.isCreativeFlyOrSpectator()) return; // Leaves - creative no clip
NOCLIP.set(noClipDirection);
Vec3 vec3 = entity.position();
entity.move(MoverType.PISTON, new Vec3(progress * direction.getStepX(), progress * direction.getStepY(), progress * direction.getStepZ()));
@@ -0,0 +1,25 @@
package fun.bm.lophine.config.modules.function;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import me.earthme.luminol.config.IConfigModule;
import me.earthme.luminol.config.flags.ConfigClassInfo;
import me.earthme.luminol.config.flags.ConfigInfo;
import me.earthme.luminol.enums.EnumConfigCategory;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.CarpetServerProtocol;
import java.util.Set;
@ConfigClassInfo(name = "creative_fly_no_clip", category = EnumConfigCategory.FUNCTION)
public class CreativeFlyNoClipConfig implements IConfigModule {
@ConfigInfo(name = "enabled", comments = """
Whether to enable creative fly no clip.
When enabled, players in creative mode will not collide with blocks while flying.
This allows them to pass through blocks without obstruction.""")
public static boolean enabled = false;
@Override
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> e) {
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "creativeNoClip", enabled));
}
}
@@ -0,0 +1,114 @@
package org.leavesmc.leaves.protocol;
import com.mojang.logging.LogUtils;
import fun.bm.lophine.config.modules.function.CreativeFlyNoClipConfig;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
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.HashMap;
import java.util.Locale;
import java.util.Map;
@LeavesProtocol.Register(namespace = "carpet")
public class CarpetServerProtocol implements LeavesProtocol {
private static final Logger LOGGER = LogUtils.getClassLogger();
public static final String PROTOCOL_ID = "carpet";
public static final String VERSION = ProtocolUtils.buildProtocolVersion(PROTOCOL_ID);
private static final String HI = "69";
private static final String HELLO = "420";
@Contract("_ -> new")
public static Identifier id(String path) {
return Identifier.fromNamespaceAndPath(PROTOCOL_ID, path);
}
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(ServerPlayer player) {
CompoundTag data = new CompoundTag();
data.putString(HI, VERSION);
ProtocolUtils.sendPayloadPacket(player, new CarpetPayload(data));
}
@ProtocolHandler.PayloadReceiver(payload = CarpetPayload.class)
private static void handleHello(@NotNull ServerPlayer player, @NotNull CarpetServerProtocol.CarpetPayload payload) {
if (payload.nbt.contains(HELLO)) {
LOGGER.info("Player {} joined with carpet {}", player.getScoreboardName(), payload.nbt.getString(HELLO).orElse("Unknown"));
CompoundTag data = new CompoundTag();
CarpetRules.write(data);
ProtocolUtils.sendPayloadPacket(player, new CarpetPayload(data));
}
}
@Override
public boolean isActive() {
return CreativeFlyNoClipConfig.enabled;
} // re-edit for creative no clip
public static class CarpetRules {
private static final Map<String, CarpetRule> rules = new HashMap<>();
public static void write(@NotNull CompoundTag tag) {
CompoundTag rulesNbt = new CompoundTag();
rules.values().forEach(rule -> rule.writeNBT(rulesNbt));
tag.put("Rules", rulesNbt);
}
public static void register(CarpetRule rule) {
rules.put(rule.name, rule);
}
}
public record CarpetRule(String identifier, String name, String value) {
@NotNull
@Contract("_, _, _ -> new")
public static CarpetRule of(String identifier, String name, Enum<?> value) {
return new CarpetRule(identifier, name, value.name().toLowerCase(Locale.ROOT));
}
@NotNull
@Contract("_, _, _ -> new")
public static CarpetRule of(String identifier, String name, boolean value) {
return new CarpetRule(identifier, name, Boolean.toString(value));
}
public void writeNBT(@NotNull CompoundTag rules) {
CompoundTag rule = new CompoundTag();
String key = name;
while (rules.contains(key)) {
key = key + "2";
}
rule.putString("Value", value);
rule.putString("Manager", identifier);
rule.putString("Rule", name);
rules.put(key, rule);
}
}
public record CarpetPayload(CompoundTag nbt) implements LeavesCustomPayload {
@ID
private static final Identifier HELLO_ID = CarpetServerProtocol.id("hello");
@Codec
private static final StreamCodec<FriendlyByteBuf, CarpetPayload> CODEC = StreamCodec.composite(
ByteBufCodecs.COMPOUND_TAG, CarpetPayload::nbt, CarpetPayload::new
);
}
}