Mod dependency resolution improvements (#7972)
* soft dependencies + better mod resolution algorithm * update ModMeta#toString * var * add #7962 bugfix * Use existing code to resolve * add state text for mod dialog * bugfix * fix error text * remove external resolver class It's simpler like that :)
This commit is contained in:
@@ -150,12 +150,16 @@ mod.incompatiblemod = [red]Incompatible
|
|||||||
mod.blacklisted = [red]Unsupported
|
mod.blacklisted = [red]Unsupported
|
||||||
mod.unmetdependencies = [red]Unmet Dependencies
|
mod.unmetdependencies = [red]Unmet Dependencies
|
||||||
mod.erroredcontent = [red]Content Errors
|
mod.erroredcontent = [red]Content Errors
|
||||||
|
mod.circulardependencies = [red]Circular Dependencies
|
||||||
|
mod.incompletedependencies = [red]Incomplete Dependencies
|
||||||
|
|
||||||
mod.requiresversion.details = Requires game version: [accent]{0}[]\nYour game is outdated. This mod requires a newer version of the game (possibly a beta/alpha release) to function.
|
mod.requiresversion.details = Requires game version: [accent]{0}[]\nYour game is outdated. This mod requires a newer version of the game (possibly a beta/alpha release) to function.
|
||||||
mod.outdatedv7.details = This mod is incompatible with the latest version of the game. The author must update it, and add [accent]minGameVersion: 136[] to its [accent]mod.json[] file.
|
mod.outdatedv7.details = This mod is incompatible with the latest version of the game. The author must update it, and add [accent]minGameVersion: 136[] to its [accent]mod.json[] file.
|
||||||
mod.blacklisted.details = This mod has been manually blacklisted for causing crashes or other issues with this version of the game. Do not use it.
|
mod.blacklisted.details = This mod has been manually blacklisted for causing crashes or other issues with this version of the game. Do not use it.
|
||||||
mod.missingdependencies.details = This mod is missing dependencies: {0}
|
mod.missingdependencies.details = This mod is missing dependencies: {0}
|
||||||
mod.erroredcontent.details = This mod caused errors when loading. Ask the mod author to fix them.
|
mod.erroredcontent.details = This mod caused errors when loading. Ask the mod author to fix them.
|
||||||
|
mod.circulardependencies.details = This mod has dependencies that depends on each other.
|
||||||
|
mod.incompletedependencies.details = This mod is unable to be loaded due to invalid or missing dependencies: {0}.
|
||||||
|
|
||||||
mod.requiresversion = Requires game version: [red]{0}
|
mod.requiresversion = Requires game version: [red]{0}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class Mods implements Loadable{
|
|||||||
private int totalSprites;
|
private int totalSprites;
|
||||||
private static ObjectFloatMap<String> textureResize = new ObjectFloatMap<>();
|
private static ObjectFloatMap<String> textureResize = new ObjectFloatMap<>();
|
||||||
private MultiPacker packer;
|
private MultiPacker packer;
|
||||||
|
|
||||||
private ModClassLoader mainLoader = new ModClassLoader(getClass().getClassLoader());
|
private ModClassLoader mainLoader = new ModClassLoader(getClass().getClassLoader());
|
||||||
|
|
||||||
Seq<LoadedMod> mods = new Seq<>();
|
Seq<LoadedMod> mods = new Seq<>();
|
||||||
@@ -103,7 +104,7 @@ public class Mods implements Loadable{
|
|||||||
|
|
||||||
file.copyTo(dest);
|
file.copyTo(dest);
|
||||||
try{
|
try{
|
||||||
var loaded = loadMod(dest, true);
|
var loaded = loadMod(dest, true, true);
|
||||||
mods.add(loaded);
|
mods.add(loaded);
|
||||||
requiresReload = true;
|
requiresReload = true;
|
||||||
//enable the mod on import
|
//enable the mod on import
|
||||||
@@ -418,19 +419,57 @@ public class Mods implements Loadable{
|
|||||||
|
|
||||||
/** Loads all mods from the folder, but does not call any methods on them.*/
|
/** Loads all mods from the folder, but does not call any methods on them.*/
|
||||||
public void load(){
|
public void load(){
|
||||||
var files = resolveDependencies(Seq.with(modDirectory.list()).filter(f ->
|
var candidates = new Seq<Fi>();
|
||||||
f.extEquals("jar") || f.extEquals("zip") || (f.isDirectory() && (f.child("mod.json").exists() || f.child("mod.hjson").exists()))
|
|
||||||
));
|
// Add local mods
|
||||||
|
Seq.with(modDirectory.list())
|
||||||
|
.filter(f -> f.extEquals("jar") || f.extEquals("zip") || (f.isDirectory() && (f.child("mod.json").exists() || f.child("mod.hjson").exists())))
|
||||||
|
.each(candidates::add);
|
||||||
|
|
||||||
|
// Add Steam workshop mods
|
||||||
|
platform.getWorkshopContent(LoadedMod.class)
|
||||||
|
.each(candidates::add);
|
||||||
|
|
||||||
|
var mapping = new ObjectMap<String, Fi>();
|
||||||
|
var metas = new Seq<ModMeta>();
|
||||||
|
|
||||||
|
for(Fi file : candidates){
|
||||||
|
ModMeta meta = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
Fi zip = file.isDirectory() ? file : new ZipFi(file);
|
||||||
|
|
||||||
|
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
|
||||||
|
zip = zip.list()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = findMeta(zip);
|
||||||
|
}catch(Throwable ignored){
|
||||||
|
}
|
||||||
|
|
||||||
|
if(meta == null || meta.name == null) continue;
|
||||||
|
metas.add(meta);
|
||||||
|
mapping.put(meta.name, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolved = resolveDependencies(metas);
|
||||||
|
for(var entry : resolved){
|
||||||
|
var file = mapping.get(entry.key);
|
||||||
|
var steam = platform.getWorkshopContent(LoadedMod.class).contains(file);
|
||||||
|
|
||||||
for(Fi file : files){
|
|
||||||
Log.debug("[Mods] Loading mod @", file);
|
Log.debug("[Mods] Loading mod @", file);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
LoadedMod mod = loadMod(file);
|
LoadedMod mod = loadMod(file, false, entry.value == ModState.enabled);
|
||||||
|
mod.state = entry.value;
|
||||||
mods.add(mod);
|
mods.add(mod);
|
||||||
|
if(steam) mod.addSteamID(file.name());
|
||||||
}catch(Throwable e){
|
}catch(Throwable e){
|
||||||
if(e instanceof ClassNotFoundException && e.getMessage().contains("mindustry.plugin.Plugin")){
|
if(e instanceof ClassNotFoundException && e.getMessage().contains("mindustry.plugin.Plugin")){
|
||||||
Log.info("Plugin '@' is outdated and needs to be ported to 6.0! Update its main class to inherit from 'mindustry.mod.Plugin'. See https://mindustrygame.github.io/wiki/modding/6-migrationv6/", file.name());
|
Log.info("Plugin '@' is outdated and needs to be ported to 6.0! Update its main class to inherit from 'mindustry.mod.Plugin'. See https://mindustrygame.github.io/wiki/modding/6-migrationv6/", file.name());
|
||||||
|
}else if(steam){
|
||||||
|
Log.err("Failed to load mod workshop file @. Skipping.", file);
|
||||||
|
Log.err(e);
|
||||||
}else{
|
}else{
|
||||||
Log.err("Failed to load mod file @. Skipping.", file);
|
Log.err("Failed to load mod file @. Skipping.", file);
|
||||||
Log.err(e);
|
Log.err(e);
|
||||||
@@ -438,21 +477,19 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//load workshop mods now
|
// Resolve the state
|
||||||
for(Fi file : resolveDependencies(platform.getWorkshopContent(LoadedMod.class))){
|
mods.each(this::updateDependencies);
|
||||||
try{
|
for(var mod : mods){
|
||||||
LoadedMod mod = loadMod(file);
|
// Skip mods where the state has already been resolved
|
||||||
mods.add(mod);
|
if(mod.state != ModState.enabled)continue;
|
||||||
mod.addSteamID(file.name());
|
if(!mod.isSupported()){
|
||||||
}catch(Throwable e){
|
mod.state = ModState.unsupported;
|
||||||
Log.err("Failed to load mod workshop file @. Skipping.", file);
|
}else if(!mod.shouldBeEnabled()){
|
||||||
Log.err(e);
|
mod.state = ModState.disabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveModState();
|
|
||||||
sortMods();
|
sortMods();
|
||||||
|
|
||||||
buildFiles();
|
buildFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,18 +498,6 @@ public class Mods implements Loadable{
|
|||||||
mods.sort(Structs.comps(Structs.comparingInt(m -> m.state.ordinal()), Structs.comparing(m -> m.name)));
|
mods.sort(Structs.comps(Structs.comparingInt(m -> m.state.ordinal()), Structs.comparing(m -> m.name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveModState(){
|
|
||||||
mods.each(this::updateDependencies);
|
|
||||||
|
|
||||||
for(LoadedMod mod : mods){
|
|
||||||
mod.state =
|
|
||||||
!mod.isSupported() ? ModState.unsupported :
|
|
||||||
mod.hasUnmetDependencies() ? ModState.missingDependencies :
|
|
||||||
!mod.shouldBeEnabled() ? ModState.disabled :
|
|
||||||
ModState.enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDependencies(LoadedMod mod){
|
private void updateDependencies(LoadedMod mod){
|
||||||
mod.dependencies.clear();
|
mod.dependencies.clear();
|
||||||
mod.missingDependencies.clear();
|
mod.missingDependencies.clear();
|
||||||
@@ -485,22 +510,10 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void topoSort(LoadedMod mod, Seq<LoadedMod> stack, ObjectSet<LoadedMod> 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. */
|
/** @return mods ordered in the correct way needed for dependencies. */
|
||||||
private Seq<LoadedMod> orderedMods(){
|
public Seq<LoadedMod> orderedMods(){
|
||||||
ObjectSet<LoadedMod> visited = new ObjectSet<>();
|
var mapping = mods.asMap(m -> m.meta.name);
|
||||||
Seq<LoadedMod> result = new Seq<>();
|
return resolveDependencies(mods.map(m -> m.meta)).orderedKeys().map(mapping::get);
|
||||||
eachEnabled(mod -> {
|
|
||||||
if(!visited.contains(mod)){
|
|
||||||
topoSort(mod, result, visited);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoadedMod locateMod(String name){
|
public LoadedMod locateMod(String name){
|
||||||
@@ -758,12 +771,12 @@ public class Mods implements Loadable{
|
|||||||
|
|
||||||
/** Iterates through each mod with a main class. */
|
/** Iterates through each mod with a main class. */
|
||||||
public void eachClass(Cons<Mod> cons){
|
public void eachClass(Cons<Mod> cons){
|
||||||
mods.each(p -> p.main != null, p -> contextRun(p, () -> cons.get(p.main)));
|
orderedMods().each(p -> p.main != null, p -> contextRun(p, () -> cons.get(p.main)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Iterates through each enabled mod. */
|
/** Iterates through each enabled mod. */
|
||||||
public void eachEnabled(Cons<LoadedMod> cons){
|
public void eachEnabled(Cons<LoadedMod> cons){
|
||||||
mods.each(LoadedMod::enabled, cons);
|
orderedMods().each(LoadedMod::enabled, cons);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void contextRun(LoadedMod mod, Runnable run){
|
public void contextRun(LoadedMod mod, Runnable run){
|
||||||
@@ -793,78 +806,71 @@ public class Mods implements Loadable{
|
|||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolves the loading order of a list mods/plugins using their internal names.
|
/** Resolves the loading order of a list mods/plugins using their internal names. */
|
||||||
* It also skips non-mods files or folders. */
|
public OrderedMap<String, ModState> resolveDependencies(Seq<ModMeta> metas){
|
||||||
public Seq<Fi> resolveDependencies(Seq<Fi> files){
|
var context = new ModResolutionContext();
|
||||||
ObjectMap<String, Fi> fileMapping = new ObjectMap<>();
|
|
||||||
ObjectMap<String, Seq<String>> dependencies = new ObjectMap<>();
|
|
||||||
|
|
||||||
for(Fi file : files){
|
for(var meta : metas){
|
||||||
ModMeta meta = null;
|
Seq<ModDependency> dependencies = new Seq<>();
|
||||||
|
for(var dependency : meta.dependencies){
|
||||||
try{
|
dependencies.add(new ModDependency(dependency, true));
|
||||||
Fi zip = file.isDirectory() ? file : new ZipFi(file);
|
|
||||||
|
|
||||||
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
|
|
||||||
zip = zip.list()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
meta = findMeta(zip);
|
|
||||||
}catch(Throwable ignored){
|
|
||||||
}
|
}
|
||||||
|
for(var dependency : meta.softDependencies){
|
||||||
if(meta == null || meta.name == null) continue;
|
dependencies.add(new ModDependency(dependency, false));
|
||||||
dependencies.put(meta.name, meta.dependencies);
|
|
||||||
fileMapping.put(meta.name, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectSet<String> visited = new ObjectSet<>();
|
|
||||||
OrderedSet<String> ordered = new OrderedSet<>();
|
|
||||||
|
|
||||||
for(String modName : dependencies.keys()){
|
|
||||||
if(!ordered.contains(modName)){
|
|
||||||
// Adds the loaded mods at the beginning of the list
|
|
||||||
ordered.add(modName, 0);
|
|
||||||
resolveDependencies(modName, dependencies, ordered, visited);
|
|
||||||
visited.clear();
|
|
||||||
}
|
}
|
||||||
|
context.dependencies.put(meta.name, dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds the invalid mods
|
for(var key : context.dependencies.keys()){
|
||||||
for(String missingMod : dependencies.keys()){
|
if (context.ordered.contains(key)) {
|
||||||
if(!ordered.contains(missingMod)) ordered.add(missingMod, 0);
|
continue;
|
||||||
|
}
|
||||||
|
resolve(key, context);
|
||||||
|
context.visited.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Seq<Fi> resolved = ordered.orderedItems().map(fileMapping::get);
|
var result = new OrderedMap<String, ModState>();
|
||||||
// Since the resolver explores the dependencies from leaves to the root, reverse the seq
|
for(var name : context.ordered){
|
||||||
resolved.reverse();
|
result.put(name, ModState.enabled);
|
||||||
return resolved;
|
}
|
||||||
|
result.putAll(context.invalid);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recursive search of dependencies */
|
private boolean resolve(String element, ModResolutionContext context){
|
||||||
public void resolveDependencies(String modName, ObjectMap<String, Seq<String>> dependencies, OrderedSet<String> ordered, ObjectSet<String> visited){
|
context.visited.add(element);
|
||||||
visited.add(modName);
|
for(final var dependency : context.dependencies.get(element)){
|
||||||
|
// Circular dependencies ?
|
||||||
for(String dependency : dependencies.get(modName)){
|
if(context.visited.contains(dependency.name) && !context.ordered.contains(dependency.name)){
|
||||||
// Checks if the dependency tree isn't circular and that the dependency is not missing
|
context.invalid.put(dependency.name, ModState.circularDependencies);
|
||||||
if(!visited.contains(dependency) && dependencies.containsKey(dependency)){
|
return false;
|
||||||
// Skips if the dependency was already explored in a separate tree
|
// If dependency present, resolve it, or if it's not required, ignore it
|
||||||
if(ordered.contains(dependency)) continue;
|
}else if(context.dependencies.containsKey(dependency.name)){
|
||||||
ordered.add(dependency);
|
if(!context.ordered.contains(dependency.name) && !resolve(dependency.name, context) && dependency.required){
|
||||||
resolveDependencies(dependency, dependencies, ordered, visited);
|
context.invalid.put(element, ModState.incompleteDependencies);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The dependency is missing, but if not required, skip
|
||||||
|
}else if(dependency.required){
|
||||||
|
context.invalid.put(element, ModState.missingDependencies);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!context.ordered.contains(element)){
|
||||||
|
context.ordered.add(element);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads a mod file+meta, but does not add it to the list.
|
/** Loads a mod file+meta, but does not add it to the list.
|
||||||
* Note that directories can be loaded as mods. */
|
* Note that directories can be loaded as mods. */
|
||||||
private LoadedMod loadMod(Fi sourceFile) throws Exception{
|
private LoadedMod loadMod(Fi sourceFile) throws Exception{
|
||||||
return loadMod(sourceFile, false);
|
return loadMod(sourceFile, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads a mod file+meta, but does not add it to the list.
|
/** Loads a mod file+meta, but does not add it to the list.
|
||||||
* Note that directories can be loaded as mods. */
|
* Note that directories can be loaded as mods. */
|
||||||
private LoadedMod loadMod(Fi sourceFile, boolean overwrite) throws Exception{
|
private LoadedMod loadMod(Fi sourceFile, boolean overwrite, boolean initialize) throws Exception{
|
||||||
Time.mark();
|
Time.mark();
|
||||||
|
|
||||||
ZipFi rootZip = null;
|
ZipFi rootZip = null;
|
||||||
@@ -930,7 +936,8 @@ public class Mods implements Loadable{
|
|||||||
!skipModLoading() &&
|
!skipModLoading() &&
|
||||||
Core.settings.getBool("mod-" + baseName + "-enabled", true) &&
|
Core.settings.getBool("mod-" + baseName + "-enabled", true) &&
|
||||||
Version.isAtLeast(meta.minGameVersion) &&
|
Version.isAtLeast(meta.minGameVersion) &&
|
||||||
(meta.getMinMajor() >= 136 || headless)
|
(meta.getMinMajor() >= 136 || headless) &&
|
||||||
|
initialize
|
||||||
){
|
){
|
||||||
if(ios){
|
if(ios){
|
||||||
throw new ModLoadException("Java class mods are not supported on iOS.");
|
throw new ModLoadException("Java class mods are not supported on iOS.");
|
||||||
@@ -1152,6 +1159,7 @@ public class Mods implements Loadable{
|
|||||||
public String name, minGameVersion = "0";
|
public String name, minGameVersion = "0";
|
||||||
public @Nullable String displayName, author, description, subtitle, version, main, repo;
|
public @Nullable String displayName, author, description, subtitle, version, main, repo;
|
||||||
public Seq<String> dependencies = Seq.with();
|
public Seq<String> dependencies = Seq.with();
|
||||||
|
public Seq<String> softDependencies = Seq.with();
|
||||||
/** Hidden mods are only server-side or client-side, and do not support adding new content. */
|
/** Hidden mods are only server-side or client-side, and do not support adding new content. */
|
||||||
public boolean hidden;
|
public boolean hidden;
|
||||||
/** If true, this mod should be loaded as a Java class mod. This is technically optional, but highly recommended. */
|
/** If true, this mod should be loaded as a Java class mod. This is technically optional, but highly recommended. */
|
||||||
@@ -1185,19 +1193,27 @@ public class Mods implements Loadable{
|
|||||||
int dot = ver.indexOf(".");
|
int dot = ver.indexOf(".");
|
||||||
return dot != -1 ? Strings.parseInt(ver.substring(0, dot), 0) : Strings.parseInt(ver, 0);
|
return dot != -1 ? Strings.parseInt(ver.substring(0, dot), 0) : Strings.parseInt(ver, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "ModMeta{" +
|
return "ModMeta{" +
|
||||||
"name='" + name + '\'' +
|
"name='" + name + '\'' +
|
||||||
", author='" + author + '\'' +
|
", minGameVersion='" + minGameVersion + '\'' +
|
||||||
", version='" + version + '\'' +
|
", displayName='" + displayName + '\'' +
|
||||||
", main='" + main + '\'' +
|
", author='" + author + '\'' +
|
||||||
", minGameVersion='" + minGameVersion + '\'' +
|
", description='" + description + '\'' +
|
||||||
", hidden=" + hidden +
|
", subtitle='" + subtitle + '\'' +
|
||||||
", repo=" + repo +
|
", version='" + version + '\'' +
|
||||||
", texturescale=" + texturescale +
|
", main='" + main + '\'' +
|
||||||
'}';
|
", repo='" + repo + '\'' +
|
||||||
|
", dependencies=" + dependencies +
|
||||||
|
", softDependencies=" + softDependencies +
|
||||||
|
", hidden=" + hidden +
|
||||||
|
", java=" + java +
|
||||||
|
", keepOutlines=" + keepOutlines +
|
||||||
|
", texturescale=" + texturescale +
|
||||||
|
", pregenerated=" + pregenerated +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1211,7 +1227,26 @@ public class Mods implements Loadable{
|
|||||||
enabled,
|
enabled,
|
||||||
contentErrors,
|
contentErrors,
|
||||||
missingDependencies,
|
missingDependencies,
|
||||||
|
incompleteDependencies,
|
||||||
|
circularDependencies,
|
||||||
unsupported,
|
unsupported,
|
||||||
disabled,
|
disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ModResolutionContext {
|
||||||
|
public final ObjectMap<String, Seq<ModDependency>> dependencies = new ObjectMap<>();
|
||||||
|
public final ObjectSet<String> visited = new ObjectSet<>();
|
||||||
|
public final OrderedSet<String> ordered = new OrderedSet<>();
|
||||||
|
public final ObjectMap<String, ModState> invalid = new OrderedMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ModDependency{
|
||||||
|
public final String name;
|
||||||
|
public final boolean required;
|
||||||
|
|
||||||
|
public ModDependency(String name, boolean required){
|
||||||
|
this.name = name;
|
||||||
|
this.required = required;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,6 +329,10 @@ public class ModsDialog extends BaseDialog{
|
|||||||
return "@mod.blacklisted";
|
return "@mod.blacklisted";
|
||||||
}else if(!item.isSupported()){
|
}else if(!item.isSupported()){
|
||||||
return "@mod.incompatiblegame";
|
return "@mod.incompatiblegame";
|
||||||
|
}else if(item.state == ModState.circularDependencies){
|
||||||
|
return "@mod.circulardependencies";
|
||||||
|
}else if(item.state == ModState.incompleteDependencies){
|
||||||
|
return "@mod.incompletedependencies";
|
||||||
}else if(item.hasUnmetDependencies()){
|
}else if(item.hasUnmetDependencies()){
|
||||||
return "@mod.unmetdependencies";
|
return "@mod.unmetdependencies";
|
||||||
}else if(item.hasContentErrors()){
|
}else if(item.hasContentErrors()){
|
||||||
@@ -346,6 +350,10 @@ public class ModsDialog extends BaseDialog{
|
|||||||
return "@mod.blacklisted.details";
|
return "@mod.blacklisted.details";
|
||||||
}else if(!item.isSupported()){
|
}else if(!item.isSupported()){
|
||||||
return Core.bundle.format("mod.requiresversion.details", item.meta.minGameVersion);
|
return Core.bundle.format("mod.requiresversion.details", item.meta.minGameVersion);
|
||||||
|
}else if(item.state == ModState.circularDependencies){
|
||||||
|
return "@mod.circulardependencies.details";
|
||||||
|
}else if(item.state == ModState.incompleteDependencies){
|
||||||
|
return Core.bundle.format("mod.incompletedependencies.details", item.missingDependencies.toString(", "));
|
||||||
}else if(item.hasUnmetDependencies()){
|
}else if(item.hasUnmetDependencies()){
|
||||||
return Core.bundle.format("mod.missingdependencies.details", item.missingDependencies.toString(", "));
|
return Core.bundle.format("mod.missingdependencies.details", item.missingDependencies.toString(", "));
|
||||||
}else if(item.hasContentErrors()){
|
}else if(item.hasContentErrors()){
|
||||||
|
|||||||
Reference in New Issue
Block a user