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

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

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.mod.Mods.*;
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.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
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.*;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.modules.*;

View File

@@ -374,6 +374,37 @@ public class UI implements ApplicationListener, Loadable{
}}.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){
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.mindustry.*;
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}. */

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.type;
package io.anuke.mindustry.ctype;
/** Do not rearrange, ever! */
public enum ContentType{
@@ -13,7 +13,8 @@ public enum ContentType{
effect,
zone,
loadout,
typeid;
typeid,
error;
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.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.audio.*;
import io.anuke.arc.math.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*;
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.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
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.content.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
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.pooling.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.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.mindustry.*;
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.type.*;

View File

@@ -13,12 +13,12 @@ import io.anuke.arc.util.io.Streams.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Schematic.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
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.JsonValue;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.type.BaseUnit;
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.content.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*;
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.serialization.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.MapIO.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.LegacyColorMapper.*;
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.core.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
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.WriteClass;
import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.type.Bullet;

View File

@@ -1,8 +1,8 @@
package io.anuke.mindustry.io.versions;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID;
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.blocks.*;
import static io.anuke.mindustry.Vars.updateEditorOnChange;
import static io.anuke.mindustry.Vars.*;
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> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && 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> 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> 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> oresOnly = b -> 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)) && !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)) && !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)) && !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)) && !headless && 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 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{
if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){
String[] split = jsonData.asString().split("/");
if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){
String[] split = jsonData.asString().split("/");
return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}");
}
}catch(Throwable ignored){
return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}");
}
//try to parse "liquid/amount" syntax
try{
if(jsonData.isString() && jsonData.asString().contains("/")){
String[] split = jsonData.asString().split("/");
if(type == LiquidStack.class){
return (T)fromJson(LiquidStack.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
}else if(type == ConsumeLiquid.class){
return (T)fromJson(ConsumeLiquid.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
}
if(jsonData.isString() && jsonData.asString().contains("/")){
String[] split = jsonData.asString().split("/");
if(type == LiquidStack.class){
return (T)fromJson(LiquidStack.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
}else if(type == ConsumeLiquid.class){
return (T)fromJson(ConsumeLiquid.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
}
}catch(Throwable ignored){
}
if(Content.class.isAssignableFrom(type)){
@@ -168,7 +162,7 @@ public class ContentParser{
T two = (T)Vars.content.getByName(ctype, jsonData.asString());
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){
content.minfo.mod = mod;
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){
@@ -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){
T first = Vars.content.getByName(type, name); //try vanilla replacement
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.g2d.*;
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.ArcAnnotate.*;
import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Jval.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.graphics.MultiPacker.*;
import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*;
import java.io.*;
import java.net.*;
@@ -42,6 +45,11 @@ public class Mods implements Loadable{
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
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.
* Call this in init(). */
public FileHandle getConfig(Mod mod){
@@ -260,7 +268,7 @@ public class Mods implements Loadable{
mod.state =
!mod.isSupported() ? ModState.unsupported :
mod.hasUnmetDependencies() ? ModState.missingDependencies :
!mod.enabled() /*TODO check disabled state!*/ ? ModState.disabled :
!mod.shouldBeEnabled() ? ModState.disabled :
ModState.enabled;
}
}
@@ -333,13 +341,68 @@ public class Mods implements Loadable{
try{
PropertiesUtils.load(bundle.getProperties(), file.reader());
}catch(Exception e){
throw new RuntimeException("Error loading bundle: " + file + "/" + locale, e);
Log.err("Error loading bundle: " + file + "/" + locale, e);
}
}
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.*/
public void reloadContent(){
//epic memory leak
@@ -369,10 +432,6 @@ public class Mods implements Loadable{
requiresReload = false;
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! */
@@ -396,7 +455,6 @@ public class Mods implements Loadable{
Core.app.post(() -> {
Log.err("Error loading script {0} for mod {1}.", file.name(), mod.meta.name);
e.printStackTrace();
//if(!headless) ui.showException(e);
});
break;
}
@@ -461,8 +519,10 @@ public class Mods implements Loadable{
}catch(Throwable e){
if(current != content.getLastAdded() && content.getLastAdded() != null){
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<>();
/** Script files to run. */
public Array<FileHandle> scripts = new Array<>();
/** Content with intialization code. */
public ObjectSet<Content> erroredContent = new ObjectSet<>();
/** Current state of this mod. */
public ModState state = ModState.disabled;
@@ -611,13 +673,21 @@ public class Mods implements Loadable{
}
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(){
return !missingDependencies.isEmpty();
}
public boolean hasContentErrors(){
return !erroredContent.isEmpty();
}
/** @return whether this mod is supported by the game verison */
public boolean isSupported(){
if(Version.build <= 0 || meta.minGameVersion == null) return true;
@@ -711,9 +781,9 @@ public class Mods implements Loadable{
public enum ModState{
enabled,
disabled,
contentErrors,
missingDependencies,
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.scene.ui.layout.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ui.*;
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.mindustry.content.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ui.*;
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.scene.ui.layout.Table;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.ctype.UnlockableContent;
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.mindustry.content.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*;
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.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*;
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.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.type;
import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.ctype.ContentType;
//currently unimplemented, see trello for implementation plans
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.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;

View File

@@ -25,7 +25,7 @@ import static io.anuke.mindustry.gen.Tex.*;
public class Styles{
public static Drawable black, black9, black8, black6, black3, none, flatDown, flatOver;
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 ScrollPaneStyle defaultPane, horizontalPane, smallPane;
public static KeybindDialogStyle defaultKeybindDialog;
@@ -110,6 +110,14 @@ public class Styles{
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
transt = new TextButtonStyle(){{
down = flatDown;
up = none;
over = flatOver;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
clearTogglet = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;

View File

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

View File

@@ -165,6 +165,9 @@ public class ModsDialog extends FloatingDialog{
}else if(mod.hasUnmetDependencies()){
t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row();
}else if(mod.hasContentErrors()){
t.labelWrap("$mod.erroredcontent").growX();
t.row();
}
}).width(mobile ? 430f : 500f);
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.util.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.*;
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);
MobileButton
play = new MobileButton(Icon.play2, "$campaign", ui.deploy::show),
custom = new MobileButton(Icon.playCustom, "$customgame", ui.custom::show),
maps = new MobileButton(Icon.load, "$loadgame", ui.load::show),
join = new MobileButton(Icon.add, "$joingame", ui.join::show),
editor = new MobileButton(Icon.editor, "$editor", ui.maps::show),
play = new MobileButton(Icon.play2, "$campaign", () -> checkPlay(ui.deploy::show)),
custom = new MobileButton(Icon.playCustom, "$customgame", () -> checkPlay(ui.custom::show)),
maps = new MobileButton(Icon.load, "$loadgame", () -> checkPlay(ui.load::show)),
join = new MobileButton(Icon.add, "$joingame", () -> checkPlay(ui.join::show)),
editor = new MobileButton(Icon.editor, "$editor", () -> checkPlay(ui.maps::show)),
tools = new MobileButton(Icon.tools, "$settings", ui.settings::show),
mods = new MobileButton(Icon.wiki, "$mods", ui.mods::show),
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,
new Buttoni("$play", Icon.play2Small,
new Buttoni("$campaign", Icon.play2Small, ui.deploy::show),
new Buttoni("$joingame", Icon.addSmall, ui.join::show),
new Buttoni("$customgame", Icon.editorSmall, ui.custom::show),
new Buttoni("$loadgame", Icon.loadSmall, ui.load::show),
new Buttoni("$tutorial", Icon.infoSmall, control::playTutorial)
new Buttoni("$campaign", Icon.play2Small, () -> checkPlay(ui.deploy::show)),
new Buttoni("$joingame", Icon.addSmall, () -> checkPlay(ui.join::show)),
new Buttoni("$customgame", Icon.editorSmall, () -> checkPlay(ui.custom::show)),
new Buttoni("$loadgame", Icon.loadSmall, () -> checkPlay(ui.load::show)),
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),
//not enough space for this button
//new Buttoni("$schematics", Icon.pasteSmall, ui.schematics::show),
@@ -180,6 +180,14 @@ public class MenuFragment extends Fragment{
}).width(width).growY();
}
private void checkPlay(Runnable run){
if(!mods.hasContentErrors()){
run.run();
}else{
ui.showInfo("$mod.noerrorplay");
}
}
private void fadeInMenu(){
submenu.clearActions();
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.pooling.*;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
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)
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();