Mod import/export dialog, restarting
This commit is contained in:
@@ -64,6 +64,15 @@ uploadingpreviewfile = Uploading Preview File
|
|||||||
committingchanges = Comitting Changes
|
committingchanges = Comitting Changes
|
||||||
done = Done
|
done = Done
|
||||||
|
|
||||||
|
mods = Mods
|
||||||
|
mods.none = [LIGHT_GRAY]No mods found!
|
||||||
|
mod.enabled = [lightgray]Enabled
|
||||||
|
mod.disabled = [scarlet]Disabled
|
||||||
|
mod.requiresrestart = The game will now close to apply the mod changes.
|
||||||
|
mod.import = Import Mod
|
||||||
|
mod.remove.confirm = This mod will be deleted.
|
||||||
|
mod.author = [LIGHT_GRAY]Author:[] {0}
|
||||||
|
|
||||||
about.button = About
|
about.button = About
|
||||||
name = Name:
|
name = Name:
|
||||||
noname = Pick a[accent] player name[] first.
|
noname = Pick a[accent] player name[] first.
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
|
|||||||
public DeployDialog deploy;
|
public DeployDialog deploy;
|
||||||
public TechTreeDialog tech;
|
public TechTreeDialog tech;
|
||||||
public MinimapDialog minimap;
|
public MinimapDialog minimap;
|
||||||
|
public ModsDialog mods;
|
||||||
|
|
||||||
public Cursor drillCursor, unloadCursor;
|
public Cursor drillCursor, unloadCursor;
|
||||||
|
|
||||||
@@ -222,6 +223,7 @@ public class UI implements ApplicationListener, Loadable{
|
|||||||
deploy = new DeployDialog();
|
deploy = new DeployDialog();
|
||||||
tech = new TechTreeDialog();
|
tech = new TechTreeDialog();
|
||||||
minimap = new MinimapDialog();
|
minimap = new MinimapDialog();
|
||||||
|
mods = new ModsDialog();
|
||||||
|
|
||||||
Group group = Core.scene.root;
|
Group group = Core.scene.root;
|
||||||
|
|
||||||
@@ -410,6 +412,18 @@ public class UI implements ApplicationListener, Loadable{
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showOkText(String title, String text, Runnable confirmed){
|
||||||
|
FloatingDialog dialog = new FloatingDialog(title);
|
||||||
|
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
|
||||||
|
dialog.buttons.defaults().size(200f, 54f).pad(2f);
|
||||||
|
dialog.setFillParent(false);
|
||||||
|
dialog.buttons.addButton("$ok", () -> {
|
||||||
|
dialog.hide();
|
||||||
|
confirmed.run();
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
public String formatAmount(int number){
|
public String formatAmount(int number){
|
||||||
if(number >= 1000000){
|
if(number >= 1000000){
|
||||||
return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]";
|
return Strings.fixed(number / 1000000f, 1) + "[gray]mil[]";
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ import io.anuke.arc.collection.*;
|
|||||||
import io.anuke.arc.files.*;
|
import io.anuke.arc.files.*;
|
||||||
import io.anuke.arc.function.*;
|
import io.anuke.arc.function.*;
|
||||||
import io.anuke.arc.util.*;
|
import io.anuke.arc.util.*;
|
||||||
import io.anuke.mindustry.io.*;
|
import io.anuke.arc.util.serialization.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.*;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
public class Mods{
|
public class Mods{
|
||||||
|
private Json json = new Json();
|
||||||
private Array<LoadedMod> loaded = new Array<>();
|
private Array<LoadedMod> loaded = new Array<>();
|
||||||
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
|
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
|
||||||
|
private boolean requiresRestart;
|
||||||
|
|
||||||
/** Returns a file named 'config.json' in a special folder for the specified plugin.
|
/** Returns a file named 'config.json' in a special folder for the specified plugin.
|
||||||
* Call this in init(). */
|
* Call this in init(). */
|
||||||
@@ -28,13 +31,44 @@ public class Mods{
|
|||||||
return loaded.find(l -> l.mod.getClass() == type);
|
return loaded.find(l -> l.mod.getClass() == type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Imports an external mod file.*/
|
||||||
|
public void importMod(FileHandle file) throws IOException{
|
||||||
|
FileHandle dest = modDirectory.child(file.name());
|
||||||
|
if(dest.exists()){
|
||||||
|
throw new IOException("A mod with the same filename already exists!");
|
||||||
|
}
|
||||||
|
|
||||||
|
file.copyTo(dest);
|
||||||
|
try{
|
||||||
|
loaded.add(loadMod(file));
|
||||||
|
requiresRestart = true;
|
||||||
|
}catch(IOException e){
|
||||||
|
dest.delete();
|
||||||
|
throw e;
|
||||||
|
}catch(Throwable t){
|
||||||
|
dest.delete();
|
||||||
|
throw new IOException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes a mod file and marks it for requiring a restart. */
|
||||||
|
public void removeMod(LoadedMod mod){
|
||||||
|
mod.file.delete();
|
||||||
|
loaded.remove(mod);
|
||||||
|
requiresRestart = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requiresRestart(){
|
||||||
|
return requiresRestart;
|
||||||
|
}
|
||||||
|
|
||||||
/** Loads all mods from the folder, but does call any methods on them.*/
|
/** Loads all mods from the folder, but does call any methods on them.*/
|
||||||
public void load(){
|
public void load(){
|
||||||
for(FileHandle file : modDirectory.list()){
|
for(FileHandle file : modDirectory.list()){
|
||||||
if(!file.extension().equals("jar") || !file.extension().equals("zip")) continue;
|
if(!file.extension().equals("jar") && !file.extension().equals("zip")) continue;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
loaded.add(loadmod(file));
|
loaded.add(loadMod(file));
|
||||||
}catch(IllegalArgumentException ignored){
|
}catch(IllegalArgumentException ignored){
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Log.err("Failed to load plugin file {0}. Skipping.", file);
|
Log.err("Failed to load plugin file {0}. Skipping.", file);
|
||||||
@@ -55,22 +89,28 @@ public class Mods{
|
|||||||
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
|
loaded.each(p -> p.mod != null, p -> cons.accept(p.mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadedMod loadmod(FileHandle jar) throws Exception{
|
/** Loads a mod file+meta, but does not add it to the list. */
|
||||||
|
private LoadedMod loadMod(FileHandle jar) throws Exception{
|
||||||
FileHandle zip = new ZipFileHandle(jar);
|
FileHandle zip = new ZipFileHandle(jar);
|
||||||
|
|
||||||
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json");
|
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("plugin.json");
|
||||||
if(!metaf.exists()){
|
if(!metaf.exists()){
|
||||||
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", jar);
|
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json' file, skipping.", jar);
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException("No mod.json found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ModMeta meta = JsonIO.read(ModMeta.class, metaf.readString());
|
ModMeta meta = json.fromJson(ModMeta.class, metaf.readString());
|
||||||
String camelized = meta.name.replace(" ", "");
|
String camelized = meta.name.replace(" ", "");
|
||||||
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
|
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
|
||||||
Mod mainMod;
|
Mod mainMod;
|
||||||
|
|
||||||
//make sure the main class exists before loading it; if it doesn't just don't put it there
|
//make sure the main class exists before loading it; if it doesn't just don't put it there
|
||||||
if(zip.child(mainClass.replace('.', '/') + ".class").exists()){
|
if(zip.child(mainClass.replace('.', '/') + ".class").exists()){
|
||||||
|
//other platforms don't have standard java class loaders
|
||||||
|
if(mobile){
|
||||||
|
throw new IllegalArgumentException("This mod is not compatible with " + (ios ? "iOS" : "Android") + ".");
|
||||||
|
}
|
||||||
|
|
||||||
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
|
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
|
||||||
Class<?> main = classLoader.loadClass(mainClass);
|
Class<?> main = classLoader.loadClass(mainClass);
|
||||||
metas.put(main, meta);
|
metas.put(main, meta);
|
||||||
@@ -92,6 +132,8 @@ public class Mods{
|
|||||||
public final @Nullable Mod mod;
|
public final @Nullable Mod mod;
|
||||||
/** This mod's metadata. */
|
/** This mod's metadata. */
|
||||||
public final ModMeta meta;
|
public final ModMeta meta;
|
||||||
|
//TODO implement
|
||||||
|
protected boolean enabled;
|
||||||
|
|
||||||
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){
|
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
|||||||
82
core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java
Normal file
82
core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package io.anuke.mindustry.ui.dialogs;
|
||||||
|
|
||||||
|
import io.anuke.arc.*;
|
||||||
|
import io.anuke.mindustry.gen.*;
|
||||||
|
import io.anuke.mindustry.mod.Mods.*;
|
||||||
|
import io.anuke.mindustry.ui.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
|
public class ModsDialog extends FloatingDialog{
|
||||||
|
|
||||||
|
public ModsDialog(){
|
||||||
|
super("$mods");
|
||||||
|
addCloseButton();
|
||||||
|
shown(this::setup);
|
||||||
|
|
||||||
|
hidden(() -> {
|
||||||
|
if(mods.requiresRestart()){
|
||||||
|
ui.showOkText("$mods", "$mod.requiresrestart", () -> {
|
||||||
|
Core.app.exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(){
|
||||||
|
cont.clear();
|
||||||
|
cont.defaults().width(520f).pad(4);
|
||||||
|
if(!mods.all().isEmpty()){
|
||||||
|
cont.pane(table -> {
|
||||||
|
table.margin(10f).top();
|
||||||
|
for(LoadedMod mod : mods.all()){
|
||||||
|
table.table(Styles.black6, t -> {
|
||||||
|
t.defaults().pad(2).left().top();
|
||||||
|
t.margin(14f).left();
|
||||||
|
t.table(title -> {
|
||||||
|
title.left();
|
||||||
|
title.add("[accent]" + mod.meta.name + "[lightgray] v" + mod.meta.version);
|
||||||
|
title.add().growX();
|
||||||
|
|
||||||
|
title.addImageButton(Icon.trash16Small, Styles.cleari, () -> ui.showConfirm("$confirm", "$mod.remove.confirm", () -> {
|
||||||
|
mods.removeMod(mod);
|
||||||
|
setup();
|
||||||
|
})).size(50f);
|
||||||
|
}).growX().left().padTop(-14f).padRight(-14f);
|
||||||
|
|
||||||
|
t.row();
|
||||||
|
if(mod.meta.author != null){
|
||||||
|
t.add(Core.bundle.format("mod.author", mod.meta.author));
|
||||||
|
t.row();
|
||||||
|
}
|
||||||
|
if(mod.meta.description != null){
|
||||||
|
t.labelWrap("[lightgray]" + mod.meta.description).growX();
|
||||||
|
t.row();
|
||||||
|
}
|
||||||
|
|
||||||
|
}).width(500f);
|
||||||
|
table.row();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}else{
|
||||||
|
cont.table(Styles.black6, t -> t.add("$mods.none")).height(80f);
|
||||||
|
}
|
||||||
|
|
||||||
|
cont.row();
|
||||||
|
|
||||||
|
cont.addImageTextButton("$mod.import", Icon.add, () -> {
|
||||||
|
platform.showFileChooser(true, "zip", file -> {
|
||||||
|
try{
|
||||||
|
mods.importMod(file);
|
||||||
|
setup();
|
||||||
|
}catch(IOException e){
|
||||||
|
ui.showException(e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).margin(12f).width(500f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -163,6 +163,7 @@ public class MenuFragment extends Fragment{
|
|||||||
),
|
),
|
||||||
new Buttoni("$editor", Icon.editorSmall, ui.maps::show),
|
new Buttoni("$editor", Icon.editorSmall, ui.maps::show),
|
||||||
steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
|
steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
|
||||||
|
new Buttoni("$mods", Icon.wikiSmall, ui.mods::show),
|
||||||
new Buttoni("$settings", Icon.toolsSmall, ui.settings::show),
|
new Buttoni("$settings", Icon.toolsSmall, ui.settings::show),
|
||||||
new Buttoni("$about.button", Icon.infoSmall, ui.about::show),
|
new Buttoni("$about.button", Icon.infoSmall, ui.about::show),
|
||||||
new Buttoni("$quit", Icon.exitSmall, Core.app::exit)
|
new Buttoni("$quit", Icon.exitSmall, Core.app::exit)
|
||||||
|
|||||||
Reference in New Issue
Block a user