Last active
April 22, 2023 23:40
-
-
Save LOOHP/550f861ab4ad219cf74dc18c214ba530 to your computer and use it in GitHub Desktop.
Source code of EditableSigns (https://www.spigotmc.org/resources/93850/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.loohp.editablesigns; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.UUID; | |
import java.util.function.Consumer; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import org.bukkit.Bukkit; | |
import org.bukkit.Location; | |
import org.bukkit.Material; | |
import org.bukkit.block.Block; | |
import org.bukkit.block.BlockFace; | |
import org.bukkit.block.Sign; | |
import org.bukkit.block.data.BlockData; | |
import org.bukkit.block.data.Directional; | |
import org.bukkit.command.Command; | |
import org.bukkit.command.CommandSender; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.EventPriority; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.block.Action; | |
import org.bukkit.event.block.BlockPlaceEvent; | |
import org.bukkit.event.block.SignChangeEvent; | |
import org.bukkit.event.player.PlayerInteractEvent; | |
import org.bukkit.inventory.EquipmentSlot; | |
import org.bukkit.inventory.ItemStack; | |
import org.bukkit.plugin.Plugin; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import net.md_5.bungee.api.ChatColor; | |
public class EditableSigns extends JavaPlugin implements Listener { | |
public static final Pattern REVERT_COLOR_CODE_PATTERN = Pattern.compile("\u00a7[0-9A-Fa-fk-or]|\u00a7x(?:\u00a7[0-9A-Fa-f]){6}"); | |
private static final boolean FOLIA = classExist("io.papermc.paper.threadedregions.scheduler.AsyncScheduler"); | |
public static EditableSigns plugin; | |
public static boolean handleLegacyColorCodes = true; | |
public static String reloadPluginMessage = ""; | |
public static String noPermissionMessage = ""; | |
private static boolean useBukkitOpenSign; | |
private static Constructor<?> nmsBlockPositionConstructor; | |
private static Constructor<?> nmsPacketPlayOutOpenSignEditorConstructor; | |
private static Class<?> craftWorldClass; | |
private static Method craftWorldGetHandleMethod; | |
private static boolean post118GetBlockEntity; | |
private static Method getTileEntityMethod; | |
private static Class<?> nmsTileEntitySignClass; | |
private static Class<?> nmsEntityHumanClass; | |
private static boolean tileEntitySignUsesModernMethods; | |
private static Field nmsTileEntitySignIsEditableField; | |
private static Field nmsTileEntitySignEntityHumanField; | |
private static Method nmsTileEntitySignSetEditableMethod; | |
private static Method nmsTileEntitySignSetUUIDMethod; | |
private static Class<?> craftPlayerClass; | |
private static Method craftPlayerGetHandleMethod; | |
private static Field playerConnectionField; | |
private static Class<?> nmsPacketClass; | |
private static Method sendPacketMethod; | |
private static boolean classExist(String className) { | |
try { | |
Class.forName(className); | |
return true; | |
} catch (ClassNotFoundException e) { | |
return false; | |
} | |
} | |
public static void runTaskLater(Plugin plugin, Location location, Runnable runnable, int delay) { | |
if (FOLIA) { | |
//noinspection rawtypes,unchecked | |
Bukkit.getRegionScheduler().runDelayed(plugin, location, (Consumer) st -> runnable.run(), delay); | |
} else { | |
Bukkit.getScheduler().runTaskLater(plugin, runnable, delay); | |
} | |
} | |
@Override | |
public void onEnable() { | |
plugin = this; | |
getConfig().options().copyDefaults(true); | |
saveConfig(); | |
loadConfig(); | |
try { | |
if (Arrays.stream(Player.class.getMethods()).noneMatch(each -> each.getName().equals("openSign"))) { | |
useBukkitOpenSign = false; | |
Class<?> nmsPacketPlayOutOpenSignEditorClass = getNMSClass("net.minecraft.server.%s.PacketPlayOutOpenSignEditor", "net.minecraft.network.protocol.game.PacketPlayOutOpenSignEditor"); | |
Class<?> nmsBlockPositionClass = getNMSClass("net.minecraft.server.%s.BlockPosition", "net.minecraft.core.BlockPosition"); | |
nmsBlockPositionConstructor = nmsBlockPositionClass.getConstructor(int.class, int.class, int.class); | |
nmsPacketPlayOutOpenSignEditorConstructor = nmsPacketPlayOutOpenSignEditorClass.getConstructor(nmsBlockPositionClass); | |
craftWorldClass = getNMSClass("org.bukkit.craftbukkit.%s.CraftWorld"); | |
craftWorldGetHandleMethod = craftWorldClass.getMethod("getHandle"); | |
post118GetBlockEntity = false; | |
try { | |
getTileEntityMethod = craftWorldGetHandleMethod.getReturnType().getMethod("getTileEntity", nmsBlockPositionClass); | |
} catch (Exception e) { | |
getTileEntityMethod = craftWorldGetHandleMethod.getReturnType().getMethod("getBlockEntity", nmsBlockPositionClass, boolean.class); | |
post118GetBlockEntity = true; | |
} | |
nmsTileEntitySignClass = getNMSClass("net.minecraft.server.%s.TileEntitySign", "net.minecraft.world.level.block.entity.TileEntitySign"); | |
nmsEntityHumanClass = getNMSClass("net.minecraft.server.%s.EntityHuman", "net.minecraft.world.entity.player.EntityHuman"); | |
tileEntitySignUsesModernMethods = false; | |
try { | |
nmsTileEntitySignIsEditableField = nmsTileEntitySignClass.getDeclaredField("isEditable"); | |
nmsTileEntitySignEntityHumanField = Arrays.asList(nmsTileEntitySignClass.getDeclaredFields()).stream().filter(each -> each.getType().equals(nmsEntityHumanClass)).findFirst().get(); | |
} catch (NoSuchFieldException e) { | |
nmsTileEntitySignSetEditableMethod = nmsTileEntitySignClass.getMethod("a", boolean.class); | |
nmsTileEntitySignSetUUIDMethod = nmsTileEntitySignClass.getMethod("a", UUID.class); | |
tileEntitySignUsesModernMethods = true; | |
} | |
craftPlayerClass = getNMSClass("org.bukkit.craftbukkit.%s.entity.CraftPlayer"); | |
craftPlayerGetHandleMethod = craftPlayerClass.getMethod("getHandle"); | |
try { | |
playerConnectionField = craftPlayerGetHandleMethod.getReturnType().getField("playerConnection"); | |
} catch (NoSuchFieldException e) { | |
playerConnectionField = craftPlayerGetHandleMethod.getReturnType().getField("b"); | |
} | |
nmsPacketClass = getNMSClass("net.minecraft.server.%s.Packet", "net.minecraft.network.protocol.Packet"); | |
try { | |
sendPacketMethod = playerConnectionField.getType().getMethod("sendPacket", nmsPacketClass); | |
} catch (NoSuchMethodException e) { | |
sendPacketMethod = playerConnectionField.getType().getMethod("a", nmsPacketClass); | |
} | |
} else { | |
useBukkitOpenSign = true; | |
} | |
} catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException | SecurityException e) { | |
e.printStackTrace(); | |
} | |
getCommand("editablesigns").setExecutor(this); | |
getServer().getPluginManager().registerEvents(this, this); | |
getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "[EditableSigns] EditableSigns has been enabled."); | |
} | |
@Override | |
public void onDisable() { | |
getServer().getConsoleSender().sendMessage(ChatColor.RED + "[EditableSigns] EditableSigns has been disabled."); | |
} | |
@Override | |
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { | |
if (args.length == 0) { | |
sender.sendMessage(ChatColor.WHITE + "EditableSigns written by LOOHP!"); | |
sender.sendMessage(ChatColor.GOLD + "You are running EditableSigns version: " + getDescription().getVersion()); | |
return true; | |
} | |
if (args[0].equalsIgnoreCase("reload")) { | |
if (sender.hasPermission("editablesigns.reload")) { | |
reloadConfig(); | |
sender.sendMessage(reloadPluginMessage); | |
} else { | |
sender.sendMessage(noPermissionMessage); | |
} | |
return true; | |
} | |
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', Bukkit.spigot().getConfig().getString("messages.unknown-command"))); | |
return true; | |
} | |
@Override | |
public List<String> onTabComplete(CommandSender sender, Command cmd, String label, String[] args) { | |
List<String> tab = new ArrayList<>(); | |
switch (args.length) { | |
case 0: | |
if (sender.hasPermission("editablesigns.reload")) { | |
tab.add("reload"); | |
} | |
return tab; | |
case 1: | |
if (sender.hasPermission("editablesigns.reload")) { | |
if ("reload".startsWith(args[0].toLowerCase())) { | |
tab.add("reload"); | |
} | |
} | |
return tab; | |
} | |
return tab; | |
} | |
public void loadConfig() { | |
reloadConfig(); | |
handleLegacyColorCodes = getConfig().getBoolean("HandleLegacyColorCodes"); | |
reloadPluginMessage = ChatColor.translateAlternateColorCodes('&', getConfig().getString("Messages.Reload")); | |
noPermissionMessage = ChatColor.translateAlternateColorCodes('&', getConfig().getString("Messages.NoPermission")); | |
} | |
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | |
public void onInteract(PlayerInteractEvent event) { | |
Player player = event.getPlayer(); | |
if (!player.hasPermission("editablesigns.use")) { | |
return; | |
} | |
Block block = event.getClickedBlock(); | |
if (block != null && block.getType().toString().contains("SIGN") && !player.isSneaking() && event.getAction().equals(Action.RIGHT_CLICK_BLOCK) && isInteractingWithAir(player)) { | |
Material material = block.getType(); | |
Block attachedBlock; | |
BlockData data = block.getBlockData(); | |
if (data instanceof Directional) { | |
Directional directional = (Directional) data; | |
attachedBlock = block.getRelative(directional.getFacing().getOppositeFace()); | |
} else { | |
attachedBlock = block.getRelative(BlockFace.DOWN); | |
} | |
BlockPlaceEvent placeEvent = new BlockPlaceEvent(block, block.getState(), attachedBlock, new ItemStack(material), player, true, EquipmentSlot.HAND); | |
Bukkit.getPluginManager().callEvent(placeEvent); | |
if (!placeEvent.isCancelled()) { | |
Sign sign = (Sign) block.getState(); | |
if (handleLegacyColorCodes) { | |
String[] lines = sign.getLines(); | |
for (int i = 0; i < lines.length; i++) { | |
sign.setLine(i, revertColorCode('&', lines[i])); | |
} | |
sign.update(); | |
} | |
runTaskLater(this, block.getLocation(), () -> { | |
if (useBukkitOpenSign) { | |
sign.setEditable(true); | |
sign.update(); | |
runTaskLater(this, block.getLocation(), () -> player.openSign(sign), 1); | |
} else { | |
try { | |
Object entityPlayer = craftPlayerGetHandleMethod.invoke(craftPlayerClass.cast(player)); | |
Object blockPosition = nmsBlockPositionConstructor.newInstance(block.getX(), block.getY(), block.getZ()); | |
Object tileEntity; | |
if (post118GetBlockEntity) { | |
tileEntity = getTileEntityMethod.invoke(craftWorldGetHandleMethod.invoke(craftWorldClass.cast(block.getWorld())), blockPosition, true); | |
} else { | |
tileEntity = getTileEntityMethod.invoke(craftWorldGetHandleMethod.invoke(craftWorldClass.cast(block.getWorld())), blockPosition); | |
} | |
Object nmsSign = nmsTileEntitySignClass.cast(tileEntity); | |
if (tileEntitySignUsesModernMethods) { | |
nmsTileEntitySignSetEditableMethod.invoke(nmsSign, true); | |
nmsTileEntitySignSetUUIDMethod.invoke(nmsSign, player.getUniqueId()); | |
} else { | |
nmsTileEntitySignIsEditableField.setAccessible(true); | |
nmsTileEntitySignIsEditableField.set(nmsSign, true); | |
nmsTileEntitySignEntityHumanField.setAccessible(true); | |
nmsTileEntitySignEntityHumanField.set(nmsSign, nmsEntityHumanClass.cast(entityPlayer)); | |
} | |
Object packet = nmsPacketPlayOutOpenSignEditorConstructor.newInstance(blockPosition); | |
sendPacketMethod.invoke(playerConnectionField.get(entityPlayer), packet); | |
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | | |
InvocationTargetException e) { | |
e.printStackTrace(); | |
} | |
} | |
}, 2); | |
} | |
} | |
} | |
@EventHandler(priority = EventPriority.NORMAL) | |
public void onSignChange(SignChangeEvent event) { | |
if (!handleLegacyColorCodes) { | |
return; | |
} | |
String[] lines = event.getLines(); | |
for (int i = 0; i < lines.length; i++) { | |
event.setLine(i, ChatColor.translateAlternateColorCodes('&', lines[i])); | |
} | |
} | |
private String revertColorCode(char code, String str) { | |
Matcher matcher = REVERT_COLOR_CODE_PATTERN.matcher(str); | |
StringBuffer sb = new StringBuffer(str.length()); | |
while (matcher.find()) { | |
matcher.appendReplacement(sb, matcher.group().replace('\u00a7', code)); | |
} | |
matcher.appendTail(sb); | |
return sb.toString(); | |
} | |
private boolean isInteractingWithAir(Player player) { | |
try { | |
ItemStack mainHand = player.getEquipment().getItemInMainHand(); | |
ItemStack offHand = player.getEquipment().getItemInOffHand(); | |
return (mainHand == null || mainHand.getType().equals(Material.AIR)) && (offHand == null || !offHand.getType().toString().contains("DYE")); | |
} catch (Throwable e) { | |
@SuppressWarnings("deprecation") | |
ItemStack item = player.getItemInHand(); | |
return item == null || item.getType().equals(Material.AIR); | |
} | |
} | |
private static Class<?> getNMSClass(String path, String... paths) throws ClassNotFoundException { | |
String version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3]; | |
ClassNotFoundException error = null; | |
try { | |
return Class.forName(path.replace("%s", version)); | |
} catch (ClassNotFoundException e) { | |
error = e; | |
} | |
for (String classpath : paths) { | |
try { | |
return Class.forName(classpath.replace("%s", version)); | |
} catch (ClassNotFoundException e) { | |
error = e; | |
} | |
} | |
throw error; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment