diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index b81792596c..d7476036c9 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -113,7 +113,7 @@ committingchanges = Committing Changes done = Done feature.unsupported = Your device does not support this feature. -mods.alphainfo = Keep in mind that mods are in alpha, and[scarlet] may be very buggy[].\nReport any issues you find to the Mindustry GitHub. +mods.initfailed = [red]⚠[] The previous Mindustry instance failed to initialize. This was likely caused by misbehaving mods.\n\nTo prevent a crash loop, [red]all mods have been disabled.[]\n\nTo disable this feature, turn it off in [accent]Settings->Game->Disable Mods On Startup Crash[]. mods = Mods mods.none = [lightgray]No mods found! mods.guide = Modding Guide @@ -800,6 +800,7 @@ setting.logichints.name = Logic Hints setting.flow.name = Display Resource Flow Rate setting.backgroundpause.name = Pause In Background setting.buildautopause.name = Auto-Pause Building +setting.modcrashdisable = Disable Mods On Startup Crash setting.animatedwater.name = Animated Surfaces setting.animatedshields.name = Animated Shields setting.antialias.name = Antialias[lightgray] (requires restart)[] diff --git a/core/src/mindustry/ClientLauncher.java b/core/src/mindustry/ClientLauncher.java index 978361898a..d8c0de2556 100644 --- a/core/src/mindustry/ClientLauncher.java +++ b/core/src/mindustry/ClientLauncher.java @@ -34,6 +34,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform @Override public void setup(){ + checkLaunch(); loadLogger(); loader = new LoadRenderer(); @@ -145,7 +146,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform finished = true; Events.fire(new ClientLoadEvent()); super.resize(graphics.getWidth(), graphics.getHeight()); - app.post(() -> app.post(() -> app.post(() -> app.post(() -> super.resize(graphics.getWidth(), graphics.getHeight()))))); + app.post(() -> app.post(() -> app.post(() -> app.post(() -> { + super.resize(graphics.getWidth(), graphics.getHeight()); + + //mark initialization as complete + finishLaunch(); + })))); } }else{ asyncCore.begin(); @@ -168,6 +174,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform lastTime = Time.nanos(); } + @Override + public void exit(){ + //on graceful exit, finish the launch normally. + Vars.finishLaunch(); + } + @Override public void init(){ setup(); @@ -182,6 +194,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform @Override public void pause(){ + //when the user tabs out on mobile, the exit() event doesn't fire reliably - in that case, just assume they're about to kill the app + //this isn't 100% reliable but it should work for most cases + if(mobile){ + Vars.finishLaunch(); + } if(finished){ super.pause(); } diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index 3846092846..45e3606c8e 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -32,6 +32,8 @@ import java.util.*; import static arc.Core.*; public class Vars implements Loadable{ + /** Whether the game failed to launch last time. */ + public static boolean failedToLaunch = false; /** Whether to load locales.*/ public static boolean loadLocales = true; /** Whether the logger is loaded. */ @@ -172,6 +174,8 @@ public class Vars implements Loadable{ public static Fi schematicDirectory; /** data subdirectory used for bleeding edge build versions */ public static Fi bebuildDirectory; + /** file used to store launch ID */ + public static Fi launchIDFile; /** empty map, indicates no current map */ public static Map emptyMap; /** map file extension */ @@ -284,6 +288,27 @@ public class Vars implements Loadable{ maps.load(); } + /** Checks if a launch failure occurred. + * If this is the case, failedToLaunch is set to true. */ + public static void checkLaunch(){ + settings.setAppName(appName); + launchIDFile = settings.getDataDirectory().child("launchid.dat"); + + if(launchIDFile.exists()){ + failedToLaunch = true; + }else{ + failedToLaunch = false; + launchIDFile.writeString("go away"); + } + } + + /** Cleans up after a successful launch. */ + public static void finishLaunch(){ + if(launchIDFile != null){ + launchIDFile.delete(); + } + } + public static void loadLogger(){ if(loadedLogger) return; diff --git a/core/src/mindustry/core/Control.java b/core/src/mindustry/core/Control.java index 8109bbcd7c..2ca30a2d51 100644 --- a/core/src/mindustry/core/Control.java +++ b/core/src/mindustry/core/Control.java @@ -59,6 +59,15 @@ public class Control implements ApplicationListener, Loadable{ saves = new Saves(); sound = new SoundControl(); + //show dialog saying that mod loading was skipped. + Events.on(ClientLoadEvent.class, e -> { + if(Vars.mods.skipModLoading() && Vars.mods.list().any()){ + Time.runTask(4f, () -> { + ui.showInfo("@mods.initfailed"); + }); + } + }); + Events.on(StateChangeEvent.class, event -> { if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){ Time.runTask(5f, platform::updateRPC); diff --git a/core/src/mindustry/mod/ContentParser.java b/core/src/mindustry/mod/ContentParser.java index 12fb11fde8..818d518963 100644 --- a/core/src/mindustry/mod/ContentParser.java +++ b/core/src/mindustry/mod/ContentParser.java @@ -406,6 +406,12 @@ public class ContentParser{ this.currentMod = mod; this.currentContent = cont; run.run(); + + //check nulls after parsing + if(cont != null){ + toBeParsed.remove(cont); + checkNullFields(cont); + } }); } diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index 41e2bf31ea..6b3f3ecaee 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -259,6 +259,11 @@ public class Mods implements Loadable{ return requiresReload; } + /** @return whether to skip mod loading due to previous initialization failure. */ + public boolean skipModLoading(){ + return failedToLaunch && Core.settings.getBool("modcrashdisable", true); + } + /** Loads all mods from the folder, but does not call any methods on them.*/ public void load(){ for(Fi file : modDirectory.list()){ @@ -683,8 +688,13 @@ public class Mods implements Loadable{ //make sure the main class exists before loading it; if it doesn't just don't put it there //if the mod is explicitly marked as java, try loading it anyway - if((mainFile.exists() || meta.java) && - Core.settings.getBool("mod-" + baseName + "-enabled", true) && Version.isAtLeast(meta.minGameVersion) && (meta.getMinMajor() >= 105 || headless)){ + if( + (mainFile.exists() || meta.java) && + !skipModLoading() && + Core.settings.getBool("mod-" + baseName + "-enabled", true) && + Version.isAtLeast(meta.minGameVersion) && + (meta.getMinMajor() >= 105 || headless) + ){ if(ios){ throw new IllegalArgumentException("Java class mods are not supported on iOS."); @@ -711,11 +721,16 @@ public class Mods implements Loadable{ } } + //skip mod loading if it failed + if(skipModLoading()){ + Core.settings.put("mod-" + baseName + "-enabled", false); + } + if(!headless){ Log.info("Loaded mod '@' in @ms", meta.name, Time.elapsed()); } - return new LoadedMod(sourceFile, zip, mainMod, loader, meta); + return new LoadedMod(sourceFile, zip, mainMod, loader, meta); }catch(Exception e){ //delete root zip file so it can be closed on windows if(rootZip != null) rootZip.delete(); diff --git a/core/src/mindustry/ui/dialogs/ModsDialog.java b/core/src/mindustry/ui/dialogs/ModsDialog.java index 7c5f766db6..bd3f63bf03 100644 --- a/core/src/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/mindustry/ui/dialogs/ModsDialog.java @@ -106,11 +106,6 @@ public class ModsDialog extends BaseDialog{ } }); - shown(() -> Core.app.post(() -> { - Core.settings.getBoolOnce("modsalpha", () -> { - ui.showText("@mods", "@mods.alphainfo"); - }); - })); } void modError(Throwable error){ diff --git a/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java index e531581f9b..60acd1637b 100644 --- a/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -327,6 +327,10 @@ public class SettingsMenuDialog extends SettingsDialog{ game.checkPref("buildautopause", false); } + if(!ios){ + game.checkPref("modcrashdisable", true); + } + if(steam){ game.sliderPref("playerlimit", 16, 2, 32, i -> { platform.updateLobby(); diff --git a/core/src/mindustry/world/blocks/environment/ShallowLiquid.java b/core/src/mindustry/world/blocks/environment/ShallowLiquid.java index 28d94c05fe..bea6797f37 100644 --- a/core/src/mindustry/world/blocks/environment/ShallowLiquid.java +++ b/core/src/mindustry/world/blocks/environment/ShallowLiquid.java @@ -1,10 +1,14 @@ package mindustry.world.blocks.environment; +import arc.util.*; import mindustry.world.*; -//do not use in mods! +/** + * Do not use in mods. This class provides no new functionality, and is only used for the Mindustry sprite generator. + * Use the standard Floor class instead. + * */ public class ShallowLiquid extends Floor{ - public Floor liquidBase, floorBase; + public @Nullable Floor liquidBase, floorBase; public float liquidOpacity = 0.35f; public ShallowLiquid(String name){ diff --git a/core/src/mindustry/world/blocks/production/Drill.java b/core/src/mindustry/world/blocks/production/Drill.java index 373d5bb9de..bfefd96050 100644 --- a/core/src/mindustry/world/blocks/production/Drill.java +++ b/core/src/mindustry/world/blocks/production/Drill.java @@ -38,7 +38,7 @@ public class Drill extends Block{ public float warmupSpeed = 0.02f; //return variables for countOre - protected Item returnItem; + protected @Nullable Item returnItem; protected int returnCount; /** Whether to draw the item this drill is mining. */ diff --git a/core/src/mindustry/world/blocks/production/GenericCrafter.java b/core/src/mindustry/world/blocks/production/GenericCrafter.java index 39a2e2ac4f..25021ad20d 100644 --- a/core/src/mindustry/world/blocks/production/GenericCrafter.java +++ b/core/src/mindustry/world/blocks/production/GenericCrafter.java @@ -3,6 +3,7 @@ package mindustry.world.blocks.production; import arc.graphics.g2d.*; import arc.math.*; import arc.struct.*; +import arc.util.*; import arc.util.io.*; import mindustry.content.*; import mindustry.entities.*; @@ -13,8 +14,8 @@ import mindustry.world.draw.*; import mindustry.world.meta.*; public class GenericCrafter extends Block{ - public ItemStack outputItem; - public LiquidStack outputLiquid; + public @Nullable ItemStack outputItem; + public @Nullable LiquidStack outputLiquid; public float craftTime = 80; public Effect craftEffect = Fx.none; diff --git a/gradle.properties b/gradle.properties index a8ba31e088..6a5b6bcb50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=e402c9743e107fb53ed6f069ae72a19a59fbfd3b +archash=f6caa903fd34c016a902b70c4d41690f5f022431