Merge branches 'master' and 'safe-mod-loading' of https://github.com/Anuken/Mindustry

# Conflicts:
#	core/src/io/anuke/mindustry/mod/Mods.java
This commit is contained in:
Anuken
2019-12-15 13:55:30 -05:00
62 changed files with 420 additions and 253 deletions

View File

@@ -18,7 +18,6 @@ import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.game.Saves.*; import io.anuke.mindustry.game.Saves.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.ui.dialogs.*; import io.anuke.mindustry.ui.dialogs.*;
import java.io.*; import java.io.*;
@@ -144,7 +143,7 @@ public class AndroidLauncher extends AndroidApplication{
useImmersiveMode = true; useImmersiveMode = true;
depth = 0; depth = 0;
hideStatusBar = true; hideStatusBar = true;
errorHandler = ModCrashHandler::handle; //errorHandler = ModCrashHandler::handle;
}}); }});
checkFiles(getIntent()); checkFiles(getIntent());
} }

View File

@@ -100,8 +100,11 @@ mod.enabled = [lightgray]Enabled
mod.disabled = [scarlet]Disabled mod.disabled = [scarlet]Disabled
mod.disable = Disable 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 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

@@ -123,7 +123,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
for(ApplicationListener listener : modules){ for(ApplicationListener listener : modules){
listener.init(); listener.init();
} }
mods.each(Mod::init); mods.eachClass(Mod::init);
finished = true; finished = true;
Events.fire(new ClientLoadEvent()); Events.fire(new ClientLoadEvent());
super.resize(graphics.getWidth(), graphics.getHeight()); super.resize(graphics.getWidth(), graphics.getHeight());

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.*;
@@ -25,6 +26,7 @@ public class ContentLoader{
private Array<Content>[] contentMap = new Array[ContentType.values().length]; private Array<Content>[] contentMap = new Array[ContentType.values().length];
private MappableContent[][] temporaryMapper; private MappableContent[][] temporaryMapper;
private @Nullable LoadedMod currentMod; private @Nullable LoadedMod currentMod;
private @Nullable Content lastAdded;
private ObjectSet<Cons<Content>> initialization = new ObjectSet<>(); private ObjectSet<Cons<Content>> initialization = new ObjectSet<>();
private ContentList[] content = { private ContentList[] content = {
new Fx(), new Fx(),
@@ -114,8 +116,8 @@ public class ContentLoader{
try{ try{
callable.get(content); callable.get(content);
}catch(Throwable e){ }catch(Throwable e){
if(content.mod != null){ if(content.minfo.mod != null){
mods.handleError(new ModLoadException(content, e), content.mod); mods.handleContentError(content, e);
}else{ }else{
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -146,11 +148,27 @@ public class ContentLoader{
//clear all content, currently not used //clear all content, currently not used
} }
/** Get last piece of content created for error-handling purposes. */
public @Nullable Content getLastAdded(){
return lastAdded;
}
/** Remove last content added in case of an exception. */
public void removeLast(){
if(lastAdded != null && contentMap[lastAdded.getContentType().ordinal()].peek() == lastAdded){
contentMap[lastAdded.getContentType().ordinal()].pop();
if(lastAdded instanceof MappableContent){
contentNameMap[lastAdded.getContentType().ordinal()].remove(((MappableContent)lastAdded).name);
}
}
}
public void handleContent(Content content){ public void handleContent(Content content){
this.lastAdded = content;
contentMap[content.getContentType().ordinal()].add(content); contentMap[content.getContentType().ordinal()].add(content);
} }
public void setCurrentMod(LoadedMod mod){ public void setCurrentMod(@Nullable LoadedMod mod){
this.currentMod = mod; this.currentMod = mod;
} }
@@ -163,7 +181,7 @@ public class ContentLoader{
throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')"); throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')");
} }
if(currentMod != null){ if(currentMod != null){
content.mod = currentMod; content.minfo.mod = currentMod;
} }
contentNameMap[content.getContentType().ordinal()].put(content.name, content); contentNameMap[content.getContentType().ordinal()].put(content.name, content);
} }

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

@@ -219,7 +219,7 @@ public class NetServer implements ApplicationListener{
@Override @Override
public void init(){ public void init(){
mods.each(mod -> mod.registerClientCommands(clientCommands)); mods.eachClass(mod -> mod.registerClientCommands(clientCommands));
} }
private void registerCommands(){ private void registerCommands(){

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,16 +4,14 @@ 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}. */
public abstract class Content implements Comparable<Content>{ public abstract class Content implements Comparable<Content>{
public final short id; public final short id;
/** The mod that loaded this piece of content. */ /** Info on which mod this content was loaded from. */
public @Nullable LoadedMod mod; public @NonNull ModContentInfo minfo = new ModContentInfo();
/** File that this content was loaded from. */
public @Nullable FileHandle sourceFile;
public Content(){ public Content(){
this.id = (short)Vars.content.getBy(getContentType()).size; this.id = (short)Vars.content.getBy(getContentType()).size;
@@ -37,6 +35,11 @@ public abstract class Content implements Comparable<Content>{
public void load(){ public void load(){
} }
/** @return whether an error ocurred during mod loading. */
public boolean hasErrored(){
return minfo.error != null;
}
@Override @Override
public int compareTo(Content c){ public int compareTo(Content c){
return Integer.compare(id, c.id); return Integer.compare(id, c.id);
@@ -46,4 +49,13 @@ public abstract class Content implements Comparable<Content>{
public String toString(){ public String toString(){
return getContentType().name() + "#" + id; return getContentType().name() + "#" + id;
} }
public static class ModContentInfo{
/** The mod that loaded this piece of content. */
public @Nullable LoadedMod mod;
/** File that this content was loaded from. */
public @Nullable FileHandle sourceFile;
/** The error that occurred during loading, if applicable. Null if no error occurred. */
public @Nullable String error;
}
} }

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.*;
@@ -137,7 +138,7 @@ public abstract class BulletType extends Content{
} }
for(int i = 0; i < lightining; i++){ for(int i = 0; i < lightining; i++){
Lightning.create(b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength); Lightning.createLighting(Lightning.nextSeed(), b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength);
} }
} }
@@ -150,7 +151,7 @@ public abstract class BulletType extends Content{
public void update(Bullet b){ public void update(Bullet b){
if(homingPower > 0.0001f){ if(homingPower > 0.0001f){
TargetTrait target = Units.closestTarget(b.getTeam(), b.x, b.y, homingRange); TargetTrait target = Units.closestTarget(b.getTeam(), b.x, b.y, homingRange, e -> !e.isFlying() || collidesAir);
if(target != null){ if(target != null){
b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f)); b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f));
} }

View File

@@ -44,7 +44,11 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
/** Create a lighting branch at a location. Use Team.none to damage everyone. */ /** Create a lighting branch at a location. Use Team.none to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){ public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
Call.createLighting(lastSeed++, team, color, damage, x, y, targetAngle, length); Call.createLighting(nextSeed(), team, color, damage, x, y, targetAngle, length);
}
public static int nextSeed(){
return lastSeed++;
} }
/** Do not invoke! */ /** Do not invoke! */

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.");
} }
} }
@@ -258,11 +252,15 @@ public class ContentParser{
if(research[0] != null){ if(research[0] != null){
Block parent = find(ContentType.block, research[0]); Block parent = find(ContentType.block, research[0]);
TechNode baseNode = TechTree.create(parent, block); TechNode baseNode = TechTree.create(parent, block);
LoadedMod cur = currentMod;
postreads.add(() -> { postreads.add(() -> {
currentContent = block;
currentMod = cur;
TechNode parnode = TechTree.all.find(t -> t.block == parent); TechNode parnode = TechTree.all.find(t -> t.block == parent);
if(parnode == null){ if(parnode == null){
throw new ModLoadException("Block '" + parent.name + "' isn't in the tech tree, but '" + block.name + "' requires it to be researched.", block); throw new IllegalArgumentException("Block '" + parent.name + "' isn't in the tech tree, but '" + block.name + "' requires it to be researched.");
} }
if(!parnode.children.contains(baseNode)){ if(!parnode.children.contains(baseNode)){
parnode.children.add(baseNode); parnode.children.add(baseNode);
@@ -304,7 +302,7 @@ public class ContentParser{
if(value.has(key)){ if(value.has(key)){
return value.getString(key); return value.getString(key);
}else{ }else{
throw new IllegalArgumentException((currentContent == null ? "" : currentContent.sourceFile + ": ") + "You are missing a \"" + key + "\". It must be added before the file can be parsed."); throw new IllegalArgumentException("You are missing a \"" + key + "\". It must be added before the file can be parsed.");
} }
} }
@@ -382,13 +380,18 @@ public class ContentParser{
} }
} }
public void finishParsing(){ private void attempt(Runnable run){
try{ try{
reads.each(Runnable::run); run.run();
postreads.each(Runnable::run); }catch(Throwable t){
}catch(Exception e){ //don't overwrite double errors
Vars.mods.handleError(new ModLoadException("Error occurred parsing content: " + currentContent, currentContent, e), currentMod); markError(currentContent, t);
} }
}
public void finishParsing(){
reads.each(this::attempt);
postreads.each(this::attempt);
reads.clear(); reads.clear();
postreads.clear(); postreads.clear();
toBeParsed.clear(); toBeParsed.clear();
@@ -421,14 +424,48 @@ public class ContentParser{
currentMod = mod; currentMod = mod;
boolean located = locate(type, name) != null; boolean located = locate(type, name) != null;
Content c = parsers.get(type).parse(mod.name, name, value); Content c = parsers.get(type).parse(mod.name, name, value);
c.minfo.sourceFile = file;
toBeParsed.add(c); toBeParsed.add(c);
if(!located){ if(!located){
c.sourceFile = file; c.minfo.mod = mod;
c.mod = mod;
} }
return c; return c;
} }
public void markError(Content content, LoadedMod mod, FileHandle file, Throwable error){
content.minfo.mod = mod;
content.minfo.sourceFile = file;
content.minfo.error = makeError(error, file);
if(mod != null){
mod.erroredContent.add(content);
}
}
public void markError(Content content, Throwable error){
if(content.minfo != null && !content.hasErrored()){
markError(content, content.minfo.mod, content.minfo.sourceFile, error);
}
}
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

@@ -1,19 +1,9 @@
package io.anuke.mindustry.mod; package io.anuke.mindustry.mod;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.ui.*;
public class ModCrashHandler{ public class ModCrashHandler{
public static void handle(Throwable t){ public static void handle(Throwable t){
/*
Array<Throwable> list = Strings.getCauses(t); Array<Throwable> list = Strings.getCauses(t);
Throwable modCause = list.find(e -> e instanceof ModLoadException); Throwable modCause = list.find(e -> e instanceof ModLoadException);
@@ -62,6 +52,6 @@ public class ModCrashHandler{
}); });
}else{ }else{
throw new RuntimeException(t); throw new RuntimeException(t);
} }*/
} }
} }

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.*;
@@ -33,16 +36,20 @@ public class Mods implements Loadable{
private @Nullable Scripts scripts; private @Nullable Scripts scripts;
private ContentParser parser = new ContentParser(); private ContentParser parser = new ContentParser();
private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>(); private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>();
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites"); private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites", "sprites-override");
private int totalSprites; private int totalSprites;
private MultiPacker packer; private MultiPacker packer;
private Array<LoadedMod> loaded = new Array<>(); private Array<LoadedMod> mods = new Array<>();
private Array<LoadedMod> disabled = new Array<>();
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){
@@ -53,19 +60,19 @@ public class Mods implements Loadable{
/** Returns a list of files per mod subdirectory. */ /** Returns a list of files per mod subdirectory. */
public void listFiles(String directory, Cons2<LoadedMod, FileHandle> cons){ public void listFiles(String directory, Cons2<LoadedMod, FileHandle> cons){
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
FileHandle file = mod.root.child(directory); FileHandle file = mod.root.child(directory);
if(file.exists()){ if(file.exists()){
for(FileHandle child : file.list()){ for(FileHandle child : file.list()){
cons.get(mod, child); cons.get(mod, child);
} }
} }
} });
} }
/** @return the loaded mod found by class, or null if not found. */ /** @return the loaded mod found by class, or null if not found. */
public @Nullable LoadedMod getMod(Class<? extends Mod> type){ public @Nullable LoadedMod getMod(Class<? extends Mod> type){
return loaded.find(l -> l.mod != null && l.mod.getClass() == type); return mods.find(m -> m.enabled() && m.main != null && m.main.getClass() == type);//loaded.find(l -> l.mod != null && l.mod.getClass() == type);
} }
/** Imports an external mod file.*/ /** Imports an external mod file.*/
@@ -77,7 +84,7 @@ public class Mods implements Loadable{
file.copyTo(dest); file.copyTo(dest);
try{ try{
loaded.add(loadMod(dest)); mods.add(loadMod(dest));
requiresReload = true; requiresReload = true;
}catch(IOException e){ }catch(IOException e){
dest.delete(); dest.delete();
@@ -91,19 +98,19 @@ public class Mods implements Loadable{
/** Repacks all in-game sprites. */ /** Repacks all in-game sprites. */
@Override @Override
public void loadAsync(){ public void loadAsync(){
if(loaded.isEmpty()) return; if(!mods.contains(LoadedMod::enabled)) return;
Time.mark(); Time.mark();
packer = new MultiPacker(); packer = new MultiPacker();
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
Array<FileHandle> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png")); Array<FileHandle> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png"));
Array<FileHandle> overrides = mod.root.child("sprites-override").findAll(f -> f.extension().equals("png")); Array<FileHandle> overrides = mod.root.child("sprites-override").findAll(f -> f.extension().equals("png"));
packSprites(sprites, mod, true); packSprites(sprites, mod, true);
packSprites(overrides, mod, false); packSprites(overrides, mod, false);
Log.debug("Packed {0} images for mod '{1}'.", sprites.size + overrides.size, mod.meta.name); Log.debug("Packed {0} images for mod '{1}'.", sprites.size + overrides.size, mod.meta.name);
totalSprites += sprites.size + overrides.size; totalSprites += sprites.size + overrides.size;
} });
for(AtlasRegion region : Core.atlas.getRegions()){ for(AtlasRegion region : Core.atlas.getRegions()){
PageType type = getPage(region); PageType type = getPage(region);
@@ -149,7 +156,7 @@ public class Mods implements Loadable{
//generate new icons //generate new icons
for(Array<Content> arr : content.getContentMap()){ for(Array<Content> arr : content.getContentMap()){
arr.each(c -> { arr.each(c -> {
if(c instanceof UnlockableContent && c.mod != null){ if(c instanceof UnlockableContent && c.minfo.mod != null){
UnlockableContent u = (UnlockableContent)c; UnlockableContent u = (UnlockableContent)c;
u.createIcons(packer); u.createIcons(packer);
} }
@@ -198,8 +205,7 @@ public class Mods implements Loadable{
ui.showErrorMessage("$mod.delete.error"); ui.showErrorMessage("$mod.delete.error");
return; return;
} }
loaded.remove(mod); mods.remove(mod);
disabled.remove(mod);
requiresReload = true; requiresReload = true;
} }
@@ -225,11 +231,7 @@ public class Mods implements Loadable{
Log.debug("[Mods] Loading mod {0}", file); Log.debug("[Mods] Loading mod {0}", file);
try{ try{
LoadedMod mod = loadMod(file); LoadedMod mod = loadMod(file);
if(mod.enabled() || headless){ mods.add(mod);
loaded.add(mod);
}else{
disabled.add(mod);
}
}catch(Exception e){ }catch(Exception e){
Log.err("Failed to load mod file {0}. Skipping.", file); Log.err("Failed to load mod file {0}. Skipping.", file);
Log.err(e); Log.err(e);
@@ -240,11 +242,7 @@ public class Mods implements Loadable{
for(FileHandle file : platform.getWorkshopContent(LoadedMod.class)){ for(FileHandle file : platform.getWorkshopContent(LoadedMod.class)){
try{ try{
LoadedMod mod = loadMod(file); LoadedMod mod = loadMod(file);
if(mod.enabled()){ mods.add(mod);
loaded.add(mod);
}else{
disabled.add(mod);
}
mod.addSteamID(file.name()); mod.addSteamID(file.name());
}catch(Exception e){ }catch(Exception e){
Log.err("Failed to load mod workshop file {0}. Skipping.", file); Log.err("Failed to load mod workshop file {0}. Skipping.", file);
@@ -252,28 +250,27 @@ public class Mods implements Loadable{
} }
} }
resolveDependencies(); resolveModState();
sortMods();
//sort mods to make sure servers handle them properly.
loaded.sort(Structs.comparing(m -> m.name));
buildFiles(); buildFiles();
} }
private void resolveDependencies(){ private void sortMods(){
Array<LoadedMod> incompatible = loaded.select(m -> !m.isSupported()); //sort mods to make sure servers handle them properly.
loaded.removeAll(incompatible); mods.sort(Structs.comps(Structs.comparingInt(m -> -m.state.ordinal()), Structs.comparing(m -> m.name)));
disabled.addAll(incompatible); }
for(LoadedMod mod : Array.<LoadedMod>withArrays(loaded, disabled)){ private void resolveModState(){
updateDependencies(mod); mods.each(this::updateDependencies);
for(LoadedMod mod : mods){
mod.state =
!mod.isSupported() ? ModState.unsupported :
mod.hasUnmetDependencies() ? ModState.missingDependencies :
!mod.shouldBeEnabled() ? ModState.disabled :
ModState.enabled;
} }
disabled.addAll(loaded.select(LoadedMod::hasUnmetDependencies));
loaded.removeAll(LoadedMod::hasUnmetDependencies);
disabled.each(mod -> setEnabled(mod, false));
disabled.distinct();
loaded.distinct();
} }
private void updateDependencies(LoadedMod mod){ private void updateDependencies(LoadedMod mod){
@@ -298,16 +295,16 @@ public class Mods implements Loadable{
private Array<LoadedMod> orderedMods(){ private Array<LoadedMod> orderedMods(){
ObjectSet<LoadedMod> visited = new ObjectSet<>(); ObjectSet<LoadedMod> visited = new ObjectSet<>();
Array<LoadedMod> result = new Array<>(); Array<LoadedMod> result = new Array<>();
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
if(!visited.contains(mod)){ if(!visited.contains(mod)){
topoSort(mod, result, visited); topoSort(mod, result, visited);
} }
} });
return result; return result;
} }
private LoadedMod locateMod(String name){ private LoadedMod locateMod(String name){
return loaded.find(mod -> mod.name.equals(name)); return mods.find(mod -> mod.enabled() && mod.name.equals(name));
} }
private void buildFiles(){ private void buildFiles(){
@@ -344,21 +341,75 @@ 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
//TODO make it less epic //TODO make it less epic
Core.atlas = new TextureAtlas(Core.files.internal("sprites/sprites.atlas")); Core.atlas = new TextureAtlas(Core.files.internal("sprites/sprites.atlas"));
loaded.clear(); mods.clear();
disabled.clear();
load(); load();
Sounds.dispose(); Sounds.dispose();
Sounds.load(); Sounds.load();
@@ -381,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(() -> ui.showErrorMessage("$mod.scripts.unsupported"));
}
} }
/** This must be run on the main thread! */ /** This must be run on the main thread! */
@@ -392,7 +439,7 @@ public class Mods implements Loadable{
Time.mark(); Time.mark();
try{ try{
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
if(mod.root.child("scripts").exists()){ if(mod.root.child("scripts").exists()){
content.setCurrentMod(mod); content.setCurrentMod(mod);
mod.scripts = mod.root.child("scripts").findAll(f -> f.extension().equals("js")); mod.scripts = mod.root.child("scripts").findAll(f -> f.extension().equals("js"));
@@ -408,13 +455,12 @@ 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;
} }
} }
} }
} });
}finally{ }finally{
content.setCurrentMod(null); content.setCurrentMod(null);
} }
@@ -464,56 +510,43 @@ public class Mods implements Loadable{
//make sure mod content is in proper order //make sure mod content is in proper order
runs.sort(); runs.sort();
runs.each(l -> safeRun(l.mod, () -> { for(LoadRun l : runs){
Content current = content.getLastAdded();
try{ try{
//this binds the content but does not load it entirely //this binds the content but does not load it entirely
Content loaded = parser.parse(l.mod, l.file.nameWithoutExtension(), l.file.readString("UTF-8"), l.file, l.type); Content loaded = parser.parse(l.mod, l.file.nameWithoutExtension(), l.file.readString("UTF-8"), l.file, l.type);
Log.debug("[{0}] Loaded '{1}'.", l.mod.meta.name, Log.debug("[{0}] Loaded '{1}'.", l.mod.meta.name, (loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded));
(loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded)); }catch(Throwable e){
}catch(Exception e){ if(current != content.getLastAdded() && content.getLastAdded() != null){
throw new RuntimeException("Failed to parse content file '" + l.file + "' for mod '" + l.mod.meta.name + "'.", e); parser.markError(content.getLastAdded(), l.mod, l.file, e);
}else{
ErrorContent error = new ErrorContent();
parser.markError(error, l.mod, l.file, e);
}
} }
})); }
//this finishes parsing content fields //this finishes parsing content fields
parser.finishParsing(); parser.finishParsing();
} }
/** @return all loaded mods. */ public void handleContentError(Content content, Throwable error){
public Array<LoadedMod> all(){ parser.markError(content, error);
return loaded;
}
/** @return all disabled mods. */
public Array<LoadedMod> disabled(){
return disabled;
}
/** @return a list of mod names only, without versions. */
public Array<String> getModNames(){
return loaded.select(l -> !l.meta.hidden).map(l -> l.name + ":" + l.meta.version);
} }
/** @return a list of mods and versions, in the format name:version. */ /** @return a list of mods and versions, in the format name:version. */
public Array<String> getModStrings(){ public Array<String> getModStrings(){
return loaded.select(l -> !l.meta.hidden).map(l -> l.name + ":" + l.meta.version); return mods.select(l -> !l.meta.hidden && l.enabled()).map(l -> l.name + ":" + l.meta.version);
} }
/** Makes a mod enabled or disabled. shifts it.*/ /** Makes a mod enabled or disabled. shifts it.*/
public void setEnabled(LoadedMod mod, boolean enabled){ public void setEnabled(LoadedMod mod, boolean enabled){
if(mod.enabled() != enabled){ if(mod.enabled() != enabled){
Core.settings.putSave("mod-" + mod.name + "-enabled", enabled); Core.settings.putSave("mod-" + mod.name + "-enabled", enabled);
Core.settings.save();
requiresReload = true; requiresReload = true;
if(!enabled){ mod.state = enabled ? ModState.enabled : ModState.disabled;
loaded.remove(mod); mods.each(this::updateDependencies);
if(!disabled.contains(mod)) disabled.add(mod); sortMods();
}else{
if(!loaded.contains(mod)) loaded.add(mod);
disabled.remove(mod);
}
loaded.each(this::updateDependencies);
disabled.each(this::updateDependencies);
} }
} }
@@ -530,43 +563,25 @@ public class Mods implements Loadable{
return result; return result;
} }
/** Iterates through each mod with a main class.*/ public Array<LoadedMod> list(){
public void each(Cons<Mod> cons){ return mods;
loaded.each(p -> p.mod != null, p -> safeRun(p, () -> cons.get(p.mod)));
} }
public void handleError(Throwable t, LoadedMod mod){ /** Iterates through each mod with a main class. */
Array<Throwable> causes = Strings.getCauses(t); public void eachClass(Cons<Mod> cons){
Content content = null; mods.each(p -> p.main != null, p -> contextRun(p, () -> cons.get(p.main)));
for(Throwable e : causes){
if(e instanceof ModLoadException && ((ModLoadException) e).content != null){
content = ((ModLoadException) e).content;
}
}
String realCause = "<???>";
for(int i = causes.size -1 ; i >= 0; i--){
if(causes.get(i).getMessage() != null){
realCause = causes.get(i).getMessage();
break;
}
}
setEnabled(mod, false);
if(content != null){
throw new ModLoadException(Strings.format("Error loading '{0}' from mod '{1}' ({2}):\n{3}",
content, mod.meta.name, content.sourceFile == null ? "<unknown file>" : content.sourceFile.name(), realCause), content, t);
}else{
throw new ModLoadException("Error loading mod " + mod.meta.name, t);
}
} }
public void safeRun(LoadedMod mod, Runnable run){ /** Iterates through each enabled mod. */
public void eachEnabled(Cons<LoadedMod> cons){
mods.each(LoadedMod::enabled, cons);
}
public void contextRun(LoadedMod mod, Runnable run){
try{ try{
run.run(); run.run();
}catch(Throwable t){ }catch(Throwable t){
handleError(t, mod); throw new RuntimeException("Error loading mod " + mod.meta.name, t);
} }
} }
@@ -589,7 +604,7 @@ public class Mods implements Loadable{
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main; String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
String baseName = meta.name.toLowerCase().replace(" ", "-"); String baseName = meta.name.toLowerCase().replace(" ", "-");
if(loaded.contains(m -> m.name.equals(baseName)) || disabled.contains(m -> m.name.equals(baseName))){ if(mods.contains(m -> m.name.equals(baseName))){
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported."); throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
} }
@@ -633,7 +648,7 @@ public class Mods implements Loadable{
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */ /** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
public final FileHandle root; public final FileHandle root;
/** The mod's main class; may be null. */ /** The mod's main class; may be null. */
public final @Nullable Mod mod; public final @Nullable Mod main;
/** Internal mod name. Used for textures. */ /** Internal mod name. Used for textures. */
public final String name; public final String name;
/** This mod's metadata. */ /** This mod's metadata. */
@@ -644,16 +659,24 @@ 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. */
public ModState state = ModState.disabled;
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){ public LoadedMod(FileHandle file, FileHandle root, Mod main, ModMeta meta){
this.root = root; this.root = root;
this.file = file; this.file = file;
this.mod = mod; this.main = main;
this.meta = meta; this.meta = meta;
this.name = meta.name.toLowerCase().replace(" ", "-"); this.name = meta.name.toLowerCase().replace(" ", "-");
} }
public boolean enabled(){ public boolean enabled(){
return state == ModState.enabled || state == ModState.contentErrors;
}
public boolean shouldBeEnabled(){
return Core.settings.getBool("mod-" + name + "-enabled", true); return Core.settings.getBool("mod-" + name + "-enabled", true);
} }
@@ -661,6 +684,10 @@ public class Mods implements Loadable{
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;
@@ -752,37 +779,11 @@ public class Mods implements Loadable{
} }
} }
/** Thrown when an error occurs while loading a mod.*/ public enum ModState{
public static class ModLoadException extends RuntimeException{ enabled,
public Content content; contentErrors,
public LoadedMod mod; missingDependencies,
unsupported,
public ModLoadException(String message, Throwable cause){ disabled,
super(message, cause);
}
public ModLoadException(String message, @Nullable Content content, Throwable cause){
super(message, cause);
this.content = content;
if(content != null){
this.mod = content.mod;
}
}
public ModLoadException(@Nullable Content content, Throwable cause){
super(cause);
this.content = content;
if(content != null){
this.mod = content.mod;
}
}
public ModLoadException(String message, @Nullable Content content){
super(message);
this.content = content;
if(content != null){
this.mod = content.mod;
}
}
} }
} }

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.*;
@@ -172,8 +173,8 @@ public class Zone extends UnlockableContent{
@Override @Override
public void init(){ public void init(){
if(generator instanceof MapGenerator && mod != null){ if(generator instanceof MapGenerator && minfo.mod != null){
((MapGenerator)generator).removePrefix(mod.name); ((MapGenerator)generator).removePrefix(minfo.mod.name);
} }
generator.init(loadout); generator.init(loadout);

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

@@ -2,7 +2,6 @@ package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.Net.*; import io.anuke.arc.Net.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*; import io.anuke.arc.files.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
@@ -75,7 +74,7 @@ public class ModsDialog extends FloatingDialog{
hidden(() -> { hidden(() -> {
if(mods.requiresReload()){ if(mods.requiresReload()){
ui.loadAnd("$reloading", () -> { ui.loadAnd("$reloading", () -> {
mods.all().each(mod -> { mods.eachEnabled(mod -> {
if(mod.hasUnmetDependencies()){ if(mod.hasUnmetDependencies()){
ui.showErrorMessage(Core.bundle.format("mod.nowdisabled", mod.name, mod.missingDependencies.toString(", "))); ui.showErrorMessage(Core.bundle.format("mod.nowdisabled", mod.name, mod.missingDependencies.toString(", ")));
} }
@@ -107,14 +106,13 @@ public class ModsDialog extends FloatingDialog{
cont.defaults().width(mobile ? 500 : 560f).pad(4); cont.defaults().width(mobile ? 500 : 560f).pad(4);
cont.add("$mod.reloadrequired").visible(mods::requiresReload).center().get().setAlignment(Align.center); cont.add("$mod.reloadrequired").visible(mods::requiresReload).center().get().setAlignment(Align.center);
cont.row(); cont.row();
if(!(mods.all().isEmpty() && mods.disabled().isEmpty())){ if(!mods.list().isEmpty()){
cont.pane(table -> { cont.pane(table -> {
table.margin(10f).top(); table.margin(10f).top();
Array<LoadedMod> all = Array.withArrays(mods.all(), mods.disabled());
boolean anyDisabled = false; boolean anyDisabled = false;
for(LoadedMod mod : all){ for(LoadedMod mod : mods.list()){
if(!mod.enabled() && !anyDisabled && mods.all().size > 0){ if(!mod.enabled() && !anyDisabled && mods.list().size > 0){
anyDisabled = true; anyDisabled = true;
table.row(); table.row();
table.addImage().growX().height(4f).pad(6f).color(Pal.gray); table.addImage().growX().height(4f).pad(6f).color(Pal.gray);
@@ -167,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

@@ -10,6 +10,7 @@ import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.*;
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.content.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
@@ -461,6 +462,6 @@ public class PlacementFragment extends Fragment{
/** Returns the block currently being hovered over in the world. */ /** Returns the block currently being hovered over in the world. */
Block tileDisplayBlock(){ Block tileDisplayBlock(){
return hoverTile == null ? null : hoverTile.block().synthetic() ? hoverTile.block() : hoverTile.drop() != null ? hoverTile.overlay().itemDrop != null ? hoverTile.overlay() : hoverTile.floor() : null; return hoverTile == null ? null : hoverTile.block().synthetic() ? hoverTile.block() : hoverTile.drop() != null && hoverTile.block() == Blocks.air ? hoverTile.overlay().itemDrop != null ? hoverTile.overlay() : hoverTile.floor() : null;
} }
} }

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

@@ -224,6 +224,10 @@ public class BuildBlock extends Block{
return false; return false;
} }
if(cblock.requirements.length != accumulator.length || totalAccumulator.length != cblock.requirements.length){
setConstruct(previous, cblock);
}
float maxProgress = core == null ? amount : checkRequired(core.items, amount, false); float maxProgress = core == null ? amount : checkRequired(core.items, amount, false);
for(int i = 0; i < cblock.requirements.length; i++){ for(int i = 0; i < cblock.requirements.length; i++){

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

@@ -17,7 +17,6 @@ import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.desktop.steam.*; import io.anuke.mindustry.desktop.steam.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
@@ -195,12 +194,9 @@ public class DesktopLauncher extends ClientLauncher{
boolean fbgp = badGPU; boolean fbgp = badGPU;
CrashSender.send(e, file -> { CrashSender.send(e, file -> {
Array<Throwable> causes = Strings.getCauses(e); Throwable fc = Strings.getFinalCause(e);
Throwable fc = causes.find(t -> t instanceof ModLoadException);
if(fc == null) fc = Strings.getFinalCause(e);
Throwable cause = fc;
if(!fbgp){ if(!fbgp){
dialog.get(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + cause.getClass().getSimpleName().replace("Exception", "") + (cause.getMessage() == null ? "" : ":\n" + cause.getMessage()))); dialog.get(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + fc.getClass().getSimpleName().replace("Exception", "") + (fc.getMessage() == null ? "" : ":\n" + fc.getMessage())));
} }
}); });
} }

View File

@@ -0,0 +1,7 @@
- Added various new features for modding - see documentation
- Added server block state autosync
- Added support for Thai font
- Added JS scripting - unstable, not well tested
- Fixed liquid junction to item bridge crash
- Fixed launch items not saving
- General maintenance bugfixes and translation updates

View File

@@ -0,0 +1,7 @@
- Added various new features for modding - see documentation
- Added server block state autosync
- Added support for Thai font
- Added JS scripting - unstable, not well tested
- Fixed liquid junction to item bridge crash
- Fixed launch items not saving
- General maintenance bugfixes and translation updates

View File

@@ -0,0 +1,7 @@
- Added various new features for modding - see documentation
- Added server block state autosync
- Added support for Thai font
- Added JS scripting - unstable, not well tested
- Fixed liquid junction to item bridge crash
- Fixed launch items not saving
- General maintenance bugfixes and translation updates

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=3076d3a079f4bf388432c40d8e6c4bf9927dce9a

View File

@@ -10,7 +10,6 @@ import io.anuke.arc.util.io.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Saves.*; import io.anuke.mindustry.game.Saves.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
import org.robovm.apple.coregraphics.*; import org.robovm.apple.coregraphics.*;
import org.robovm.apple.foundation.*; import org.robovm.apple.foundation.*;
@@ -153,7 +152,7 @@ public class IOSLauncher extends IOSApplication.Delegate{
UINavigationController.attemptRotationToDeviceOrientation(); UINavigationController.attemptRotationToDeviceOrientation();
} }
}, new IOSApplicationConfiguration(){{ }, new IOSApplicationConfiguration(){{
errorHandler = ModCrashHandler::handle; //errorHandler = ModCrashHandler::handle;
}}); }});
} }

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;

View File

@@ -25,7 +25,7 @@ public class ScriptStubGenerator{
Array<String> blacklist = Array.with("plugin", "mod", "net", "io", "tools"); Array<String> blacklist = Array.with("plugin", "mod", "net", "io", "tools");
Array<String> nameBlacklist = Array.with("ClientLauncher", "NetClient", "NetServer", "ClassAccess"); Array<String> nameBlacklist = Array.with("ClientLauncher", "NetClient", "NetServer", "ClassAccess");
Array<Class<?>> whitelist = Array.with(Draw.class, Fill.class, Lines.class, Core.class, TextureAtlas.class, TextureRegion.class, Time.class, System.class, PrintStream.class, Array<Class<?>> whitelist = Array.with(Draw.class, Fill.class, Lines.class, Core.class, TextureAtlas.class, TextureRegion.class, Time.class, System.class, PrintStream.class,
AtlasRegion.class, String.class, Mathf.class, Angles.class, Color.class, Runnable.class, Object.class, Icon.class, Tex.class, Sounds.class, Musics.class, Call.class); AtlasRegion.class, String.class, Mathf.class, Angles.class, Color.class, Runnable.class, Object.class, Icon.class, Tex.class, Sounds.class, Musics.class, Call.class, Texture.class, TextureData.class, Pixmap.class);
Array<String> nopackage = Array.with("java.lang", "java"); Array<String> nopackage = Array.with("java.lang", "java");
String fileTemplate = "package io.anuke.mindustry.mod;\n" + String fileTemplate = "package io.anuke.mindustry.mod;\n" +