|
17 | 17 | import top.ellan.mahjong.runtime.ServerScheduler; |
18 | 18 | import top.ellan.mahjong.table.core.DefaultTableRuntimeServices; |
19 | 19 | import top.ellan.mahjong.table.core.MahjongTableManager; |
| 20 | +import java.lang.reflect.InvocationTargetException; |
| 21 | +import java.lang.reflect.Method; |
| 22 | +import java.lang.reflect.Proxy; |
| 23 | +import java.util.Collection; |
20 | 24 | import java.util.Locale; |
21 | 25 | import java.util.Objects; |
22 | 26 | import java.util.Set; |
@@ -80,14 +84,9 @@ public void onEnable() { |
80 | 84 | this::database, |
81 | 85 | this::reloadMahjongConfiguration |
82 | 86 | ); |
83 | | - PluginCommand command = this.getCommand("mahjong"); |
84 | | - if (command == null) { |
85 | | - this.getLogger().severe("MahjongPaper command is missing from plugin.yml; disabling plugin."); |
86 | | - this.getServer().getPluginManager().disablePlugin(this); |
| 87 | + if (!this.registerMahjongCommand(mahjongCommand)) { |
87 | 88 | return; |
88 | 89 | } |
89 | | - command.setExecutor(mahjongCommand); |
90 | | - command.setTabCompleter(mahjongCommand); |
91 | 90 |
|
92 | 91 | this.getServer().getPluginManager().registerEvents(this.tableManager, this); |
93 | 92 | this.scheduler.runGlobal(() -> { |
@@ -137,6 +136,113 @@ private void handleDatabaseStartupFailure(DatabaseService.InitializationExceptio |
137 | 136 | } |
138 | 137 | } |
139 | 138 |
|
| 139 | + private boolean registerMahjongCommand(MahjongCommand mahjongCommand) { |
| 140 | + PaperCommandRegistrationResult paperResult = this.registerPaperCommand(mahjongCommand); |
| 141 | + if (paperResult == PaperCommandRegistrationResult.REGISTERED) { |
| 142 | + return true; |
| 143 | + } |
| 144 | + if (paperResult == PaperCommandRegistrationResult.FAILED) { |
| 145 | + this.getServer().getPluginManager().disablePlugin(this); |
| 146 | + return false; |
| 147 | + } |
| 148 | + |
| 149 | + PluginCommand command = this.getCommand("mahjong"); |
| 150 | + if (command == null) { |
| 151 | + this.getLogger().severe("MahjongPaper command is missing from plugin.yml; disabling plugin."); |
| 152 | + this.getServer().getPluginManager().disablePlugin(this); |
| 153 | + return false; |
| 154 | + } |
| 155 | + command.setExecutor(mahjongCommand); |
| 156 | + command.setTabCompleter(mahjongCommand); |
| 157 | + return true; |
| 158 | + } |
| 159 | + |
| 160 | + private PaperCommandRegistrationResult registerPaperCommand(MahjongCommand mahjongCommand) { |
| 161 | + try { |
| 162 | + Class<?> lifecycleEventsClass = Class.forName("io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents"); |
| 163 | + Class<?> lifecycleEventTypeClass = Class.forName("io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType"); |
| 164 | + Class<?> lifecycleEventHandlerClass = Class.forName("io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler"); |
| 165 | + Class<?> lifecycleEventManagerClass = Class.forName("io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager"); |
| 166 | + Object commandsEventType = lifecycleEventsClass.getField("COMMANDS").get(null); |
| 167 | + Object lifecycleManager = this.getClass().getMethod("getLifecycleManager").invoke(this); |
| 168 | + Object handler = Proxy.newProxyInstance( |
| 169 | + lifecycleEventHandlerClass.getClassLoader(), |
| 170 | + new Class<?>[] { lifecycleEventHandlerClass }, |
| 171 | + (proxy, method, args) -> { |
| 172 | + if ("run".equals(method.getName()) && args != null && args.length == 1) { |
| 173 | + this.registerPaperCommandOnEvent(args[0], mahjongCommand); |
| 174 | + } |
| 175 | + return null; |
| 176 | + } |
| 177 | + ); |
| 178 | + |
| 179 | + Method registerEventHandler = lifecycleEventManagerClass.getMethod( |
| 180 | + "registerEventHandler", |
| 181 | + lifecycleEventTypeClass, |
| 182 | + lifecycleEventHandlerClass |
| 183 | + ); |
| 184 | + registerEventHandler.invoke(lifecycleManager, commandsEventType, handler); |
| 185 | + return PaperCommandRegistrationResult.REGISTERED; |
| 186 | + } catch (ClassNotFoundException | NoSuchMethodException ex) { |
| 187 | + return PaperCommandRegistrationResult.UNAVAILABLE; |
| 188 | + } catch (IllegalAccessException | InvocationTargetException | NoSuchFieldException ex) { |
| 189 | + this.getLogger().log(Level.SEVERE, "Failed to register MahjongPaper command through Paper lifecycle events.", ex); |
| 190 | + return PaperCommandRegistrationResult.FAILED; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + private void registerPaperCommandOnEvent(Object event, MahjongCommand mahjongCommand) throws ReflectiveOperationException { |
| 195 | + Object registrar = event.getClass().getMethod("registrar").invoke(event); |
| 196 | + Class<?> commandsClass = Class.forName("io.papermc.paper.command.brigadier.Commands"); |
| 197 | + Class<?> basicCommandClass = Class.forName("io.papermc.paper.command.brigadier.BasicCommand"); |
| 198 | + Object basicCommand = Proxy.newProxyInstance( |
| 199 | + basicCommandClass.getClassLoader(), |
| 200 | + new Class<?>[] { basicCommandClass }, |
| 201 | + (proxy, method, args) -> this.invokePaperBasicCommand(proxy, mahjongCommand, method, args) |
| 202 | + ); |
| 203 | + Method register = commandsClass.getMethod( |
| 204 | + "register", |
| 205 | + String.class, |
| 206 | + String.class, |
| 207 | + Collection.class, |
| 208 | + basicCommandClass |
| 209 | + ); |
| 210 | + register.invoke( |
| 211 | + registrar, |
| 212 | + "mahjong", |
| 213 | + "Manage MahjongPaper tables and rounds. Use /mahjong help for command explanations.", |
| 214 | + java.util.List.of(), |
| 215 | + basicCommand |
| 216 | + ); |
| 217 | + } |
| 218 | + |
| 219 | + private Object invokePaperBasicCommand(Object proxy, MahjongCommand mahjongCommand, Method method, Object[] args) throws ReflectiveOperationException { |
| 220 | + return switch (method.getName()) { |
| 221 | + case "execute" -> { |
| 222 | + mahjongCommand.onCommand(this.paperCommandSender(args), null, "mahjong", (String[]) args[1]); |
| 223 | + yield null; |
| 224 | + } |
| 225 | + case "suggest" -> mahjongCommand.onTabComplete(this.paperCommandSender(args), null, "mahjong", (String[]) args[1]); |
| 226 | + case "canUse" -> mahjongCommand.canUse((org.bukkit.command.CommandSender) args[0]); |
| 227 | + case "permission" -> mahjongCommand.permission(); |
| 228 | + case "toString" -> "MahjongPaperBasicCommand"; |
| 229 | + case "hashCode" -> System.identityHashCode(mahjongCommand); |
| 230 | + case "equals" -> args != null && args.length == 1 && args[0] == proxy; |
| 231 | + default -> null; |
| 232 | + }; |
| 233 | + } |
| 234 | + |
| 235 | + private org.bukkit.command.CommandSender paperCommandSender(Object[] args) throws ReflectiveOperationException { |
| 236 | + Object sourceStack = args[0]; |
| 237 | + return (org.bukkit.command.CommandSender) sourceStack.getClass().getMethod("getSender").invoke(sourceStack); |
| 238 | + } |
| 239 | + |
| 240 | + private enum PaperCommandRegistrationResult { |
| 241 | + REGISTERED, |
| 242 | + UNAVAILABLE, |
| 243 | + FAILED |
| 244 | + } |
| 245 | + |
140 | 246 | public String reloadMahjongConfiguration() { |
141 | 247 | this.reloadConfig(); |
142 | 248 |
|
|
0 commit comments