From 9e4e58baf9cd7c9eb4b3f62c6488646cb0656917 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 27 Oct 2019 18:07:18 -0400 Subject: [PATCH] Added mod dependencies --- core/assets/bundles/bundle.properties | 2 + core/src/io/anuke/mindustry/mod/Mods.java | 69 +++++++++++++++++-- .../mindustry/ui/dialogs/ModsDialog.java | 10 ++- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 05a0864c55..60f590b873 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -94,6 +94,8 @@ mods.report = Report Bug mod.enabled = [lightgray]Enabled mod.disabled = [scarlet]Disabled mod.disable = Disable +mod.missingdependencies = [scarlet]Missing dependencies: {0} +mod.nowdisabled = [scarlet]Mod '{0}' is missing dependencies:[accent] {1}\n[lightgray]These mods need to be downloaded first.\nThis mod will be automatically disabled. mod.enable = Enable mod.requiresrestart = The game will now close to apply the mod changes. mod.reloadrequired = [scarlet]Reload Required diff --git a/core/src/io/anuke/mindustry/mod/Mods.java b/core/src/io/anuke/mindustry/mod/Mods.java index 836c8679ae..1bbdf2b9ad 100644 --- a/core/src/io/anuke/mindustry/mod/Mods.java +++ b/core/src/io/anuke/mindustry/mod/Mods.java @@ -200,14 +200,61 @@ public class Mods implements Loadable{ } } + resolveDependencies(); //sort mods to make sure servers handle them properly. loaded.sort(Structs.comparing(m -> m.name)); buildFiles(); } - private void buildFiles(){ + private void resolveDependencies(){ + for(LoadedMod mod : Array.withArrays(loaded, disabled)){ + updateDependencies(mod); + } + + disabled.addAll(loaded.select(LoadedMod::hasUnmetDependencies)); + loaded.removeAll(LoadedMod::hasUnmetDependencies); + disabled.each(mod -> setEnabled(mod, false)); + disabled.distinct(); + loaded.distinct(); + } + + private void updateDependencies(LoadedMod mod){ + mod.dependencies.clear(); + mod.missingDependencies.clear(); + mod.dependencies = mod.meta.dependencies.map(this::locateMod); + + for(int i = 0; i < mod.dependencies.size; i++){ + if(mod.dependencies.get(i) == null){ + mod.missingDependencies.add(mod.meta.dependencies.get(i)); + } + } + } + + private void topoSort(LoadedMod mod, Array stack, ObjectSet visited){ + visited.add(mod); + mod.dependencies.each(m -> !visited.contains(m), m -> topoSort(m, stack, visited)); + stack.add(mod); + } + + /** @return mods ordered in the correct way needed for dependencies. */ + private Array orderedMods(){ + ObjectSet visited = new ObjectSet<>(); + Array result = new Array<>(); for(LoadedMod mod : loaded){ + if(!visited.contains(mod)){ + topoSort(mod, result, visited); + } + } + return result; + } + + private LoadedMod locateMod(String name){ + return loaded.find(mod -> mod.name.equals(name)); + } + + private void buildFiles(){ + for(LoadedMod mod : orderedMods()){ boolean zipFolder = !mod.file.isDirectory() && mod.root.parent() != null; String parentName = zipFolder ? mod.root.name() : null; for(FileHandle file : mod.root.list()){ @@ -256,7 +303,6 @@ public class Mods implements Loadable{ loaded.clear(); disabled.clear(); load(); - buildFiles(); Sounds.dispose(); Sounds.load(); Core.assets.finishLoading(); @@ -275,8 +321,7 @@ public class Mods implements Loadable{ /** Creates all the content found in mod files. */ public void loadContent(){ - - for(LoadedMod mod : loaded){ + for(LoadedMod mod : orderedMods()){ safeRun(mod, () -> { if(mod.root.child("content").exists()){ FileHandle contentRoot = mod.root.child("content"); @@ -336,11 +381,13 @@ public class Mods implements Loadable{ requiresReload = true; if(!enabled){ loaded.remove(mod); - disabled.add(mod); + if(!disabled.contains(mod)) disabled.add(mod); }else{ - loaded.add(mod); + if(!loaded.contains(mod)) loaded.add(mod); disabled.remove(mod); } + loaded.each(this::updateDependencies); + disabled.each(this::updateDependencies); } } @@ -465,6 +512,10 @@ public class Mods implements Loadable{ public final String name; /** This mod's metadata. */ public final ModMeta meta; + /** This mod's dependencies as already-loaded mods. */ + public Array dependencies = new Array<>(); + /** All missing dependencies of this mod as strings. */ + public Array missingDependencies = new Array<>(); public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){ this.root = root; @@ -478,6 +529,10 @@ public class Mods implements Loadable{ return Core.settings.getBool("mod-" + name + "-enabled", true); } + public boolean hasUnmetDependencies(){ + return !missingDependencies.isEmpty(); + } + @Override public String getSteamID(){ return Core.settings.getString(name + "-steamid", null); @@ -548,7 +603,7 @@ public class Mods implements Loadable{ /** Plugin metadata information.*/ public static class ModMeta{ public String name, author, description, version, main; - public String[] dependencies = {}; //TODO implement + public Array dependencies = Array.with(); /** Hidden mods are only server-side or client-side, and do not support adding new content. */ public boolean hidden; } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java index ef0556202f..b74fdcbffe 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ModsDialog.java @@ -70,6 +70,11 @@ public class ModsDialog extends FloatingDialog{ hidden(() -> { if(mods.requiresReload()){ ui.loadAnd("$reloading", () -> { + mods.all().each(mod -> { + if(mod.hasUnmetDependencies()){ + ui.showErrorMessage(Core.bundle.format("mod.nowdisabled", mod.name, mod.missingDependencies.toString(", "))); + } + }); mods.reloadContent(); }); } @@ -141,7 +146,10 @@ public class ModsDialog extends FloatingDialog{ t.labelWrap("[lightgray]" + mod.meta.description).growX(); t.row(); } - + if(mod.hasUnmetDependencies()){ + t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX(); + t.row(); + } }).width(500f); table.row(); }