feat: commandSender implementation
This commit is contained in:
parent
288c45537c
commit
34af0e46a6
@ -13,6 +13,11 @@ dependencies {
|
|||||||
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
|
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
|
||||||
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
||||||
implementation("io.github.astonbitecode:j4rs:0.24.0")
|
implementation("io.github.astonbitecode:j4rs:0.24.0")
|
||||||
|
implementation("org.apache.maven:maven-resolver-provider:3.9.6")
|
||||||
|
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
|
||||||
|
implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
|
||||||
|
implementation("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
|
||||||
|
implementation("org.apache.maven.resolver:maven-resolver-util:1.9.18")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|||||||
@ -0,0 +1,131 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
|
||||||
|
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||||
|
import org.eclipse.aether.RepositorySystem;
|
||||||
|
import org.eclipse.aether.RepositorySystemSession;
|
||||||
|
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||||
|
import org.eclipse.aether.collection.CollectRequest;
|
||||||
|
import org.eclipse.aether.graph.Dependency;
|
||||||
|
import org.eclipse.aether.repository.LocalRepository;
|
||||||
|
import org.eclipse.aether.repository.RemoteRepository;
|
||||||
|
import org.eclipse.aether.resolution.DependencyRequest;
|
||||||
|
import org.eclipse.aether.resolution.DependencyResult;
|
||||||
|
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
|
||||||
|
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||||
|
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||||
|
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||||
|
|
||||||
|
public final class LibraryResolver {
|
||||||
|
private LibraryResolver() {}
|
||||||
|
|
||||||
|
public static List<File> resolveLibraries(
|
||||||
|
String coordinates,
|
||||||
|
File baseDir
|
||||||
|
) {
|
||||||
|
if (coordinates == null || coordinates.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> unique = new LinkedHashSet<>();
|
||||||
|
for (String line : coordinates.split("\n")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
unique.add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unique.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseDir.exists()) {
|
||||||
|
baseDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
RepositorySystem system = newRepositorySystem();
|
||||||
|
RepositorySystemSession session = newSession(system, baseDir);
|
||||||
|
List<RemoteRepository> repos = List.of(
|
||||||
|
new RemoteRepository.Builder(
|
||||||
|
"papermc",
|
||||||
|
"default",
|
||||||
|
"https://repo.papermc.io/repository/maven-public/"
|
||||||
|
).build(),
|
||||||
|
new RemoteRepository.Builder(
|
||||||
|
"central",
|
||||||
|
"default",
|
||||||
|
"https://repo1.maven.org/maven2/"
|
||||||
|
).build()
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<File> files = new LinkedHashSet<>();
|
||||||
|
for (String coord : unique) {
|
||||||
|
try {
|
||||||
|
Dependency dependency = new Dependency(
|
||||||
|
new DefaultArtifact(coord),
|
||||||
|
"runtime"
|
||||||
|
);
|
||||||
|
CollectRequest collectRequest = new CollectRequest(
|
||||||
|
dependency,
|
||||||
|
repos
|
||||||
|
);
|
||||||
|
DependencyRequest request = new DependencyRequest(
|
||||||
|
collectRequest,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
DependencyResult result = system.resolveDependencies(
|
||||||
|
session,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
result
|
||||||
|
.getArtifactResults()
|
||||||
|
.forEach(artifactResult -> {
|
||||||
|
File file = artifactResult.getArtifact().getFile();
|
||||||
|
if (file != null && file.exists()) {
|
||||||
|
files.add(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(
|
||||||
|
"[PatchBukkit] Failed to resolve library " +
|
||||||
|
coord +
|
||||||
|
": " +
|
||||||
|
e.getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RepositorySystem newRepositorySystem() {
|
||||||
|
var locator = MavenRepositorySystemUtils.newServiceLocator();
|
||||||
|
locator.addService(
|
||||||
|
RepositoryConnectorFactory.class,
|
||||||
|
BasicRepositoryConnectorFactory.class
|
||||||
|
);
|
||||||
|
locator.addService(
|
||||||
|
TransporterFactory.class,
|
||||||
|
HttpTransporterFactory.class
|
||||||
|
);
|
||||||
|
return locator.getService(RepositorySystem.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RepositorySystemSession newSession(
|
||||||
|
RepositorySystem system,
|
||||||
|
File baseDir
|
||||||
|
) {
|
||||||
|
DefaultRepositorySystemSession session =
|
||||||
|
MavenRepositorySystemUtils.newSession();
|
||||||
|
LocalRepository localRepo = new LocalRepository(baseDir);
|
||||||
|
session.setLocalRepositoryManager(
|
||||||
|
system.newLocalRepositoryManager(session, localRepo)
|
||||||
|
);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,8 @@ import java.io.InputStream;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import org.bukkit.plugin.InvalidDescriptionException;
|
import org.bukkit.plugin.InvalidDescriptionException;
|
||||||
@ -32,12 +34,34 @@ public class PatchBukkitPluginClassLoader
|
|||||||
|
|
||||||
public PatchBukkitPluginClassLoader(ClassLoader parent, File file)
|
public PatchBukkitPluginClassLoader(ClassLoader parent, File file)
|
||||||
throws MalformedURLException, InvalidDescriptionException {
|
throws MalformedURLException, InvalidDescriptionException {
|
||||||
super(new URL[] { file.toURI().toURL() }, parent);
|
this(parent, file, new URL[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchBukkitPluginClassLoader(
|
||||||
|
ClassLoader parent,
|
||||||
|
File file,
|
||||||
|
URL[] extraUrls
|
||||||
|
) throws MalformedURLException, InvalidDescriptionException {
|
||||||
|
super(buildUrls(file, extraUrls), parent);
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.description = loadDescription(file);
|
this.description = loadDescription(file);
|
||||||
this.dataFolder = new File(file.getParentFile(), description.getName());
|
this.dataFolder = new File(file.getParentFile(), description.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static URL[] buildUrls(File file, URL[] extraUrls)
|
||||||
|
throws MalformedURLException {
|
||||||
|
List<URL> urls = new ArrayList<>();
|
||||||
|
urls.add(file.toURI().toURL());
|
||||||
|
if (extraUrls != null) {
|
||||||
|
for (URL url : extraUrls) {
|
||||||
|
if (url != null) {
|
||||||
|
urls.add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls.toArray(new URL[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private static PluginDescriptionFile loadDescription(File file)
|
private static PluginDescriptionFile loadDescription(File file)
|
||||||
throws InvalidDescriptionException {
|
throws InvalidDescriptionException {
|
||||||
try (JarFile jar = new JarFile(file)) {
|
try (JarFile jar = new JarFile(file)) {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
package org.patchbukkit.loader;
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -19,7 +22,12 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
@SuppressWarnings({ "deprecation", "removal" })
|
@SuppressWarnings({ "deprecation", "removal" })
|
||||||
public class PatchBukkitPluginLoader implements PluginLoader {
|
public class PatchBukkitPluginLoader implements PluginLoader {
|
||||||
|
|
||||||
public static JavaPlugin createPlugin(String jarPath, String mainClass) {
|
public static JavaPlugin createPlugin(
|
||||||
|
String jarPath,
|
||||||
|
String mainClass,
|
||||||
|
String extraClasspath,
|
||||||
|
String libraryCoordinates
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
File jarFile = new File(jarPath);
|
File jarFile = new File(jarPath);
|
||||||
if (!jarFile.exists()) {
|
if (!jarFile.exists()) {
|
||||||
@ -29,10 +37,41 @@ public class PatchBukkitPluginLoader implements PluginLoader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LinkedHashSet<URL> extraUrls = new LinkedHashSet<>();
|
||||||
|
if (extraClasspath != null && !extraClasspath.isBlank()) {
|
||||||
|
String[] paths = extraClasspath.split(File.pathSeparator);
|
||||||
|
for (String path : paths) {
|
||||||
|
if (path == null || path.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File extraFile = new File(path.trim());
|
||||||
|
if (extraFile.exists()) {
|
||||||
|
extraUrls.add(extraFile.toURI().toURL());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libraryCoordinates != null && !libraryCoordinates.isBlank()) {
|
||||||
|
File libsDir = new File(jarFile.getParentFile(), "patchbukkit-libs");
|
||||||
|
if (!libsDir.exists()) {
|
||||||
|
libsDir.mkdirs();
|
||||||
|
}
|
||||||
|
List<File> libraries = LibraryResolver.resolveLibraries(
|
||||||
|
libraryCoordinates,
|
||||||
|
libsDir
|
||||||
|
);
|
||||||
|
for (File lib : libraries) {
|
||||||
|
if (lib != null && lib.exists()) {
|
||||||
|
extraUrls.add(lib.toURI().toURL());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PatchBukkitPluginClassLoader classLoader =
|
PatchBukkitPluginClassLoader classLoader =
|
||||||
new PatchBukkitPluginClassLoader(
|
new PatchBukkitPluginClassLoader(
|
||||||
PatchBukkitPluginLoader.class.getClassLoader(),
|
PatchBukkitPluginLoader.class.getClassLoader(),
|
||||||
jarFile
|
jarFile,
|
||||||
|
extraUrls.toArray(new URL[0])
|
||||||
);
|
);
|
||||||
|
|
||||||
Class<?> jarClass = Class.forName(mainClass, true, classLoader);
|
Class<?> jarClass = Class.forName(mainClass, true, classLoader);
|
||||||
|
|||||||
@ -38,9 +38,13 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
public void registerInterface(@NotNull Class<? extends PluginLoader> loader)
|
public void registerInterface(@NotNull Class<? extends PluginLoader> loader)
|
||||||
throws IllegalArgumentException {}
|
throws IllegalArgumentException {}
|
||||||
|
|
||||||
|
public void registerPlugin(@NotNull Plugin plugin) {
|
||||||
|
plugins.put(plugin.getName().toLowerCase(), plugin);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Plugin getPlugin(@NotNull String name) {
|
public @Nullable Plugin getPlugin(@NotNull String name) {
|
||||||
return plugins.get(name);
|
return plugins.get(name.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -97,6 +97,8 @@ public class PatchBukkitServer implements Server {
|
|||||||
private final String bukkitVersion = Versioning.getBukkitVersion();
|
private final String bukkitVersion = Versioning.getBukkitVersion();
|
||||||
private final CommandMap commandMap = new PatchBukkitCommandMap();
|
private final CommandMap commandMap = new PatchBukkitCommandMap();
|
||||||
private final BukkitScheduler scheduler = new PatchBukkitScheduler();
|
private final BukkitScheduler scheduler = new PatchBukkitScheduler();
|
||||||
|
private final PatchBukkitPluginManager pluginManager =
|
||||||
|
new PatchBukkitPluginManager(this);
|
||||||
|
|
||||||
|
|
||||||
private final Map<UUID, Player> onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
private final Map<UUID, Player> onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
@ -122,6 +124,10 @@ public class PatchBukkitServer implements Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerPlugin(@NotNull Plugin plugin) {
|
||||||
|
this.pluginManager.registerPlugin(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPluginMessage(
|
public void sendPluginMessage(
|
||||||
@NotNull Plugin source,
|
@NotNull Plugin source,
|
||||||
@ -457,7 +463,7 @@ public class PatchBukkitServer implements Server {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull PluginManager getPluginManager() {
|
public @NotNull PluginManager getPluginManager() {
|
||||||
return new PatchBukkitPluginManager(this);
|
return this.pluginManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -0,0 +1,180 @@
|
|||||||
|
package org.patchbukkit.command;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.permissions.PermissibleBase;
|
||||||
|
import org.bukkit.permissions.Permission;
|
||||||
|
import org.bukkit.permissions.PermissionAttachment;
|
||||||
|
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||||
|
import org.bukkit.permissions.ServerOperator;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
|
||||||
|
public class PatchBukkitCommandSender implements CommandSender, ServerOperator {
|
||||||
|
|
||||||
|
private final Server server;
|
||||||
|
private final String name;
|
||||||
|
private final PermissibleBase perm;
|
||||||
|
private boolean op;
|
||||||
|
|
||||||
|
private final Spigot spigot = new Spigot() {
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@NotNull BaseComponent component) {
|
||||||
|
PatchBukkitCommandSender.this.sendMessage(BaseComponent.toLegacyText(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@NotNull BaseComponent... components) {
|
||||||
|
PatchBukkitCommandSender.this.sendMessage(BaseComponent.toLegacyText(components));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@NotNull UUID sender, @NotNull BaseComponent component) {
|
||||||
|
PatchBukkitCommandSender.this.sendMessage(sender, BaseComponent.toLegacyText(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@NotNull UUID sender, @NotNull BaseComponent... components) {
|
||||||
|
PatchBukkitCommandSender.this.sendMessage(sender, BaseComponent.toLegacyText(components));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public PatchBukkitCommandSender() {
|
||||||
|
this(Bukkit.getServer(), "CommandSender");
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchBukkitCommandSender(@Nullable Server server, @Nullable String name) {
|
||||||
|
this.server = server != null ? server : Bukkit.getServer();
|
||||||
|
this.name = name != null ? name : "CommandSender";
|
||||||
|
this.op = false;
|
||||||
|
this.perm = new PermissibleBase(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPermissionSet(@NotNull String name) {
|
||||||
|
return this.perm.isPermissionSet(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPermissionSet(@NotNull Permission perm) {
|
||||||
|
return this.perm.isPermissionSet(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(@NotNull String name) {
|
||||||
|
return this.perm.hasPermission(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(@NotNull Permission perm) {
|
||||||
|
return this.perm.hasPermission(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) {
|
||||||
|
return this.perm.addAttachment(plugin, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) {
|
||||||
|
return this.perm.addAttachment(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value,
|
||||||
|
int ticks) {
|
||||||
|
return this.perm.addAttachment(plugin, name, value, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) {
|
||||||
|
return this.perm.addAttachment(plugin, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttachment(@NotNull PermissionAttachment attachment) {
|
||||||
|
this.perm.removeAttachment(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recalculatePermissions() {
|
||||||
|
this.perm.recalculatePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||||
|
return this.perm.getEffectivePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOp() {
|
||||||
|
return this.op;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOp(boolean value) {
|
||||||
|
this.op = value;
|
||||||
|
this.perm.recalculatePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@NotNull String message) {
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.server != null && this.server.getLogger() != null) {
|
||||||
|
this.server.getLogger().info(message);
|
||||||
|
} else {
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@NotNull String... messages) {
|
||||||
|
if (messages == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String message : messages) {
|
||||||
|
this.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@Nullable UUID sender, @NotNull String message) {
|
||||||
|
this.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(@Nullable UUID sender, @NotNull String... messages) {
|
||||||
|
this.sendMessage(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Server getServer() {
|
||||||
|
return this.server != null ? this.server : Bukkit.getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Spigot spigot() {
|
||||||
|
return this.spigot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Component name() {
|
||||||
|
return Component.text(this.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,35 +12,40 @@ import org.bukkit.permissions.Permission;
|
|||||||
import org.bukkit.permissions.PermissionAttachment;
|
import org.bukkit.permissions.PermissionAttachment;
|
||||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.checkerframework.checker.units.qual.m;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.patchbukkit.PatchBukkitServer;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
public class PatchBukkitConsoleCommandSender implements ConsoleCommandSender {
|
public class PatchBukkitConsoleCommandSender
|
||||||
|
extends PatchBukkitCommandSender
|
||||||
|
implements ConsoleCommandSender
|
||||||
|
{
|
||||||
|
|
||||||
|
private @Nullable Conversation conversation;
|
||||||
|
|
||||||
|
public PatchBukkitConsoleCommandSender() {
|
||||||
|
super(Bukkit.getServer(), "CONSOLE");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@NotNull String message) {
|
public void sendMessage(@NotNull String message) {
|
||||||
System.out.println(message);
|
super.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@NotNull String... messages) {
|
public void sendMessage(@NotNull String... messages) {
|
||||||
System.out.println(messages);
|
super.sendMessage(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@Nullable UUID sender, @NotNull String message) {
|
public void sendMessage(@Nullable UUID sender, @NotNull String message) {
|
||||||
// TODO Auto-generated method stub
|
super.sendMessage(sender, message);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@Nullable UUID sender, @NotNull String... messages) {
|
public void sendMessage(@Nullable UUID sender, @NotNull String... messages) {
|
||||||
// TODO Auto-generated method stub
|
super.sendMessage(sender, messages);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,85 +55,73 @@ public class PatchBukkitConsoleCommandSender implements ConsoleCommandSender {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getName() {
|
public @NotNull String getName() {
|
||||||
return "CONSOLE";
|
return super.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Spigot spigot() {
|
public @NotNull Spigot spigot() {
|
||||||
// TODO Auto-generated method stub
|
return super.spigot();
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'spigot'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Component name() {
|
public @NotNull Component name() {
|
||||||
return Component.text("CONSOLE");
|
return Component.text(getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPermissionSet(@NotNull String name) {
|
public boolean isPermissionSet(@NotNull String name) {
|
||||||
// TODO Auto-generated method stub
|
return super.isPermissionSet(name);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPermissionSet(@NotNull Permission perm) {
|
public boolean isPermissionSet(@NotNull Permission perm) {
|
||||||
// TODO Auto-generated method stub
|
return super.isPermissionSet(perm);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermission(@NotNull String name) {
|
public boolean hasPermission(@NotNull String name) {
|
||||||
// TODO Auto-generated method stub
|
return super.hasPermission(name);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'hasPermission'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermission(@NotNull Permission perm) {
|
public boolean hasPermission(@NotNull Permission perm) {
|
||||||
// TODO Auto-generated method stub
|
return super.hasPermission(perm);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'hasPermission'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) {
|
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) {
|
||||||
// TODO Auto-generated method stub
|
return super.addAttachment(plugin, name, value);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) {
|
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) {
|
||||||
// TODO Auto-generated method stub
|
return super.addAttachment(plugin);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value,
|
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value,
|
||||||
int ticks) {
|
int ticks) {
|
||||||
// TODO Auto-generated method stub
|
return super.addAttachment(plugin, name, value, ticks);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) {
|
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) {
|
||||||
// TODO Auto-generated method stub
|
return super.addAttachment(plugin, ticks);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'addAttachment'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAttachment(@NotNull PermissionAttachment attachment) {
|
public void removeAttachment(@NotNull PermissionAttachment attachment) {
|
||||||
// TODO Auto-generated method stub
|
super.removeAttachment(attachment);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'removeAttachment'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recalculatePermissions() {
|
public void recalculatePermissions() {
|
||||||
// TODO Auto-generated method stub
|
super.recalculatePermissions();
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'recalculatePermissions'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||||
// TODO Auto-generated method stub
|
return super.getEffectivePermissions();
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getEffectivePermissions'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -138,50 +131,55 @@ public class PatchBukkitConsoleCommandSender implements ConsoleCommandSender {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOp(boolean value) {
|
public void setOp(boolean value) {
|
||||||
// TODO Auto-generated method stub
|
// Console is always op; ignore attempts to change.
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setOp'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConversing() {
|
public boolean isConversing() {
|
||||||
// TODO Auto-generated method stub
|
return this.conversation != null;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'isConversing'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void acceptConversationInput(@NotNull String input) {
|
public void acceptConversationInput(@NotNull String input) {
|
||||||
// TODO Auto-generated method stub
|
if (this.conversation != null) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'acceptConversationInput'");
|
this.conversation.acceptInput(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean beginConversation(@NotNull Conversation conversation) {
|
public boolean beginConversation(@NotNull Conversation conversation) {
|
||||||
// TODO Auto-generated method stub
|
if (this.conversation != null) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'beginConversation'");
|
return false;
|
||||||
|
}
|
||||||
|
this.conversation = conversation;
|
||||||
|
conversation.begin();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abandonConversation(@NotNull Conversation conversation) {
|
public void abandonConversation(@NotNull Conversation conversation) {
|
||||||
// TODO Auto-generated method stub
|
if (this.conversation != null && this.conversation.equals(conversation)) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'abandonConversation'");
|
conversation.abandon();
|
||||||
|
this.conversation = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abandonConversation(@NotNull Conversation conversation, @NotNull ConversationAbandonedEvent details) {
|
public void abandonConversation(@NotNull Conversation conversation, @NotNull ConversationAbandonedEvent details) {
|
||||||
// TODO Auto-generated method stub
|
if (this.conversation != null && this.conversation.equals(conversation)) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'abandonConversation'");
|
conversation.abandon(details);
|
||||||
|
this.conversation = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendRawMessage(@NotNull String message) {
|
public void sendRawMessage(@NotNull String message) {
|
||||||
// TODO Auto-generated method stub
|
this.sendMessage(message);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'sendRawMessage'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendRawMessage(@Nullable UUID sender, @NotNull String message) {
|
public void sendRawMessage(@Nullable UUID sender, @NotNull String message) {
|
||||||
// TODO Auto-generated method stub
|
this.sendMessage(sender, message);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'sendRawMessage'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{BinaryHeap, HashMap, HashSet},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
@ -68,6 +68,17 @@ pub struct Plugin {
|
|||||||
pub commands: HashMap<String, Command>,
|
pub commands: HashMap<String, Command>,
|
||||||
|
|
||||||
pub listeners: HashMap<String, Instance>,
|
pub listeners: HashMap<String, Instance>,
|
||||||
|
|
||||||
|
// Dependency metadata (normalized to lowercase)
|
||||||
|
pub provides: Vec<String>,
|
||||||
|
pub depends: Vec<String>,
|
||||||
|
pub soft_depends: Vec<String>,
|
||||||
|
pub load_before: Vec<String>,
|
||||||
|
pub load_after: Vec<String>,
|
||||||
|
pub classpath_deps: Vec<String>,
|
||||||
|
|
||||||
|
// Library coordinates (original casing preserved)
|
||||||
|
pub libraries: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PluginManager {
|
pub struct PluginManager {
|
||||||
@ -82,7 +93,15 @@ impl PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_plugin(&mut self, plugin: Plugin) {
|
pub fn add_plugin(&mut self, plugin: Plugin) {
|
||||||
self.plugins.insert(plugin.name.clone(), plugin);
|
let key = normalize_name(&plugin.name);
|
||||||
|
if self.plugins.contains_key(&key) {
|
||||||
|
log::warn!(
|
||||||
|
"Duplicate plugin name detected ({}). Keeping the first instance.",
|
||||||
|
plugin.name
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.plugins.insert(key, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_paper_plugin<P: AsRef<Path>>(
|
pub fn load_paper_plugin<P: AsRef<Path>>(
|
||||||
@ -97,20 +116,110 @@ impl PluginManager {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (spigot_depends, spigot_soft_depends, spigot_load_before, spigot_load_after, spigot_provides, spigot_libraries, spigot_skip_libraries) = match &parsed_spigot_plugin {
|
||||||
|
Some(config) => (
|
||||||
|
normalize_names(config.depend.clone()),
|
||||||
|
normalize_names(config.softdepend.clone()),
|
||||||
|
normalize_names(config.loadbefore.clone()),
|
||||||
|
Vec::new(),
|
||||||
|
normalize_names(config.provides.clone()),
|
||||||
|
config.libraries.clone().unwrap_or_default(),
|
||||||
|
config.paper_skip_libraries.unwrap_or(false),
|
||||||
|
),
|
||||||
|
None => (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), false),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut paper_depends = Vec::new();
|
||||||
|
let mut paper_soft_depends = Vec::new();
|
||||||
|
let mut paper_load_before = Vec::new();
|
||||||
|
let mut paper_load_after = Vec::new();
|
||||||
|
let mut paper_classpath = Vec::new();
|
||||||
|
|
||||||
|
if let Some(ref deps) = parsed_paper_plugin.dependencies {
|
||||||
|
for (name, dep) in deps
|
||||||
|
.get_bootstrap_deps()
|
||||||
|
.into_iter()
|
||||||
|
.chain(deps.get_server_deps())
|
||||||
|
{
|
||||||
|
let normalized = normalize_name(name);
|
||||||
|
if dep.required {
|
||||||
|
paper_depends.push(normalized.clone());
|
||||||
|
} else {
|
||||||
|
paper_soft_depends.push(normalized.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
match dep.load {
|
||||||
|
crate::config::paper::LoadOrder::Before => {
|
||||||
|
paper_load_before.push(normalized.clone());
|
||||||
|
}
|
||||||
|
crate::config::paper::LoadOrder::After => {
|
||||||
|
paper_load_after.push(normalized.clone());
|
||||||
|
}
|
||||||
|
crate::config::paper::LoadOrder::Omit => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dep.join_classpath {
|
||||||
|
paper_classpath.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut provides = normalize_names(parsed_paper_plugin.provides.clone());
|
||||||
|
provides.extend(spigot_provides);
|
||||||
|
|
||||||
|
let mut depends = paper_depends;
|
||||||
|
depends.extend(spigot_depends);
|
||||||
|
|
||||||
|
let mut soft_depends = paper_soft_depends;
|
||||||
|
soft_depends.extend(spigot_soft_depends);
|
||||||
|
|
||||||
|
let mut load_before = paper_load_before;
|
||||||
|
load_before.extend(spigot_load_before);
|
||||||
|
|
||||||
|
let mut load_after = paper_load_after;
|
||||||
|
load_after.extend(spigot_load_after);
|
||||||
|
|
||||||
|
let classpath_deps = paper_classpath;
|
||||||
|
|
||||||
|
let commands = match &parsed_spigot_plugin {
|
||||||
|
Some(config) => config.commands.clone().unwrap_or_default(),
|
||||||
|
None => HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let libraries = if spigot_skip_libraries {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
spigot_libraries
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = parsed_paper_plugin.name.clone();
|
||||||
|
let version = parsed_paper_plugin.version.clone();
|
||||||
|
let main_class = parsed_paper_plugin.main.clone();
|
||||||
|
let paper_config = parsed_paper_plugin;
|
||||||
|
let spigot_config = parsed_spigot_plugin;
|
||||||
|
|
||||||
let plugin = Plugin {
|
let plugin = Plugin {
|
||||||
name: parsed_paper_plugin.name.clone(),
|
name,
|
||||||
version: parsed_paper_plugin.version.clone(),
|
version,
|
||||||
main_class: parsed_paper_plugin.main.clone(),
|
main_class,
|
||||||
plugin_type: PluginType::Paper(PaperPluginData {
|
plugin_type: PluginType::Paper(PaperPluginData {
|
||||||
paper_config: parsed_paper_plugin,
|
paper_config,
|
||||||
spigot_config: parsed_spigot_plugin,
|
spigot_config,
|
||||||
}),
|
}),
|
||||||
state: PluginState::Registered,
|
state: PluginState::Registered,
|
||||||
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
||||||
path: jar_path.as_ref().to_path_buf(),
|
path: jar_path.as_ref().to_path_buf(),
|
||||||
instance: None,
|
instance: None,
|
||||||
commands: HashMap::new(),
|
commands,
|
||||||
listeners: HashMap::new(),
|
listeners: HashMap::new(),
|
||||||
|
|
||||||
|
provides,
|
||||||
|
depends: dedupe_names(depends),
|
||||||
|
soft_depends: dedupe_names(soft_depends),
|
||||||
|
load_before: dedupe_names(load_before),
|
||||||
|
load_after: dedupe_names(load_after),
|
||||||
|
classpath_deps: dedupe_names(classpath_deps),
|
||||||
|
libraries: dedupe_strings(libraries),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_plugin(plugin);
|
self.add_plugin(plugin);
|
||||||
@ -124,19 +233,41 @@ impl PluginManager {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let parsed_spigot_plugin = SpigotPluginYml::from_str(spigot_plugin_config)?;
|
let parsed_spigot_plugin = SpigotPluginYml::from_str(spigot_plugin_config)?;
|
||||||
|
|
||||||
|
let depends = normalize_names(parsed_spigot_plugin.depend.clone());
|
||||||
|
let soft_depends = normalize_names(parsed_spigot_plugin.softdepend.clone());
|
||||||
|
let load_before = normalize_names(parsed_spigot_plugin.loadbefore.clone());
|
||||||
|
let provides = normalize_names(parsed_spigot_plugin.provides.clone());
|
||||||
|
let libraries = if parsed_spigot_plugin.paper_skip_libraries.unwrap_or(false) {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
parsed_spigot_plugin.libraries.clone().unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = parsed_spigot_plugin.name.clone();
|
||||||
|
let version = parsed_spigot_plugin.version.clone();
|
||||||
|
let main_class = parsed_spigot_plugin.main.clone();
|
||||||
|
let spigot_config = parsed_spigot_plugin.clone();
|
||||||
|
let commands = parsed_spigot_plugin.commands.clone().unwrap_or_default();
|
||||||
|
|
||||||
let plugin = Plugin {
|
let plugin = Plugin {
|
||||||
name: parsed_spigot_plugin.name.clone(),
|
name,
|
||||||
version: parsed_spigot_plugin.version.clone(),
|
version,
|
||||||
main_class: parsed_spigot_plugin.main.clone(),
|
main_class,
|
||||||
plugin_type: PluginType::Spigot(SpigotPluginData {
|
plugin_type: PluginType::Spigot(SpigotPluginData { spigot_config }),
|
||||||
spigot_config: parsed_spigot_plugin.clone(),
|
|
||||||
}),
|
|
||||||
state: PluginState::Registered,
|
state: PluginState::Registered,
|
||||||
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
data_folder: jar_path.as_ref().parent().unwrap().join("data"),
|
||||||
path: jar_path.as_ref().to_path_buf(),
|
path: jar_path.as_ref().to_path_buf(),
|
||||||
instance: None,
|
instance: None,
|
||||||
commands: parsed_spigot_plugin.commands.unwrap_or(HashMap::new()),
|
commands,
|
||||||
listeners: HashMap::new(),
|
listeners: HashMap::new(),
|
||||||
|
|
||||||
|
provides,
|
||||||
|
depends: dedupe_names(depends),
|
||||||
|
soft_depends: dedupe_names(soft_depends),
|
||||||
|
load_before: dedupe_names(load_before),
|
||||||
|
load_after: Vec::new(),
|
||||||
|
classpath_deps: Vec::new(),
|
||||||
|
libraries: dedupe_strings(libraries),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_plugin(plugin);
|
self.add_plugin(plugin);
|
||||||
@ -151,6 +282,9 @@ impl PluginManager {
|
|||||||
InvocationArg::empty(),
|
InvocationArg::empty(),
|
||||||
)?;
|
)?;
|
||||||
for (_plugin_name, plugin) in &mut self.plugins {
|
for (_plugin_name, plugin) in &mut self.plugins {
|
||||||
|
if plugin.instance.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let plugin_instance = plugin.instance.as_ref().unwrap();
|
let plugin_instance = plugin.instance.as_ref().unwrap();
|
||||||
let plugin_instance = jvm.clone_instance(&plugin_instance).unwrap();
|
let plugin_instance = jvm.clone_instance(&plugin_instance).unwrap();
|
||||||
|
|
||||||
@ -185,6 +319,9 @@ impl PluginManager {
|
|||||||
InvocationArg::empty(),
|
InvocationArg::empty(),
|
||||||
)?;
|
)?;
|
||||||
for (_plugin_name, plugin) in &mut self.plugins {
|
for (_plugin_name, plugin) in &mut self.plugins {
|
||||||
|
if plugin.instance.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let plugin_instance = plugin.instance.as_ref().unwrap();
|
let plugin_instance = plugin.instance.as_ref().unwrap();
|
||||||
let plugin_instance = jvm.clone_instance(&plugin_instance).unwrap();
|
let plugin_instance = jvm.clone_instance(&plugin_instance).unwrap();
|
||||||
|
|
||||||
@ -219,18 +356,33 @@ impl PluginManager {
|
|||||||
command_tx: mpsc::Sender<JvmCommand>,
|
command_tx: mpsc::Sender<JvmCommand>,
|
||||||
command_manager: &mut CommandManager,
|
command_manager: &mut CommandManager,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (_plugin_name, plugin) in &mut self.plugins {
|
let load_order = self.compute_load_order();
|
||||||
|
|
||||||
|
for plugin_key in load_order {
|
||||||
|
let (classpath, libraries) = match self.plugins.get(&plugin_key) {
|
||||||
|
Some(plugin) => (
|
||||||
|
self.classpath_string_for(plugin),
|
||||||
|
self.library_string_for(plugin),
|
||||||
|
),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let plugin = match self.plugins.get_mut(&plugin_key) {
|
||||||
|
Some(plugin) => plugin,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
let plugin_instance = jvm.invoke_static(
|
let plugin_instance = jvm.invoke_static(
|
||||||
"org.patchbukkit.loader.PatchBukkitPluginLoader",
|
"org.patchbukkit.loader.PatchBukkitPluginLoader",
|
||||||
"createPlugin",
|
"createPlugin",
|
||||||
&[
|
&[
|
||||||
InvocationArg::try_from(&plugin.path.to_string_lossy().to_string())?,
|
InvocationArg::try_from(&plugin.path.to_string_lossy().to_string())?,
|
||||||
InvocationArg::try_from(&plugin.main_class)?,
|
InvocationArg::try_from(&plugin.main_class)?,
|
||||||
|
InvocationArg::try_from(&classpath)?,
|
||||||
|
InvocationArg::try_from(&libraries)?,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
plugin.instance = Some(plugin_instance);
|
plugin.instance = Some(plugin_instance);
|
||||||
|
|
||||||
for (cmd_name, cmd_data) in &plugin.commands {
|
for (cmd_name, cmd_data) in &plugin.commands {
|
||||||
match command_manager
|
match command_manager
|
||||||
.register_command(
|
.register_command(
|
||||||
@ -265,4 +417,232 @@ impl PluginManager {
|
|||||||
self.plugins.clear();
|
self.plugins.clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_dependency_name(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
provides_map: &HashMap<String, String>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let key = normalize_name(name);
|
||||||
|
if self.plugins.contains_key(&key) {
|
||||||
|
Some(key)
|
||||||
|
} else {
|
||||||
|
provides_map.get(&key).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_load_order(&mut self) -> Vec<String> {
|
||||||
|
let mut provides_map: HashMap<String, String> = HashMap::new();
|
||||||
|
for (key, plugin) in &self.plugins {
|
||||||
|
for provide in &plugin.provides {
|
||||||
|
if provide.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
provides_map.entry(provide.clone()).or_insert_with(|| key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut active: HashSet<String> = self.plugins.keys().cloned().collect();
|
||||||
|
|
||||||
|
let mut missing_required_by_plugin: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
for (key, plugin) in &self.plugins {
|
||||||
|
let mut missing_required = Vec::new();
|
||||||
|
for dep in &plugin.depends {
|
||||||
|
if self.resolve_dependency_name(dep, &provides_map).is_none() {
|
||||||
|
missing_required.push(dep.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !missing_required.is_empty() {
|
||||||
|
missing_required_by_plugin.insert(key.clone(), missing_required);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, missing_required) in missing_required_by_plugin {
|
||||||
|
if let Some(plugin) = self.plugins.get_mut(&key) {
|
||||||
|
plugin.state = PluginState::Errored;
|
||||||
|
active.remove(&key);
|
||||||
|
log::warn!(
|
||||||
|
"Skipping plugin {} due to missing required dependencies: {}",
|
||||||
|
plugin.name,
|
||||||
|
missing_required.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut edges: HashMap<String, HashSet<String>> = HashMap::new();
|
||||||
|
let mut indegree: HashMap<String, usize> =
|
||||||
|
active.iter().map(|k| (k.clone(), 0)).collect();
|
||||||
|
|
||||||
|
for (key, plugin) in &self.plugins {
|
||||||
|
if !active.contains(key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut before = Vec::new();
|
||||||
|
for dep in plugin
|
||||||
|
.depends
|
||||||
|
.iter()
|
||||||
|
.chain(plugin.soft_depends.iter())
|
||||||
|
{
|
||||||
|
if let Some(resolved) = self.resolve_dependency_name(dep, &provides_map) {
|
||||||
|
if resolved != *key && active.contains(&resolved) {
|
||||||
|
before.push(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for dep in before {
|
||||||
|
add_edge(&mut edges, &mut indegree, &dep, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for target in &plugin.load_before {
|
||||||
|
if let Some(resolved) = self.resolve_dependency_name(target, &provides_map) {
|
||||||
|
if resolved != *key && active.contains(&resolved) {
|
||||||
|
add_edge(&mut edges, &mut indegree, key, &resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for target in &plugin.load_after {
|
||||||
|
if let Some(resolved) = self.resolve_dependency_name(target, &provides_map) {
|
||||||
|
if resolved != *key && active.contains(&resolved) {
|
||||||
|
add_edge(&mut edges, &mut indegree, &resolved, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ready: BinaryHeap<std::cmp::Reverse<String>> = indegree
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(key, °)| {
|
||||||
|
if deg == 0 {
|
||||||
|
Some(std::cmp::Reverse(key.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut order = Vec::with_capacity(active.len());
|
||||||
|
|
||||||
|
while let Some(std::cmp::Reverse(node)) = ready.pop() {
|
||||||
|
order.push(node.clone());
|
||||||
|
if let Some(children) = edges.get(&node) {
|
||||||
|
for child in children {
|
||||||
|
if let Some(deg) = indegree.get_mut(child) {
|
||||||
|
if *deg > 0 {
|
||||||
|
*deg -= 1;
|
||||||
|
}
|
||||||
|
if *deg == 0 {
|
||||||
|
ready.push(std::cmp::Reverse(child.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.len() != active.len() {
|
||||||
|
let mut remaining: Vec<String> = active
|
||||||
|
.into_iter()
|
||||||
|
.filter(|k| !order.contains(k))
|
||||||
|
.collect();
|
||||||
|
remaining.sort();
|
||||||
|
log::warn!(
|
||||||
|
"Detected plugin dependency cycle(s). Loading remaining plugins in name order: {}",
|
||||||
|
remaining.join(", ")
|
||||||
|
);
|
||||||
|
order.extend(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
order
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classpath_string_for(&self, plugin: &Plugin) -> String {
|
||||||
|
if plugin.classpath_deps.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut provides_map: HashMap<String, String> = HashMap::new();
|
||||||
|
for (key, plugin) in &self.plugins {
|
||||||
|
for provide in &plugin.provides {
|
||||||
|
if provide.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
provides_map.entry(provide.clone()).or_insert_with(|| key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut paths = Vec::new();
|
||||||
|
for dep in &plugin.classpath_deps {
|
||||||
|
if let Some(resolved) = self.resolve_dependency_name(dep, &provides_map) {
|
||||||
|
if let Some(dep_plugin) = self.plugins.get(&resolved) {
|
||||||
|
paths.push(dep_plugin.path.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.join(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn library_string_for(&self, plugin: &Plugin) -> String {
|
||||||
|
if plugin.libraries.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
plugin.libraries.join("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_name(name: &str) -> String {
|
||||||
|
name.trim().to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_names(names: Option<Vec<String>>) -> Vec<String> {
|
||||||
|
names
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| normalize_name(&name))
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dedupe_names(names: Vec<String>) -> Vec<String> {
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let mut output = Vec::new();
|
||||||
|
for name in names {
|
||||||
|
if seen.insert(name.clone()) {
|
||||||
|
output.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dedupe_strings(values: Vec<String>) -> Vec<String> {
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let mut output = Vec::new();
|
||||||
|
for value in values {
|
||||||
|
let trimmed = value.trim().to_string();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if seen.insert(trimmed.clone()) {
|
||||||
|
output.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_edge(
|
||||||
|
edges: &mut HashMap<String, HashSet<String>>,
|
||||||
|
indegree: &mut HashMap<String, usize>,
|
||||||
|
from: &str,
|
||||||
|
to: &str,
|
||||||
|
) {
|
||||||
|
if from == to {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let entry = edges.entry(from.to_string()).or_default();
|
||||||
|
if entry.insert(to.to_string()) {
|
||||||
|
if let Some(count) = indegree.get_mut(to) {
|
||||||
|
*count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user