mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-13 19:51:27 +01:00
Rename plugins to Extensions and add basic support.
This commit is contained in:
parent
c5172a7275
commit
846b668c1b
3
.gitignore
vendored
3
.gitignore
vendored
@ -50,3 +50,6 @@ gradle-app.setting
|
|||||||
# gradle/wrapper/gradle-wrapper.properties
|
# gradle/wrapper/gradle-wrapper.properties
|
||||||
|
|
||||||
/src/main/java/com/mcecraft/
|
/src/main/java/com/mcecraft/
|
||||||
|
|
||||||
|
# When running the demo we generate the extensions folder
|
||||||
|
extensions/
|
||||||
|
@ -13,6 +13,8 @@ import net.minestom.server.data.DataType;
|
|||||||
import net.minestom.server.data.SerializableData;
|
import net.minestom.server.data.SerializableData;
|
||||||
import net.minestom.server.entity.EntityManager;
|
import net.minestom.server.entity.EntityManager;
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.extensions.Extension;
|
||||||
|
import net.minestom.server.extensions.ExtensionManager;
|
||||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||||
import net.minestom.server.fluids.Fluid;
|
import net.minestom.server.fluids.Fluid;
|
||||||
import net.minestom.server.gamedata.loottables.LootTableManager;
|
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||||
@ -33,7 +35,6 @@ import net.minestom.server.network.packet.server.play.PluginMessagePacket;
|
|||||||
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
|
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
|
||||||
import net.minestom.server.particle.Particle;
|
import net.minestom.server.particle.Particle;
|
||||||
import net.minestom.server.ping.ResponseDataConsumer;
|
import net.minestom.server.ping.ResponseDataConsumer;
|
||||||
import net.minestom.server.plugins.PluginManager;
|
|
||||||
import net.minestom.server.potion.PotionType;
|
import net.minestom.server.potion.PotionType;
|
||||||
import net.minestom.server.recipe.RecipeManager;
|
import net.minestom.server.recipe.RecipeManager;
|
||||||
import net.minestom.server.registry.ResourceGatherer;
|
import net.minestom.server.registry.ResourceGatherer;
|
||||||
@ -123,7 +124,7 @@ public class MinecraftServer {
|
|||||||
private static BiomeManager biomeManager;
|
private static BiomeManager biomeManager;
|
||||||
private static AdvancementManager advancementManager;
|
private static AdvancementManager advancementManager;
|
||||||
|
|
||||||
private static PluginManager pluginManager;
|
private static ExtensionManager extensionManager;
|
||||||
|
|
||||||
private static UpdateManager updateManager;
|
private static UpdateManager updateManager;
|
||||||
private static MinecraftServer minecraftServer;
|
private static MinecraftServer minecraftServer;
|
||||||
@ -180,7 +181,7 @@ public class MinecraftServer {
|
|||||||
|
|
||||||
updateManager = new UpdateManager();
|
updateManager = new UpdateManager();
|
||||||
|
|
||||||
pluginManager = PluginManager.getInstance();
|
extensionManager = new ExtensionManager();
|
||||||
|
|
||||||
lootTableManager = new LootTableManager();
|
lootTableManager = new LootTableManager();
|
||||||
tagManager = new TagManager();
|
tagManager = new TagManager();
|
||||||
@ -452,8 +453,14 @@ public class MinecraftServer {
|
|||||||
updateManager.start();
|
updateManager.start();
|
||||||
nettyServer.start(address, port);
|
nettyServer.start(address, port);
|
||||||
long t1 = -System.nanoTime();
|
long t1 = -System.nanoTime();
|
||||||
pluginManager.loadPlugins();
|
extensionManager.loadExtensionJARs();
|
||||||
LOGGER.info("Plugins loaded in " + (t1 + System.nanoTime()) / 1_000_000D + "ms");
|
// Init extensions
|
||||||
|
// TODO: Extensions should handle depending on each other and have a load-order.
|
||||||
|
extensionManager.getExtensions().forEach(Extension::preInitialize);
|
||||||
|
extensionManager.getExtensions().forEach(Extension::initialize);
|
||||||
|
extensionManager.getExtensions().forEach(Extension::postInitialize);
|
||||||
|
|
||||||
|
LOGGER.info("Extensions loaded in " + (t1 + System.nanoTime()) / 1_000_000D + "ms");
|
||||||
LOGGER.info("Minestom server started successfully.");
|
LOGGER.info("Minestom server started successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
src/main/java/net/minestom/server/extensions/Extension.java
Normal file
41
src/main/java/net/minestom/server/extensions/Extension.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package net.minestom.server.extensions;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
public abstract class Extension {
|
||||||
|
private JsonObject description;
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
protected Extension() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preInitialize() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void initialize();
|
||||||
|
|
||||||
|
public void postInitialize() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preTerminate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void terminate();
|
||||||
|
|
||||||
|
public void postTerminate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Logger getLogger() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
package net.minestom.server.extensions;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ExtensionManager {
|
||||||
|
private final Map<URL, URLClassLoader> extensionLoaders = new HashMap<>();
|
||||||
|
private final Map<String, Extension> extensions = new HashMap<>();
|
||||||
|
private final File extensionFolder = new File("extensions");
|
||||||
|
|
||||||
|
public ExtensionManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadExtensionJARs() {
|
||||||
|
if (!extensionFolder.exists()) {
|
||||||
|
if (!extensionFolder.mkdirs()) {
|
||||||
|
log.error("Could not find or create the extension folder, extensions will not be loaded!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : extensionFolder.listFiles()) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!file.getName().endsWith(".jar")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
URLClassLoader loader;
|
||||||
|
try {
|
||||||
|
URL url = file.toURI().toURL();
|
||||||
|
loader = loadJar(url);
|
||||||
|
extensionLoaders.put(url, loader);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
log.error(String.format("Failed to get URL for file %s.", file.getPath()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InputStream extensionInputStream = loader.getResourceAsStream("extension.json");
|
||||||
|
if (extensionInputStream == null) {
|
||||||
|
log.error(String.format("Failed to find extension.json in the file '%s'.", file.getPath()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject extensionDescription = JsonParser.parseReader(new InputStreamReader(extensionInputStream)).getAsJsonObject();
|
||||||
|
|
||||||
|
String mainClass = extensionDescription.get("entrypoint").getAsString();
|
||||||
|
String extensionName = extensionDescription.get("name").getAsString();
|
||||||
|
|
||||||
|
if (extensions.containsKey(extensionName.toLowerCase())) {
|
||||||
|
log.error(String.format("An extension called '%s' has already been registered.", extensionName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> jarClass;
|
||||||
|
try {
|
||||||
|
jarClass = Class.forName(mainClass, true, loader);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
log.error(String.format("Could not find main class '%s' in extension '%s'.", mainClass, extensionName), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Extension> extensionClass;
|
||||||
|
try {
|
||||||
|
extensionClass = jarClass.asSubclass(Extension.class);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
log.error(String.format("Main class '%s' in '%s' does not extend the 'extension superclass'.", mainClass, extensionName), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Constructor<? extends Extension> constructor;
|
||||||
|
try {
|
||||||
|
constructor = extensionClass.getDeclaredConstructor();
|
||||||
|
// Let's just make it accessible, plugin creators don't have to make this public.
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
log.error(String.format("Main class '%s' in '%s' does not define a no-args constructor.", mainClass, extensionName), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Extension extension = null;
|
||||||
|
try {
|
||||||
|
// Is annotated with NotNull
|
||||||
|
extension = constructor.newInstance();
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
log.error(String.format("Main class '%s' in '%s' cannot be an abstract class.", mainClass, extensionName), e);
|
||||||
|
return;
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
// We made it accessible, should not occur
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
log.error(
|
||||||
|
String.format(
|
||||||
|
"While instantiating the main class '%s' in '%s' an exception was thrown.", mainClass, extensionName
|
||||||
|
), e.getTargetException()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Set description
|
||||||
|
try {
|
||||||
|
Field descriptionField = extensionClass.getSuperclass().getDeclaredField("description");
|
||||||
|
descriptionField.setAccessible(true);
|
||||||
|
descriptionField.set(extension, extensionDescription);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// We made it accessible, should not occur
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
log.error(String.format("Main class '%s' in '%s' has no description field.", mainClass, extensionName), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Set Logger
|
||||||
|
try {
|
||||||
|
Field descriptionField = extensionClass.getSuperclass().getDeclaredField("logger");
|
||||||
|
descriptionField.setAccessible(true);
|
||||||
|
descriptionField.set(extension, LoggerFactory.getLogger(extensionClass));
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// We made it accessible, should not occur
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
log.error(String.format("Main class '%s' in '%s' has no logger field.", mainClass, extensionName), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.put(extensionName.toLowerCase(), extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a URL into the classpath.
|
||||||
|
*
|
||||||
|
* @param url {@link URL} (usually a JAR) that should be loaded.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public URLClassLoader loadJar(@NotNull URL url) {
|
||||||
|
return URLClassLoader.newInstance(new URL[]{url}, ExtensionManager.class.getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public File getExtensionFolder() {
|
||||||
|
return extensionFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public List<Extension> getExtensions() {
|
||||||
|
return new ArrayList<>(extensions.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Extension getExtension(@NotNull String name) {
|
||||||
|
return extensions.get(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Map<URL, URLClassLoader> getExtensionLoaders() {
|
||||||
|
return new HashMap<>(extensionLoaders);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package net.minestom.server.plugins;
|
|
||||||
|
|
||||||
public abstract class Plugin {
|
|
||||||
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package net.minestom.server.plugins;
|
|
||||||
|
|
||||||
public @interface PluginDescription {
|
|
||||||
|
|
||||||
String name();
|
|
||||||
String description();
|
|
||||||
String version();
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package net.minestom.server.plugins;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class PluginLoader {
|
|
||||||
|
|
||||||
private static PluginLoader instance = null;
|
|
||||||
|
|
||||||
//Singleton
|
|
||||||
public static PluginLoader getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new PluginLoader();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PluginLoader() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Plugin> loadPlugin(String path) {
|
|
||||||
JarFile jarFile;
|
|
||||||
URLClassLoader cl;
|
|
||||||
try {
|
|
||||||
jarFile = new JarFile(path);
|
|
||||||
URL[] urls = new URL[]{new URL("jar:file:" + path + "!/")};
|
|
||||||
cl = URLClassLoader.newInstance(urls);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
final List<Plugin> plugins = new ArrayList<>();
|
|
||||||
final Enumeration<JarEntry> e = jarFile.entries();
|
|
||||||
while (e.hasMoreElements()) {
|
|
||||||
try {
|
|
||||||
final JarEntry je = e.nextElement();
|
|
||||||
if (je.isDirectory() || !je.getName().endsWith(".class")) continue;
|
|
||||||
// -6 because of .class
|
|
||||||
String className = je.getName().substring(0, je.getName().length() - 6);
|
|
||||||
className = className.replace('/', '.');
|
|
||||||
final Class<?> c;
|
|
||||||
c = cl.loadClass(className);
|
|
||||||
Type superclass = c.getGenericSuperclass();
|
|
||||||
if (superclass != null && Plugin.class.getTypeName().equals(superclass.getTypeName()))
|
|
||||||
try {
|
|
||||||
plugins.add((Plugin) c.getConstructor().newInstance());
|
|
||||||
} catch (final ReflectiveOperationException | ArrayIndexOutOfBoundsException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (final Throwable ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.unmodifiableList(plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package net.minestom.server.plugins;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class PluginManager {
|
|
||||||
|
|
||||||
private static PluginManager instance = null;
|
|
||||||
|
|
||||||
//Singleton
|
|
||||||
public static PluginManager getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new PluginManager();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final PluginLoader loader = PluginLoader.getInstance();
|
|
||||||
|
|
||||||
private final File pluginsDir;
|
|
||||||
|
|
||||||
private PluginManager() {
|
|
||||||
pluginsDir = new File("plugins");
|
|
||||||
if (!pluginsDir.exists()||!pluginsDir.isDirectory()) {
|
|
||||||
if (!pluginsDir.mkdir()) {
|
|
||||||
log.error("Couldn't create plugins dir, plugins will not be loaded.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadPlugins() {
|
|
||||||
|
|
||||||
File[] files = pluginsDir.listFiles();
|
|
||||||
if(files != null) {
|
|
||||||
for (final File plugin : files) {
|
|
||||||
loader.loadPlugin(plugin.getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user