diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 54ef8fbc43..7de68ffae8 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -581,7 +581,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion // Cater for the mad modder using trailing '-' in their repo name - convert the mods list so // it requires our new, Windows-safe local name (no trailing blanks) - for ((oldName, newName) in gameParameters.mods.map { it to it.repoNameToFolderName() }) { + for ((oldName, newName) in gameParameters.mods.map { it to it.repoNameToFolderName(onlyOuterBlanks = true) }) { if (newName == oldName) continue gameParameters.mods.remove(oldName) gameParameters.mods.add(newName) diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index f59e2d8a65..f79e0c43ed 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -230,6 +230,8 @@ class Ruleset { allRulesetObjects() + sequenceOf(modOptions) fun load(folderHandle: FileHandle) { + // Note: Most files are loaded using createHashmap, which sets originRuleset automatically. + // For other files containing IRulesetObject's we'll have to remember to do so manually - e.g. Tech. val modOptionsFile = folderHandle.child("ModOptions.json") if (modOptionsFile.exists()) { try { @@ -250,6 +252,7 @@ class Ruleset { for (tech in techColumn.techs) { if (tech.cost == 0) tech.cost = techColumn.techCost tech.column = techColumn + tech.originRuleset = name technologies[tech.name] = tech } } @@ -261,7 +264,10 @@ class Ruleset { val terrainsFile = folderHandle.child("Terrains.json") if (terrainsFile.exists()) { terrains += createHashmap(json().fromJsonFile(Array::class.java, terrainsFile)) - for (terrain in terrains.values) terrain.setTransients() + for (terrain in terrains.values) { + terrain.originRuleset = name + terrain.setTransients() + } } val resourcesFile = folderHandle.child("TileResources.json") @@ -316,6 +322,7 @@ class Ruleset { // Append child policies of this branch for (policy in branch.policies) { policy.branch = branch + policy.originRuleset = name if (policy.requires == null) { policy.requires = arrayListOf(branch.name) } @@ -366,6 +373,7 @@ class Ruleset { val globalUniquesFile = folderHandle.child("GlobalUniques.json") if (globalUniquesFile.exists()) { globalUniques = json().fromJsonFile(GlobalUniques::class.java, globalUniquesFile) + globalUniques.originRuleset = name } val victoryTypesFile = folderHandle.child("VictoryTypes.json") diff --git a/core/src/com/unciv/models/ruleset/RulesetValidator.kt b/core/src/com/unciv/models/ruleset/RulesetValidator.kt index 25423bd615..3f879c39c6 100644 --- a/core/src/com/unciv/models/ruleset/RulesetValidator.kt +++ b/core/src/com/unciv/models/ruleset/RulesetValidator.kt @@ -475,8 +475,8 @@ class RulesetValidator(val ruleset: Ruleset) { namedObj: INamed?, severityToReport: UniqueType.UniqueComplianceErrorSeverity ): List { - var name = namedObj?.name ?: "" - if (namedObj != null && namedObj is IRulesetObject) name = "${namedObj.originRuleset}: $name" + val prefix = (if (namedObj is IRulesetObject) "${namedObj.originRuleset}: " else "") + + (if (namedObj == null) "The" else "${namedObj.name}'s") if (unique.type == null) { if (!tryFixUnknownUniques) return emptyList() val similarUniques = UniqueType.values().filter { @@ -490,17 +490,17 @@ class RulesetValidator(val ruleset: Ruleset) { return when { // Malformed conditional unique.text.count { it=='<' } != unique.text.count { it=='>' } ->listOf( - RulesetError("$name's unique \"${unique.text}\" contains mismatched conditional braces!", + RulesetError("$prefix 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.", + "$prefix 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" + + "$prefix unique \"${unique.text}\" looks like it may be a misspelling of:\n" + similarUniques.joinToString("\n") { uniqueType -> var text = "\"${uniqueType.text}" if (unique.conditionals.isNotEmpty()) @@ -513,7 +513,7 @@ class RulesetValidator(val ruleset: Ruleset) { } RulesetCache.modCheckerAllowUntypedUniques -> emptyList() else -> listOf(RulesetError( - "$name's unique \"${unique.text}\" not found in Unciv's unique types.", + "$prefix unique \"${unique.text}\" not found in Unciv's unique types.", RulesetErrorSeverity.OK)) } } @@ -521,12 +521,12 @@ class RulesetValidator(val ruleset: Ruleset) { val rulesetErrors = RulesetErrorList() if (namedObj is IHasUniques && !unique.type.canAcceptUniqueTarget(namedObj.getUniqueTarget())) - rulesetErrors.add(RulesetError("$name's unique \"${unique.text}\" is not allowed on its target type", RulesetErrorSeverity.Warning)) + rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" is not allowed on its target type", RulesetErrorSeverity.Warning)) val typeComplianceErrors = unique.type.getComplianceErrors(unique, ruleset) for (complianceError in typeComplianceErrors) { if (complianceError.errorSeverity <= severityToReport) - rulesetErrors.add(RulesetError("$name's unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," + + rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," + " which does not fit parameter type" + " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !", complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport) @@ -536,13 +536,13 @@ class RulesetValidator(val ruleset: Ruleset) { for (conditional in unique.conditionals) { if (conditional.type == null) { rulesetErrors.add( - "$name's unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," + + "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," + " which is of an unknown type!", RulesetErrorSeverity.Warning ) } else { if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None }) - rulesetErrors.add("$name's unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," + + rulesetErrors.add("$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," + " which is a Unique type not allowed as conditional or trigger.", RulesetErrorSeverity.Warning) @@ -550,7 +550,7 @@ class RulesetValidator(val ruleset: Ruleset) { conditional.type.getComplianceErrors(conditional, ruleset) for (complianceError in conditionalComplianceErrors) { if (complianceError.errorSeverity == severityToReport) - rulesetErrors.add(RulesetError( "$name's unique \"${unique.text}\" contains the conditional \"${conditional.text}\"." + + rulesetErrors.add(RulesetError( "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"." + " This contains the parameter ${complianceError.parameterName} which does not fit parameter type" + " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !", complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport) @@ -570,7 +570,7 @@ class RulesetValidator(val ruleset: Ruleset) { if (deprecationAnnotation != null) { val replacementUniqueText = unique.getReplacementText(ruleset) val deprecationText = - "$name's unique \"${unique.text}\" is deprecated ${deprecationAnnotation.message}," + + "$prefix unique \"${unique.text}\" is deprecated ${deprecationAnnotation.message}," + if (deprecationAnnotation.replaceWith.expression != "") " replace with \"${replacementUniqueText}\"" else "" val severity = if (deprecationAnnotation.level == DeprecationLevel.WARNING) RulesetErrorSeverity.WarningOptionsOnly // Not user-visible @@ -584,7 +584,7 @@ class RulesetValidator(val ruleset: Ruleset) { } -class RulesetError(val text:String, val errorSeverityToReport: RulesetErrorSeverity) +class RulesetError(val text: String, val errorSeverityToReport: RulesetErrorSeverity) enum class RulesetErrorSeverity(val color: Color) { OK(Color.GREEN), diff --git a/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt b/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt index 269e972663..f5242fabaf 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/GitHub.kt @@ -463,9 +463,10 @@ object Github { * Replaces '-' with blanks but ensures no leading or trailing blanks. * As mad modders know no limits, trailing "-" did indeed happen, causing things to break due to trailing blanks on a folder name. * As "test-" and "test" are different allowed repository names, trimmed blanks are replaced with one overscore per side. + * @param onlyOuterBlanks If `true` ignores inner dashes - only start and end are treated. Useful when modders have manually creted local folder names using dashes. */ - fun String.repoNameToFolderName(): String { - var result = replace('-', ' ') + fun String.repoNameToFolderName(onlyOuterBlanks: Boolean = false): String { + var result = if (onlyOuterBlanks) this else replace('-', ' ') if (result.endsWith(' ')) result = result.trimEnd() + outerBlankReplacement if (result.startsWith(' ')) result = outerBlankReplacement + result.trimStart() return result diff --git a/docs/Modders/Mod-file-structure/1-Overview.md b/docs/Modders/Mod-file-structure/1-Overview.md index 3113a7cbfe..3dbe3b8702 100644 --- a/docs/Modders/Mod-file-structure/1-Overview.md +++ b/docs/Modders/Mod-file-structure/1-Overview.md @@ -30,6 +30,7 @@ The JSON files that make up mods can have many different fields, and as not all - [Difficulties.json](5-Miscellaneous-JSON-files.md#difficultiesjson) - [Eras.json](5-Miscellaneous-JSON-files.md#erasjson) - [ModOptions.json](5-Miscellaneous-JSON-files.md#modoptionsjson) + - [GlobalUniques.json](5-Miscellaneous-JSON-files.md#globaluniquesjson) - [Tutorials.json](5-Miscellaneous-JSON-files.md#tutorialsjson) - [Stats](3-Map-related-JSON-files.md#stats) - [Sounds](../Images-and-Audio.md#sounds) diff --git a/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md b/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md index 197fd671db..b6b79f159c 100644 --- a/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md +++ b/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md @@ -203,6 +203,14 @@ The formula for the gold cost of a unit upgrade is (rounded down to a multiple o ) ^ `exponent` With `civModifier` being the multiplicative aggregate of ["\[relativeAmount\]% Gold cost of upgrading"](../uniques.md#global-uniques) uniques that apply. +## GlobalUniques.json + +Defines uniques that apply globally. e.g. Vanilla rulesets define the effects of Unhappiness here. +Only the `uniques` field is used, but a name must still be set (the Ruleset validator might display it). +When extension rulesets define GlobalUniques, all uniques are merged. At the moment there is no way to change/remove uniques set by a base mod. + +[link to original](https://github.com/yairm210/Unciv/tree/master/android/assets/jsons/GlobalUniques.json) + ## Tutorials.json [link to original](https://github.com/yairm210/Unciv/tree/master/android/assets/jsons/Tutorials.json)