diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index 210cc39ca4..f38b9a5eec 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -366,6 +366,12 @@ Upload map = Karte hochladen Could not upload map! = Kann Karte nicht hochladen! Map uploaded successfully! = Karte hochladen war erfolgreich! Saving... = Speichern... +It looks like your saved game can't be loaded! = Dieser Spielstand kann nicht geladen werden! +If you could copy your game data ("Copy saved game to clipboard" - = Wenn Sie die Spieldaten kopieren ("Gespeichertes Spiel in die Zwischenablage kopieren"), + paste into an email to yairm210@hotmail.com) = und in eine mail an mich (yairm210@hotmail.com) einfügen, +I could maybe help you figure out what went wrong, since this isn't supposed to happen! = dann kann ich eventuell helfen den Grund zu finden - das sollte nicht passieren! +Missing mods: [mods] = Der Spielstand benötigt das/die mod(s) [mods], diese sind aber nicht verfügbar. + # Options diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index b0c698e18f..d112610b9d 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -366,6 +366,11 @@ Upload map = Could not upload map! = Map uploaded successfully! = Saving... = +It looks like your saved game can't be loaded! = +If you could copy your game data ("Copy saved game to clipboard" - = + paste into an email to yairm210@hotmail.com) = +I could maybe help you figure out what went wrong, since this isn't supposed to happen! = +Missing mods: [mods] = # Options diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index cd14ddd747..a20a652ca6 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -17,6 +17,8 @@ import com.unciv.models.ruleset.RulesetCache import java.util.* import kotlin.collections.ArrayList +class UncivShowableException(missingMods: String) : Exception(missingMods) + class GameInfo { @Transient lateinit var difficultyObject: Difficulty // Since this is static game-wide, and was taking a large part of nextTurn @Transient lateinit var currentPlayerCiv:CivilizationInfo // this is called thousands of times, no reason to search for it with a find{} every time @@ -219,6 +221,15 @@ class GameInfo { fun setTransients() { tileMap.gameInfo = this ruleSet = RulesetCache.getComplexRuleset(gameParameters.mods) + // any mod the saved game lists that is currently not installed causes null pointer + // exceptions in this routine unless it contained no new objects or was very simple. + // Player's fault, so better complain early: + val missingMods = gameParameters.mods + .filterNot { it in ruleSet.mods } + .joinToString(limit = 120) { it } + if (missingMods.isNotEmpty()) { + throw UncivShowableException("Missing mods: [$missingMods]") + } // Renames as of version 3.1.8, because of translation conflicts with the property "Range" and the difficulty "Immortal" // Needs to be BEFORE tileMap.setTransients, because the units' setTransients is called from there @@ -370,3 +381,4 @@ class GameInfo { } } + diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 77e5a19813..8d2740d3ea 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -3,6 +3,7 @@ package com.unciv.models.ruleset import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.unciv.JsonParser +import com.unciv.logic.UncivShowableException import com.unciv.models.ruleset.tech.TechColumn import com.unciv.models.ruleset.tech.Technology import com.unciv.models.ruleset.tile.Terrain @@ -91,10 +92,11 @@ class Ruleset() { if(buildingsFile.exists()) { buildings += createHashmap(jsonParser.getFromJson(Array::class.java, buildingsFile)) for (building in buildings.values) { - if (building.requiredTech == null) continue - val column = technologies[building.requiredTech!!]!!.column - if (building.cost == 0) - building.cost = if (building.isWonder || building.isNationalWonder) column!!.wonderCost else column!!.buildingCost + if (building.cost == 0) { + val column = technologies[building.requiredTech]?.column + ?: throw UncivShowableException("Building (${building.name}) must either have an explicit cost or a required tech in the same mod") + building.cost = if (building.isWonder || building.isNationalWonder) column.wonderCost else column.buildingCost + } } } @@ -149,14 +151,18 @@ object RulesetCache :HashMap(){ this[""] = Ruleset().apply { load(Gdx.files.internal("jsons")) } for(modFolder in Gdx.files.local("mods").list()){ + if (modFolder.name().startsWith('.')) continue try{ val modRuleset = Ruleset() modRuleset.load(modFolder.child("jsons")) modRuleset.name = modFolder.name() this[modRuleset.name] = modRuleset + println ("Mod loaded successfully: " + modRuleset.name) } catch (ex:Exception){ - println( "Exception loading " + modFolder.name() + ": " + ex.message ) + println ("Exception loading mod '${modFolder.name()}':") + println (" ${ex.localizedMessage}") + println (" (Source file ${ex.stackTrace[0].fileName} line ${ex.stackTrace[0].lineNumber})") } } } diff --git a/core/src/com/unciv/ui/saves/LoadGameScreen.kt b/core/src/com/unciv/ui/saves/LoadGameScreen.kt index e807f619f3..084f2610b0 100644 --- a/core/src/com/unciv/ui/saves/LoadGameScreen.kt +++ b/core/src/com/unciv/ui/saves/LoadGameScreen.kt @@ -10,6 +10,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.unciv.UncivGame import com.unciv.logic.GameSaver +import com.unciv.logic.UncivShowableException import com.unciv.models.translations.tr import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.utils.disable @@ -44,11 +45,17 @@ class LoadGameScreen : PickerScreen() { catch (ex:Exception){ val cantLoadGamePopup = Popup(this) cantLoadGamePopup.addGoodSizedLabel("It looks like your saved game can't be loaded!").row() - cantLoadGamePopup.addGoodSizedLabel("If you could copy your game data (\"Copy saved game to clipboard\" - ").row() - cantLoadGamePopup.addGoodSizedLabel(" paste into an email to yairm210@hotmail.com)").row() - cantLoadGamePopup.addGoodSizedLabel("I could maybe help you figure out what went wrong, since this isn't supposed to happen!").row() - cantLoadGamePopup.open() - ex.printStackTrace() + if (ex is UncivShowableException && ex.localizedMessage != null) { + // thrown exceptions are our own tests and can be shown to the user + cantLoadGamePopup.addGoodSizedLabel(ex.localizedMessage).row() + cantLoadGamePopup.open() + } else { + cantLoadGamePopup.addGoodSizedLabel("If you could copy your game data (\"Copy saved game to clipboard\" - ").row() + cantLoadGamePopup.addGoodSizedLabel(" paste into an email to yairm210@hotmail.com)").row() + cantLoadGamePopup.addGoodSizedLabel("I could maybe help you figure out what went wrong, since this isn't supposed to happen!").row() + cantLoadGamePopup.open() + ex.printStackTrace() + } } } diff --git a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt index 98813f5b28..ec24fc7849 100644 --- a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt +++ b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt @@ -61,7 +61,8 @@ internal object DesktopLauncher { val modDirectory = File("mods") if(modDirectory.exists()) { for (mod in modDirectory.listFiles()!!){ - TexturePacker.process(settings, mod.path + "/Images", mod.path, "game") + if (!mod.isHidden && File(mod.path + "/Images").exists()) + TexturePacker.process(settings, mod.path + "/Images", mod.path, "game") } } @@ -97,6 +98,6 @@ internal object DesktopLauncher { presence.details=currentPlayerCiv.nation.getLeaderDisplayName().tr() presence.largeImageKey = "logo" // The actual image is uploaded to the discord app / applications webpage presence.largeImageText ="Turn".tr()+" " + currentPlayerCiv.gameInfo.turns - DiscordRPC.INSTANCE.Discord_UpdatePresence(presence); + DiscordRPC.INSTANCE.Discord_UpdatePresence(presence) } }