diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index ffc8fac630..8ff04d7c7d 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -1437,6 +1437,12 @@ Invisible to others = Bison = Cocoa = +# Exceptions that _may_ be shown to the user + +Building '[buildingName]' is buildable and therefore must either have an explicit cost or reference an existing tech. = +Nation [nationName] is not found! = +Unit [unitName] doesn't seem to exist! = + # In English we just paste all these conditionals at the end of each unique, but in your language that # may not turn into valid sentences. Therefore we have the following two translations to determine @@ -1467,5 +1473,3 @@ ConditionalsPlacement = ########################### AUTOMATICALLY GENERATED TRANSLATABLE STRINGS ########################### - - diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 4b04d88ebb..601915f244 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -22,9 +22,6 @@ import com.unciv.ui.audio.MusicTrackChooserFlags import java.util.* -class MissingModsException(val missingMods: String) : UncivShowableException("Missing mods: [$missingMods]") -open class UncivShowableException(errorText: String) : Exception(errorText) - class GameInfo { //region Fields - Serialized var civilizations = mutableListOf() diff --git a/core/src/com/unciv/logic/UncivExceptions.kt b/core/src/com/unciv/logic/UncivExceptions.kt new file mode 100644 index 0000000000..f6638ae909 --- /dev/null +++ b/core/src/com/unciv/logic/UncivExceptions.kt @@ -0,0 +1,24 @@ +package com.unciv.logic + +import com.unciv.models.translations.tr + +/** + * An [Exception] wrapper marking an Exception as suitable to be shown to the user. + * + * @param [errorText] should be the _**untranslated**_ error message. + * Use [getLocalizedMessage] to get the translated [message], _**or**_ use auto-translating helpers like .toLabel() or FormattedLine(). + * Usual formatting (`[] or {}`) applies, as does the need to include the text in templates.properties. + */ +open class UncivShowableException( + errorText: String, + override val cause: Throwable? = null +) : Exception(errorText) { + // override because we _definitely_ have a non-null message from [errorText] + override val message: String + get() = super.message!! + override fun getLocalizedMessage() = message.tr() +} + +class MissingModsException( + val missingMods: String +) : UncivShowableException("Missing mods: [$missingMods]") diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 8cedfc7202..cfa5d55b09 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -56,13 +56,13 @@ class ModOptions : IHasUniques { var modUrl = "" var author = "" var modSize = 0 - + @Deprecated("As of 3.18.15") var maxXPfromBarbarians = 30 override var uniques = ArrayList() - // If these two are delegated with "by lazy", the mod download process crashes and burns + // If these two are delegated with "by lazy", the mod download process crashes and burns // Instead, Ruleset.load sets them, which is preferable in this case anyway override var uniqueObjects: List = listOf() override var uniqueMap: Map> = mapOf() @@ -307,21 +307,21 @@ class Ruleset { } val difficultiesFile = folderHandle.child("Difficulties.json") - if (difficultiesFile.exists()) + if (difficultiesFile.exists()) difficulties += createHashmap(json().fromJsonFile(Array::class.java, difficultiesFile)) val globalUniquesFile = folderHandle.child("GlobalUniques.json") if (globalUniquesFile.exists()) { globalUniques = json().fromJsonFile(GlobalUniques::class.java, globalUniquesFile) } - + val victoryTypesFiles = folderHandle.child("VictoryTypes.json") if (victoryTypesFiles.exists()) { victories += createHashmap(json().fromJsonFile(Array::class.java, victoryTypesFiles)) } - - + + // Add objects that might not be present in base ruleset mods, but are required if (modOptions.isBaseRuleset) { // This one should be temporary @@ -355,7 +355,7 @@ class Ruleset { for (building in buildings.values) { if (building.cost == 0 && !building.hasUnique(UniqueType.Unbuildable)) { val column = technologies[building.requiredTech]?.column - ?: throw UncivShowableException("Building (${building.name}) is buildable and therefore must either have an explicit cost or reference an existing tech") + ?: throw UncivShowableException("Building '[${building.name}]' is buildable and therefore must either have an explicit cost or reference an existing tech.") building.cost = if (building.isAnyWonder()) column.wonderCost else column.buildingCost } } @@ -389,7 +389,7 @@ class Ruleset { forOptionsPopup: Boolean ) { val name = if (uniqueContainer is INamed) uniqueContainer.name else "" - + for (unique in uniqueContainer.uniqueObjects) { val errors = checkUnique( unique, @@ -424,12 +424,12 @@ class Ruleset { unique.text.count { it=='<' } != unique.text.count { it=='>' } ->listOf( RulesetError("$name's unique \"${unique.text}\" contains mismatched conditional braces!", RulesetErrorSeverity.Warning)) - + // This should only ever happen if a bug is or has been introduced that prevents Unique.type from being set for a valid UniqueType, I think.\ equalUniques.isNotEmpty() -> listOf(RulesetError( "$name's unique \"${unique.text}\" looks like it should be fine, but for some reason isn't recognized.", RulesetErrorSeverity.OK)) - + similarUniques.isNotEmpty() -> { val text = "$name's unique \"${unique.text}\" looks like it may be a misspelling of:\n" + @@ -503,7 +503,7 @@ class Ruleset { if (unique.type.targetTypes.none { uniqueTarget.canAcceptUniqueTarget(it) } // the 'consume unit' conditional causes a triggerable unique to become a unit action && !(uniqueTarget==UniqueTarget.Unit - && unique.isTriggerable + && unique.isTriggerable && unique.conditionals.any { it.type == UniqueType.ConditionalConsumeUnit })) rulesetErrors.add( "$name's unique \"${unique.text}\" cannot be put on this type of object!", @@ -681,10 +681,10 @@ class Ruleset { for (terrain in improvement.terrainsCanBeBuiltOn) if (!terrains.containsKey(terrain) && terrain != "Land" && terrain != "Water") lines += "${improvement.name} can be built on terrain $terrain which does not exist!" - if (improvement.terrainsCanBeBuiltOn.isEmpty() - && !improvement.hasUnique(UniqueType.CanOnlyImproveResource) - && !improvement.hasUnique(UniqueType.Unbuildable) - && !improvement.name.startsWith(Constants.remove) + if (improvement.terrainsCanBeBuiltOn.isEmpty() + && !improvement.hasUnique(UniqueType.CanOnlyImproveResource) + && !improvement.hasUnique(UniqueType.Unbuildable) + && !improvement.name.startsWith(Constants.remove) && improvement.name !in RoadStatus.values().map { it.removeAction } && improvement.name != Constants.cancelImprovementOrder ) { @@ -792,7 +792,7 @@ class Ruleset { lines += "${policy.name} requires policy $prereq which does not exist!" checkUniques(policy, lines, rulesetSpecific, forOptionsPopup) } - + for (policy in policyBranches.values.flatMap { it.policies + it }) if (policy != policies[policy.name]) lines += "More than one policy with the name ${policy.name} exists!" @@ -818,13 +818,13 @@ class Ruleset { for (unitType in unitTypes.values) { checkUniques(unitType, lines, rulesetSpecific, forOptionsPopup) } - + for (victoryType in victories.values) { - for (requiredUnit in victoryType.requiredSpaceshipParts) + for (requiredUnit in victoryType.requiredSpaceshipParts) if (!units.contains(requiredUnit)) lines.add("Victory type ${victoryType.name} requires adding the non-existant unit $requiredUnit to the capital to win!", RulesetErrorSeverity.Warning) - for (milestone in victoryType.milestoneObjects) - if (milestone.type == null) + for (milestone in victoryType.milestoneObjects) + if (milestone.type == null) lines.add("Victory type ${victoryType.name} has milestone ${milestone.uniqueDescription} that is of an unknown type!", RulesetErrorSeverity.Error) for (victory in victories.values) if (victory.name != victoryType.name && victory.milestones == victoryType.milestones) @@ -863,10 +863,10 @@ object RulesetCache : HashMap() { clear() for (ruleset in BaseRuleset.values()) { val fileName = "jsons/${ruleset.fullName}" - val fileHandle = + val fileHandle = if (consoleMode) FileHandle(fileName) else Gdx.files.internal(fileName) - this[ruleset.fullName] = Ruleset().apply { + this[ruleset.fullName] = Ruleset().apply { load(fileHandle, printOutput) name = ruleset.fullName } @@ -949,7 +949,7 @@ object RulesetCache : HashMap() { val loadedMods = mods.asSequence() .filter { containsKey(it) } .map { this[it]!! } - .filter { !it.modOptions.isBaseRuleset } + + .filter { !it.modOptions.isBaseRuleset } + baseRuleset for (mod in loadedMods.sortedByDescending { it.modOptions.isBaseRuleset }) { @@ -982,7 +982,7 @@ object RulesetCache : HashMap() { // This happens if a building is dependent on a tech not in the base ruleset // because newRuleset.updateBuildingCosts() in getComplexRuleset() throws an error Ruleset.RulesetErrorList() - .apply { add(ex.message!!.tr(), Ruleset.RulesetErrorSeverity.Error) } + .apply { add(ex.message, Ruleset.RulesetErrorSeverity.Error) } } } diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt b/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt index 670a08e4a0..7c28a146c0 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorLoadTab.kt @@ -122,7 +122,7 @@ class MapEditorLoadTab( val rulesetIncompatibilities = map.getRulesetIncompatibility(ruleset) if (rulesetIncompatibilities.isNotEmpty()) { map.removeMissingTerrainModReferences(ruleset) - val message = "{This map has errors:}\n\n".tr() + + val message = "{This map has errors:}\n\n" + rulesetIncompatibilities.sorted().joinToString("\n") { it.tr() } + "\n\n{The incompatible elements have been removed.}" ToastPopup(message, editorScreen, 4000L) @@ -134,7 +134,7 @@ class MapEditorLoadTab( } catch (ex: Throwable) { needPopup = false popup?.close() - println("Error displaying map \"$chosenMap\": ${ex.localizedMessage}") + println("Error displaying map \"$chosenMap\": ${ex.message}") Gdx.input.inputProcessor = editorScreen.stage ToastPopup("Error loading map!", editorScreen) } @@ -143,9 +143,9 @@ class MapEditorLoadTab( needPopup = false Gdx.app.postRunnable { popup?.close() - println("Error loading map \"$chosenMap\": ${ex.localizedMessage}") - ToastPopup("Error loading map!".tr() + - (if (ex is UncivShowableException) "\n" + ex.message else ""), editorScreen) + println("Error loading map \"$chosenMap\": ${ex.message}") + ToastPopup("{Error loading map!}" + + (if (ex is UncivShowableException) "\n{${ex.message}}" else ""), editorScreen) } } }