Fully implemented safe content loading

This commit is contained in:
Anuken
2019-12-15 13:54:51 -05:00
parent bcc8f65ac8
commit 1c1db3990f
43 changed files with 234 additions and 62 deletions

View File

@@ -102,6 +102,9 @@ mod.disable = Disable
mod.delete.error = Unable to delete mod. File may be in use. mod.delete.error = Unable to delete mod. File may be in use.
mod.requiresversion = [scarlet]Requires min game version: [accent]{0} mod.requiresversion = [scarlet]Requires min game version: [accent]{0}
mod.missingdependencies = [scarlet]Missing dependencies: {0} mod.missingdependencies = [scarlet]Missing dependencies: {0}
mod.erroredcontent = [scarlet]Content Errors
mod.errors = Errors have occurred loading content.
mod.noerrorplay = [scarlet]You have mods with errors.[] Either disable the affected mods or fix the errors before playing.
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.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.enable = Enable
mod.requiresrestart = The game will now close to apply the mod changes. mod.requiresrestart = The game will now close to apply the mod changes.

View File

@@ -315,7 +315,7 @@ waves.invalid = Неверные волны в буфере обмена.
waves.copied = Волны скопированы. waves.copied = Волны скопированы.
waves.none = Враги не были определены.\nОбратите внимание, что пустые волны будут автоматически заменены обычной волной. waves.none = Враги не были определены.\nОбратите внимание, что пустые волны будут автоматически заменены обычной волной.
editor.default = [lightgray]<По умолчанию> editor.default = [lightgray]<По умолчанию>
details = Подробная информация details = Подробности
edit = Редактировать… edit = Редактировать…
editor.name = Название: editor.name = Название:
editor.spawn = Создать боевую единицу editor.spawn = Создать боевую единицу

View File

@@ -8,6 +8,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.ai.*; import io.anuke.mindustry.ai.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
@@ -195,6 +196,7 @@ public class Vars implements Loadable{
public static void init(){ public static void init(){
Serialization.init(); Serialization.init();
DefaultSerializers.typeMappings.put("io.anuke.mindustry.type.ContentType", "io.anuke.mindustry.ctype.ContentType");
if(loadLocales){ if(loadLocales){
//load locales //load locales

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;

View File

@@ -11,6 +11,7 @@ import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
@@ -22,7 +23,6 @@ import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID; import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.modules.*; import io.anuke.mindustry.world.modules.*;

View File

@@ -374,6 +374,37 @@ public class UI implements ApplicationListener, Loadable{
}}.show(); }}.show();
} }
public void showExceptions(String text, String... messages){
loadfrag.hide();
new Dialog(""){{
setFillParent(true);
cont.margin(15);
cont.add("$error.title").colspan(2);
cont.row();
cont.addImage().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add(text).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.row();
//cont.pane(p -> {
for(int i = 0; i < messages.length; i += 2){
String btext = messages[i];
String details = messages[i + 1];
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(details).color(Color.lightGray).left()), true);
cont.add(btext).right();
cont.addButton("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().left();
cont.row();
cont.add(col).colspan(2).pad(2);
cont.row();
}
//}).colspan(2);
cont.addButton("$ok", this::hide).size(300, 50).fillX().colspan(2);
}}.show();
}
public void showText(String titleText, String text){ public void showText(String titleText, String text){
showText(titleText, text, Align.center); showText(titleText, text, Align.center);
} }

View File

@@ -4,7 +4,6 @@ import io.anuke.arc.files.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.*;
/** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */ /** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.type; package io.anuke.mindustry.ctype;
/** Do not rearrange, ever! */ /** Do not rearrange, ever! */
public enum ContentType{ public enum ContentType{
@@ -13,7 +13,8 @@ public enum ContentType{
effect, effect,
zone, zone,
loadout, loadout,
typeid; typeid,
error;
public static final ContentType[] all = values(); public static final ContentType[] all = values();
} }

View File

@@ -12,6 +12,7 @@ import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.audio.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.Content; import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.*; import io.anuke.mindustry.entities.effect.*;

View File

@@ -9,6 +9,7 @@ import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.entities.units.*;

View File

@@ -15,6 +15,7 @@ import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.*; import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;

View File

@@ -6,7 +6,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;

View File

@@ -13,12 +13,12 @@ import io.anuke.arc.util.io.Streams.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Schematic.*; import io.anuke.mindustry.game.Schematic.*;
import io.anuke.mindustry.input.*; import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.Placement.*; import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.production.*; import io.anuke.mindustry.world.blocks.production.*;

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.util.serialization.Json;
import io.anuke.arc.util.serialization.Json.Serializable; import io.anuke.arc.util.serialization.Json.Serializable;
import io.anuke.arc.util.serialization.JsonValue; import io.anuke.arc.util.serialization.JsonValue;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.type.BaseUnit; import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.util.serialization.Json.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;

View File

@@ -6,10 +6,10 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.MapIO.*; import io.anuke.mindustry.io.MapIO.*;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.LegacyColorMapper.*; import io.anuke.mindustry.world.LegacyColorMapper.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.util.io.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.io;
import io.anuke.annotations.Annotations.ReadClass; import io.anuke.annotations.Annotations.ReadClass;
import io.anuke.annotations.Annotations.WriteClass; import io.anuke.annotations.Annotations.WriteClass;
import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.Effects; import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect; import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.type.Bullet; import io.anuke.mindustry.entities.type.Bullet;

View File

@@ -1,8 +1,8 @@
package io.anuke.mindustry.io.versions; package io.anuke.mindustry.io.versions;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID; import io.anuke.mindustry.type.TypeID;
import java.io.*; import java.io.*;

View File

@@ -13,15 +13,15 @@ import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.updateEditorOnChange; import static io.anuke.mindustry.Vars.*;
public abstract class FilterOption{ public abstract class FilterOption{
public static final Boolf<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)); public static final Boolf<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full));
public static final Boolf<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)); public static final Boolf<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full));
public static final Boolf<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full))); public static final Boolf<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)));
public static final Boolf<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full))); public static final Boolf<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)));
public static final Boolf<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full))); public static final Boolf<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)));
public static final Boolf<Block> oresOnly = b -> b instanceof OverlayFloor && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)); public static final Boolf<Block> oresOnly = b -> b instanceof OverlayFloor && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full));
public static final Boolf<Block> anyOptional = b -> floorsOnly.get(b) || wallsOnly.get(b) || oresOnly.get(b) || b == Blocks.air; public static final Boolf<Block> anyOptional = b -> floorsOnly.get(b) || wallsOnly.get(b) || oresOnly.get(b) || b == Blocks.air;
public abstract void build(Table table); public abstract void build(Table table);

File diff suppressed because one or more lines are too long

View File

@@ -138,26 +138,20 @@ public class ContentParser{
} }
//try to parse "item/amount" syntax //try to parse "item/amount" syntax
try{ if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){
if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){ String[] split = jsonData.asString().split("/");
String[] split = jsonData.asString().split("/");
return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}"); return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}");
}
}catch(Throwable ignored){
} }
//try to parse "liquid/amount" syntax //try to parse "liquid/amount" syntax
try{ if(jsonData.isString() && jsonData.asString().contains("/")){
if(jsonData.isString() && jsonData.asString().contains("/")){ String[] split = jsonData.asString().split("/");
String[] split = jsonData.asString().split("/"); if(type == LiquidStack.class){
if(type == LiquidStack.class){ return (T)fromJson(LiquidStack.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
return (T)fromJson(LiquidStack.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}"); }else if(type == ConsumeLiquid.class){
}else if(type == ConsumeLiquid.class){ return (T)fromJson(ConsumeLiquid.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
return (T)fromJson(ConsumeLiquid.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
}
} }
}catch(Throwable ignored){
} }
if(Content.class.isAssignableFrom(type)){ if(Content.class.isAssignableFrom(type)){
@@ -168,7 +162,7 @@ public class ContentParser{
T two = (T)Vars.content.getByName(ctype, jsonData.asString()); T two = (T)Vars.content.getByName(ctype, jsonData.asString());
if(two != null) return two; if(two != null) return two;
throw new IllegalArgumentException("\"" + jsonData.name + "\": No " + ctype + " found with name '" + jsonData.asString() + "'."); throw new IllegalArgumentException("\"" + jsonData.name + "\": No " + ctype + " found with name '" + jsonData.asString() + "'.\nMake sure '" + jsonData.asString() + "' is spelled correctly, and that it really exists!\nThis may also occur because its file failed to parse.");
} }
} }
@@ -442,7 +436,10 @@ public class ContentParser{
public void markError(Content content, LoadedMod mod, FileHandle file, Throwable error){ public void markError(Content content, LoadedMod mod, FileHandle file, Throwable error){
content.minfo.mod = mod; content.minfo.mod = mod;
content.minfo.sourceFile = file; content.minfo.sourceFile = file;
content.minfo.error = Strings.parseException(error, true); content.minfo.error = makeError(error, file);
if(mod != null){
mod.erroredContent.add(content);
}
} }
public void markError(Content content, Throwable error){ public void markError(Content content, Throwable error){
@@ -451,6 +448,24 @@ public class ContentParser{
} }
} }
private String makeError(Throwable t, FileHandle file){
StringBuilder builder = new StringBuilder();
builder.append("[lightgray]").append("File: ").append(file.name()).append("[]\n\n");
if(t.getMessage() != null && t instanceof JsonParseException){
builder.append("[accent][[JsonParse][] ").append(":\n").append(t.getMessage());
}else{
Array<Throwable> causes = Strings.getCauses(t);
for(Throwable e : causes){
builder.append("[accent][[").append(e.getClass().getSimpleName().replace("Exception", ""))
.append("][] ")
.append(e.getMessage() != null ?
e.getMessage().replace("io.anuke.mindustry.", "").replace("io.anuke.arc.", "") : "").append("\n");
}
}
return builder.toString();
}
private <T extends MappableContent> T locate(ContentType type, String name){ private <T extends MappableContent> T locate(ContentType type, String name){
T first = Vars.content.getByName(type, name); //try vanilla replacement T first = Vars.content.getByName(type, name); //try vanilla replacement
return first != null ? first : Vars.content.getByName(type, currentMod.name + "-" + name); return first != null ? first : Vars.content.getByName(type, currentMod.name + "-" + name);

View File

@@ -9,19 +9,22 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Texture.*; import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.g2d.TextureAtlas.*; import io.anuke.arc.graphics.g2d.TextureAtlas.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Jval.*; import io.anuke.arc.util.serialization.Jval.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.graphics.MultiPacker.*; import io.anuke.mindustry.graphics.MultiPacker.*;
import io.anuke.mindustry.plugin.*; import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@@ -42,6 +45,11 @@ public class Mods implements Loadable{
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>(); private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
private boolean requiresReload; private boolean requiresReload;
public Mods(){
Events.on(ClientLoadEvent.class, e -> Core.app.post(this::checkWarnings));
Events.on(ContentReloadEvent.class, e -> Core.app.post(this::checkWarnings));
}
/** 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(). */
public FileHandle getConfig(Mod mod){ public FileHandle getConfig(Mod mod){
@@ -260,7 +268,7 @@ public class Mods implements Loadable{
mod.state = mod.state =
!mod.isSupported() ? ModState.unsupported : !mod.isSupported() ? ModState.unsupported :
mod.hasUnmetDependencies() ? ModState.missingDependencies : mod.hasUnmetDependencies() ? ModState.missingDependencies :
!mod.enabled() /*TODO check disabled state!*/ ? ModState.disabled : !mod.shouldBeEnabled() ? ModState.disabled :
ModState.enabled; ModState.enabled;
} }
} }
@@ -333,13 +341,68 @@ public class Mods implements Loadable{
try{ try{
PropertiesUtils.load(bundle.getProperties(), file.reader()); PropertiesUtils.load(bundle.getProperties(), file.reader());
}catch(Exception e){ }catch(Exception e){
throw new RuntimeException("Error loading bundle: " + file + "/" + locale, e); Log.err("Error loading bundle: " + file + "/" + locale, e);
} }
} }
bundle = bundle.getParent(); bundle = bundle.getParent();
} }
} }
/** Check all warnings related to content and show relevant dialogs. Client only. */
private void checkWarnings(){
//show 'scripts have errored' info
if(scripts != null && scripts.hasErrored()){
Core.settings.getBoolOnce("scripts-errored2", () -> ui.showErrorMessage("$mod.scripts.unsupported"));
}
//show list of errored content
if(mods.contains(LoadedMod::hasContentErrors)){
ui.loadfrag.hide();
new Dialog(""){{
setFillParent(true);
cont.margin(15);
cont.add("$error.title");
cont.row();
cont.addImage().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add("$mod.errors").wrap().growX().center().get().setAlignment(Align.center);
cont.row();
cont.pane(p -> {
mods.each(m -> m.enabled() && m.hasContentErrors(), m -> {
p.add(m.name).color(Pal.accent).left();
p.row();
p.addImage().fillX().pad(4).color(Pal.accent);
p.row();
p.table(d -> {
d.left().marginLeft(15f);
for(Content c : m.erroredContent){
d.add(c.minfo.sourceFile.nameWithoutExtension()).left().padRight(10);
d.addImageTextButton("$details", Icon.arrowDownSmall, Styles.transt, () -> {
new Dialog(""){{
setFillParent(true);
cont.pane(e -> e.add(c.minfo.error)).grow();
cont.row();
cont.addImageTextButton("$ok", Icon.backSmall, this::hide).size(240f, 60f);
}}.show();
}).size(190f, 50f).left().marginLeft(6);
d.row();
}
}).left();
p.row();
});
});
cont.row();
cont.addButton("$ok", this::hide).size(300, 50);
}}.show();
}
}
public boolean hasContentErrors(){
return mods.contains(LoadedMod::hasContentErrors);
}
/** Reloads all mod content. How does this even work? I refuse to believe that it functions correctly.*/ /** Reloads all mod content. How does this even work? I refuse to believe that it functions correctly.*/
public void reloadContent(){ public void reloadContent(){
//epic memory leak //epic memory leak
@@ -369,10 +432,6 @@ public class Mods implements Loadable{
requiresReload = false; requiresReload = false;
Events.fire(new ContentReloadEvent()); Events.fire(new ContentReloadEvent());
if(scripts != null && scripts.hasErrored()){
Core.app.post(() -> Core.settings.getBoolOnce("scripts-errored", () -> ui.showErrorMessage("$mod.scripts.unsupported")));
}
} }
/** This must be run on the main thread! */ /** This must be run on the main thread! */
@@ -396,7 +455,6 @@ public class Mods implements Loadable{
Core.app.post(() -> { Core.app.post(() -> {
Log.err("Error loading script {0} for mod {1}.", file.name(), mod.meta.name); Log.err("Error loading script {0} for mod {1}.", file.name(), mod.meta.name);
e.printStackTrace(); e.printStackTrace();
//if(!headless) ui.showException(e);
}); });
break; break;
} }
@@ -461,8 +519,10 @@ public class Mods implements Loadable{
}catch(Throwable e){ }catch(Throwable e){
if(current != content.getLastAdded() && content.getLastAdded() != null){ if(current != content.getLastAdded() && content.getLastAdded() != null){
parser.markError(content.getLastAdded(), l.mod, l.file, e); parser.markError(content.getLastAdded(), l.mod, l.file, e);
}else{
ErrorContent error = new ErrorContent();
parser.markError(error, l.mod, l.file, e);
} }
//throw new RuntimeException("Failed to parse content file '" + l.file + "' for mod '" + l.mod.meta.name + "'.", e);
} }
} }
@@ -599,6 +659,8 @@ public class Mods implements Loadable{
public Array<String> missingDependencies = new Array<>(); public Array<String> missingDependencies = new Array<>();
/** Script files to run. */ /** Script files to run. */
public Array<FileHandle> scripts = new Array<>(); public Array<FileHandle> scripts = new Array<>();
/** Content with intialization code. */
public ObjectSet<Content> erroredContent = new ObjectSet<>();
/** Current state of this mod. */ /** Current state of this mod. */
public ModState state = ModState.disabled; public ModState state = ModState.disabled;
@@ -611,13 +673,21 @@ public class Mods implements Loadable{
} }
public boolean enabled(){ public boolean enabled(){
return state == ModState.enabled; return state == ModState.enabled || state == ModState.contentErrors;
}
public boolean shouldBeEnabled(){
return Core.settings.getBool("mod-" + name + "-enabled", true);
} }
public boolean hasUnmetDependencies(){ public boolean hasUnmetDependencies(){
return !missingDependencies.isEmpty(); return !missingDependencies.isEmpty();
} }
public boolean hasContentErrors(){
return !erroredContent.isEmpty();
}
/** @return whether this mod is supported by the game verison */ /** @return whether this mod is supported by the game verison */
public boolean isSupported(){ public boolean isSupported(){
if(Version.build <= 0 || meta.minGameVersion == null) return true; if(Version.build <= 0 || meta.minGameVersion == null) return true;
@@ -711,9 +781,9 @@ public class Mods implements Loadable{
public enum ModState{ public enum ModState{
enabled, enabled,
disabled, contentErrors,
missingDependencies, missingDependencies,
unsupported, unsupported,
contentErrors disabled,
} }
} }

View File

@@ -0,0 +1,12 @@
package io.anuke.mindustry.type;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
/** Represents a blank type of content that has an error. Replaces anything that failed to parse. */
public class ErrorContent extends Content{
@Override
public ContentType getContentType(){
return ContentType.error;
}
}

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
public class Liquid extends UnlockableContent{ public class Liquid extends UnlockableContent{

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.type.Player; import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.graphics.Pal; import io.anuke.mindustry.graphics.Pal;

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.math.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.type;
import io.anuke.arc.func.*; import io.anuke.arc.func.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
public class TypeID extends MappableContent{ public class TypeID extends MappableContent{

View File

@@ -8,6 +8,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.type; package io.anuke.mindustry.type;
import io.anuke.mindustry.ctype.Content; import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.ctype.ContentType;
//currently unimplemented, see trello for implementation plans //currently unimplemented, see trello for implementation plans
public class WeatherEvent extends Content{ public class WeatherEvent extends Content{

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -25,7 +25,7 @@ import static io.anuke.mindustry.gen.Tex.*;
public class Styles{ public class Styles{
public static Drawable black, black9, black8, black6, black3, none, flatDown, flatOver; public static Drawable black, black9, black8, black6, black3, none, flatDown, flatOver;
public static ButtonStyle defaultb, waveb; public static ButtonStyle defaultb, waveb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet; public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt;
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali; public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane; public static ScrollPaneStyle defaultPane, horizontalPane, smallPane;
public static KeybindDialogStyle defaultKeybindDialog; public static KeybindDialogStyle defaultKeybindDialog;
@@ -110,6 +110,14 @@ public class Styles{
fontColor = Color.white; fontColor = Color.white;
disabledFontColor = Color.gray; disabledFontColor = Color.gray;
}}; }};
transt = new TextButtonStyle(){{
down = flatDown;
up = none;
over = flatOver;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
clearTogglet = new TextButtonStyle(){{ clearTogglet = new TextButtonStyle(){{
font = Fonts.def; font = Fonts.def;
fontColor = Color.white; fontColor = Color.white;

View File

@@ -10,9 +10,9 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
public class DatabaseDialog extends FloatingDialog{ public class DatabaseDialog extends FloatingDialog{

View File

@@ -165,6 +165,9 @@ public class ModsDialog extends FloatingDialog{
}else if(mod.hasUnmetDependencies()){ }else if(mod.hasUnmetDependencies()){
t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX(); t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row(); t.row();
}else if(mod.hasContentErrors()){
t.labelWrap("$mod.erroredcontent").growX();
t.row();
} }
}).width(mobile ? 430f : 500f); }).width(mobile ? 430f : 500f);
table.row(); table.row();

View File

@@ -17,6 +17,7 @@ import io.anuke.arc.scene.ui.ImageButton.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;

View File

@@ -88,11 +88,11 @@ public class MenuFragment extends Fragment{
container.defaults().size(size).pad(5).padTop(4f); container.defaults().size(size).pad(5).padTop(4f);
MobileButton MobileButton
play = new MobileButton(Icon.play2, "$campaign", ui.deploy::show), play = new MobileButton(Icon.play2, "$campaign", () -> checkPlay(ui.deploy::show)),
custom = new MobileButton(Icon.playCustom, "$customgame", ui.custom::show), custom = new MobileButton(Icon.playCustom, "$customgame", () -> checkPlay(ui.custom::show)),
maps = new MobileButton(Icon.load, "$loadgame", ui.load::show), maps = new MobileButton(Icon.load, "$loadgame", () -> checkPlay(ui.load::show)),
join = new MobileButton(Icon.add, "$joingame", ui.join::show), join = new MobileButton(Icon.add, "$joingame", () -> checkPlay(ui.join::show)),
editor = new MobileButton(Icon.editor, "$editor", ui.maps::show), editor = new MobileButton(Icon.editor, "$editor", () -> checkPlay(ui.maps::show)),
tools = new MobileButton(Icon.tools, "$settings", ui.settings::show), tools = new MobileButton(Icon.tools, "$settings", ui.settings::show),
mods = new MobileButton(Icon.wiki, "$mods", ui.mods::show), mods = new MobileButton(Icon.wiki, "$mods", ui.mods::show),
donate = new MobileButton(Icon.link, "$website", () -> Core.net.openURI("https://anuke.itch.io/mindustry")), donate = new MobileButton(Icon.link, "$website", () -> Core.net.openURI("https://anuke.itch.io/mindustry")),
@@ -153,13 +153,13 @@ public class MenuFragment extends Fragment{
buttons(t, buttons(t,
new Buttoni("$play", Icon.play2Small, new Buttoni("$play", Icon.play2Small,
new Buttoni("$campaign", Icon.play2Small, ui.deploy::show), new Buttoni("$campaign", Icon.play2Small, () -> checkPlay(ui.deploy::show)),
new Buttoni("$joingame", Icon.addSmall, ui.join::show), new Buttoni("$joingame", Icon.addSmall, () -> checkPlay(ui.join::show)),
new Buttoni("$customgame", Icon.editorSmall, ui.custom::show), new Buttoni("$customgame", Icon.editorSmall, () -> checkPlay(ui.custom::show)),
new Buttoni("$loadgame", Icon.loadSmall, ui.load::show), new Buttoni("$loadgame", Icon.loadSmall, () -> checkPlay(ui.load::show)),
new Buttoni("$tutorial", Icon.infoSmall, control::playTutorial) new Buttoni("$tutorial", Icon.infoSmall, () -> checkPlay(control::playTutorial))
), ),
new Buttoni("$editor", Icon.editorSmall, ui.maps::show), steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null, new Buttoni("$editor", Icon.editorSmall, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show), new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show),
//not enough space for this button //not enough space for this button
//new Buttoni("$schematics", Icon.pasteSmall, ui.schematics::show), //new Buttoni("$schematics", Icon.pasteSmall, ui.schematics::show),
@@ -180,6 +180,14 @@ public class MenuFragment extends Fragment{
}).width(width).growY(); }).width(width).growY();
} }
private void checkPlay(Runnable run){
if(!mods.hasContentErrors()){
run.run();
}else{
ui.showInfo("$mod.noerrorplay");
}
}
private void fadeInMenu(){ private void fadeInMenu(){
submenu.clearActions(); submenu.clearActions();
submenu.actions(Actions.alpha(1f, 0.15f, Interpolation.fade)); submenu.actions(Actions.alpha(1f, 0.15f, Interpolation.fade));

View File

@@ -18,6 +18,7 @@ import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.pooling.*; import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*; import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*;

View File

@@ -47,7 +47,7 @@ public class MechPad extends Block{
@Remote(targets = Loc.both, called = Loc.server) @Remote(targets = Loc.both, called = Loc.server)
public static void onMechFactoryTap(Player player, Tile tile){ public static void onMechFactoryTap(Player player, Tile tile){
if(player == null || !(tile.block() instanceof MechPad) || !checkValidTap(tile, player)) return; if(player == null || tile == null || !(tile.block() instanceof MechPad) || !checkValidTap(tile, player)) return;
MechFactoryEntity entity = tile.ent(); MechFactoryEntity entity = tile.ent();

View File

@@ -1,3 +1,3 @@
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m org.gradle.jvmargs=-Xms256m -Xmx1024m
archash=190918590e8401b1686ecb9167e3c2a9e77eafaa archash=b4f2a66518d2582ea4275193dfee128b186df9b3

View File

@@ -16,7 +16,7 @@ import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.io.SaveIO;
import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.ContentType; import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.BlockPart; import io.anuke.mindustry.world.blocks.BlockPart;