diff --git a/FIXED_UPSTREAM_ISSUES.md b/FIXED_UPSTREAM_ISSUES.md index 4bb31c879..041936ff9 100644 --- a/FIXED_UPSTREAM_ISSUES.md +++ b/FIXED_UPSTREAM_ISSUES.md @@ -13,4 +13,6 @@ - Simple Difficulty(And any other similar mods) thirst is not getting reset on player respawn[(Luohuayu/CatServer#536)](https://github.com/Luohuayu/CatServer/issues/536)[(MohistMC/Mohist#2905)](https://github.com/MohistMC/Mohist/issues/2905) - Ring dupe bug in The Betweenlands mod[(Luohuayu/CatServer#204)](https://github.com/Luohuayu/CatServer/issues/204) -**All fixes have been contributed to the upstream project.** \ No newline at end of file +## More + +- It is recommended to install [HybridFix](https://github.com/HaHaWTH/HybridFix), which aims to improve Forge+Bukkit compatibility and fix behaviour inconsistencies. \ No newline at end of file diff --git a/src/main/java/catserver/server/command/internal/CommandCatserver.java b/src/main/java/catserver/server/command/internal/CommandCatserver.java index 46b8e680b..d2f961f0d 100644 --- a/src/main/java/catserver/server/command/internal/CommandCatserver.java +++ b/src/main/java/catserver/server/command/internal/CommandCatserver.java @@ -13,15 +13,43 @@ import org.bukkit.craftbukkit.v1_12_R1.CraftServer; import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; +import org.spigotmc.SneakyThrow; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Locale; +import java.util.Set; public class CommandCatserver extends Command { public CommandCatserver(String name) { super(name); this.description = "CatServer related commands"; - this.usageMessage = "/catserver worlds|reload|reloadall|dumpitem"; // CatRoom - Dump item command + this.usageMessage = "/catserver worlds|reload|reloadall|dumpitem|dumplisteners"; setPermission("catserver.command.catserver"); } + private static final MethodHandle EVENT_TYPES_HANDLE; + + static { + try { + final Field eventTypesField = HandlerList.class.getDeclaredField("EVENT_TYPES"); + eventTypesField.setAccessible(true); + EVENT_TYPES_HANDLE = MethodHandles.lookup().unreflectGetter(eventTypesField); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + @Override public boolean execute(CommandSender sender, String commandLabel, String[] args) { if (!testPermission(sender)) return true; @@ -30,37 +58,52 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return false; } - if (args[0].equals("worlds")) { - sender.sendMessage(formatStringLength("Dim", 8) + " " + formatStringLength("Name", 8) + " " + formatStringLength("Type", 8)); - for (Integer dimension : DimensionManager.getStaticDimensionIDs()) { - World world = DimensionManager.getWorld(dimension, false); - String name = (world != null ? world.getWorld().getName() : "(Unload)"); - String type = DimensionManager.getProviderType(dimension).toString(); - sender.sendMessage(formatStringLength(String.valueOf(dimension), 8) + " " + formatStringLength(name, 8) + " " + formatStringLength(type, 8)); + switch (args[0].toLowerCase(Locale.ROOT)) { + case "worlds" -> { + sender.sendMessage(formatStringLength("Dim", 8) + " " + formatStringLength("Name", 8) + " " + formatStringLength("Type", 8)); + for (Integer dimension : DimensionManager.getStaticDimensionIDs()) { + World world = DimensionManager.getWorld(dimension, false); + String name = (world != null ? world.getWorld().getName() : "(Unload)"); + String type = DimensionManager.getProviderType(dimension).toString(); + sender.sendMessage(formatStringLength(String.valueOf(dimension), 8) + " " + formatStringLength(name, 8) + " " + formatStringLength(type, 8)); + } } - } else if (args[0].equals("reload")) { - CatServer.getConfig().loadConfig(); - sender.sendMessage(ChatColor.GREEN + "Configuration reload complete."); - } else if (args[0].equals("reloadall")) { - CatServer.getConfig().loadConfig(); - ((CraftServer) Bukkit.getServer()).reloadConfig(); - sender.sendMessage(ChatColor.GREEN + "Configuration reload complete."); - } else if (args[0].equals("dumpitem")) { // CatRoom start - Dump item command - if (!(sender instanceof Player player)) { - sender.sendMessage(ChatColor.RED + "Only players can use this command."); - return true; + case "reload" -> { + CatServer.getConfig().loadConfig(); + sender.sendMessage(ChatColor.GREEN + "Configuration reload complete."); } - var itemInHand = ((CraftPlayer) player).getHandle().getHeldItemMainhand(); - if (itemInHand.isEmpty()) { - sender.sendMessage(ChatColor.RED + "You are not holding any item."); - return true; + case "reloadall" -> { + CatServer.getConfig().loadConfig(); + ((CraftServer) Bukkit.getServer()).reloadConfig(); + sender.sendMessage(ChatColor.GREEN + "Configuration reload complete."); + } + case "dumpitem" -> { + if (!(sender instanceof Player player)) { + sender.sendMessage(ChatColor.RED + "Only players can use this command."); + return true; + } + var itemInHand = ((CraftPlayer) player).getHandle().getHeldItemMainhand(); + if (itemInHand.isEmpty()) { + sender.sendMessage(ChatColor.RED + "You are not holding any item."); + return true; + } + sender.sendMessage(ItemStackUtils.formatItemStackToPrettyString(itemInHand)); + TextComponent message = new TextComponent("[Click to insert give command]"); + message.setColor(net.md_5.bungee.api.ChatColor.GREEN); + message.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, ItemStackUtils.itemStackToGiveCommand(itemInHand))); + sender.spigot().sendMessage(message); + } + case "dumplisteners" -> { + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "Usage: /catserver dumplisteners tofile"); + return true; + } + if (args[1].equals("tofile")) { + this.dumpToFile(sender); + } else { + sender.sendMessage(ChatColor.RED + "Usage: /catserver dumplisteners tofile"); + } } - sender.sendMessage(ItemStackUtils.formatItemStackToPrettyString(itemInHand)); - TextComponent message = new TextComponent("[Click to insert give command]"); - message.setColor(net.md_5.bungee.api.ChatColor.GREEN); - message.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, ItemStackUtils.itemStackToGiveCommand(itemInHand))); - sender.spigot().sendMessage(message); - // CatRoom end - Dump item command } return true; @@ -73,4 +116,57 @@ private static String formatStringLength(String str, int size) { } return str; } + + private void dumpToFile(final CommandSender sender) { + final File file = new File("debug/listeners-" + + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); + file.getParentFile().mkdirs(); + try (final PrintWriter writer = new PrintWriter(file)) { + for (final String eventClass : eventClassNames()) { + final HandlerList handlers; + try { + handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null); + } catch (final ReflectiveOperationException e) { + continue; + } + if (handlers.getRegisteredListeners().length != 0) { + writer.println(eventClass); + } + for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) { + writer.println(" - " + registeredListener); + } + } + } catch (final IOException ex) { + throw new RuntimeException(ex); + } + sender.sendMessage(ChatColor.GREEN + "Dumped listeners to " + file); + } + + @SuppressWarnings("unchecked") + private static Set eventClassNames() { + try { + return (Set) EVENT_TYPES_HANDLE.invokeExact(); + } catch (final Throwable e) { + SneakyThrow.sneaky(e); + return Collections.emptySet(); // Unreachable + } + } + + private static Class findClass(final String className) throws ClassNotFoundException { + try { + return Class.forName(className); + } catch (final ClassNotFoundException ignore) { + for (final Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) { + if (!plugin.isEnabled()) { + continue; + } + + try { + return Class.forName(className, false, plugin.getClass().getClassLoader()); + } catch (final ClassNotFoundException ignore0) { + } + } + } + throw new ClassNotFoundException(className); + } } diff --git a/src/main/java/org/bukkit/event/HandlerList.java b/src/main/java/org/bukkit/event/HandlerList.java index 7d5efffbb..ea0fd274a 100644 --- a/src/main/java/org/bukkit/event/HandlerList.java +++ b/src/main/java/org/bukkit/event/HandlerList.java @@ -29,6 +29,13 @@ public class HandlerList { */ private static ArrayList allLists = new ArrayList(); + // CatRoom start + /** + * Event types which have instantiated a {@link HandlerList}. + */ + private static final java.util.Set EVENT_TYPES = java.util.concurrent.ConcurrentHashMap.newKeySet(); + // CatRoom end + /** * Bake all handler lists. Best used just after all normal event * registration is complete, ie just after all plugins are loaded if @@ -90,6 +97,12 @@ public static void unregisterAll(Listener listener) { * The HandlerList is then added to meta-list for use in bakeAll() */ public HandlerList() { + // CatRoom start + java.lang.StackWalker.getInstance(java.util.EnumSet.of(java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE), 4) + .walk(s -> s.filter(f -> Event.class.isAssignableFrom(f.getDeclaringClass())).findFirst()) + .map(f -> f.getDeclaringClass().getName()) + .ifPresent(EVENT_TYPES::add); + // CatRoom end handlerslots = new EnumMap>(EventPriority.class); for (EventPriority o : EventPriority.values()) { handlerslots.put(o, new ArrayList()); diff --git a/src/main/java/org/bukkit/plugin/RegisteredListener.java b/src/main/java/org/bukkit/plugin/RegisteredListener.java index 944f90881..7d02ce468 100644 --- a/src/main/java/org/bukkit/plugin/RegisteredListener.java +++ b/src/main/java/org/bukkit/plugin/RegisteredListener.java @@ -70,4 +70,17 @@ public void callEvent(final Event event) throws EventException { public boolean isIgnoringCancelled() { return ignoreCancelled; } + + // CatRoom start + @Override + public String toString() { + return "RegisteredListener{" + + "plugin=\"" + this.plugin.getName() + + "\", listener=\"" + this.listener + + "\", executor=\"" + this.executor + + "\", priority=\"" + this.priority.name() + " (" + this.priority.getSlot() + ")" + + "\", ignoringCancelled=" + this.ignoreCancelled + + "}"; + } + // CatRoom end } diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java index 1c61dd22b..32c4b65b6 100644 --- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -475,7 +475,7 @@ public void clearPlugins() { */ public void callEvent(Event event) { if (event.getHandlers().getRegisteredListeners().length == 0) return; // CatRoom - Skip event if no listeners - if (CatServer.getConfig().fakePlayerEventPass && event instanceof PlayerEvent && ((PlayerEvent) event).getPlayer() instanceof CraftFakePlayer) return; // CatServer + if (CatServer.getConfig().fakePlayerEventPass && event instanceof PlayerEvent playerEvent && playerEvent.getPlayer() instanceof CraftFakePlayer) return; // CatServer if (event.isAsynchronous() || !server.isPrimaryThread()) { // CatServer if (Thread.holdsLock(this)) { throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); @@ -558,7 +558,7 @@ public void registerEvent(Class event, Listener listener, Event throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); } - if (useTimings) { // Always false here + if (false) { // CatRoom - Disable Timings getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); } else { getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));