Defense against circular references in Promotions (#9694)

* Promotion picker - working defense against circular references

* Mod checker - complain about circular references
This commit is contained in:
SomeTroglodyte 2023-06-29 08:18:07 +02:00 committed by GitHub
parent 6eeb630b6c
commit 1694a59fd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 7 deletions

View File

@ -10,6 +10,7 @@ import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.Promotion
import com.unciv.models.stats.INamed
import com.unciv.models.stats.Stats
@ -387,6 +388,7 @@ class RulesetValidator(val ruleset: Ruleset) {
if (!ruleset.unitTypes.containsKey(unitType) && (ruleset.unitTypes.isNotEmpty() || !vanillaRuleset.unitTypes.containsKey(unitType)))
lines.add("${promotion.name} references unit type $unitType, which does not exist!", RulesetErrorSeverity.Warning)
checkUniques(promotion, lines, rulesetSpecific, tryFixUnknownUniques)
checkPromotionCircularReferences(lines)
}
for (unitType in ruleset.unitTypes.values) {
@ -426,6 +428,24 @@ class RulesetValidator(val ruleset: Ruleset) {
return lines
}
private fun checkPromotionCircularReferences(lines: RulesetErrorList) {
fun recursiveCheck(history: LinkedHashSet<Promotion>, promotion: Promotion, level: Int) {
if (promotion in history) {
lines.add("Circular Reference in Promotions: ${history.joinToString("→") { it.name }}→${promotion.name}", RulesetErrorSeverity.Warning)
return
}
if (level > 99) return
history.add(promotion)
for (prerequisiteName in promotion.prerequisites) {
val prerequisite = ruleset.unitPromotions[prerequisiteName] ?: continue
recursiveCheck(history.toCollection(linkedSetOf()), prerequisite, level + 1)
}
}
for (promotion in ruleset.unitPromotions.values) {
recursiveCheck(linkedSetOf(), promotion, 0)
}
}
fun checkUniques(
uniqueContainer: IHasUniques,

View File

@ -6,7 +6,6 @@ import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.Promotion
import com.unciv.models.translations.tr
import com.unciv.utils.Log
internal class PromotionTree(val unit: MapUnit) {
/** Ordered set of Promotions to show - by Json column/row and translated name */
@ -92,10 +91,7 @@ internal class PromotionTree(val unit: MapUnit) {
for (node in nodes.values) {
for (prerequisite in node.promotion.prerequisites) {
val parent = nodes[prerequisite] ?: continue
if (node in allChildren(parent)) {
Log.debug("Ignoring circular reference: %s requires %s", node, parent)
continue
}
if (detectLoop(node, parent)) continue
node.parents += parent
parent.children += node
if (node.level > 0 && node.baseName == parent.baseName)
@ -153,8 +149,21 @@ internal class PromotionTree(val unit: MapUnit) {
fun allNodes() = nodes.values.asSequence()
fun allRoots() = allNodes().filter { it.isRoot }
private fun allChildren(node: PromotionNode): Sequence<PromotionNode> {
return sequenceOf(node) + node.children.flatMap { allChildren(it) }
private fun detectLoop(node: PromotionNode, parent: PromotionNode): Boolean {
if (parent == node) return true
val loopCheck = HashSet<PromotionNode>(nodes.size)
loopCheck.add(node)
fun detectRecursive(parent: PromotionNode, level: Int): Boolean {
if (level > 99) return true
if (parent in loopCheck) return true
loopCheck.add(parent)
for (child in parent.children) {
if (detectRecursive(child, level + 1)) return true
}
return false
}
return detectRecursive(parent, 0)
}
private fun getReachableNode(promotion: Promotion): PromotionNode? =