Skip to content

Commit

Permalink
Workaround for SPIGOT-7391 using reflection on TileEntitySign
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Deltik committed Jul 16, 2023
1 parent cdf42bd commit 2a8b34e
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 26 additions & 2 deletions src/net/deltik/mc/signedit/interactions/UiSignEditInteraction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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<Method> optionalMethod = Arrays.stream(Player.class.getDeclaredMethods())
.filter(method -> method.getName().equals("openSign") && method.getParameterCount() == 2)
.filter(method -> {
Expand All @@ -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
* <p>
* Bug report: <a href="https://hub.spigotmc.org/jira/browse/SPIGOT-7391">SPIGOT-7391</a>
* <p>
* 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
* <p>
Expand All @@ -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);
}

Expand Down

0 comments on commit 2a8b34e

Please sign in to comment.