diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 24807883d1..563251aea8 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -18,8 +18,8 @@ import io.anuke.mindustry.game.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.input.*; import io.anuke.mindustry.maps.*; +import io.anuke.mindustry.mod.*; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.plugin.*; import io.anuke.mindustry.world.blocks.defense.ForceProjector.*; import java.nio.charset.*; @@ -120,8 +120,8 @@ public class Vars implements Loadable{ public static FileHandle tmpDirectory; /** data subdirectory used for saves */ public static FileHandle saveDirectory; - /** data subdirectory used for plugins */ - public static FileHandle pluginDirectory; + /** data subdirectory used for mods */ + public static FileHandle modDirectory; /** map file extension */ public static final String mapExtension = "msav"; /** save file extension */ @@ -138,7 +138,7 @@ public class Vars implements Loadable{ public static DefaultWaves defaultWaves; public static LoopControl loops; public static Platform platform = new Platform(){}; - public static Plugins plugins; + public static Mods mods; public static World world; public static Maps maps; @@ -193,6 +193,7 @@ public class Vars implements Loadable{ Version.init(); + mods = new Mods(); content = new ContentLoader(); loops = new LoopControl(); defaultWaves = new DefaultWaves(); @@ -240,7 +241,9 @@ public class Vars implements Loadable{ mapPreviewDirectory = dataDirectory.child("previews/"); saveDirectory = dataDirectory.child("saves/"); tmpDirectory = dataDirectory.child("tmp/"); - pluginDirectory = dataDirectory.child("plugins/"); + modDirectory = dataDirectory.child("mods/"); + + modDirectory.mkdirs(); maps.load(); } diff --git a/core/src/io/anuke/mindustry/mod/Mod.java b/core/src/io/anuke/mindustry/mod/Mod.java new file mode 100644 index 0000000000..aa4d8198f5 --- /dev/null +++ b/core/src/io/anuke/mindustry/mod/Mod.java @@ -0,0 +1,27 @@ +package io.anuke.mindustry.mod; + +import io.anuke.arc.files.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.*; + +public class Mod{ + /** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/ + public FileHandle getConfig(){ + return Vars.mods.getConfig(this); + } + + /** Called after all plugins have been created and commands have been registered.*/ + public void init(){ + + } + + /** Register any commands to be used on the server side, e.g. from the console. */ + public void registerServerCommands(CommandHandler handler){ + + } + + /** Register any commands to be used on the client side, e.g. sent from an in-game player.. */ + public void registerClientCommands(CommandHandler handler){ + + } +} diff --git a/core/src/io/anuke/mindustry/mod/Mods.java b/core/src/io/anuke/mindustry/mod/Mods.java new file mode 100644 index 0000000000..363a29f3e5 --- /dev/null +++ b/core/src/io/anuke/mindustry/mod/Mods.java @@ -0,0 +1,94 @@ +package io.anuke.mindustry.mod; + +import io.anuke.annotations.Annotations.*; +import io.anuke.arc.collection.*; +import io.anuke.arc.files.*; +import io.anuke.arc.function.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.io.*; +import io.anuke.mindustry.plugin.*; +import io.anuke.mindustry.plugin.Plugins.*; + +import java.net.*; + +import static io.anuke.mindustry.Vars.*; + +public class Mods{ + private Array loaded = new Array<>(); + private ObjectMap, ModMeta> metas = new ObjectMap<>(); + + /** Returns a file named 'config.json' in a special folder for the specified plugin. + * Call this in init(). */ + public FileHandle getConfig(Mod mod){ + ModMeta load = metas.get(mod.getClass()); + if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!"); + return modDirectory.child(load.name).child("config.json"); + } + + /** @return the loaded plugin found by class, or null if not found. */ + public @Nullable LoadedMod getMod(Class type){ + return loaded.find(l -> l.mod.getClass() == type); + } + + /** Loads all plugins from the folder, but does call any methods on them.*/ + public void load(){ + for(FileHandle file : modDirectory.list()){ + if(!file.extension().equals("jar") || !file.extension().equals("zi[")) continue; + + try{ + loaded.add(loadmod(file)); + }catch(IllegalArgumentException ignored){ + }catch(Exception e){ + Log.err("Failed to load plugin file {0}. Skipping.", file); + e.printStackTrace(); + } + } + } + + /** @return all loaded plugins. */ + public Array all(){ + return loaded; + } + + /** Iterates through each plugin.*/ + public void each(Consumer cons){ + loaded.each(p -> cons.accept(p.mod)); + } + + private LoadedMod loadmod(FileHandle jar) throws Exception{ + FileHandle zip = new ZipFileHandle(jar); + + FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json"); + if(!metaf.exists()){ + Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", jar); + throw new IllegalArgumentException(); + } + + ModMeta meta = JsonIO.read(ModMeta.class, metaf.readString()); + + URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader()); + Class main = classLoader.loadClass(meta.main); + metas.put(main, meta); + return new LoadedMod(jar, zip, (Mod)main.getDeclaredConstructor().newInstance(), meta); + } + + /** Represents a plugin that has been loaded from a jar file.*/ + public static class LoadedMod{ + public final FileHandle jarFile; + public final FileHandle zipRoot; + public final @Nullable Mod mod; + public final ModMeta meta; + + public LoadedMod(FileHandle jarFile, FileHandle zipRoot, Mod mod, ModMeta meta){ + this.zipRoot = zipRoot; + this.jarFile = jarFile; + this.mod = mod; + this.meta = meta; + } + } + + /** Plugin metadata information.*/ + public static class ModMeta{ + public String name, author, description, version, main; + } +} diff --git a/core/src/io/anuke/mindustry/plugin/Plugin.java b/core/src/io/anuke/mindustry/plugin/Plugin.java index e94ad6a7ae..d3f4da04da 100644 --- a/core/src/io/anuke/mindustry/plugin/Plugin.java +++ b/core/src/io/anuke/mindustry/plugin/Plugin.java @@ -1,28 +1,7 @@ package io.anuke.mindustry.plugin; -import io.anuke.arc.files.*; -import io.anuke.arc.util.*; -import io.anuke.mindustry.*; +import io.anuke.mindustry.mod.*; -public abstract class Plugin{ +public abstract class Plugin extends Mod{ - /** @return the config file for this plugin, as the file 'plugins/[plugin-name]/config.json'.*/ - public FileHandle getConfig(){ - return Vars.plugins.getConfig(this); - } - - /** Called after all plugins have been created and commands have been registered.*/ - public void init(){ - - } - - /** Register any commands to be used on the server side, e.g. from the console. */ - public void registerServerCommands(CommandHandler handler){ - - } - - /** Register any commands to be used on the client side, e.g. sent from an in-game player.. */ - public void registerClientCommands(CommandHandler handler){ - - } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java index 21e5f76ce2..3e5a55d2f3 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/TechTreeDialog.java @@ -4,6 +4,7 @@ import io.anuke.arc.*; import io.anuke.arc.collection.*; import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.g2d.*; +import io.anuke.arc.input.*; import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; import io.anuke.arc.scene.*; diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index fd1c0ca1bd..f414772b3d 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -18,6 +18,7 @@ import io.anuke.mindustry.gen.*; import io.anuke.mindustry.io.*; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.*; +import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.net.Administration.*; import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.plugin.*; @@ -51,7 +52,6 @@ public class ServerControl implements ApplicationListener{ private PrintWriter socketOutput; public ServerControl(String[] args){ - plugins = new Plugins(); Core.settings.defaults( "shufflemode", "normal", @@ -134,7 +134,6 @@ public class ServerControl implements ApplicationListener{ }); customMapDirectory.mkdirs(); - pluginDirectory.mkdirs(); Thread thread = new Thread(this::readCommands, "Server Controls"); thread.setDaemon(true); @@ -178,10 +177,10 @@ public class ServerControl implements ApplicationListener{ }); //initialize plugins - plugins.each(io.anuke.mindustry.plugin.Plugin::init); + plugins.each(Plugin::init); - if(!plugins.all().isEmpty()){ - info("&lc{0} plugins loaded.", plugins.all().size); + if(!mods.all().isEmpty()){ + info("&lc{0} plugins loaded.", mods.all().size); } info("&lcServer loaded. Type &ly'help'&lc for help."); @@ -324,28 +323,28 @@ public class ServerControl implements ApplicationListener{ } }); - handler.register("plugins", "Display all loaded plugins.", arg -> { - if(!plugins.all().isEmpty()){ - info("Plugins:"); - for(LoadedPlugin plugin : plugins.all()){ - info(" &ly{0} &lcv{1}", plugin.meta.name, plugin.meta.version); + handler.register("mods", "Display all loaded mods.", arg -> { + if(!mods.all().isEmpty()){ + info("Mods:"); + for(LoadedMod mod : mods.all()){ + info(" &ly{0} &lcv{1}", mod.meta.name, mod.meta.version); } }else{ - info("No plugins found."); + info("No mods found."); } - info("&lyPlugin directory: &lb&fi{0}", pluginDirectory.file().getAbsoluteFile().toString()); + info("&lyMod directory: &lb&fi{0}", modDirectory.file().getAbsoluteFile().toString()); }); - handler.register("plugin", "", "Display information about a loaded plugin.", arg -> { - LoadedPlugin plugin = plugins.all().find(p -> p.meta.name.equalsIgnoreCase(arg[0])); - if(plugin != null){ - info("Name: &ly{0}", plugin.meta.name); - info("Version: &ly{0}", plugin.meta.version); - info("Author: &ly{0}", plugin.meta.author); - info("Path: &ly{0}", plugin.jarFile.path()); - info("Description: &ly{0}", plugin.meta.description); + handler.register("mod", "", "Display information about a loaded plugin.", arg -> { + LoadedMod mod = mods.all().find(p -> p.meta.name.equalsIgnoreCase(arg[0])); + if(mod != null){ + info("Name: &ly{0}", mod.meta.name); + info("Version: &ly{0}", mod.meta.version); + info("Author: &ly{0}", mod.meta.author); + info("Path: &ly{0}", mod.jarFile.path()); + info("Description: &ly{0}", mod.meta.description); }else{ - info("No plugin with name &ly'{0}'&lg found."); + info("No mod with name &ly'{0}'&lg found."); } }); @@ -757,8 +756,9 @@ public class ServerControl implements ApplicationListener{ info("&ly{0}&lg MB collected. Memory usage now at &ly{1}&lg MB.", pre - post, post); }); - plugins.each(p -> p.registerServerCommands(handler)); - plugins.each(p -> p.registerClientCommands(netServer.clientCommands)); + mods.each(p -> p.registerServerCommands(handler)); + //TODO + //plugins.each(p -> p.registerClientCommands(netServer.clientCommands)); } private void readCommands(){