diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerLiddedOpenEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerLiddedOpenEvent.java new file mode 100644 index 0000000000..3dbb9c1c17 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerLiddedOpenEvent.java @@ -0,0 +1,89 @@ +package io.papermc.paper.event.player; + +import io.papermc.paper.block.LidMode; +import io.papermc.paper.block.LidState; +import io.papermc.paper.block.Lidded; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; + +/** + * Called when a player opens a {@link Lidded} block. + * + *

+ * This is called every time a player opens a {@link Lidded} block + * regardless of if the lid is already open (e.g. multiple players). + *

+ * Cancelling this event prevents the player from being considered in other {@link Lidded} methods: + * they will not contribute to the {@link Lidded#getTrueLidState()} and {@link Lidded#getEffectiveLidState()}. + *

+ * This event is called twice for double chests, once for each half. + */ +@NullMarked +public class PlayerLiddedOpenEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + private final Lidded blockState; + private final Block block; + private boolean cancelled; + + @ApiStatus.Internal + public PlayerLiddedOpenEvent(final @NotNull Player who, final @NotNull Lidded blockState, final @NotNull Block block) { + super(who); + this.cancelled = false; + this.blockState = blockState; + this.block = block; + } + + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the {@link Lidded} block involved in this event. + * @return the lidded block + */ + @NotNull + public Lidded getLidded() { + return blockState; + } + + /** + * Gets the block involved in this event. + * @return the block + */ + @NotNull + public Block getBlock() { + return block; + } + + /** + * Gets if the block would appear to open, if this event is not cancelled. + * return if the block would appear to open + */ + public boolean isOpening() { + return blockState.getLidMode() == LidMode.DEFAULT && blockState.getTrueLidState() == LidState.CLOSED; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static @NotNull HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch index 55c2d9d02a..759bb0beae 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch @@ -8,7 +8,7 @@ protected abstract void onOpen(Level level, BlockPos pos, BlockState state); -@@ -20,10 +_,94 @@ +@@ -20,10 +_,109 @@ protected abstract void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount); @@ -33,13 +33,17 @@ - public void incrementOpeners(Player player, Level level, BlockPos pos, BlockState state) { + // Paper start - add Improved Lidded API + private io.papermc.paper.block.LidMode apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ private final java.util.Set cancelledPlayers = new java.util.HashSet<>(); // Paper - store players whose opening was cancelled by PlayerLiddedOpenEvent ++ + public void startForceLiddedLidOpen(Level level, BlockPos pos, BlockState state) { + incrementOpeners(null, level, pos, state); + } ++ + public void stopForceLiddedLidOpen(Level level, BlockPos pos, BlockState state) { + decrementOpeners(null, level, pos, state); + apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; + } ++ + public void startForceLiddedLidClose(Level level, BlockPos pos, BlockState state) { + if (this.getTrueLidState() == io.papermc.paper.block.LidState.OPEN) { + this.onClose(level, pos, state); @@ -47,6 +51,7 @@ + } + this.openerCountChanged(level, pos, state, this.openCount, 0); + } ++ + public void stopForceLiddedLidClose(Level level, BlockPos pos, BlockState state) { + if (this.getTrueLidState() == io.papermc.paper.block.LidState.OPEN) { + this.onOpen(level, pos, state); @@ -56,12 +61,15 @@ + this.openerCountChanged(level, pos, state, 0, this.openCount); + apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; + } ++ + public io.papermc.paper.block.LidMode getLidMode() { + return apiLidMode; + } ++ + public void setLidMode(final io.papermc.paper.block.LidMode targetLidMode) { + apiLidMode = targetLidMode; + } ++ + public io.papermc.paper.block.LidState getEffectiveLidState() { + return switch (apiLidMode) { + case OPEN_UNTIL_VIEWED, FORCED_OPEN -> io.papermc.paper.block.LidState.OPEN; @@ -69,6 +77,7 @@ + default -> getTrueLidState(); + }; + } ++ + public io.papermc.paper.block.LidState getTrueLidState() { + boolean virtualViewerPresent = (apiLidMode == io.papermc.paper.block.LidMode.FORCED_OPEN || apiLidMode == io.papermc.paper.block.LidMode.OPEN_UNTIL_VIEWED); + int trueOpenCount = this.openCount - (virtualViewerPresent ? 1 : 0); @@ -80,6 +89,12 @@ + // Paper end - add Improved Lidded API + + public void incrementOpeners(@javax.annotation.Nullable Player player, Level level, BlockPos pos, BlockState state) { // Paper - make player nullable for New Lidded API ++ // Paper start - Call PlayerLiddedOpenEvent ++ if (player != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLiddedOpenEvent(player, level, pos)) { ++ cancelledPlayers.add(player); ++ return; ++ } ++ // Paper end - Call PlayerLiddedOpenEvent + // Paper start - add Improved Lidded API + if (this.openCount == 0 && apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { + apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; @@ -104,7 +119,7 @@ if (i == 0) { this.onOpen(level, pos, state); level.gameEvent(player, GameEvent.CONTAINER_OPEN, pos); -@@ -31,11 +_,43 @@ +@@ -31,11 +_,44 @@ } this.openerCountChanged(level, pos, state, i, this.openCount); @@ -122,6 +137,7 @@ - public void decrementOpeners(Player player, Level level, BlockPos pos, BlockState state) { + public void decrementOpeners(@javax.annotation.Nullable Player player, Level level, BlockPos pos, BlockState state) { // Paper - make player nullable for New Lidded API ++ if (player != null && cancelledPlayers.remove(player)) return; // Paper - do not decrement if player's opening was cancelled by PlayerLiddedOpenEvent + int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added + if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative int i = this.openCount--; @@ -149,7 +165,17 @@ if (this.openCount == 0) { this.onClose(level, pos, state); level.gameEvent(player, GameEvent.CONTAINER_CLOSE, pos); -@@ -59,8 +_,14 @@ +@@ -53,14 +_,24 @@ + + public void recheckOpeners(Level level, BlockPos pos, BlockState state) { + List playersWithContainerOpen = this.getPlayersWithContainerOpen(level, pos); ++ // Paper start - maintain cancelledPlayers, list of players with the chest open, but without the lid. ++ cancelledPlayers.removeIf(java.util.function.Predicate.not(playersWithContainerOpen::contains)); ++ playersWithContainerOpen.removeIf(cancelledPlayers::contains); ++ // Paper end - maintain cancelledPlayers, list of players with the chest open, but without the lid. + this.maxInteractionRange = 0.0; + + for (Player player : playersWithContainerOpen) { this.maxInteractionRange = Math.max(player.blockInteractionRange(), this.maxInteractionRange); } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch index 8350411188..1481778f3b 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch @@ -46,12 +46,13 @@ this.openCount = type; if (type == 0) { this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSING; -@@ -159,6 +_,71 @@ +@@ -159,6 +_,72 @@ level.updateNeighborsAt(pos, state.getBlock()); } + // Paper start - add Improved Lidded API + private io.papermc.paper.block.LidMode apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ private final java.util.Set cancelledPlayers = new java.util.HashSet<>(); // Paper - store players whose opening was cancelled by PlayerLiddedOpenEvent + + public void startForceLiddedLidOpen() { + this.openCount++; @@ -118,10 +119,16 @@ @Override public void startOpen(Player player) { if (!this.remove && !player.isSpectator()) { -@@ -166,13 +_,36 @@ +@@ -166,20 +_,63 @@ this.openCount = 0; } ++ // Paper start - Call PlayerLiddedOpenEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLiddedOpenEvent(player, this.level, this.worldPosition)) { ++ cancelledPlayers.add(player); ++ return; ++ } ++ // Paper end - Call PlayerLiddedOpenEvent + // Paper start - add Improved Lidded API + if (this.openCount == 0) { + if (apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { @@ -155,9 +162,10 @@ } } -@@ -180,6 +_,19 @@ + @Override public void stopOpen(Player player) { if (!this.remove && !player.isSpectator()) { ++ if (cancelledPlayers.remove(player)) return; // Paper - do not decrement if player's opening was cancelled by PlayerLiddedOpenEvent this.openCount--; + + // Paper start - add Improved Lidded API 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 e37aaf77f9..e9225e2f52 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 @@ -2271,4 +2271,14 @@ public class CraftEventFactory { return event; } // Paper end - add EntityFertilizeEggEvent + + public static boolean callPlayerLiddedOpenEvent(net.minecraft.world.entity.player.Player who, final Level world, final BlockPos pos) { + Player player = (Player) who.getBukkitEntity(); + Block block = CraftBlock.at(world, pos); + io.papermc.paper.block.PaperLidded blockState = (io.papermc.paper.block.PaperLidded) CraftBlockStates.getBlockState(block); + + io.papermc.paper.event.player.PlayerLiddedOpenEvent event = new io.papermc.paper.event.player.PlayerLiddedOpenEvent(player, blockState, block); + + return event.callEvent(); + } }