From c4fbf88aaa698bbe13e9f931b2a5323bd371c5cd Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sat, 18 Oct 2025 15:41:53 +0200 Subject: [PATCH 01/12] fix: bed explosions inconsistencies with vanilla --- .../sources/net/minecraft/world/level/block/BedBlock.java.patch | 2 +- .../java/org/bukkit/craftbukkit/event/CraftEventFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch index be634ae10560..97c943829d2d 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch @@ -13,7 +13,7 @@ level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); return InteractionResult.SUCCESS_SERVER; } else if (state.getValue(OCCUPIED)) { -+ if (!bedRule.explodes()) return this.explodeBed(state, level, pos); // Paper - check explode first ++ if (bedRule.explodes()) return this.explodeBed(state, level, pos); // Paper - check explode first if (!this.kickVillagerOutOfBed(level, pos)) { player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 277d3b755c14..51e3bf0aae0e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -307,7 +307,7 @@ public static PlayerBedFailEnterEvent callPlayerBedFailEnterEvent( (org.bukkit.entity.Player) player.getBukkitEntity(), asFailReason(bedSleepingProblem), org.bukkit.craftbukkit.block.CraftBlock.at(player.level(), bed), - !player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE).canSleep().test(player.level()), // TODO - snapshot - check if canSleep is correct + player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE).explodes(), io.papermc.paper.adventure.PaperAdventure.asAdventure(bedSleepingProblem.message())); event.callEvent(); return event; From a689cad3dea66ef1ba1d0a0793ebc19dd2b20709 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sat, 18 Oct 2025 17:44:21 +0200 Subject: [PATCH 02/12] fix: player bed events --- .../event/player/PlayerBedFailEnterEvent.java | 14 ++++----- .../event/player/PlayerBedEnterEvent.java | 15 ++++------ .../server/level/ServerPlayer.java.patch | 12 ++++++-- .../world/entity/player/Player.java.patch | 7 +++++ .../craftbukkit/event/CraftEventFactory.java | 30 +++++++++++++++---- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index c30873dfbf98..b83a9a18aeed 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -83,11 +83,8 @@ public static HandlerList getHandlerList() { public enum FailReason { /** * The world doesn't allow sleeping (ex. Nether or The End). Entering - * the bed is prevented and the bed explodes. - * - * @deprecated TODO - snapshot - no longer exists in vanilla + * the bed is prevented. */ - @Deprecated(since = "1.21.11") NOT_POSSIBLE_HERE, /** * Entering the bed is prevented due to it not being night nor @@ -96,10 +93,7 @@ public enum FailReason { * If the event is forcefully allowed during daytime, the player will * enter the bed (and set its bed location), but might get immediately * thrown out again. - * - * @deprecated TODO - snapshot - no longer exists in vanilla */ - @Deprecated(since = "1.21.11") NOT_POSSIBLE_NOW, /** * Entering the bed is prevented due to the player being too far away. @@ -116,6 +110,10 @@ public enum FailReason { /** * Entering the bed is prevented due to there being monsters nearby. */ - NOT_SAFE + NOT_SAFE, + /** + * Entering the bed is prevented and the bed explodes. + */ + EXPLOSION } } diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java index 8b68c9fe2709..7c4c00ec2cc7 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -137,12 +137,8 @@ public enum BedEnterResult { * Nether, The End or Custom Worlds). This is based on * {@link World#isBedWorks()} and {@link World#isNatural()}. *

- * Entering the bed is prevented and if {@link World#isBedWorks()} is - * {@code false} then the bed explodes. - * - * @deprecated TODO - snapshot - no longer exists in vanilla + * Entering the bed is prevented */ - @Deprecated(since = "1.21.11") NOT_POSSIBLE_HERE, /** * Entering the bed is prevented due to it not being night nor @@ -151,10 +147,7 @@ public enum BedEnterResult { * If the event is forcefully allowed during daytime, the player will * enter the bed (and set its bed location), but might get immediately * thrown out again. - * - * @deprecated TODO - snapshot - no longer exists in vanilla */ - @Deprecated(since = "1.21.11") NOT_POSSIBLE_NOW, /** * Entering the bed is prevented due to the player being too far away. @@ -171,6 +164,10 @@ public enum BedEnterResult { /** * Entering the bed is prevented due to there being some other problem. */ - OTHER_PROBLEM; + OTHER_PROBLEM, + /** + * Entering the bed is prevented and the bed explodes. + */ + EXPLOSION; } } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 8659dc0f90d5..351b72ddba6d 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -896,18 +896,24 @@ this.enteredNetherPosition = null; } } -@@ -1174,9 +_,8 @@ +@@ -1174,11 +_,12 @@ this.containerMenu.broadcastChanges(); } - @Override - public Either startSleepInBed(BlockPos bedPos) { - Direction direction = this.level().getBlockState(bedPos).getValue(HorizontalDirectionalBlock.FACING); +- if (!this.isSleeping() && this.isAlive()) { +- BedRule bedRule = this.level().environmentAttributes().getValue(EnvironmentAttributes.BED_RULE, bedPos); + // CraftBukkit start - moved bed result checks from below into separate method + private Either getBedResult(BlockPos bedPos, Direction direction) { - if (!this.isSleeping() && this.isAlive()) { - BedRule bedRule = this.level().environmentAttributes().getValue(EnvironmentAttributes.BED_RULE, bedPos); ++ BedRule bedRule = this.level().environmentAttributes().getValue(EnvironmentAttributes.BED_RULE, bedPos); ++ if (bedRule.explodes()) { ++ return Either.left(Player.BedSleepingProblem.EXPLOSION); ++ } else if (!this.isSleeping() && this.isAlive()) { boolean canSleep = bedRule.canSleep(this.level()); + boolean canSetSpawn = bedRule.canSetSpawn(this.level()); + if (!canSetSpawn && !canSleep) { @@ -1190,7 +_,7 @@ } else { if (canSetSpawn) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch index 4c254e2c6c41..0046f3b4403e 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch @@ -526,3 +526,10 @@ } } +@@ -2042,5 +_,6 @@ + public static final Player.BedSleepingProblem OBSTRUCTED = new Player.BedSleepingProblem(Component.translatable("block.minecraft.bed.obstructed")); + public static final Player.BedSleepingProblem OTHER_PROBLEM = new Player.BedSleepingProblem(null); + public static final Player.BedSleepingProblem NOT_SAFE = new Player.BedSleepingProblem(Component.translatable("block.minecraft.bed.not_safe")); ++ public static final Player.BedSleepingProblem EXPLOSION = new Player.BedSleepingProblem(null); // Paper - Added to properly handle explosions in bed events + } + } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 51e3bf0aae0e..7490560810f5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -288,8 +288,10 @@ public static boolean callPlayerSignOpenEvent(Player player, Sign sign, Side sid return !event.isCancelled(); } - public static PlayerBedFailEnterEvent.FailReason asFailReason(final net.minecraft.world.entity.player.Player.BedSleepingProblem sleepingProblem) { - if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { + public static PlayerBedFailEnterEvent.FailReason asFailReason(final net.minecraft.world.attribute.BedRule bedRule, final net.minecraft.world.entity.player.Player.BedSleepingProblem sleepingProblem) { + if (bedRule.explodes()) { + return PlayerBedFailEnterEvent.FailReason.EXPLOSION; + } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { return PlayerBedFailEnterEvent.FailReason.OTHER_PROBLEM; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE) { return PlayerBedFailEnterEvent.FailReason.NOT_SAFE; @@ -297,17 +299,26 @@ public static PlayerBedFailEnterEvent.FailReason asFailReason(final net.minecraf return PlayerBedFailEnterEvent.FailReason.OBSTRUCTED; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.TOO_FAR_AWAY) { return PlayerBedFailEnterEvent.FailReason.TOO_FAR_AWAY; + } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.EXPLOSION) { + return PlayerBedFailEnterEvent.FailReason.EXPLOSION; + } else if (sleepingProblem.message() != null) { + if (bedRule.canSleep() == net.minecraft.world.attribute.BedRule.Rule.NEVER) { + return PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE; + } else if (bedRule.canSleep() == net.minecraft.world.attribute.BedRule.Rule.WHEN_DARK) { + return PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_NOW; + } } throw new IllegalArgumentException(sleepingProblem.toString()); } public static PlayerBedFailEnterEvent callPlayerBedFailEnterEvent( net.minecraft.world.entity.player.Player player, BlockPos bed, net.minecraft.world.entity.player.Player.BedSleepingProblem bedSleepingProblem) { + net.minecraft.world.attribute.BedRule bedRule = player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); final var event = new PlayerBedFailEnterEvent( (org.bukkit.entity.Player) player.getBukkitEntity(), - asFailReason(bedSleepingProblem), + asFailReason(bedRule, bedSleepingProblem), org.bukkit.craftbukkit.block.CraftBlock.at(player.level(), bed), - player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE).explodes(), + bedSleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.EXPLOSION, io.papermc.paper.adventure.PaperAdventure.asAdventure(bedSleepingProblem.message())); event.callEvent(); return event; @@ -323,8 +334,17 @@ public static Either BedEnterResult.OK).map(java.util.function.Function.identity(), java.util.function.Function.identity()); PlayerBedEnterEvent event = new PlayerBedEnterEvent((Player) player.getBukkitEntity(), CraftBlock.at(player.level(), bed), bedEnterResult); From c6c55642d036297dfb9612747ff4387004592307 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sat, 18 Oct 2025 19:02:34 +0200 Subject: [PATCH 03/12] fix: World#isBedWorks implementation and beds-related javadocs --- .../paper/event/player/PlayerBedFailEnterEvent.java | 3 ++- paper-api/src/main/java/org/bukkit/World.java | 9 ++++----- .../org/bukkit/event/player/PlayerBedEnterEvent.java | 7 ++++--- .../src/main/java/org/bukkit/craftbukkit/CraftWorld.java | 4 +++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index b83a9a18aeed..1ebbc8591a8e 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -83,7 +83,8 @@ public static HandlerList getHandlerList() { public enum FailReason { /** * The world doesn't allow sleeping (ex. Nether or The End). Entering - * the bed is prevented. + * the bed is prevented but the bed doesn't explode. When the bed + * explodes, {@link #EXPLOSION} is used instead. */ NOT_POSSIBLE_HERE, /** diff --git a/paper-api/src/main/java/org/bukkit/World.java b/paper-api/src/main/java/org/bukkit/World.java index 7b22c923b467..e2360ed84aae 100644 --- a/paper-api/src/main/java/org/bukkit/World.java +++ b/paper-api/src/main/java/org/bukkit/World.java @@ -2794,9 +2794,7 @@ default double getHumidity(int x, int z) { /** * Gets if this world is natural. * - * When false, compasses spin randomly, and using a bed to set the respawn - * point or sleep, is disabled. When true, nether portals can spawn - * zombified piglins. + * When false, the moon is not visible and eyeblossoms do not open/close * * @return true if world is natural */ @@ -2805,11 +2803,12 @@ default double getHumidity(int x, int z) { /** * Gets if beds work in this world. * - * A non-working bed will blow up when trying to sleep. {@link #isNatural()} - * defines if a bed can be used to set spawn point. + * A non-working bed can blow up when trying to sleep, but that may + * not always be the case. * * @return true if beds work in this world */ + // TODO - snapshot: Should we deprecate this method since the underlying setting was split in 3 different environment attributes? public boolean isBedWorks(); /** diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java index 7c4c00ec2cc7..762d6c717e56 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -133,9 +133,10 @@ public enum BedEnterResult { */ OK, /** - * The world doesn't allow sleeping or saving the spawn point (eg, - * Nether, The End or Custom Worlds). This is based on - * {@link World#isBedWorks()} and {@link World#isNatural()}. + * The world doesn't allow sleeping (eg, Nether, The End or Custom Worlds), but + * saving the spawn point may still be allowed. See {@link com.destroystokyo.paper.event.player.PlayerSetSpawnEvent}. + * for spawn point changes. This is only called when sleeping isn't allowed and the bed + * doesn't explode. When the bed explodes, {@link #EXPLOSION} is called instead. *

* Entering the bed is prevented */ diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index fac6f0e6019e..76e41ba410bc 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -57,6 +57,7 @@ import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; +import net.minecraft.world.attribute.BedRule; import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; @@ -1373,7 +1374,8 @@ public boolean isNatural() { @Override public boolean isBedWorks() { - return this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE).canSleep().test(this.world); // TODO - snapshot - check if canSleep is correct + BedRule bedRule = this.world.environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); + return !bedRule.explodes() && bedRule.canSleep().test(this.world); } @Override From 7d4fc6d8a865ea8bee5930eee8e00c99c36e9a05 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sun, 19 Oct 2025 02:48:42 +0200 Subject: [PATCH 04/12] feat: new player bed events api to better match vanilla changes --- .../paper/block/bed/BedEnterAction.java | 46 +++++++ .../paper/block/bed/BedEnterActionImpl.java | 10 ++ .../paper/block/bed/BedEnterProblem.java | 37 ++++++ .../block/bed/BedEnterProblemBridge.java | 30 +++++ .../paper/block/bed/BedRuleResult.java | 39 ++++++ .../paper/block/bed/BedRuleResultImpl.java | 12 ++ .../papermc/paper/block/bed/package-info.java | 4 + .../event/player/PlayerBedFailEnterEvent.java | 39 +++++- .../event/player/PlayerBedEnterEvent.java | 45 ++++++- .../world/level/block/BedBlock.java.patch | 2 +- .../block/bed/BedEnterProblemBridgeImpl.java | 31 +++++ .../paper/block/bed/BedEnterProblemImpl.java | 10 ++ .../craftbukkit/event/CraftEventFactory.java | 125 ++++++++++++++---- ...ermc.paper.block.bed.BedEnterProblemBridge | 1 + 14 files changed, 395 insertions(+), 36 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/package-info.java create mode 100644 paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java create mode 100644 paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java new file mode 100644 index 000000000000..76513ee9fe23 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java @@ -0,0 +1,46 @@ +package io.papermc.paper.block.bed; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +/** + * An action type that represents the action that will happen after + * {@link org.bukkit.event.player.PlayerBedEnterEvent} and that is + * happening during {@link io.papermc.paper.event.player.PlayerBedFailEnterEvent} + */ +@ApiStatus.Experimental +public sealed interface BedEnterAction permits BedEnterActionImpl { + + /** + * Gets if the player is allowed to sleep in the bed. + * This can be {@link BedRuleResult#ALLOWED} at the same time as {@link #canSetSpawn()}, + * but will always be {@link BedRuleResult#UNDEFINED} if {@link #problem()} is anything but {@code null} + * + * @return whether the player is allowed to sleep + */ + BedRuleResult canSleep(); + + /** + * Gets if the player is allowed to save its spawn point in the bed. + * This can be {@link BedRuleResult#ALLOWED} at the same time as {@link #canSleep()}, + * but will always be {@link BedRuleResult#UNDEFINED} if {@link #problem()} is anything but {@code null} + *

+ * Note: This can also be {@link BedRuleResult#UNDEFINED} at any time if the event containing + * this action is called from sources external to Paper. + * + * @return whether the player is allowed to save its spawn point + */ + BedRuleResult canSetSpawn(); + + /** + * A problem is an issue that prevents the player from sleeping and from saving its spawn point + *

+ * When a problem was found, both {@link #canSleep()} and {@link #canSetSpawn()} + * will be {@link BedRuleResult#UNDEFINED} + * + * @return any of {@link BedEnterProblem}s if one is found, otherwise {@code null} + */ + @Nullable + BedEnterProblem problem(); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java new file mode 100644 index 000000000000..89352acbda11 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java @@ -0,0 +1,10 @@ +package io.papermc.paper.block.bed; + +import org.jspecify.annotations.Nullable; + +public record BedEnterActionImpl( + BedRuleResult canSleep, + BedRuleResult canSetSpawn, + @Nullable BedEnterProblem problem +) implements BedEnterAction { +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java new file mode 100644 index 000000000000..5c93a952dde5 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java @@ -0,0 +1,37 @@ +package io.papermc.paper.block.bed; + +import org.jetbrains.annotations.ApiStatus; + +/** + * + */ +@ApiStatus.NonExtendable +@ApiStatus.Experimental +public interface BedEnterProblem { + + /** + * + */ + BedEnterProblem TOO_FAR_AWAY = BedEnterProblemBridge.instance().createTooFarAwayProblem(); + + /** + * + */ + BedEnterProblem OBSTRUCTED = BedEnterProblemBridge.instance().createObstructedProblem(); + + /** + * + */ + BedEnterProblem NOT_SAFE = BedEnterProblemBridge.instance().createMonstersNearbyProblem(); + + /** + * + */ + BedEnterProblem EXPLOSION = BedEnterProblemBridge.instance().createExplosionProblem(); + + /** + * + */ + BedEnterProblem OTHER = BedEnterProblemBridge.instance().createOtherProblem(); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java new file mode 100644 index 000000000000..65cc913fbbf9 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java @@ -0,0 +1,30 @@ +package io.papermc.paper.block.bed; + +import org.jetbrains.annotations.ApiStatus; +import java.util.Optional; +import java.util.ServiceLoader; + +/** + * @hidden + */ +@ApiStatus.Internal +public interface BedEnterProblemBridge { + + static BedEnterProblemBridge instance() { + final class Holder { + static final Optional INSTANCE = ServiceLoader.load(BedEnterProblemBridge.class, BedEnterProblemBridge.class.getClassLoader()).findFirst(); + } + return Holder.INSTANCE.orElseThrow(); + } + + BedEnterProblem createTooFarAwayProblem(); + + BedEnterProblem createObstructedProblem(); + + BedEnterProblem createMonstersNearbyProblem(); + + BedEnterProblem createExplosionProblem(); + + BedEnterProblem createOtherProblem(); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java new file mode 100644 index 000000000000..2038f9fb0481 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java @@ -0,0 +1,39 @@ +package io.papermc.paper.block.bed; + +import net.kyori.adventure.util.TriState; +import org.jetbrains.annotations.ApiStatus; + +/** + * + */ +@ApiStatus.Experimental +public sealed interface BedRuleResult permits BedRuleResultImpl { + + /** + * + */ + BedRuleResult ALLOWED = new BedRuleResultImpl(TriState.TRUE, null); + + /** + * + */ + BedRuleResult TOO_MUCH_LIGHT = new BedRuleResultImpl(TriState.FALSE, "when_dark"); + + /** + * + */ + BedRuleResult NEVER = new BedRuleResultImpl(TriState.FALSE, "never"); + + /** + * + */ + BedRuleResult UNDEFINED = new BedRuleResultImpl(TriState.NOT_SET, null); + + /** + * Returns {@link TriState#TRUE} if this result is a success, or {@link TriState#NOT_SET} when {@link #UNDEFINED} + * + * @return whether this result is a success + */ + TriState success(); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java new file mode 100644 index 000000000000..5832c6c2b0dc --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java @@ -0,0 +1,12 @@ +package io.papermc.paper.block.bed; + +import net.kyori.adventure.util.TriState; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +@ApiStatus.Internal +public record BedRuleResultImpl( + TriState success, + @Nullable String ruleId +) implements BedRuleResult { +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/package-info.java b/paper-api/src/main/java/io/papermc/paper/block/bed/package-info.java new file mode 100644 index 000000000000..d7f5cbbb3e94 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.papermc.paper.block.bed; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index 1ebbc8591a8e..81c870e97d59 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -1,5 +1,9 @@ package io.papermc.paper.event.player; +import io.papermc.paper.block.bed.BedEnterAction; +import io.papermc.paper.block.bed.BedEnterActionImpl; +import io.papermc.paper.block.bed.BedEnterProblem; +import io.papermc.paper.block.bed.BedRuleResult; import net.kyori.adventure.text.Component; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -15,26 +19,57 @@ public class PlayerBedFailEnterEvent extends PlayerEvent implements Cancellable private static final HandlerList HANDLER_LIST = new HandlerList(); - private final FailReason failReason; + private final @Deprecated(since = "1.21.11") FailReason failReason; private final Block bed; + private final BedEnterAction enterAction; private boolean willExplode; private @Nullable Component message; private boolean cancelled; @ApiStatus.Internal - public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, final Block bed, final boolean willExplode, final @Nullable Component message) { + public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, final Block bed, final boolean willExplode, final @Nullable Component message, BedEnterAction enterAction) { super(player); this.failReason = failReason; this.bed = bed; + this.enterAction = enterAction; this.willExplode = willExplode; this.message = message; } + @ApiStatus.Internal + @Deprecated(since = "1.21.11") + public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, final Block bed, final boolean willExplode, final @Nullable Component message) { + this(player, failReason, bed, willExplode, message, PlayerBedFailEnterEvent.fromFailReason(failReason)); + } + + // This is what we have to do for backwards compatibility... + private static BedEnterAction fromFailReason(FailReason failReason) { + return switch (failReason) { + case NOT_POSSIBLE_HERE -> new BedEnterActionImpl(BedRuleResult.NEVER, BedRuleResult.UNDEFINED, null); + case NOT_POSSIBLE_NOW -> new BedEnterActionImpl(BedRuleResult.TOO_MUCH_LIGHT, BedRuleResult.UNDEFINED, null); + case TOO_FAR_AWAY -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.TOO_FAR_AWAY); + case OBSTRUCTED -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OBSTRUCTED); + case NOT_SAFE -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.NOT_SAFE); + case OTHER_PROBLEM -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OTHER); + case EXPLOSION -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.EXPLOSION); + }; + } + + @Deprecated(since = "1.21.11") public FailReason getFailReason() { return this.failReason; } + /** + * This describes the default outcome of this event. + * + * @return the action representing the default outcome of this event + */ + public BedEnterAction enterAction() { + return this.enterAction; + } + public Block getBed() { return this.bed; } diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java index 762d6c717e56..f98be2da7561 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -1,6 +1,9 @@ package org.bukkit.event.player; -import org.bukkit.World; +import io.papermc.paper.block.bed.BedEnterAction; +import io.papermc.paper.block.bed.BedEnterActionImpl; +import io.papermc.paper.block.bed.BedEnterProblem; +import io.papermc.paper.block.bed.BedRuleResult; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -16,14 +19,22 @@ public class PlayerBedEnterEvent extends PlayerEvent implements Cancellable { private static final HandlerList HANDLER_LIST = new HandlerList(); private final Block bed; - private final BedEnterResult bedEnterResult; + private final @Deprecated(since = "1.21.11") BedEnterResult bedEnterResult; + private final @NotNull BedEnterAction enterAction; private Result useBed = Result.DEFAULT; @ApiStatus.Internal - public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull BedEnterResult bedEnterResult) { + public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull BedEnterResult bedEnterResult, @NotNull BedEnterAction enterAction) { super(player); this.bed = bed; this.bedEnterResult = bedEnterResult; + this.enterAction = enterAction; + } + + @ApiStatus.Internal + @Deprecated(since = "1.21.11", forRemoval = true) + public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull BedEnterResult bedEnterResult) { + this(player, bed, bedEnterResult, PlayerBedEnterEvent.fromBedEnterResult(bedEnterResult)); } @ApiStatus.Internal @@ -32,6 +43,20 @@ public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed) { this(player, bed, BedEnterResult.OK); } + // This is what we have to do for backwards compatibility... + private static @NotNull BedEnterAction fromBedEnterResult(@NotNull BedEnterResult bedEnterResult) { + return switch (bedEnterResult) { + case OK -> new BedEnterActionImpl(BedRuleResult.ALLOWED, BedRuleResult.UNDEFINED, null); + case NOT_POSSIBLE_HERE -> new BedEnterActionImpl(BedRuleResult.NEVER, BedRuleResult.UNDEFINED, null); + case NOT_POSSIBLE_NOW -> new BedEnterActionImpl(BedRuleResult.TOO_MUCH_LIGHT, BedRuleResult.UNDEFINED, null); + case TOO_FAR_AWAY -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.TOO_FAR_AWAY); + case OBSTRUCTED -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OBSTRUCTED); + case NOT_SAFE -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.NOT_SAFE); + case OTHER_PROBLEM -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OTHER); + case EXPLOSION -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.EXPLOSION); + }; + } + /** * Returns the bed block involved in this event. * @@ -48,10 +73,21 @@ public Block getBed() { * @return the bed enter result representing the default outcome of this event */ @NotNull + @Deprecated(since = "1.21.11") public BedEnterResult getBedEnterResult() { return this.bedEnterResult; } + /** + * This describes the default outcome of this event. + * + * @return the action representing the default outcome of this event + */ + @ApiStatus.Experimental + public @NotNull BedEnterAction enterAction() { + return this.enterAction; + } + /** * This controls the action to take with the bed that was clicked on. *

@@ -100,7 +136,7 @@ public void setUseBed(@NotNull Result useBed) { */ @Override public boolean isCancelled() { - return this.useBed == Result.DENY || this.useBed == Result.DEFAULT && this.bedEnterResult != BedEnterResult.OK; + return this.useBed == Result.DENY || this.useBed == Result.DEFAULT && this.enterAction.canSleep() != BedRuleResult.ALLOWED; } /** @@ -127,6 +163,7 @@ public static HandlerList getHandlerList() { /** * Represents the default possible outcomes of this event. */ + @Deprecated(since = "1.21.11") public enum BedEnterResult { /** * The player will enter the bed. diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch index 97c943829d2d..655ee85cbdb9 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch @@ -35,7 +35,7 @@ + // CraftBukkit start - handling bed explosion from below here + if (event.getWillExplode()) { // Paper - PlayerBedFailEnterEvent + this.explodeBed(finalBlockState, level, finalBlockPos); -+ } else ++ } + // CraftBukkit end if (bedSleepingProblem.message() != null) { - player.displayClientMessage(bedSleepingProblem.message(), true); diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java new file mode 100644 index 000000000000..c6da11ffcba4 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java @@ -0,0 +1,31 @@ +package io.papermc.paper.block.bed; + +import net.minecraft.world.entity.player.Player; + +public class BedEnterProblemBridgeImpl implements BedEnterProblemBridge { + + @Override + public BedEnterProblem createTooFarAwayProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.TOO_FAR_AWAY); + } + + @Override + public BedEnterProblem createObstructedProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.OBSTRUCTED); + } + + @Override + public BedEnterProblem createMonstersNearbyProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.NOT_SAFE); + } + + @Override + public BedEnterProblem createExplosionProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.EXPLOSION); + } + + @Override + public BedEnterProblem createOtherProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.OTHER_PROBLEM); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java new file mode 100644 index 000000000000..02811e35d7a3 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java @@ -0,0 +1,10 @@ +package io.papermc.paper.block.bed; + +import net.minecraft.world.entity.player.Player; +import org.jspecify.annotations.NullMarked; + +@NullMarked +record BedEnterProblemImpl( + Player.BedSleepingProblem vanillaProblem +) implements BedEnterProblem { +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 7490560810f5..4bbefbe0bdb3 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -25,7 +25,6 @@ import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -34,7 +33,6 @@ import net.minecraft.util.Unit; import net.minecraft.world.Container; import net.minecraft.world.InteractionHand; -import net.minecraft.world.attribute.EnvironmentAttribute; import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageTypes; @@ -288,66 +286,135 @@ public static boolean callPlayerSignOpenEvent(Player player, Sign sign, Side sid return !event.isCancelled(); } - public static PlayerBedFailEnterEvent.FailReason asFailReason(final net.minecraft.world.attribute.BedRule bedRule, final net.minecraft.world.entity.player.Player.BedSleepingProblem sleepingProblem) { - if (bedRule.explodes()) { - return PlayerBedFailEnterEvent.FailReason.EXPLOSION; - } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { - return PlayerBedFailEnterEvent.FailReason.OTHER_PROBLEM; + public static com.mojang.datafixers.util.Pair asFailReason( + final net.minecraft.world.entity.player.Player player, final net.minecraft.world.attribute.BedRule bedRule, final net.minecraft.world.entity.player.Player.BedSleepingProblem sleepingProblem) { + PlayerBedFailEnterEvent.FailReason failReason = null; + io.papermc.paper.block.bed.BedEnterProblem enterProblem = null; + if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { + failReason = PlayerBedFailEnterEvent.FailReason.OTHER_PROBLEM; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.OTHER; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE) { - return PlayerBedFailEnterEvent.FailReason.NOT_SAFE; + failReason = PlayerBedFailEnterEvent.FailReason.NOT_SAFE; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.NOT_SAFE; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED) { - return PlayerBedFailEnterEvent.FailReason.OBSTRUCTED; + failReason = PlayerBedFailEnterEvent.FailReason.OBSTRUCTED; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.OBSTRUCTED; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.TOO_FAR_AWAY) { - return PlayerBedFailEnterEvent.FailReason.TOO_FAR_AWAY; + failReason = PlayerBedFailEnterEvent.FailReason.TOO_FAR_AWAY; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.TOO_FAR_AWAY; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.EXPLOSION) { - return PlayerBedFailEnterEvent.FailReason.EXPLOSION; + failReason = PlayerBedFailEnterEvent.FailReason.EXPLOSION; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.EXPLOSION; } else if (sleepingProblem.message() != null) { if (bedRule.canSleep() == net.minecraft.world.attribute.BedRule.Rule.NEVER) { - return PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE; + failReason = PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE; } else if (bedRule.canSleep() == net.minecraft.world.attribute.BedRule.Rule.WHEN_DARK) { - return PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_NOW; + failReason = PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_NOW; } } - throw new IllegalArgumentException(sleepingProblem.toString()); + + if (failReason == null) { + throw new IllegalArgumentException(sleepingProblem.toString()); + } + + return com.mojang.datafixers.util.Pair.of( + failReason, + new io.papermc.paper.block.bed.BedEnterActionImpl( + CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep().test(player.level()), enterProblem != null), + CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn().test(player.level()), enterProblem != null), + enterProblem + ) + ); + } + + public static io.papermc.paper.block.bed.BedRuleResult asBedRuleResult(net.minecraft.world.attribute.BedRule.Rule rule, boolean value, boolean problemFound) { + if (problemFound) { + return io.papermc.paper.block.bed.BedRuleResult.UNDEFINED; + } else if (rule == net.minecraft.world.attribute.BedRule.Rule.ALWAYS) { + return io.papermc.paper.block.bed.BedRuleResult.ALLOWED; + } else if (rule == net.minecraft.world.attribute.BedRule.Rule.WHEN_DARK) { + if (value) { + return io.papermc.paper.block.bed.BedRuleResult.ALLOWED; + } else { + return io.papermc.paper.block.bed.BedRuleResult.TOO_MUCH_LIGHT; + } + } else if (rule == net.minecraft.world.attribute.BedRule.Rule.NEVER) { + return io.papermc.paper.block.bed.BedRuleResult.NEVER; + } + throw new IllegalArgumentException(rule.toString()); } public static PlayerBedFailEnterEvent callPlayerBedFailEnterEvent( net.minecraft.world.entity.player.Player player, BlockPos bed, net.minecraft.world.entity.player.Player.BedSleepingProblem bedSleepingProblem) { net.minecraft.world.attribute.BedRule bedRule = player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); + com.mojang.datafixers.util.Pair actionPair = asFailReason(player, bedRule, bedSleepingProblem); final var event = new PlayerBedFailEnterEvent( (org.bukkit.entity.Player) player.getBukkitEntity(), - asFailReason(bedRule, bedSleepingProblem), + actionPair.getFirst(), org.bukkit.craftbukkit.block.CraftBlock.at(player.level(), bed), bedSleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.EXPLOSION, - io.papermc.paper.adventure.PaperAdventure.asAdventure(bedSleepingProblem.message())); + io.papermc.paper.adventure.PaperAdventure.asAdventure(bedSleepingProblem.message()), + actionPair.getSecond()); event.callEvent(); return event; } - public static Either callPlayerBedEnterEvent(net.minecraft.world.entity.player.Player player, BlockPos bed, Either nmsBedResult) { - BedEnterResult bedEnterResult = nmsBedResult.mapBoth(sleepingProblem -> { + public static Either callPlayerBedEnterEvent( + net.minecraft.world.entity.player.Player player, BlockPos bed, Either nmsBedResult) { + final net.minecraft.world.attribute.BedRule bedRule = player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); + com.mojang.datafixers.util.Pair bedEnterResult = nmsBedResult.mapBoth(sleepingProblem -> { + BedEnterResult enterResult = null; + io.papermc.paper.block.bed.BedEnterProblem enterProblem = null; if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { - return BedEnterResult.OTHER_PROBLEM; + enterResult = BedEnterResult.OTHER_PROBLEM; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.OTHER; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE) { - return BedEnterResult.NOT_SAFE; + enterResult = BedEnterResult.NOT_SAFE; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.NOT_SAFE; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED) { - return BedEnterResult.OBSTRUCTED; + enterResult = BedEnterResult.OBSTRUCTED; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.OBSTRUCTED; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.TOO_FAR_AWAY) { - return BedEnterResult.TOO_FAR_AWAY; + enterResult = BedEnterResult.TOO_FAR_AWAY; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.TOO_FAR_AWAY; } else if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.EXPLOSION) { - return BedEnterResult.EXPLOSION; + enterResult = BedEnterResult.EXPLOSION; + enterProblem = io.papermc.paper.block.bed.BedEnterProblem.EXPLOSION; } else if (sleepingProblem.message() != null) { - net.minecraft.world.attribute.BedRule bedRule = player.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); if (bedRule.canSleep() == net.minecraft.world.attribute.BedRule.Rule.NEVER) { - return BedEnterResult.NOT_POSSIBLE_HERE; + enterResult = BedEnterResult.NOT_POSSIBLE_HERE; } else if (bedRule.canSleep() == net.minecraft.world.attribute.BedRule.Rule.WHEN_DARK) { - return BedEnterResult.NOT_POSSIBLE_NOW; + enterResult = BedEnterResult.NOT_POSSIBLE_NOW; } } - throw new IllegalStateException(sleepingProblem.toString()); - }, t -> BedEnterResult.OK).map(java.util.function.Function.identity(), java.util.function.Function.identity()); - PlayerBedEnterEvent event = new PlayerBedEnterEvent((Player) player.getBukkitEntity(), CraftBlock.at(player.level(), bed), bedEnterResult); + if (enterResult == null) { + throw new IllegalStateException(sleepingProblem.toString()); + } + + return com.mojang.datafixers.util.Pair.of( + enterResult, + new io.papermc.paper.block.bed.BedEnterActionImpl( + CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep().test(player.level()), enterProblem != null), + CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn().test(player.level()), enterProblem != null), + enterProblem + ) + ); + }, t -> com.mojang.datafixers.util.Pair.of( + BedEnterResult.OK, + new io.papermc.paper.block.bed.BedEnterActionImpl( + CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep().test(player.level()), false), + CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn().test(player.level()), false), + null + ) + )).map(java.util.function.Function.identity(), java.util.function.Function.identity()); + + PlayerBedEnterEvent event = new PlayerBedEnterEvent( + (Player) player.getBukkitEntity(), + CraftBlock.at(player.level(), bed), + bedEnterResult.getFirst(), + bedEnterResult.getSecond() + ); Bukkit.getServer().getPluginManager().callEvent(event); Result result = event.useBed(); diff --git a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge new file mode 100644 index 000000000000..e93018585de4 --- /dev/null +++ b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge @@ -0,0 +1 @@ +io.papermc.paper.block.bed.BedEnterProblemBridgeImpl From 09a9e9dfc03ceccc1f4405f5e8c77b65f7cdafbb Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sun, 19 Oct 2025 02:50:42 +0200 Subject: [PATCH 05/12] chore: deprecate World#isBedWorks and added 'Environment attributes API' to todo-snapshot --- paper-api/src/main/java/org/bukkit/World.java | 2 +- todo-snapshot.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paper-api/src/main/java/org/bukkit/World.java b/paper-api/src/main/java/org/bukkit/World.java index e2360ed84aae..9182251abf98 100644 --- a/paper-api/src/main/java/org/bukkit/World.java +++ b/paper-api/src/main/java/org/bukkit/World.java @@ -2808,7 +2808,7 @@ default double getHumidity(int x, int z) { * * @return true if beds work in this world */ - // TODO - snapshot: Should we deprecate this method since the underlying setting was split in 3 different environment attributes? + @Deprecated(since = "1.21.11") public boolean isBedWorks(); /** diff --git a/todo-snapshot.txt b/todo-snapshot.txt index 2113000c14fd..e0a27273a866 100644 --- a/todo-snapshot.txt +++ b/todo-snapshot.txt @@ -1,2 +1,3 @@ - Check ServerPlayer bed diffs - anything marked with 'TODO - snapshot' +- Environment attributes API From d27c1b75617d663a8cf9e62f399e424519ff9ba1 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sun, 19 Oct 2025 18:53:36 +0200 Subject: [PATCH 06/12] feat: updated new beds api to include error messages, and improved legacy constructor convertion --- .../paper/block/bed/BedEnterAction.java | 32 +++-- .../paper/block/bed/BedEnterActionBridge.java | 39 ++++++ .../paper/block/bed/BedEnterProblem.java | 40 +++++- .../block/bed/BedEnterProblemBridge.java | 30 ----- .../paper/block/bed/BedRuleResult.java | 28 ++-- .../paper/block/bed/BedRuleResultImpl.java | 8 +- .../event/player/PlayerBedFailEnterEvent.java | 19 +-- .../event/player/PlayerBedEnterEvent.java | 19 +-- .../world/level/block/BedBlock.java.patch | 16 ++- .../block/bed/BedEnterActionBridgeImpl.java | 126 ++++++++++++++++++ .../paper/block/bed/BedEnterActionImpl.java | 4 +- .../block/bed/BedEnterProblemBridgeImpl.java | 31 ----- .../paper/block/bed/BedEnterProblemImpl.java | 13 +- .../craftbukkit/event/CraftEventFactory.java | 32 +++-- ...permc.paper.block.bed.BedEnterActionBridge | 1 + ...ermc.paper.block.bed.BedEnterProblemBridge | 1 - 16 files changed, 285 insertions(+), 154 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java delete mode 100644 paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java create mode 100644 paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java rename {paper-api => paper-server}/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java (63%) delete mode 100644 paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java create mode 100644 paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterActionBridge delete mode 100644 paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java index 76513ee9fe23..459a433dd763 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterAction.java @@ -1,5 +1,6 @@ package io.papermc.paper.block.bed; +import net.kyori.adventure.text.Component; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; @@ -8,13 +9,16 @@ * {@link org.bukkit.event.player.PlayerBedEnterEvent} and that is * happening during {@link io.papermc.paper.event.player.PlayerBedFailEnterEvent} */ +@ApiStatus.NonExtendable @ApiStatus.Experimental -public sealed interface BedEnterAction permits BedEnterActionImpl { +public interface BedEnterAction { /** * Gets if the player is allowed to sleep in the bed. - * This can be {@link BedRuleResult#ALLOWED} at the same time as {@link #canSetSpawn()}, - * but will always be {@link BedRuleResult#UNDEFINED} if {@link #problem()} is anything but {@code null} + * This can be {@link BedRuleResult#ALLOWED} at the same time as {@link #canSetSpawn()} + *

+ * It is advised to check for {@link BedEnterProblem}s first, as if it returns anything but {@code null} + * the bed interaction is prevented * * @return whether the player is allowed to sleep */ @@ -22,25 +26,31 @@ public sealed interface BedEnterAction permits BedEnterActionImpl { /** * Gets if the player is allowed to save its spawn point in the bed. - * This can be {@link BedRuleResult#ALLOWED} at the same time as {@link #canSleep()}, - * but will always be {@link BedRuleResult#UNDEFINED} if {@link #problem()} is anything but {@code null} + * This can be {@link BedRuleResult#ALLOWED} at the same time as {@link #canSleep()} *

- * Note: This can also be {@link BedRuleResult#UNDEFINED} at any time if the event containing - * this action is called from sources external to Paper. + * It is advised to check for {@link BedEnterProblem}s first, as if it returns anything but {@code null} + * the bed interaction is prevented * * @return whether the player is allowed to save its spawn point */ BedRuleResult canSetSpawn(); /** - * A problem is an issue that prevents the player from sleeping and from saving its spawn point - *

- * When a problem was found, both {@link #canSleep()} and {@link #canSetSpawn()} - * will be {@link BedRuleResult#UNDEFINED} + * A problem is an issue that prevents the player from sleeping and from saving its spawn point. + * No problem being found doesn't mean the player successfully slept or set its spawn point, + * see {@link #canSleep()} and {@link #canSetSpawn()} for individual successes * * @return any of {@link BedEnterProblem}s if one is found, otherwise {@code null} */ @Nullable BedEnterProblem problem(); + /** + * Returns the default error message to be shown as an actionbar message to the player + * + * @return the error message + */ + @Nullable + Component errorMessage(); + } diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java new file mode 100644 index 000000000000..21047d57d18e --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java @@ -0,0 +1,39 @@ +package io.papermc.paper.block.bed; + +import io.papermc.paper.event.player.PlayerBedFailEnterEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerBedEnterEvent; +import org.jetbrains.annotations.ApiStatus; +import java.util.Optional; +import java.util.ServiceLoader; + +/** + * @hidden + */ +@ApiStatus.Internal +public interface BedEnterActionBridge { + + static BedEnterActionBridge instance() { + final class Holder { + static final Optional INSTANCE = ServiceLoader.load(BedEnterActionBridge.class, BedEnterActionBridge.class.getClassLoader()).findFirst(); + } + return Holder.INSTANCE.orElseThrow(); + } + + BedEnterProblem createTooFarAwayProblem(); + + BedEnterProblem createObstructedProblem(); + + BedEnterProblem createNotSafeProblem(); + + BedEnterProblem createExplosionProblem(); + + BedEnterProblem createOtherProblem(); + + @Deprecated(since = "1.21.11", forRemoval = true) + BedEnterAction fromBedEnterResult(Player player, PlayerBedEnterEvent.BedEnterResult bedEnterResult); + + @Deprecated(since = "1.21.11", forRemoval = true) + BedEnterAction fromFailReason(Player player, PlayerBedFailEnterEvent.FailReason failReason); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java index 5c93a952dde5..eb8135c8f84b 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblem.java @@ -1,37 +1,63 @@ package io.papermc.paper.block.bed; +import net.kyori.adventure.text.Component; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; /** - * + * Represents a problem that prevents a player from continuing + * the sleeping process, preventing it from sleeping and from setting + * its spawn point */ @ApiStatus.NonExtendable @ApiStatus.Experimental public interface BedEnterProblem { /** + * Happens when the bed is too far away from the player. + * Makes no explosion and has an error message * + * @see #errorMessage() */ - BedEnterProblem TOO_FAR_AWAY = BedEnterProblemBridge.instance().createTooFarAwayProblem(); + BedEnterProblem TOO_FAR_AWAY = BedEnterActionBridge.instance().createTooFarAwayProblem(); /** + * Happens when the bed has blocks one block above it. + * Makes no explosion and has an error message * + * @see #errorMessage() */ - BedEnterProblem OBSTRUCTED = BedEnterProblemBridge.instance().createObstructedProblem(); + BedEnterProblem OBSTRUCTED = BedEnterActionBridge.instance().createObstructedProblem(); /** + * Happens when there are monsters in a 16x10x16 box centered on the bed's head + * and the player is not in creative mode. Makes no explosion and has an error message * + * @see #errorMessage() */ - BedEnterProblem NOT_SAFE = BedEnterProblemBridge.instance().createMonstersNearbyProblem(); + BedEnterProblem NOT_SAFE = BedEnterActionBridge.instance().createNotSafeProblem(); /** - * + * Happens when the bed is set to explode. This is defined in the environment attributes of the world. + * This doesn't have a fixed error message, see {@link BedEnterAction#errorMessage()} + */ + BedEnterProblem EXPLOSION = BedEnterActionBridge.instance().createExplosionProblem(); + + /** + * Happens when a player tries to sleep when an invalid state, for example when a player tries + * to sleep but is already sleeping or is dead. + * This doesn't have an error message at all, the interaction is simply discarded */ - BedEnterProblem EXPLOSION = BedEnterProblemBridge.instance().createExplosionProblem(); + BedEnterProblem OTHER = BedEnterActionBridge.instance().createOtherProblem(); /** + * Returns the error message associated with this problem, if any. + * This component is sent to the client as an actionbar message + * when this problem occur * + * @return the error message */ - BedEnterProblem OTHER = BedEnterProblemBridge.instance().createOtherProblem(); + @Nullable + Component errorMessage(); } diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java deleted file mode 100644 index 65cc913fbbf9..000000000000 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridge.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.papermc.paper.block.bed; - -import org.jetbrains.annotations.ApiStatus; -import java.util.Optional; -import java.util.ServiceLoader; - -/** - * @hidden - */ -@ApiStatus.Internal -public interface BedEnterProblemBridge { - - static BedEnterProblemBridge instance() { - final class Holder { - static final Optional INSTANCE = ServiceLoader.load(BedEnterProblemBridge.class, BedEnterProblemBridge.class.getClassLoader()).findFirst(); - } - return Holder.INSTANCE.orElseThrow(); - } - - BedEnterProblem createTooFarAwayProblem(); - - BedEnterProblem createObstructedProblem(); - - BedEnterProblem createMonstersNearbyProblem(); - - BedEnterProblem createExplosionProblem(); - - BedEnterProblem createOtherProblem(); - -} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java index 2038f9fb0481..ebcb8cdf7bb6 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResult.java @@ -1,39 +1,37 @@ package io.papermc.paper.block.bed; -import net.kyori.adventure.util.TriState; import org.jetbrains.annotations.ApiStatus; /** - * + * Represents the result of a bed rule during {@link org.bukkit.event.player.PlayerBedEnterEvent} + * and {@link io.papermc.paper.event.player.PlayerBedFailEnterEvent}. Bed rules are responsible + * for allowing players to sleep and to set their spawn point */ @ApiStatus.Experimental public sealed interface BedRuleResult permits BedRuleResultImpl { /** - * + * Used when the bed rule is allowed */ - BedRuleResult ALLOWED = new BedRuleResultImpl(TriState.TRUE, null); + BedRuleResult ALLOWED = new BedRuleResultImpl(true); /** - * + * Used when the bed rule is denied due to there + * being too much light. This is the case during + * daytime without thunderstorms */ - BedRuleResult TOO_MUCH_LIGHT = new BedRuleResultImpl(TriState.FALSE, "when_dark"); + BedRuleResult TOO_MUCH_LIGHT = new BedRuleResultImpl(false); /** - * - */ - BedRuleResult NEVER = new BedRuleResultImpl(TriState.FALSE, "never"); - - /** - * + * Used when the bed rule is set to always be denied */ - BedRuleResult UNDEFINED = new BedRuleResultImpl(TriState.NOT_SET, null); + BedRuleResult NEVER = new BedRuleResultImpl(false); /** - * Returns {@link TriState#TRUE} if this result is a success, or {@link TriState#NOT_SET} when {@link #UNDEFINED} + * Returns {@code true} if this result is a success * * @return whether this result is a success */ - TriState success(); + boolean success(); } diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java index 5832c6c2b0dc..845cc866445a 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java @@ -1,12 +1,12 @@ package io.papermc.paper.block.bed; -import net.kyori.adventure.util.TriState; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.Nullable; +/** + * @hidden + */ @ApiStatus.Internal public record BedRuleResultImpl( - TriState success, - @Nullable String ruleId + boolean success ) implements BedRuleResult { } diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index 81c870e97d59..7306e24be606 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -1,9 +1,7 @@ package io.papermc.paper.event.player; import io.papermc.paper.block.bed.BedEnterAction; -import io.papermc.paper.block.bed.BedEnterActionImpl; -import io.papermc.paper.block.bed.BedEnterProblem; -import io.papermc.paper.block.bed.BedRuleResult; +import io.papermc.paper.block.bed.BedEnterActionBridge; import net.kyori.adventure.text.Component; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -40,20 +38,7 @@ public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, @ApiStatus.Internal @Deprecated(since = "1.21.11") public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, final Block bed, final boolean willExplode, final @Nullable Component message) { - this(player, failReason, bed, willExplode, message, PlayerBedFailEnterEvent.fromFailReason(failReason)); - } - - // This is what we have to do for backwards compatibility... - private static BedEnterAction fromFailReason(FailReason failReason) { - return switch (failReason) { - case NOT_POSSIBLE_HERE -> new BedEnterActionImpl(BedRuleResult.NEVER, BedRuleResult.UNDEFINED, null); - case NOT_POSSIBLE_NOW -> new BedEnterActionImpl(BedRuleResult.TOO_MUCH_LIGHT, BedRuleResult.UNDEFINED, null); - case TOO_FAR_AWAY -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.TOO_FAR_AWAY); - case OBSTRUCTED -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OBSTRUCTED); - case NOT_SAFE -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.NOT_SAFE); - case OTHER_PROBLEM -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OTHER); - case EXPLOSION -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.EXPLOSION); - }; + this(player, failReason, bed, willExplode, message, BedEnterActionBridge.instance().fromFailReason(player, failReason)); } @Deprecated(since = "1.21.11") diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java index f98be2da7561..b58195694338 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -1,8 +1,7 @@ package org.bukkit.event.player; import io.papermc.paper.block.bed.BedEnterAction; -import io.papermc.paper.block.bed.BedEnterActionImpl; -import io.papermc.paper.block.bed.BedEnterProblem; +import io.papermc.paper.block.bed.BedEnterActionBridge; import io.papermc.paper.block.bed.BedRuleResult; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -34,7 +33,7 @@ public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull @ApiStatus.Internal @Deprecated(since = "1.21.11", forRemoval = true) public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull BedEnterResult bedEnterResult) { - this(player, bed, bedEnterResult, PlayerBedEnterEvent.fromBedEnterResult(bedEnterResult)); + this(player, bed, bedEnterResult, BedEnterActionBridge.instance().fromBedEnterResult(player, bedEnterResult)); } @ApiStatus.Internal @@ -43,20 +42,6 @@ public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed) { this(player, bed, BedEnterResult.OK); } - // This is what we have to do for backwards compatibility... - private static @NotNull BedEnterAction fromBedEnterResult(@NotNull BedEnterResult bedEnterResult) { - return switch (bedEnterResult) { - case OK -> new BedEnterActionImpl(BedRuleResult.ALLOWED, BedRuleResult.UNDEFINED, null); - case NOT_POSSIBLE_HERE -> new BedEnterActionImpl(BedRuleResult.NEVER, BedRuleResult.UNDEFINED, null); - case NOT_POSSIBLE_NOW -> new BedEnterActionImpl(BedRuleResult.TOO_MUCH_LIGHT, BedRuleResult.UNDEFINED, null); - case TOO_FAR_AWAY -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.TOO_FAR_AWAY); - case OBSTRUCTED -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OBSTRUCTED); - case NOT_SAFE -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.NOT_SAFE); - case OTHER_PROBLEM -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.OTHER); - case EXPLOSION -> new BedEnterActionImpl(BedRuleResult.UNDEFINED, BedRuleResult.UNDEFINED, BedEnterProblem.EXPLOSION); - }; - } - /** * Returns the bed block involved in this event. * diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch index 655ee85cbdb9..750e55e7780a 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch @@ -9,7 +9,7 @@ bedRule.errorMessage().ifPresent(component -> player.displayClientMessage(component, true)); level.removeBlock(pos, false); BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite()); -@@ -105,22 +_,56 @@ +@@ -105,22 +_,58 @@ level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); return InteractionResult.SUCCESS_SERVER; } else if (state.getValue(OCCUPIED)) { @@ -25,6 +25,9 @@ + final BlockPos finalBlockPos = pos; + // CraftBukkit end player.startSleepInBed(pos).ifLeft(bedSleepingProblem -> { +- if (bedSleepingProblem.message() != null) { +- player.displayClientMessage(bedSleepingProblem.message(), true); +- } + // Paper start - PlayerBedFailEnterEvent + if (bedSleepingProblem != null) { + io.papermc.paper.event.player.PlayerBedFailEnterEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedFailEnterEvent(player, finalBlockPos, bedSleepingProblem); @@ -37,11 +40,12 @@ + this.explodeBed(finalBlockState, level, finalBlockPos); + } + // CraftBukkit end - if (bedSleepingProblem.message() != null) { -- player.displayClientMessage(bedSleepingProblem.message(), true); -+ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent -+ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent - } ++ // Paper start - PlayerBedFailEnterEvent ++ final net.kyori.adventure.text.Component message = event.getMessage(); ++ if (message != null) { ++ player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); ++ // Paper end - PlayerBedFailEnterEvent ++ } + } // Paper - PlayerBedFailEnterEvent }); return InteractionResult.SUCCESS_SERVER; diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java new file mode 100644 index 000000000000..1227930931fa --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java @@ -0,0 +1,126 @@ +package io.papermc.paper.block.bed; + +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.event.player.PlayerBedFailEnterEvent; +import java.util.Optional; +import net.minecraft.network.chat.Component; +import net.minecraft.world.attribute.BedRule; +import net.minecraft.world.attribute.EnvironmentAttributes; +import net.minecraft.world.entity.player.Player; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.event.player.PlayerBedEnterEvent; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class BedEnterActionBridgeImpl implements BedEnterActionBridge { + + @Override + public BedEnterProblem createTooFarAwayProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.TOO_FAR_AWAY); + } + + @Override + public BedEnterProblem createObstructedProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.OBSTRUCTED); + } + + @Override + public BedEnterProblem createNotSafeProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.NOT_SAFE); + } + + @Override + public BedEnterProblem createExplosionProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.EXPLOSION); + } + + @Override + public BedEnterProblem createOtherProblem() { + return new BedEnterProblemImpl(Player.BedSleepingProblem.OTHER_PROBLEM); + } + + @Override + public BedEnterAction fromBedEnterResult(final org.bukkit.entity.Player player, final PlayerBedEnterEvent.BedEnterResult bedEnterResult) { + Player vanillaPlayer = ((CraftPlayer) player).getHandle(); + BedRule bedRule = vanillaPlayer.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); + BedRuleResult canSetSpawn = CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn(vanillaPlayer.level())); + + if (bedEnterResult == PlayerBedEnterEvent.BedEnterResult.OK) { + return new BedEnterActionImpl( + BedRuleResult.ALLOWED, + canSetSpawn, + null, + null + ); + } + + if (bedEnterResult == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_HERE || bedEnterResult == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_NOW) { + return new BedEnterActionImpl( + bedEnterResult == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_HERE ? BedRuleResult.NEVER : BedRuleResult.TOO_MUCH_LIGHT, + canSetSpawn, + null, + bedRule.errorMessage().map(PaperAdventure::asAdventure).orElse(null) + ); + } + + BedRuleResult canSleep = CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep(vanillaPlayer.level())); + BedEnterProblem enterProblem = switch (bedEnterResult) { + case TOO_FAR_AWAY -> BedEnterProblem.TOO_FAR_AWAY; + case OBSTRUCTED -> BedEnterProblem.OBSTRUCTED; + case NOT_SAFE -> BedEnterProblem.NOT_SAFE; + case OTHER_PROBLEM -> BedEnterProblem.OTHER; + case EXPLOSION -> BedEnterProblem.EXPLOSION; + default -> throw new IllegalStateException("Unexpected value: " + bedEnterResult); + }; + Component errorMessage = ((BedEnterProblemImpl) enterProblem).vanillaProblem().message(); + if (bedEnterResult == PlayerBedEnterEvent.BedEnterResult.EXPLOSION) { + errorMessage = bedRule.errorMessage().orElse(null); + } + + return new BedEnterActionImpl( + canSleep, + canSetSpawn, + enterProblem, + errorMessage == null ? null : PaperAdventure.asAdventure(errorMessage) + ); + } + + @Override + public BedEnterAction fromFailReason(final org.bukkit.entity.Player player, final PlayerBedFailEnterEvent.FailReason failReason) { + Player vanillaPlayer = ((CraftPlayer) player).getHandle(); + BedRule bedRule = vanillaPlayer.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); + BedRuleResult canSetSpawn = CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn(vanillaPlayer.level())); + + if (failReason == PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE || failReason == PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_NOW) { + return new BedEnterActionImpl( + failReason == PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE ? BedRuleResult.NEVER : BedRuleResult.TOO_MUCH_LIGHT, + canSetSpawn, + null, + bedRule.errorMessage().map(PaperAdventure::asAdventure).orElse(null) + ); + } + + BedRuleResult canSleep = CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep(vanillaPlayer.level())); + BedEnterProblem enterProblem = switch (failReason) { + case TOO_FAR_AWAY -> BedEnterProblem.TOO_FAR_AWAY; + case OBSTRUCTED -> BedEnterProblem.OBSTRUCTED; + case NOT_SAFE -> BedEnterProblem.NOT_SAFE; + case OTHER_PROBLEM -> BedEnterProblem.OTHER; + case EXPLOSION -> BedEnterProblem.EXPLOSION; + default -> throw new IllegalStateException("Unexpected value: " + failReason); + }; + Component errorMessage = ((BedEnterProblemImpl) enterProblem).vanillaProblem().message(); + if (failReason == PlayerBedFailEnterEvent.FailReason.EXPLOSION) { + errorMessage = bedRule.errorMessage().orElse(null); + } + + return new BedEnterActionImpl( + canSleep, + canSetSpawn, + enterProblem, + errorMessage == null ? null : PaperAdventure.asAdventure(errorMessage) + ); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java similarity index 63% rename from paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java rename to paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java index 89352acbda11..b1a2eb96601f 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionImpl.java @@ -1,10 +1,12 @@ package io.papermc.paper.block.bed; +import net.kyori.adventure.text.Component; import org.jspecify.annotations.Nullable; public record BedEnterActionImpl( BedRuleResult canSleep, BedRuleResult canSetSpawn, - @Nullable BedEnterProblem problem + @Nullable BedEnterProblem problem, + @Nullable Component errorMessage ) implements BedEnterAction { } diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java deleted file mode 100644 index c6da11ffcba4..000000000000 --- a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemBridgeImpl.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.papermc.paper.block.bed; - -import net.minecraft.world.entity.player.Player; - -public class BedEnterProblemBridgeImpl implements BedEnterProblemBridge { - - @Override - public BedEnterProblem createTooFarAwayProblem() { - return new BedEnterProblemImpl(Player.BedSleepingProblem.TOO_FAR_AWAY); - } - - @Override - public BedEnterProblem createObstructedProblem() { - return new BedEnterProblemImpl(Player.BedSleepingProblem.OBSTRUCTED); - } - - @Override - public BedEnterProblem createMonstersNearbyProblem() { - return new BedEnterProblemImpl(Player.BedSleepingProblem.NOT_SAFE); - } - - @Override - public BedEnterProblem createExplosionProblem() { - return new BedEnterProblemImpl(Player.BedSleepingProblem.EXPLOSION); - } - - @Override - public BedEnterProblem createOtherProblem() { - return new BedEnterProblemImpl(Player.BedSleepingProblem.OTHER_PROBLEM); - } -} diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java index 02811e35d7a3..9782a9e05e33 100644 --- a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterProblemImpl.java @@ -1,10 +1,21 @@ package io.papermc.paper.block.bed; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; import net.minecraft.world.entity.player.Player; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked record BedEnterProblemImpl( - Player.BedSleepingProblem vanillaProblem + Player.BedSleepingProblem vanillaProblem, + @Nullable Component errorMessage ) implements BedEnterProblem { + + BedEnterProblemImpl(Player.BedSleepingProblem vanillaProblem) { + this( + vanillaProblem, + vanillaProblem.message() == null ? null : PaperAdventure.asAdventure(vanillaProblem.message()) + ); + } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 4bbefbe0bdb3..a9049f278a14 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -11,6 +11,7 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -290,6 +291,7 @@ public static com.mojang.datafixers.util.Pair bedEnterResult = nmsBedResult.mapBoth(sleepingProblem -> { BedEnterResult enterResult = null; io.papermc.paper.block.bed.BedEnterProblem enterProblem = null; + Component errorMessage = sleepingProblem.message(); if (sleepingProblem == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { enterResult = BedEnterResult.OTHER_PROBLEM; enterProblem = io.papermc.paper.block.bed.BedEnterProblem.OTHER; @@ -380,6 +383,7 @@ public static Either com.mojang.datafixers.util.Pair.of( BedEnterResult.OK, new io.papermc.paper.block.bed.BedEnterActionImpl( - CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep().test(player.level()), false), - CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn().test(player.level()), false), + CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep().test(player.level())), + CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn().test(player.level())), + null, null ) )).map(java.util.function.Function.identity(), java.util.function.Function.identity()); diff --git a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterActionBridge b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterActionBridge new file mode 100644 index 000000000000..540fa11b068c --- /dev/null +++ b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterActionBridge @@ -0,0 +1 @@ +io.papermc.paper.block.bed.BedEnterActionBridgeImpl diff --git a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge deleted file mode 100644 index e93018585de4..000000000000 --- a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.block.bed.BedEnterProblemBridge +++ /dev/null @@ -1 +0,0 @@ -io.papermc.paper.block.bed.BedEnterProblemBridgeImpl From d9d97ce8fe19aedb3dcc4b2747af507ba01b02e3 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sun, 19 Oct 2025 19:07:53 +0200 Subject: [PATCH 07/12] fix: deprecate PlayerBedFailEnterEvent.FailReason --- .../io/papermc/paper/event/player/PlayerBedFailEnterEvent.java | 1 + 1 file changed, 1 insertion(+) diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index 7306e24be606..40798b55c9be 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -100,6 +100,7 @@ public static HandlerList getHandlerList() { return HANDLER_LIST; } + @Deprecated(since = "1.21.11") public enum FailReason { /** * The world doesn't allow sleeping (ex. Nether or The End). Entering From 9863e9040a7a4224524335c001bfa417b6da2675 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Sun, 19 Oct 2025 19:16:49 +0200 Subject: [PATCH 08/12] chore: lessen BedBlock patch size --- .../minecraft/world/level/block/BedBlock.java.patch | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch index 750e55e7780a..74c912e00d0f 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch @@ -9,7 +9,7 @@ bedRule.errorMessage().ifPresent(component -> player.displayClientMessage(component, true)); level.removeBlock(pos, false); BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite()); -@@ -105,22 +_,58 @@ +@@ -105,22 +_,59 @@ level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK); return InteractionResult.SUCCESS_SERVER; } else if (state.getValue(OCCUPIED)) { @@ -26,9 +26,10 @@ + // CraftBukkit end player.startSleepInBed(pos).ifLeft(bedSleepingProblem -> { - if (bedSleepingProblem.message() != null) { -- player.displayClientMessage(bedSleepingProblem.message(), true); -- } + // Paper start - PlayerBedFailEnterEvent ++ if (false && bedSleepingProblem.message() != null) { // Moved down + player.displayClientMessage(bedSleepingProblem.message(), true); + } + if (bedSleepingProblem != null) { + io.papermc.paper.event.player.PlayerBedFailEnterEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedFailEnterEvent(player, finalBlockPos, bedSleepingProblem); + if (event.isCancelled()) { @@ -42,11 +43,9 @@ + // CraftBukkit end + // Paper start - PlayerBedFailEnterEvent + final net.kyori.adventure.text.Component message = event.getMessage(); -+ if (message != null) { -+ player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); -+ // Paper end - PlayerBedFailEnterEvent ++ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); + } -+ } // Paper - PlayerBedFailEnterEvent ++ // Paper end - PlayerBedFailEnterEvent }); return InteractionResult.SUCCESS_SERVER; } From 67954bcdb5888641256d83f7a8807835bed99124 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Fri, 24 Oct 2025 19:11:53 +0200 Subject: [PATCH 09/12] chore: make BedRuleResultImpl package-private --- .../main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java index 845cc866445a..42edaa3223f4 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedRuleResultImpl.java @@ -6,7 +6,7 @@ * @hidden */ @ApiStatus.Internal -public record BedRuleResultImpl( +record BedRuleResultImpl( boolean success ) implements BedRuleResult { } From ae6c1e2bf8d4437cc3d95aada8f5d4885516ba6a Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Fri, 24 Oct 2025 19:17:21 +0200 Subject: [PATCH 10/12] chore: replace PlayerBedFailEnterEvent's constructor with updated one instead of creating a new one --- .../paper/block/bed/BedEnterActionBridge.java | 3 -- .../event/player/PlayerBedFailEnterEvent.java | 7 ---- .../block/bed/BedEnterActionBridgeImpl.java | 37 ------------------- 3 files changed, 47 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java index 21047d57d18e..37a15e7dce74 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java @@ -33,7 +33,4 @@ final class Holder { @Deprecated(since = "1.21.11", forRemoval = true) BedEnterAction fromBedEnterResult(Player player, PlayerBedEnterEvent.BedEnterResult bedEnterResult); - @Deprecated(since = "1.21.11", forRemoval = true) - BedEnterAction fromFailReason(Player player, PlayerBedFailEnterEvent.FailReason failReason); - } diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index 40798b55c9be..e028c07f0d66 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -1,7 +1,6 @@ package io.papermc.paper.event.player; import io.papermc.paper.block.bed.BedEnterAction; -import io.papermc.paper.block.bed.BedEnterActionBridge; import net.kyori.adventure.text.Component; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -35,12 +34,6 @@ public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, this.message = message; } - @ApiStatus.Internal - @Deprecated(since = "1.21.11") - public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, final Block bed, final boolean willExplode, final @Nullable Component message) { - this(player, failReason, bed, willExplode, message, BedEnterActionBridge.instance().fromFailReason(player, failReason)); - } - @Deprecated(since = "1.21.11") public FailReason getFailReason() { return this.failReason; diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java index 1227930931fa..a727c8622abc 100644 --- a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java @@ -86,41 +86,4 @@ public BedEnterAction fromBedEnterResult(final org.bukkit.entity.Player player, errorMessage == null ? null : PaperAdventure.asAdventure(errorMessage) ); } - - @Override - public BedEnterAction fromFailReason(final org.bukkit.entity.Player player, final PlayerBedFailEnterEvent.FailReason failReason) { - Player vanillaPlayer = ((CraftPlayer) player).getHandle(); - BedRule bedRule = vanillaPlayer.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); - BedRuleResult canSetSpawn = CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn(vanillaPlayer.level())); - - if (failReason == PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE || failReason == PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_NOW) { - return new BedEnterActionImpl( - failReason == PlayerBedFailEnterEvent.FailReason.NOT_POSSIBLE_HERE ? BedRuleResult.NEVER : BedRuleResult.TOO_MUCH_LIGHT, - canSetSpawn, - null, - bedRule.errorMessage().map(PaperAdventure::asAdventure).orElse(null) - ); - } - - BedRuleResult canSleep = CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep(vanillaPlayer.level())); - BedEnterProblem enterProblem = switch (failReason) { - case TOO_FAR_AWAY -> BedEnterProblem.TOO_FAR_AWAY; - case OBSTRUCTED -> BedEnterProblem.OBSTRUCTED; - case NOT_SAFE -> BedEnterProblem.NOT_SAFE; - case OTHER_PROBLEM -> BedEnterProblem.OTHER; - case EXPLOSION -> BedEnterProblem.EXPLOSION; - default -> throw new IllegalStateException("Unexpected value: " + failReason); - }; - Component errorMessage = ((BedEnterProblemImpl) enterProblem).vanillaProblem().message(); - if (failReason == PlayerBedFailEnterEvent.FailReason.EXPLOSION) { - errorMessage = bedRule.errorMessage().orElse(null); - } - - return new BedEnterActionImpl( - canSleep, - canSetSpawn, - enterProblem, - errorMessage == null ? null : PaperAdventure.asAdventure(errorMessage) - ); - } } From 6207fb18a7a0d63bac806af05aa7ae71f631043f Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Fri, 24 Oct 2025 19:54:22 +0200 Subject: [PATCH 11/12] =?UTF-8?q?=C3=83chore:=20add=20deprecation=20notice?= =?UTF-8?q?s=20to=20javadocs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../paper/event/player/PlayerBedFailEnterEvent.java | 9 +++++++++ paper-api/src/main/java/org/bukkit/World.java | 6 ++++-- .../org/bukkit/event/player/PlayerBedEnterEvent.java | 4 ++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java index e028c07f0d66..1faa011a111f 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBedFailEnterEvent.java @@ -34,6 +34,10 @@ public PlayerBedFailEnterEvent(final Player player, final FailReason failReason, this.message = message; } + /** + * @deprecated This enum has been replaced with a system that better + * represents how beds work. See {@link #enterAction} + */ @Deprecated(since = "1.21.11") public FailReason getFailReason() { return this.failReason; @@ -44,6 +48,7 @@ public FailReason getFailReason() { * * @return the action representing the default outcome of this event */ + @ApiStatus.Experimental public BedEnterAction enterAction() { return this.enterAction; } @@ -93,6 +98,10 @@ public static HandlerList getHandlerList() { return HANDLER_LIST; } + /** + * @deprecated Enums no longer represents reliably how beds work and fail. This has been + * replaced with {@link BedEnterAction} that better fits the new beds + */ @Deprecated(since = "1.21.11") public enum FailReason { /** diff --git a/paper-api/src/main/java/org/bukkit/World.java b/paper-api/src/main/java/org/bukkit/World.java index d7b45cbcdb8d..5487c071f88a 100644 --- a/paper-api/src/main/java/org/bukkit/World.java +++ b/paper-api/src/main/java/org/bukkit/World.java @@ -2793,7 +2793,7 @@ default double getHumidity(int x, int z) { /** * Gets if this world is natural. - * + *

* When false, the moon is not visible and eyeblossoms do not open/close * * @return true if world is natural @@ -2802,11 +2802,13 @@ default double getHumidity(int x, int z) { /** * Gets if beds work in this world. - * + *

* A non-working bed can blow up when trying to sleep, but that may * not always be the case. * * @return true if beds work in this world + * @deprecated Due to 1.21.11 beds changes, a boolean no longer + * represents if they work. There is no replacement API yet */ @Deprecated(since = "1.21.11") public boolean isBedWorks(); diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java index b58195694338..a30cb27fe6b2 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -56,6 +56,8 @@ public Block getBed() { * This describes the default outcome of this event. * * @return the bed enter result representing the default outcome of this event + * @deprecated This enum has been replaced with a system that better + * represents how beds work. See {@link #enterAction} */ @NotNull @Deprecated(since = "1.21.11") @@ -147,6 +149,8 @@ public static HandlerList getHandlerList() { /** * Represents the default possible outcomes of this event. + * @deprecated Enums no longer represents reliably how beds work and fail. This has been + * replaced with {@link BedEnterAction} that better fits the new beds */ @Deprecated(since = "1.21.11") public enum BedEnterResult { From 03a043f1a54bfda196e4bc3254697b135c4d46e3 Mon Sep 17 00:00:00 2001 From: roro1506HD Date: Fri, 24 Oct 2025 19:55:54 +0200 Subject: [PATCH 12/12] chore: replace PlayerEnterBedEvent's constructor with updated one instead of creating a new one --- .../paper/block/bed/BedEnterActionBridge.java | 8 +-- .../event/player/PlayerBedEnterEvent.java | 13 ----- .../block/bed/BedEnterActionBridgeImpl.java | 56 ------------------- 3 files changed, 1 insertion(+), 76 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java index 37a15e7dce74..38f9d2ed110b 100644 --- a/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java +++ b/paper-api/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridge.java @@ -1,11 +1,8 @@ package io.papermc.paper.block.bed; -import io.papermc.paper.event.player.PlayerBedFailEnterEvent; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerBedEnterEvent; -import org.jetbrains.annotations.ApiStatus; import java.util.Optional; import java.util.ServiceLoader; +import org.jetbrains.annotations.ApiStatus; /** * @hidden @@ -30,7 +27,4 @@ final class Holder { BedEnterProblem createOtherProblem(); - @Deprecated(since = "1.21.11", forRemoval = true) - BedEnterAction fromBedEnterResult(Player player, PlayerBedEnterEvent.BedEnterResult bedEnterResult); - } diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java index a30cb27fe6b2..27aa03e06212 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -1,7 +1,6 @@ package org.bukkit.event.player; import io.papermc.paper.block.bed.BedEnterAction; -import io.papermc.paper.block.bed.BedEnterActionBridge; import io.papermc.paper.block.bed.BedRuleResult; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -30,18 +29,6 @@ public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull this.enterAction = enterAction; } - @ApiStatus.Internal - @Deprecated(since = "1.21.11", forRemoval = true) - public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed, @NotNull BedEnterResult bedEnterResult) { - this(player, bed, bedEnterResult, BedEnterActionBridge.instance().fromBedEnterResult(player, bedEnterResult)); - } - - @ApiStatus.Internal - @Deprecated(since = "1.13.2", forRemoval = true) - public PlayerBedEnterEvent(@NotNull Player player, @NotNull Block bed) { - this(player, bed, BedEnterResult.OK); - } - /** * Returns the bed block involved in this event. * diff --git a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java index a727c8622abc..8527db6c2b4a 100644 --- a/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/block/bed/BedEnterActionBridgeImpl.java @@ -1,17 +1,7 @@ package io.papermc.paper.block.bed; -import io.papermc.paper.adventure.PaperAdventure; -import io.papermc.paper.event.player.PlayerBedFailEnterEvent; -import java.util.Optional; -import net.minecraft.network.chat.Component; -import net.minecraft.world.attribute.BedRule; -import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.entity.player.Player; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.craftbukkit.event.CraftEventFactory; -import org.bukkit.event.player.PlayerBedEnterEvent; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; @NullMarked public class BedEnterActionBridgeImpl implements BedEnterActionBridge { @@ -40,50 +30,4 @@ public BedEnterProblem createExplosionProblem() { public BedEnterProblem createOtherProblem() { return new BedEnterProblemImpl(Player.BedSleepingProblem.OTHER_PROBLEM); } - - @Override - public BedEnterAction fromBedEnterResult(final org.bukkit.entity.Player player, final PlayerBedEnterEvent.BedEnterResult bedEnterResult) { - Player vanillaPlayer = ((CraftPlayer) player).getHandle(); - BedRule bedRule = vanillaPlayer.level().environmentAttributes().getDimensionValue(EnvironmentAttributes.BED_RULE); - BedRuleResult canSetSpawn = CraftEventFactory.asBedRuleResult(bedRule.canSetSpawn(), bedRule.canSetSpawn(vanillaPlayer.level())); - - if (bedEnterResult == PlayerBedEnterEvent.BedEnterResult.OK) { - return new BedEnterActionImpl( - BedRuleResult.ALLOWED, - canSetSpawn, - null, - null - ); - } - - if (bedEnterResult == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_HERE || bedEnterResult == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_NOW) { - return new BedEnterActionImpl( - bedEnterResult == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_HERE ? BedRuleResult.NEVER : BedRuleResult.TOO_MUCH_LIGHT, - canSetSpawn, - null, - bedRule.errorMessage().map(PaperAdventure::asAdventure).orElse(null) - ); - } - - BedRuleResult canSleep = CraftEventFactory.asBedRuleResult(bedRule.canSleep(), bedRule.canSleep(vanillaPlayer.level())); - BedEnterProblem enterProblem = switch (bedEnterResult) { - case TOO_FAR_AWAY -> BedEnterProblem.TOO_FAR_AWAY; - case OBSTRUCTED -> BedEnterProblem.OBSTRUCTED; - case NOT_SAFE -> BedEnterProblem.NOT_SAFE; - case OTHER_PROBLEM -> BedEnterProblem.OTHER; - case EXPLOSION -> BedEnterProblem.EXPLOSION; - default -> throw new IllegalStateException("Unexpected value: " + bedEnterResult); - }; - Component errorMessage = ((BedEnterProblemImpl) enterProblem).vanillaProblem().message(); - if (bedEnterResult == PlayerBedEnterEvent.BedEnterResult.EXPLOSION) { - errorMessage = bedRule.errorMessage().orElse(null); - } - - return new BedEnterActionImpl( - canSleep, - canSetSpawn, - enterProblem, - errorMessage == null ? null : PaperAdventure.asAdventure(errorMessage) - ); - } }