From 0450bda4fef97ef89d02f8416d81617425496c56 Mon Sep 17 00:00:00 2001 From: Isaac - The456 Date: Wed, 25 Dec 2024 01:03:12 +0000 Subject: [PATCH] Add new Lidded API --- .../java/io/papermc/paper/block/LidMode.java | 50 +++++++ .../java/io/papermc/paper/block/LidState.java | 9 ++ .../java/io/papermc/paper/block/Lidded.java | 37 +++++ .../main/java/org/bukkit/block/Barrel.java | 2 +- .../src/main/java/org/bukkit/block/Chest.java | 2 +- .../java/org/bukkit/block/EnderChest.java | 2 +- .../main/java/org/bukkit/block/Lidded.java | 17 ++- .../java/org/bukkit/block/ShulkerBox.java | 2 +- .../entity/ContainerOpenersCounter.java.patch | 132 +++++++++++++++--- .../entity/ShulkerBoxBlockEntity.java.patch | 131 ++++++++++++++++- .../io/papermc/paper/block/PaperLidded.java | 87 ++++++++++++ .../bukkit/craftbukkit/block/CraftBarrel.java | 85 ++++++----- .../bukkit/craftbukkit/block/CraftChest.java | 86 +++++++----- .../craftbukkit/block/CraftEnderChest.java | 86 +++++++----- .../craftbukkit/block/CraftShulkerBox.java | 76 ++++++---- 15 files changed, 641 insertions(+), 163 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/block/LidMode.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/LidState.java create mode 100644 paper-api/src/main/java/io/papermc/paper/block/Lidded.java create mode 100644 paper-server/src/main/java/io/papermc/paper/block/PaperLidded.java diff --git a/paper-api/src/main/java/io/papermc/paper/block/LidMode.java b/paper-api/src/main/java/io/papermc/paper/block/LidMode.java new file mode 100644 index 0000000000..a4f85cf32a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/LidMode.java @@ -0,0 +1,50 @@ +package io.papermc.paper.block; + +import org.jspecify.annotations.NullMarked; + +/** + * Represents how the lid of a block behaves. + */ +@NullMarked +public enum LidMode { + /** + * The default lid mode, the lid will open and close based on player interaction. + *

+ * the state used for this is provided with {@link Lidded#getTrueLidState()} + */ + DEFAULT, + + /** + * The lid will be forced open, regardless of player interaction. + *

+ * This needs to be manually unset with another call to {@link Lidded#setLidMode(LidMode)}. + */ + FORCED_OPEN, + + /** + * The lid will be forced closed, regardless of player interaction. + *

+ * This needs to be manually unset with another call to {@link Lidded#setLidMode(LidMode)}. + */ + FORCED_CLOSED, + + /** + * The lid will be forced open until at least one player has opened it. + *

+ * It will then revert to {@link #DEFAULT}. + *

+ * If at least one player is viewing it when this is set, it will immediately revert to + * {@link #DEFAULT}. + */ + OPEN_UNTIL_VIEWED, + + /** + * The lid will be forced closed until all players currently viewing it have closed it. + *

+ * It will then revert to {@link #DEFAULT}. + *

+ * If no players are viewing it when this is set, it will immediately revert to + * {@link #DEFAULT}. + */ + CLOSED_UNTIL_NOT_VIEWED +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/LidState.java b/paper-api/src/main/java/io/papermc/paper/block/LidState.java new file mode 100644 index 0000000000..b6e86005ce --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/LidState.java @@ -0,0 +1,9 @@ +package io.papermc.paper.block; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public enum LidState { + OPEN, + CLOSED +} diff --git a/paper-api/src/main/java/io/papermc/paper/block/Lidded.java b/paper-api/src/main/java/io/papermc/paper/block/Lidded.java new file mode 100644 index 0000000000..23c97fd9b8 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/block/Lidded.java @@ -0,0 +1,37 @@ +package io.papermc.paper.block; + +import org.bukkit.block.TileState; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface Lidded extends TileState { + + /** + * Gets the current state of the block, respecting the lidded mode. + * + * @return the effective lid state + */ + LidState getEffectiveLidState(); + + /** + * Gets how the lid would be without any lidded mode, based on players interacting with the block. + * @return the true lid state + */ + LidState getTrueLidState(); + + /** + * Gets the current lid mode of the block. + * + * @return the lid mode + */ + LidMode getLidMode(); + + /** + * Sets the lid mode of the block. + * + * @param mode the new lid mode + * @return the actually set lid mode + */ + LidMode setLidMode(LidMode mode); + +} diff --git a/paper-api/src/main/java/org/bukkit/block/Barrel.java b/paper-api/src/main/java/org/bukkit/block/Barrel.java index d3789b2b7d..c714361ba5 100644 --- a/paper-api/src/main/java/org/bukkit/block/Barrel.java +++ b/paper-api/src/main/java/org/bukkit/block/Barrel.java @@ -5,4 +5,4 @@ import org.bukkit.loot.Lootable; /** * Represents a captured state of a Barrel. */ -public interface Barrel extends Container, com.destroystokyo.paper.loottable.LootableBlockInventory, Lidded { } // Paper +public interface Barrel extends Container, com.destroystokyo.paper.loottable.LootableBlockInventory, Lidded, io.papermc.paper.block.Lidded { } // Paper diff --git a/paper-api/src/main/java/org/bukkit/block/Chest.java b/paper-api/src/main/java/org/bukkit/block/Chest.java index 5d02f9c938..85fb81c77f 100644 --- a/paper-api/src/main/java/org/bukkit/block/Chest.java +++ b/paper-api/src/main/java/org/bukkit/block/Chest.java @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; /** * Represents a captured state of a chest. */ -public interface Chest extends Container, LootableBlockInventory, Lidded { // Paper +public interface Chest extends Container, LootableBlockInventory, Lidded, io.papermc.paper.block.Lidded { // Paper /** * Gets the inventory of the chest block represented by this block state. diff --git a/paper-api/src/main/java/org/bukkit/block/EnderChest.java b/paper-api/src/main/java/org/bukkit/block/EnderChest.java index 6b66f38e55..9ec76ad38d 100644 --- a/paper-api/src/main/java/org/bukkit/block/EnderChest.java +++ b/paper-api/src/main/java/org/bukkit/block/EnderChest.java @@ -3,7 +3,7 @@ package org.bukkit.block; /** * Represents a captured state of an ender chest. */ -public interface EnderChest extends Lidded, TileState { +public interface EnderChest extends Lidded, TileState, io.papermc.paper.block.Lidded { // Paper start - More Chest Block API /** * Checks whether this ender chest is blocked by a block above diff --git a/paper-api/src/main/java/org/bukkit/block/Lidded.java b/paper-api/src/main/java/org/bukkit/block/Lidded.java index 30c7df0021..72cb0bffbe 100644 --- a/paper-api/src/main/java/org/bukkit/block/Lidded.java +++ b/paper-api/src/main/java/org/bukkit/block/Lidded.java @@ -1,25 +1,34 @@ package org.bukkit.block; +/** + * @deprecated Incomplete api. Use {@link io.papermc.paper.block.Lidded} instead. + */ +@Deprecated // Paper - Deprecate Bukkit's Lidded API public interface Lidded { /** * Sets the block's animated state to open and prevents it from being closed * until {@link #close()} is called. + * @deprecated Use {@link io.papermc.paper.block.Lidded#setLidMode(io.papermc.paper.block.LidMode)} */ + @Deprecated void open(); /** - * Sets the block's animated state to closed even if a player is currently - * viewing this block. + * Unsets a corresponding call to {@link #open()}. + * @deprecated Misleading name. Use {@link io.papermc.paper.block.Lidded#setLidMode(io.papermc.paper.block.LidMode)} */ + @Deprecated void close(); // Paper start - More Lidded Block API /** - * Checks if the block's animation state. + * Checks is the Lid is currently forced open. * - * @return true if the block's animation state is set to open. + * @return true if the block's animation state is force open. + * @deprecated Misleading name. Use {@link io.papermc.paper.block.Lidded#getLidMode()} for the direct replacement, or {@link io.papermc.paper.block.Lidded#getEffectiveLidState()} to tell if the lid is visibly open to the player instead. */ + @Deprecated boolean isOpen(); // Paper end - More Lidded Block API } diff --git a/paper-api/src/main/java/org/bukkit/block/ShulkerBox.java b/paper-api/src/main/java/org/bukkit/block/ShulkerBox.java index 5dc5318b0a..23995c48f2 100644 --- a/paper-api/src/main/java/org/bukkit/block/ShulkerBox.java +++ b/paper-api/src/main/java/org/bukkit/block/ShulkerBox.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a captured state of a ShulkerBox. */ -public interface ShulkerBox extends Container, LootableBlockInventory, Lidded { // Paper +public interface ShulkerBox extends Container, LootableBlockInventory, Lidded, io.papermc.paper.block.Lidded { // Paper /** * Get the {@link DyeColor} corresponding to this ShulkerBox 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 6d0edb5d1d..55c2d9d02a 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 @@ -4,33 +4,92 @@ private static final int CHECK_TICK_DELAY = 5; private int openCount; private double maxInteractionRange; -+ public boolean opened; // CraftBukkit ++ // public boolean opened; // CraftBukkit // Paper - Replace with new Lidded API protected abstract void onOpen(Level level, BlockPos pos, BlockState state); -@@ -20,10 +_,36 @@ +@@ -20,10 +_,94 @@ protected abstract void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount); + // CraftBukkit start -+ public void onAPIOpen(Level level, BlockPos blockPos, BlockState blockState) { -+ this.onOpen(level, blockPos, blockState); -+ } -+ -+ public void onAPIClose(Level level, BlockPos blockPos, BlockState blockState) { -+ this.onClose(level, blockPos, blockState); -+ } -+ -+ public void openerAPICountChanged(Level level, BlockPos blockPos, BlockState blockState, int count, int openCount) { -+ this.openerCountChanged(level, blockPos, blockState, count, openCount); -+ } ++ // Paper start - Replace with new Lidded API ++ // public void onAPIOpen(Level level, BlockPos blockPos, BlockState blockState) { ++ // this.onOpen(level, blockPos, blockState); ++ // } ++ // ++ // public void onAPIClose(Level level, BlockPos blockPos, BlockState blockState) { ++ // this.onClose(level, blockPos, blockState); ++ // } ++ // ++ // public void openerAPICountChanged(Level level, BlockPos blockPos, BlockState blockState, int count, int openCount) { ++ // this.openerCountChanged(level, blockPos, blockState, count, openCount); ++ // } ++ // Paper end - Replace with new Lidded API + // CraftBukkit end + protected abstract boolean isOwnContainer(Player player); - public void incrementOpeners(Player player, Level level, BlockPos pos, BlockState state) { +- 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; ++ 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); ++ level.gameEvent(null, GameEvent.CONTAINER_CLOSE, pos); ++ } ++ 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); ++ level.gameEvent(null, GameEvent.CONTAINER_OPEN, pos); ++ scheduleRecheck(level, pos, state); ++ } ++ 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; ++ case CLOSED_UNTIL_NOT_VIEWED, FORCED_CLOSED -> io.papermc.paper.block.LidState.CLOSED; ++ 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); ++ if (trueOpenCount < 0) { ++ throw new IllegalStateException("trueOpenCount is negative: " + trueOpenCount + " openCount: " + openCount + " virtualViewerPresent: " + virtualViewerPresent); ++ } ++ return trueOpenCount > 0 ? io.papermc.paper.block.LidState.OPEN : io.papermc.paper.block.LidState.CLOSED; ++ } ++ // 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 - 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; ++ stopForceLiddedLidClose(level, pos, state); ++ } ++ // Paper end - add Improved Lidded API ++ + int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added int i = this.openCount++; ++ if (apiLidMode == io.papermc.paper.block.LidMode.FORCED_CLOSED || apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) return; // Paper - add improved Lidded API + + // CraftBukkit start - Call redstone event + if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) { @@ -45,14 +104,38 @@ if (i == 0) { this.onOpen(level, pos, state); level.gameEvent(player, GameEvent.CONTAINER_OPEN, pos); -@@ -35,7 +_,20 @@ +@@ -31,11 +_,43 @@ + } + + this.openerCountChanged(level, pos, state, i, this.openCount); ++ if (player != null) // Paper - make player nullable for improved Lidded API + this.maxInteractionRange = Math.max(player.blockInteractionRange(), this.maxInteractionRange); ++ ++ // Paper start - add Improved Lidded API ++ if (player != null && apiLidMode == io.papermc.paper.block.LidMode.OPEN_UNTIL_VIEWED) { ++ // reset to default ++ apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ stopForceLiddedLidOpen(level, pos, state); ++ } ++ // Paper end - add Improved Lidded API } - public void decrementOpeners(Player player, Level level, BlockPos pos, BlockState state) { +- 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 + 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--; + ++ // Paper start - add Improved Lidded API ++ if (apiLidMode == io.papermc.paper.block.LidMode.FORCED_CLOSED || apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { ++ if (this.openCount == 0 && apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { ++ apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ stopForceLiddedLidClose(level, pos, state); ++ } ++ return; ++ } ++ // Paper end - add Improved Lidded API ++ + // CraftBukkit start - Call redstone event + if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) { + int newPower = Math.max(0, Math.min(15, this.openCount)); @@ -66,11 +149,20 @@ if (this.openCount == 0) { this.onClose(level, pos, state); level.gameEvent(player, GameEvent.CONTAINER_CLOSE, pos); -@@ -60,6 +_,7 @@ +@@ -59,8 +_,14 @@ + this.maxInteractionRange = Math.max(player.blockInteractionRange(), this.maxInteractionRange); } - int size = playersWithContainerOpen.size(); -+ if (this.opened) size++; // CraftBukkit - add dummy count from API - int i = this.openCount; +- int size = playersWithContainerOpen.size(); +- int i = this.openCount; ++ // Paper Start - Replace with add Improved Lidded API ++ boolean forceClosed = apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED || apiLidMode == io.papermc.paper.block.LidMode.FORCED_CLOSED; ++ boolean forceOpened = apiLidMode == io.papermc.paper.block.LidMode.OPEN_UNTIL_VIEWED || apiLidMode == io.papermc.paper.block.LidMode.FORCED_OPEN; ++ int size = forceClosed ? 0 : playersWithContainerOpen.size() + (forceOpened ? 1 : 0); ++ // if (this.opened) size++; // CraftBukkit - add dummy count from API ++ int i = forceClosed ? 0 : this.openCount; ++ // Paper End - Replace with add Improved Lidded API ++ if (i != size) { boolean flag = size != 0; + boolean flag1 = i != 0; 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 dba115b265..8350411188 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 @@ -7,7 +7,7 @@ + // CraftBukkit start - add fields and methods + public List transaction = new java.util.ArrayList<>(); + private int maxStack = MAX_STACK; -+ public boolean opened; ++ // public boolean opened; // Paper - replace with new Lidded API + + public List getContents() { + return this.itemStacks; @@ -38,19 +38,140 @@ public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState blockState) { super(BlockEntityType.SHULKER_BOX, pos, blockState); this.color = color; -@@ -167,6 +_,7 @@ +@@ -139,6 +_,7 @@ + @Override + public boolean triggerEvent(int id, int type) { + if (id == 1) { ++ if (apiLidMode != io.papermc.paper.block.LidMode.FORCED_CLOSED && apiLidMode != io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) this.openCount = type; // Paper - Skip mutate when forced closed by lidded api + this.openCount = type; + if (type == 0) { + this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSING; +@@ -159,6 +_,71 @@ + level.updateNeighborsAt(pos, state.getBlock()); + } + ++ // Paper start - add Improved Lidded API ++ private io.papermc.paper.block.LidMode apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ ++ public void startForceLiddedLidOpen() { ++ this.openCount++; ++ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); ++ if (this.openCount == 1) { ++ this.level.gameEvent(null, GameEvent.CONTAINER_OPEN, this.worldPosition); ++ this.level.playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); ++ } ++ } ++ ++ public void stopForceLiddedLidOpen() { ++ this.openCount--; ++ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); ++ if (this.openCount <= 0) { ++ this.level.gameEvent(null, GameEvent.CONTAINER_CLOSE, this.worldPosition); ++ this.level.playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); ++ } ++ apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ } ++ ++ public void startForceLiddedLidClose() { ++ if (this.getTrueLidState() == io.papermc.paper.block.LidState.OPEN) { ++ this.level.gameEvent(null, GameEvent.CONTAINER_CLOSE, this.worldPosition); ++ this.level.playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); ++ } ++ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, 0); ++ } ++ ++ public void stopForceLiddedLidClose() { ++ if (this.getTrueLidState() == io.papermc.paper.block.LidState.OPEN) { ++ this.level.gameEvent(null, GameEvent.CONTAINER_OPEN, this.worldPosition); ++ this.level.playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); ++ } ++ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); ++ } ++ ++ public io.papermc.paper.block.LidMode getLidMode() { ++ return apiLidMode; ++ } ++ ++ public void setLidMode(final io.papermc.paper.block.LidMode lidMode) { ++ this.apiLidMode = lidMode; ++ } ++ ++ public io.papermc.paper.block.LidState getEffectiveLidState() { ++ return switch (apiLidMode) { ++ case OPEN_UNTIL_VIEWED, FORCED_OPEN -> io.papermc.paper.block.LidState.OPEN; ++ case CLOSED_UNTIL_NOT_VIEWED, FORCED_CLOSED -> io.papermc.paper.block.LidState.CLOSED; ++ 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); ++ // ensure trueOpenCount is never negative, throw ++ if (trueOpenCount < 0) { ++ throw new IllegalStateException("trueOpenCount is negative: " + trueOpenCount + " openCount: " + openCount + " virtualViewerPresent: " + virtualViewerPresent); ++ } ++ return trueOpenCount > 0 ? io.papermc.paper.block.LidState.OPEN : io.papermc.paper.block.LidState.CLOSED; ++ } ++ // Paper end - add Improved Lidded API ++ + @Override + public void startOpen(Player player) { + if (!this.remove && !player.isSpectator()) { +@@ -166,13 +_,36 @@ + this.openCount = 0; } ++ // Paper start - add Improved Lidded API ++ if (this.openCount == 0) { ++ if (apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { ++ apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ stopForceLiddedLidClose(); ++ } ++ } ++ // Paper end - add Improved Lidded API ++ this.openCount++; -+ if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call ++ ++ // Paper start - replace with Improved Lidded API ++ // if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call ++ if (this.apiLidMode == io.papermc.paper.block.LidMode.FORCED_CLOSED || this.apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) return; ++ // Paper end - replace with Improved Lidded API ++ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); if (this.openCount == 1) { this.level.gameEvent(player, GameEvent.CONTAINER_OPEN, this.worldPosition); -@@ -180,6 +_,7 @@ + this.level + .playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); + } ++ ++ // Paper start - add Improved Lidded API ++ if (apiLidMode == io.papermc.paper.block.LidMode.OPEN_UNTIL_VIEWED) { ++ // reset to default ++ apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ stopForceLiddedLidOpen(); ++ } ++ // Paper end - add Improved Lidded API + } + } + +@@ -180,6 +_,19 @@ public void stopOpen(Player player) { if (!this.remove && !player.isSpectator()) { this.openCount--; -+ if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call. ++ ++ // Paper start - add Improved Lidded API ++ // if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call. ++ if (this.apiLidMode == io.papermc.paper.block.LidMode.FORCED_CLOSED || this.apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { ++ if (this.openCount <= 0 && this.apiLidMode == io.papermc.paper.block.LidMode.CLOSED_UNTIL_NOT_VIEWED) { ++ this.openCount = 0; ++ this.apiLidMode = io.papermc.paper.block.LidMode.DEFAULT; ++ this.stopForceLiddedLidClose(); ++ } ++ return; ++ } ++ // Paper end - add Improved Lidded API ++ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); if (this.openCount <= 0) { this.level.gameEvent(player, GameEvent.CONTAINER_CLOSE, this.worldPosition); diff --git a/paper-server/src/main/java/io/papermc/paper/block/PaperLidded.java b/paper-server/src/main/java/io/papermc/paper/block/PaperLidded.java new file mode 100644 index 0000000000..56596a8dbd --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/block/PaperLidded.java @@ -0,0 +1,87 @@ +package io.papermc.paper.block; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface PaperLidded extends Lidded, org.bukkit.block.Lidded { + + @Override + default LidMode setLidMode(final LidMode targetLidMode) { + final LidMode oldLidMode = getLidMode(); + final LidMode newLidMode = getResultantLidMode(targetLidMode); + + if (oldLidMode == newLidMode) { + // already in correct state + return newLidMode; + } + + boolean wasForcedOpen = + oldLidMode == LidMode.FORCED_OPEN || oldLidMode == LidMode.OPEN_UNTIL_VIEWED; + boolean wasForcedClosed = + oldLidMode == LidMode.FORCED_CLOSED || oldLidMode == LidMode.CLOSED_UNTIL_NOT_VIEWED; + boolean isForcedOpen = + newLidMode == LidMode.FORCED_OPEN || newLidMode == LidMode.OPEN_UNTIL_VIEWED; + boolean isForcedClosed = + newLidMode == LidMode.FORCED_CLOSED || newLidMode == LidMode.CLOSED_UNTIL_NOT_VIEWED; + + // stop any existing force open/close, if next state doesn't need it. + if (wasForcedOpen && !isForcedOpen) { + stopForceLiddedLidOpen(); + } else if (wasForcedClosed && !isForcedClosed) { + stopForceLiddedLidClose(); + } + + // start new force open/close, if it wasn't previously. + if (isForcedOpen && !wasForcedOpen) { + startForceLiddedLidOpen(); + } else if (isForcedClosed && !wasForcedClosed) { + startForceLiddedLidClose(); + } + + // return the new lid mode, so it can be stored by the implementation. + return newLidMode; + } + + private LidMode getResultantLidMode(LidMode targetLidMode) { + final LidState trueLidState = getTrueLidState(); + + // check that target lid mode is valid for true lid state. + LidMode newLidMode; + + if (targetLidMode == LidMode.CLOSED_UNTIL_NOT_VIEWED + && trueLidState == LidState.CLOSED) { + // insta-revert to default, as the lid is already closed. + newLidMode = LidMode.DEFAULT; + } else if (targetLidMode == LidMode.OPEN_UNTIL_VIEWED + && trueLidState == LidState.OPEN) { + // insta-revert to default, as the lid is already open. + newLidMode = LidMode.DEFAULT; + } else { + newLidMode = targetLidMode; + } + return newLidMode; + } + + // these should be similar to the vanilla open/close behavior. + void startForceLiddedLidOpen(); + void stopForceLiddedLidOpen(); + void startForceLiddedLidClose(); + void stopForceLiddedLidClose(); + + // bukkit lidded impl using the paper lidded api. + + @Override + default boolean isOpen() { + return getLidMode() == LidMode.FORCED_OPEN || getLidMode() == LidMode.OPEN_UNTIL_VIEWED; + } + + @Override + default void close() { + setLidMode(LidMode.DEFAULT); + } + + @Override + default void open() { + setLidMode(LidMode.FORCED_OPEN); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java index 6063f0e1fd..297605ac38 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java @@ -9,8 +9,9 @@ import org.bukkit.World; import org.bukkit.block.Barrel; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; -public class CraftBarrel extends CraftLootable implements Barrel { +public class CraftBarrel extends CraftLootable implements Barrel, io.papermc.paper.block.PaperLidded { public CraftBarrel(World world, BarrelBlockEntity tileEntity) { super(world, tileEntity); @@ -34,36 +35,6 @@ public class CraftBarrel extends CraftLootable implements Bar return new CraftInventory(this.getTileEntity()); } - @Override - public void open() { - this.requirePlaced(); - if (!this.getTileEntity().openersCounter.opened) { - BlockState blockData = this.getTileEntity().getBlockState(); - boolean open = blockData.getValue(BarrelBlock.OPEN); - - if (!open) { - this.getTileEntity().updateBlockState(blockData, true); - if (this.getWorldHandle() instanceof net.minecraft.world.level.Level) { - this.getTileEntity().playSound(blockData, SoundEvents.BARREL_OPEN); - } - } - } - this.getTileEntity().openersCounter.opened = true; - } - - @Override - public void close() { - this.requirePlaced(); - if (this.getTileEntity().openersCounter.opened) { - BlockState blockData = this.getTileEntity().getBlockState(); - this.getTileEntity().updateBlockState(blockData, false); - if (this.getWorldHandle() instanceof net.minecraft.world.level.Level) { - this.getTileEntity().playSound(blockData, SoundEvents.BARREL_CLOSE); - } - } - this.getTileEntity().openersCounter.opened = false; - } - @Override public CraftBarrel copy() { return new CraftBarrel(this, null); @@ -74,10 +45,54 @@ public class CraftBarrel extends CraftLootable implements Bar return new CraftBarrel(this, location); } - // Paper start - More Lidded Block API @Override - public boolean isOpen() { - return getTileEntity().openersCounter.opened; + public void startForceLiddedLidOpen() { + this.requirePlaced(); + this.getTileEntity().openersCounter.startForceLiddedLidOpen(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); } - // Paper end - More Lidded Block API + + @Override + public void stopForceLiddedLidOpen() { + this.requirePlaced(); + this.getTileEntity().openersCounter.stopForceLiddedLidOpen(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public void startForceLiddedLidClose() { + this.requirePlaced(); + this.getTileEntity().openersCounter.startForceLiddedLidClose(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public void stopForceLiddedLidClose() { + this.requirePlaced(); + this.getTileEntity().openersCounter.stopForceLiddedLidClose(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public io.papermc.paper.block.@NotNull LidState getEffectiveLidState() { + this.requirePlaced(); + return this.getTileEntity().openersCounter.getEffectiveLidState(); + } + + @Override + public io.papermc.paper.block.@NotNull LidState getTrueLidState() { + this.requirePlaced(); + return this.getTileEntity().openersCounter.getTrueLidState(); + } + + @Override + public io.papermc.paper.block.@NotNull LidMode getLidMode() { + this.requirePlaced(); + return this.getTileEntity().openersCounter.getLidMode(); + } + + @Override + public io.papermc.paper.block.@NotNull LidMode setLidMode(final io.papermc.paper.block.@NotNull LidMode targetLidMode) { + this.requirePlaced(); + io.papermc.paper.block.LidMode newEffectiveMode = io.papermc.paper.block.PaperLidded.super.setLidMode(targetLidMode); + this.getTileEntity().openersCounter.setLidMode(newEffectiveMode); + return newEffectiveMode; + } + } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java index cc7bf4d39b..9ec7d528cb 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java @@ -13,8 +13,9 @@ import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest; import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; -public class CraftChest extends CraftLootable implements Chest { +public class CraftChest extends CraftLootable implements Chest, io.papermc.paper.block.PaperLidded { public CraftChest(World world, ChestBlockEntity tileEntity) { super(world, tileEntity); @@ -57,32 +58,6 @@ public class CraftChest extends CraftLootable implements Chest return inventory; } - @Override - public void open() { - this.requirePlaced(); - if (!this.getTileEntity().openersCounter.opened && this.getWorldHandle() instanceof net.minecraft.world.level.Level) { - BlockState block = this.getTileEntity().getBlockState(); - int openCount = this.getTileEntity().openersCounter.getOpenerCount(); - - this.getTileEntity().openersCounter.onAPIOpen((net.minecraft.world.level.Level) this.getWorldHandle(), this.getPosition(), block); - this.getTileEntity().openersCounter.openerAPICountChanged((net.minecraft.world.level.Level) this.getWorldHandle(), this.getPosition(), block, openCount, openCount + 1); - } - this.getTileEntity().openersCounter.opened = true; - } - - @Override - public void close() { - this.requirePlaced(); - if (this.getTileEntity().openersCounter.opened && this.getWorldHandle() instanceof net.minecraft.world.level.Level) { - BlockState block = this.getTileEntity().getBlockState(); - int openCount = this.getTileEntity().openersCounter.getOpenerCount(); - - this.getTileEntity().openersCounter.onAPIClose((net.minecraft.world.level.Level) this.getWorldHandle(), this.getPosition(), block); - this.getTileEntity().openersCounter.openerAPICountChanged((net.minecraft.world.level.Level) this.getWorldHandle(), this.getPosition(), block, openCount, 0); - } - this.getTileEntity().openersCounter.opened = false; - } - @Override public CraftChest copy() { return new CraftChest(this, null); @@ -93,13 +68,6 @@ public class CraftChest extends CraftLootable implements Chest return new CraftChest(this, location); } - // Paper start - More Lidded Block API - @Override - public boolean isOpen() { - return getTileEntity().openersCounter.opened; - } - // Paper end - More Lidded Block API - // Paper start - More Chest Block API @Override public boolean isBlocked() { @@ -124,4 +92,54 @@ public class CraftChest extends CraftLootable implements Chest && ChestBlock.isChestBlockedAt(world, neighbourBlockPos); } // Paper end - More Chest Block API + + @Override + public void startForceLiddedLidOpen() { + this.requirePlaced(); + this.getTileEntity().openersCounter.startForceLiddedLidOpen(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public void stopForceLiddedLidOpen() { + this.requirePlaced(); + this.getTileEntity().openersCounter.stopForceLiddedLidOpen(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public void startForceLiddedLidClose() { + this.requirePlaced(); + this.getTileEntity().openersCounter.startForceLiddedLidClose(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public void stopForceLiddedLidClose() { + this.requirePlaced(); + this.getTileEntity().openersCounter.stopForceLiddedLidClose(this.getTileEntity().getLevel(), this.getTileEntity().getBlockPos(), this.getTileEntity().getBlockState()); + } + + @Override + public io.papermc.paper.block.@NotNull LidState getEffectiveLidState() { + this.requirePlaced(); + return this.getTileEntity().openersCounter.getEffectiveLidState(); + } + + @Override + public io.papermc.paper.block.@NotNull LidState getTrueLidState() { + this.requirePlaced(); + return this.getTileEntity().openersCounter.getTrueLidState(); + } + + @Override + public io.papermc.paper.block.@NotNull LidMode getLidMode() { + this.requirePlaced(); + return this.getTileEntity().openersCounter.getLidMode(); + } + + @Override + public io.papermc.paper.block.@NotNull LidMode setLidMode(final io.papermc.paper.block.@NotNull LidMode targetLidMode) { + this.requirePlaced(); + io.papermc.paper.block.LidMode newEffectiveMode = io.papermc.paper.block.PaperLidded.super.setLidMode(targetLidMode); + this.getTileEntity().openersCounter.setLidMode(newEffectiveMode); + return newEffectiveMode; + } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java index f45ee675a1..0c0d3038f0 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java @@ -5,8 +5,9 @@ import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.EnderChest; +import org.jetbrains.annotations.NotNull; -public class CraftEnderChest extends CraftBlockEntityState implements EnderChest { +public class CraftEnderChest extends CraftBlockEntityState implements EnderChest, io.papermc.paper.block.PaperLidded { public CraftEnderChest(World world, EnderChestBlockEntity tileEntity) { super(world, tileEntity); @@ -16,32 +17,6 @@ public class CraftEnderChest extends CraftBlockEntityState implements ShulkerBox { +public class CraftShulkerBox extends CraftLootable implements ShulkerBox, io.papermc.paper.block.PaperLidded { public CraftShulkerBox(World world, ShulkerBoxBlockEntity tileEntity) { super(world, tileEntity); @@ -42,28 +43,6 @@ public class CraftShulkerBox extends CraftLootable implem return (color == null) ? null : DyeColor.getByWoolData((byte) color.getId()); } - @Override - public void open() { - this.requirePlaced(); - if (!this.getTileEntity().opened && this.getWorldHandle() instanceof net.minecraft.world.level.Level) { - net.minecraft.world.level.Level world = this.getTileEntity().getLevel(); - world.blockEvent(this.getPosition(), this.getTileEntity().getBlockState().getBlock(), 1, 1); - world.playSound(null, this.getPosition(), SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); - } - this.getTileEntity().opened = true; - } - - @Override - public void close() { - this.requirePlaced(); - if (this.getTileEntity().opened && this.getWorldHandle() instanceof net.minecraft.world.level.Level) { - net.minecraft.world.level.Level world = this.getTileEntity().getLevel(); - world.blockEvent(this.getPosition(), this.getTileEntity().getBlockState().getBlock(), 1, 0); - world.playSound(null, this.getPosition(), SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Paper - More Lidded Block API (Wrong sound) - } - this.getTileEntity().opened = false; - } - @Override public CraftShulkerBox copy() { return new CraftShulkerBox(this, null); @@ -74,10 +53,53 @@ public class CraftShulkerBox extends CraftLootable implem return new CraftShulkerBox(this, location); } - // Paper start - More Lidded Block API @Override - public boolean isOpen() { - return getTileEntity().opened; + public void startForceLiddedLidOpen() { + this.requirePlaced(); + this.getTileEntity().startForceLiddedLidOpen(); + } + + @Override + public void stopForceLiddedLidOpen() { + this.requirePlaced(); + this.getTileEntity().stopForceLiddedLidOpen(); + } + + @Override + public void startForceLiddedLidClose() { + this.requirePlaced(); + this.getTileEntity().startForceLiddedLidClose(); + } + + @Override + public void stopForceLiddedLidClose() { + this.requirePlaced(); + this.getTileEntity().stopForceLiddedLidClose(); + } + + @Override + public io.papermc.paper.block.@NotNull LidState getEffectiveLidState() { + this.requirePlaced(); + return this.getTileEntity().getEffectiveLidState(); + } + + @Override + public io.papermc.paper.block.@NotNull LidState getTrueLidState() { + this.requirePlaced(); + return this.getTileEntity().getTrueLidState(); + } + + @Override + public io.papermc.paper.block.@NotNull LidMode getLidMode() { + this.requirePlaced(); + return this.getTileEntity().getLidMode(); + } + + @Override + public io.papermc.paper.block.@NotNull LidMode setLidMode(final io.papermc.paper.block.@NotNull LidMode targetLidMode) { + this.requirePlaced(); + io.papermc.paper.block.LidMode newEffectiveMode = io.papermc.paper.block.PaperLidded.super.setLidMode(targetLidMode); + this.getTileEntity().setLidMode(newEffectiveMode); + return newEffectiveMode; } - // Paper end - More Lidded Block API }