From 2a8b34eeac29e5c67880a5f55522dccfc53c5db2 Mon Sep 17 00:00:00 2001 From: Nick Liu Date: Sun, 16 Jul 2023 23:10:51 +0200 Subject: [PATCH] Workaround for SPIGOT-7391 using reflection on TileEntitySign Spigot 1.20 bug https://hub.spigotmc.org/jira/browse/SPIGOT-7391 is a SpigotMC-specific bug that prevents sign changes from being saved when the sign is opened by the official `Player.openSign()` method. We work around this bug by doing what Spigot should be doing: Setting the sign's player-who-may-edit-sign to the player's UUID. If this reflection call fails, the exception will be discarded silently to avoid interrupting an otherwise working native sign editor, especially on other Bukkit implementations like PaperMC. --- CHANGELOG.md | 1 + .../interactions/UiSignEditInteraction.java | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0b232..a091a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Failure and error when editing [hanging signs and wall hanging signs](https://web.archive.org/web/20230615204117/https://minecraft.fandom.com/wiki/Sign#Hanging_signs) from Minecraft 1.20 when using `compatibility.edit-validation: Extra` (#34) ![`/se paste` with SignEdit for Bukkit v1.13.9](https://i.imgur.com/1qNBnID.png) +* Workaround for a Spigot 1.20 bug where having a player open a sign does not make the sign editable ([SPIGOT-7391](https://hub.spigotmc.org/jira/browse/SPIGOT-7391)) * `java.lang.IllegalArgumentException: Invalid page number (1)` when using `compatibility.sign-ui: EditableBook` and saving a blank book ### Under the Hood diff --git a/src/net/deltik/mc/signedit/interactions/UiSignEditInteraction.java b/src/net/deltik/mc/signedit/interactions/UiSignEditInteraction.java index efa9555..6ceb589 100644 --- a/src/net/deltik/mc/signedit/interactions/UiSignEditInteraction.java +++ b/src/net/deltik/mc/signedit/interactions/UiSignEditInteraction.java @@ -46,6 +46,7 @@ import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Optional; +import java.util.UUID; import java.util.stream.IntStream; import static net.deltik.mc.signedit.CraftBukkitReflector.*; @@ -203,6 +204,11 @@ private static void openSignEditorBukkit1_18(Player player, Sign sign) */ private static boolean openSignEditorBukkit1_20(Player player, Sign sign, SideShim side) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + try { + setPlayerWhoMayEditSign(player, sign); + } catch (Exception ignored) { + } + Optional optionalMethod = Arrays.stream(Player.class.getDeclaredMethods()) .filter(method -> method.getName().equals("openSign") && method.getParameterCount() == 2) .filter(method -> { @@ -220,6 +226,24 @@ private static boolean openSignEditorBukkit1_20(Player player, Sign sign, SideSh return false; } + /** + * Work around the Spigot 1.20 limitation of not setting the player who may edit the sign + *

+ * Bug report: SPIGOT-7391 + *

+ * FIXME: Find a more reliable way than looking for the first public UUID to assign the TileEntitySign to the Player + * + * @param player The player that wants to open the sign editor + * @param sign The sign that should load into the player's sign editor + */ + private static void setPlayerWhoMayEditSign(Player player, Sign sign) throws Exception { + Object tileEntitySign = toRawTileEntity(sign); + + Field playerWhoMayEdit = getFirstFieldOfType(tileEntitySign, UUID.class, Modifier.PUBLIC); + playerWhoMayEdit.setAccessible(true); + playerWhoMayEdit.set(tileEntitySign, player.getUniqueId()); + } + /** * Take a reflection-based guess to open the sign editor for common CraftBukkit implementations prior to Bukkit 1.18 *

@@ -244,11 +268,11 @@ private void openSignEditorWithReflection(Player player, Sign sign) throws Excep openSignMethod.invoke(entityPlayer, tileEntitySign); } - private Object toRawEntity(Entity entity) throws Exception { + private static Object toRawEntity(Entity entity) throws Exception { return getDeclaredMethodRecursive(entity.getClass(), "getHandle").invoke(entity); } - private Object toRawTileEntity(BlockState blockState) throws Exception { + private static Object toRawTileEntity(BlockState blockState) throws Exception { return getDeclaredMethodRecursive(blockState.getClass(), "getTileEntity").invoke(blockState); }