mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 21:35:14 -04:00
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:
parent
6eeb630b6c
commit
1694a59fd2
@ -10,6 +10,7 @@ import com.unciv.models.ruleset.unique.IHasUniques
|
|||||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
import com.unciv.models.ruleset.unique.Unique
|
import com.unciv.models.ruleset.unique.Unique
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
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.INamed
|
||||||
import com.unciv.models.stats.Stats
|
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)))
|
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)
|
lines.add("${promotion.name} references unit type $unitType, which does not exist!", RulesetErrorSeverity.Warning)
|
||||||
checkUniques(promotion, lines, rulesetSpecific, tryFixUnknownUniques)
|
checkUniques(promotion, lines, rulesetSpecific, tryFixUnknownUniques)
|
||||||
|
checkPromotionCircularReferences(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unitType in ruleset.unitTypes.values) {
|
for (unitType in ruleset.unitTypes.values) {
|
||||||
@ -426,6 +428,24 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
return lines
|
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(
|
fun checkUniques(
|
||||||
uniqueContainer: IHasUniques,
|
uniqueContainer: IHasUniques,
|
||||||
|
@ -6,7 +6,6 @@ import com.unciv.models.ruleset.unique.StateForConditionals
|
|||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.models.ruleset.unit.Promotion
|
import com.unciv.models.ruleset.unit.Promotion
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.utils.Log
|
|
||||||
|
|
||||||
internal class PromotionTree(val unit: MapUnit) {
|
internal class PromotionTree(val unit: MapUnit) {
|
||||||
/** Ordered set of Promotions to show - by Json column/row and translated name */
|
/** 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 (node in nodes.values) {
|
||||||
for (prerequisite in node.promotion.prerequisites) {
|
for (prerequisite in node.promotion.prerequisites) {
|
||||||
val parent = nodes[prerequisite] ?: continue
|
val parent = nodes[prerequisite] ?: continue
|
||||||
if (node in allChildren(parent)) {
|
if (detectLoop(node, parent)) continue
|
||||||
Log.debug("Ignoring circular reference: %s requires %s", node, parent)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
node.parents += parent
|
node.parents += parent
|
||||||
parent.children += node
|
parent.children += node
|
||||||
if (node.level > 0 && node.baseName == parent.baseName)
|
if (node.level > 0 && node.baseName == parent.baseName)
|
||||||
@ -153,8 +149,21 @@ internal class PromotionTree(val unit: MapUnit) {
|
|||||||
|
|
||||||
fun allNodes() = nodes.values.asSequence()
|
fun allNodes() = nodes.values.asSequence()
|
||||||
fun allRoots() = allNodes().filter { it.isRoot }
|
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? =
|
private fun getReachableNode(promotion: Promotion): PromotionNode? =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user