From 2d3b6047f3f9a0520a371f8a22ba554b9a71d9d9 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:45:34 +0200 Subject: [PATCH] Allow mods with no global uniques, no ruins or no difficulties file (#13521) * Allow mods with no GlobalUniques * Allow mods omitting Difficulties to fall back to Vanilla * Allow mods with no Ancient Ruins + Checks --- core/src/com/unciv/models/ruleset/Ruleset.kt | 5 +++-- .../validation/BaseRulesetValidator.kt | 15 ++++++++++++++ .../ruleset/validation/UniqueValidator.kt | 20 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 8ecd358131..1784c01af0 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -449,16 +449,17 @@ class Ruleset { } // These should be permanent - if (ruinRewards.isEmpty()) + if (!ruinRewardsFile.exists()) ruinRewards.putAll(fallbackRuleset.ruinRewards) - if (globalUniques.uniques.isEmpty()) { + if (!globalUniquesFile.exists()) { globalUniques = fallbackRuleset.globalUniques } // If we have no victories, add all the default victories if (victories.isEmpty()) victories.putAll(fallbackRuleset.victories) if (speeds.isEmpty()) speeds.putAll(fallbackRuleset.speeds) + if (difficulties.isEmpty()) difficulties.putAll(fallbackRuleset.difficulties) if (cityStateTypes.isEmpty()) for (cityStateType in fallbackRuleset.cityStateTypes.values) diff --git a/core/src/com/unciv/models/ruleset/validation/BaseRulesetValidator.kt b/core/src/com/unciv/models/ruleset/validation/BaseRulesetValidator.kt index 2d511edcf2..40fc9d992b 100644 --- a/core/src/com/unciv/models/ruleset/validation/BaseRulesetValidator.kt +++ b/core/src/com/unciv/models/ruleset/validation/BaseRulesetValidator.kt @@ -31,6 +31,12 @@ internal class BaseRulesetValidator( * value a Set of its prerequisites including indirect ones */ private val prereqsHashMap = HashMap>() + private val anyAncientRuins: Boolean by lazy { + ruleset.tileImprovements.values.asSequence() + .flatMap { it.uniqueObjects } + .any { it.type == UniqueType.IsAncientRuinsEquivalent } + } + init { // The `UniqueValidator.checkUntypedUnique` filtering Unique test ("X not found in Unciv's unique types, and is not used as a filtering unique") // should not complain when running the RulesetInvariant version, because an Extension Mod may e.g. define additional "Aircraft" and the _use_ of the @@ -266,6 +272,15 @@ internal class BaseRulesetValidator( if (!ruleset.difficulties.containsKey(difficulty)) lines.add("${reward.name} references difficulty ${difficulty}, which does not exist!", sourceObject = reward) } + + // brainstorm: What additional constraints apply for rulesets without any ruins? + // * Any UniqueType.RuinsUpgrade is not OK -> See UniqueValidator.addUniqueTypeSpecificErrors + // * Any RuinRewards is not OK + // * Conversely, No ruinRewards is only OK for such rulesets + if (anyAncientRuins && ruleset.ruinRewards.isEmpty()) + lines.add("There are Ancient Ruins or equivalents, but not RuinRewards", sourceObject = null) + if (!anyAncientRuins && ruleset.ruinRewards.isNotEmpty()) + lines.add("There are RuinRewards, but no Ancient Ruins or equivalents", RulesetErrorSeverity.Warning, sourceObject = null) } override fun addSpecialistErrors(lines: RulesetErrorList) { diff --git a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt index a73a464ea3..6623d2012b 100644 --- a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt +++ b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt @@ -23,6 +23,12 @@ class UniqueValidator(val ruleset: Ruleset) { /** Used to determine if certain uniques are used for filtering */ private val allUniqueParameters = HashSet() + private val anyAncientRuins: Boolean by lazy { + ruleset.tileImprovements.values.asSequence() + .flatMap { it.uniqueObjects } + .any { it.type == UniqueType.IsAncientRuinsEquivalent } + } + private fun addToHashsets(uniqueHolder: IHasUniques) { for (unique in uniqueHolder.uniqueObjects) { if (unique.type == null) allNonTypedUniques.add(unique.text) @@ -94,6 +100,8 @@ class UniqueValidator(val ruleset: Ruleset) { addConditionalErrors(conditional, rulesetErrors, prefix, unique, uniqueContainer, reportRulesetSpecificErrors) } + addUniqueTypeSpecificErrors(rulesetErrors, prefix, unique, uniqueContainer, reportRulesetSpecificErrors) + val conditionals = unique.modifiers.filter { it.type?.canAcceptUniqueTarget(UniqueTarget.Conditional) == true } if (conditionals.size > 1){ val lastCheapConditional = conditionals.lastOrNull { it.type !in performanceHeavyConditionals } @@ -260,6 +268,18 @@ class UniqueValidator(val ruleset: Ruleset) { addDeprecationAnnotationErrors(conditional, "$prefix contains modifier \"${conditional.text}\" which", rulesetErrors, uniqueContainer) } + private fun addUniqueTypeSpecificErrors( + rulesetErrors: RulesetErrorList, prefix: String, unique: Unique, uniqueContainer: IHasUniques?, reportRulesetSpecificErrors: Boolean + ) { + when(unique.type) { + UniqueType.RuinsUpgrade -> { + if (reportRulesetSpecificErrors && !anyAncientRuins) + rulesetErrors.add("$prefix is pointless - there are no ancient ruins", RulesetErrorSeverity.Warning, uniqueContainer, unique) + } + else -> return + } + } + private fun addDeprecationAnnotationErrors( unique: Unique, prefix: String,