Local mod folder names preserved. (#9844)

* Reduce conversion of local mod folder names to treat only ending spaces/dashes

* Wiki entry for GlobalUniques

* Missing originRuleset coverage

* Nicer RulesetValidator messages when name or originRuleset are missing
This commit is contained in:
SomeTroglodyte 2023-07-30 16:38:33 +02:00 committed by GitHub
parent b992144ecd
commit 443bf3afdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 35 additions and 17 deletions

View File

@ -581,7 +581,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// Cater for the mad modder using trailing '-' in their repo name - convert the mods list so // 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) // 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 if (newName == oldName) continue
gameParameters.mods.remove(oldName) gameParameters.mods.remove(oldName)
gameParameters.mods.add(newName) gameParameters.mods.add(newName)

View File

@ -230,6 +230,8 @@ class Ruleset {
allRulesetObjects() + sequenceOf(modOptions) allRulesetObjects() + sequenceOf(modOptions)
fun load(folderHandle: FileHandle) { 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") val modOptionsFile = folderHandle.child("ModOptions.json")
if (modOptionsFile.exists()) { if (modOptionsFile.exists()) {
try { try {
@ -250,6 +252,7 @@ class Ruleset {
for (tech in techColumn.techs) { for (tech in techColumn.techs) {
if (tech.cost == 0) tech.cost = techColumn.techCost if (tech.cost == 0) tech.cost = techColumn.techCost
tech.column = techColumn tech.column = techColumn
tech.originRuleset = name
technologies[tech.name] = tech technologies[tech.name] = tech
} }
} }
@ -261,7 +264,10 @@ class Ruleset {
val terrainsFile = folderHandle.child("Terrains.json") val terrainsFile = folderHandle.child("Terrains.json")
if (terrainsFile.exists()) { if (terrainsFile.exists()) {
terrains += createHashmap(json().fromJsonFile(Array<Terrain>::class.java, terrainsFile)) terrains += createHashmap(json().fromJsonFile(Array<Terrain>::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") val resourcesFile = folderHandle.child("TileResources.json")
@ -316,6 +322,7 @@ class Ruleset {
// Append child policies of this branch // Append child policies of this branch
for (policy in branch.policies) { for (policy in branch.policies) {
policy.branch = branch policy.branch = branch
policy.originRuleset = name
if (policy.requires == null) { if (policy.requires == null) {
policy.requires = arrayListOf(branch.name) policy.requires = arrayListOf(branch.name)
} }
@ -366,6 +373,7 @@ class Ruleset {
val globalUniquesFile = folderHandle.child("GlobalUniques.json") val globalUniquesFile = folderHandle.child("GlobalUniques.json")
if (globalUniquesFile.exists()) { if (globalUniquesFile.exists()) {
globalUniques = json().fromJsonFile(GlobalUniques::class.java, globalUniquesFile) globalUniques = json().fromJsonFile(GlobalUniques::class.java, globalUniquesFile)
globalUniques.originRuleset = name
} }
val victoryTypesFile = folderHandle.child("VictoryTypes.json") val victoryTypesFile = folderHandle.child("VictoryTypes.json")

View File

@ -475,8 +475,8 @@ class RulesetValidator(val ruleset: Ruleset) {
namedObj: INamed?, namedObj: INamed?,
severityToReport: UniqueType.UniqueComplianceErrorSeverity severityToReport: UniqueType.UniqueComplianceErrorSeverity
): List<RulesetError> { ): List<RulesetError> {
var name = namedObj?.name ?: "" val prefix = (if (namedObj is IRulesetObject) "${namedObj.originRuleset}: " else "") +
if (namedObj != null && namedObj is IRulesetObject) name = "${namedObj.originRuleset}: $name" (if (namedObj == null) "The" else "${namedObj.name}'s")
if (unique.type == null) { if (unique.type == null) {
if (!tryFixUnknownUniques) return emptyList() if (!tryFixUnknownUniques) return emptyList()
val similarUniques = UniqueType.values().filter { val similarUniques = UniqueType.values().filter {
@ -490,17 +490,17 @@ class RulesetValidator(val ruleset: Ruleset) {
return when { return when {
// Malformed conditional // Malformed conditional
unique.text.count { it=='<' } != unique.text.count { it=='>' } ->listOf( 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)) 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.\ // 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( 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)) RulesetErrorSeverity.OK))
similarUniques.isNotEmpty() -> { similarUniques.isNotEmpty() -> {
val text = 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 -> similarUniques.joinToString("\n") { uniqueType ->
var text = "\"${uniqueType.text}" var text = "\"${uniqueType.text}"
if (unique.conditionals.isNotEmpty()) if (unique.conditionals.isNotEmpty())
@ -513,7 +513,7 @@ class RulesetValidator(val ruleset: Ruleset) {
} }
RulesetCache.modCheckerAllowUntypedUniques -> emptyList() RulesetCache.modCheckerAllowUntypedUniques -> emptyList()
else -> listOf(RulesetError( 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)) RulesetErrorSeverity.OK))
} }
} }
@ -521,12 +521,12 @@ class RulesetValidator(val ruleset: Ruleset) {
val rulesetErrors = RulesetErrorList() val rulesetErrors = RulesetErrorList()
if (namedObj is IHasUniques && !unique.type.canAcceptUniqueTarget(namedObj.getUniqueTarget())) 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) val typeComplianceErrors = unique.type.getComplianceErrors(unique, ruleset)
for (complianceError in typeComplianceErrors) { for (complianceError in typeComplianceErrors) {
if (complianceError.errorSeverity <= severityToReport) 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" + " which does not fit parameter type" +
" ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !", " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport) complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
@ -536,13 +536,13 @@ class RulesetValidator(val ruleset: Ruleset) {
for (conditional in unique.conditionals) { for (conditional in unique.conditionals) {
if (conditional.type == null) { if (conditional.type == null) {
rulesetErrors.add( 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!", " which is of an unknown type!",
RulesetErrorSeverity.Warning RulesetErrorSeverity.Warning
) )
} else { } else {
if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None }) 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.", " which is a Unique type not allowed as conditional or trigger.",
RulesetErrorSeverity.Warning) RulesetErrorSeverity.Warning)
@ -550,7 +550,7 @@ class RulesetValidator(val ruleset: Ruleset) {
conditional.type.getComplianceErrors(conditional, ruleset) conditional.type.getComplianceErrors(conditional, ruleset)
for (complianceError in conditionalComplianceErrors) { for (complianceError in conditionalComplianceErrors) {
if (complianceError.errorSeverity == severityToReport) 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" + " This contains the parameter ${complianceError.parameterName} which does not fit parameter type" +
" ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !", " ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport) complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
@ -570,7 +570,7 @@ class RulesetValidator(val ruleset: Ruleset) {
if (deprecationAnnotation != null) { if (deprecationAnnotation != null) {
val replacementUniqueText = unique.getReplacementText(ruleset) val replacementUniqueText = unique.getReplacementText(ruleset)
val deprecationText = 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 "" if (deprecationAnnotation.replaceWith.expression != "") " replace with \"${replacementUniqueText}\"" else ""
val severity = if (deprecationAnnotation.level == DeprecationLevel.WARNING) val severity = if (deprecationAnnotation.level == DeprecationLevel.WARNING)
RulesetErrorSeverity.WarningOptionsOnly // Not user-visible RulesetErrorSeverity.WarningOptionsOnly // Not user-visible

View File

@ -463,9 +463,10 @@ object Github {
* Replaces '-' with blanks but ensures no leading or trailing blanks. * 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 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. * 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 { fun String.repoNameToFolderName(onlyOuterBlanks: Boolean = false): String {
var result = replace('-', ' ') var result = if (onlyOuterBlanks) this else replace('-', ' ')
if (result.endsWith(' ')) result = result.trimEnd() + outerBlankReplacement if (result.endsWith(' ')) result = result.trimEnd() + outerBlankReplacement
if (result.startsWith(' ')) result = outerBlankReplacement + result.trimStart() if (result.startsWith(' ')) result = outerBlankReplacement + result.trimStart()
return result return result

View File

@ -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) - [Difficulties.json](5-Miscellaneous-JSON-files.md#difficultiesjson)
- [Eras.json](5-Miscellaneous-JSON-files.md#erasjson) - [Eras.json](5-Miscellaneous-JSON-files.md#erasjson)
- [ModOptions.json](5-Miscellaneous-JSON-files.md#modoptionsjson) - [ModOptions.json](5-Miscellaneous-JSON-files.md#modoptionsjson)
- [GlobalUniques.json](5-Miscellaneous-JSON-files.md#globaluniquesjson)
- [Tutorials.json](5-Miscellaneous-JSON-files.md#tutorialsjson) - [Tutorials.json](5-Miscellaneous-JSON-files.md#tutorialsjson)
- [Stats](3-Map-related-JSON-files.md#stats) - [Stats](3-Map-related-JSON-files.md#stats)
- [Sounds](../Images-and-Audio.md#sounds) - [Sounds](../Images-and-Audio.md#sounds)

View File

@ -203,6 +203,14 @@ The formula for the gold cost of a unit upgrade is (rounded down to a multiple o
) ^ `exponent` ) ^ `exponent`
With `civModifier` being the multiplicative aggregate of ["\[relativeAmount\]% Gold cost of upgrading"](../uniques.md#global-uniques) uniques that apply. 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 ## Tutorials.json
[link to original](https://github.com/yairm210/Unciv/tree/master/android/assets/jsons/Tutorials.json) [link to original](https://github.com/yairm210/Unciv/tree/master/android/assets/jsons/Tutorials.json)