Skip to content

Commit 1ac5238

Browse files
committed
fix: support Paper 1.21.11 plugin command registration
1 parent 3f9a7f2 commit 1ac5238

2 files changed

Lines changed: 126 additions & 8 deletions

File tree

src/main/java/top/ellan/mahjong/bootstrap/MahjongPaperLoader.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ public void classloader(PluginClasspathBuilder classpathBuilder) {
1414
resolver.addRepository(new RemoteRepository.Builder(
1515
"central",
1616
"default",
17-
"https://repo.maven.apache.org/maven2/"
17+
this.mavenCentralRepositoryUrl()
1818
).build());
1919

2020
this.addDependency(resolver, "io.github.ssttkkl:mahjong-utils-jvm:0.7.7");
2121
this.addDependency(resolver, "org.mariadb.jdbc:mariadb-java-client:3.5.9");
2222
this.addDependency(resolver, "com.mysql:mysql-connector-j:9.7.0");
2323
this.addDependency(resolver, "com.h2database:h2:2.4.240");
24-
this.addDependency(resolver, "com.zaxxer:HikariCP:7.0.2");
24+
this.addDependency(resolver, "com.zaxxer:HikariCP:7.1.0");
2525
this.addDependency(resolver, "org.jetbrains.kotlin:kotlin-stdlib:2.4.0");
2626
this.addDependency(resolver, "org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0");
2727

@@ -31,5 +31,17 @@ public void classloader(PluginClasspathBuilder classpathBuilder) {
3131
private void addDependency(MavenLibraryResolver resolver, String coordinates) {
3232
resolver.addDependency(new Dependency(new DefaultArtifact(coordinates), null));
3333
}
34+
35+
private String mavenCentralRepositoryUrl() {
36+
try {
37+
Object mirror = MavenLibraryResolver.class.getField("MAVEN_CENTRAL_DEFAULT_MIRROR").get(null);
38+
if (mirror instanceof String url && !url.isBlank()) {
39+
return url;
40+
}
41+
} catch (ReflectiveOperationException ignored) {
42+
// Older Paper versions do not expose the mirror constant.
43+
}
44+
return "https://repo.maven.apache.org/maven2/";
45+
}
3446
}
3547

src/main/java/top/ellan/mahjong/bootstrap/MahjongPaperPlugin.java

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
import top.ellan.mahjong.runtime.ServerScheduler;
1818
import top.ellan.mahjong.table.core.DefaultTableRuntimeServices;
1919
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;
2024
import java.util.Locale;
2125
import java.util.Objects;
2226
import java.util.Set;
@@ -80,14 +84,9 @@ public void onEnable() {
8084
this::database,
8185
this::reloadMahjongConfiguration
8286
);
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)) {
8788
return;
8889
}
89-
command.setExecutor(mahjongCommand);
90-
command.setTabCompleter(mahjongCommand);
9190

9291
this.getServer().getPluginManager().registerEvents(this.tableManager, this);
9392
this.scheduler.runGlobal(() -> {
@@ -137,6 +136,113 @@ private void handleDatabaseStartupFailure(DatabaseService.InitializationExceptio
137136
}
138137
}
139138

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+
140246
public String reloadMahjongConfiguration() {
141247
this.reloadConfig();
142248

0 commit comments

Comments
 (0)