mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-28 06:16:37 -04:00
Framework stuff: Preparation for another Warning-Suppression branch (#11128)
* Make source object available to RulesetErrorList.add * Make ruleset available to RulesetErrorList * Introduce UniqueTarget.MetaModifier - ModifierHiddenFromUsers won't stay alone * There was no use of prefix without the unique name - fold * Introduce UniqueTarget.MetaModifier - doc * Pass context down even inside UniqueValidator, convenience factory for limited RulesetErrorList's * Clean up RulesetErrorList.of factory * Reorder parameters of RulesetErrorList.add
This commit is contained in:
parent
ec1f51dfb8
commit
88df2c26e6
@ -9,6 +9,7 @@ import com.unciv.models.metadata.GameParameters
|
|||||||
import com.unciv.models.ruleset.validation.RulesetError
|
import com.unciv.models.ruleset.validation.RulesetError
|
||||||
import com.unciv.models.ruleset.validation.RulesetErrorList
|
import com.unciv.models.ruleset.validation.RulesetErrorList
|
||||||
import com.unciv.models.ruleset.validation.RulesetErrorSeverity
|
import com.unciv.models.ruleset.validation.RulesetErrorSeverity
|
||||||
|
import com.unciv.models.ruleset.validation.getRelativeTextDistance
|
||||||
import com.unciv.utils.Log
|
import com.unciv.utils.Log
|
||||||
import com.unciv.utils.debug
|
import com.unciv.utils.debug
|
||||||
|
|
||||||
@ -171,9 +172,7 @@ object RulesetCache : HashMap<String, Ruleset>() {
|
|||||||
} catch (ex: UncivShowableException) {
|
} catch (ex: UncivShowableException) {
|
||||||
// This happens if a building is dependent on a tech not in the base ruleset
|
// This happens if a building is dependent on a tech not in the base ruleset
|
||||||
// because newRuleset.updateBuildingCosts() in getComplexRuleset() throws an error
|
// because newRuleset.updateBuildingCosts() in getComplexRuleset() throws an error
|
||||||
RulesetErrorList()
|
RulesetErrorList.of(ex.message, RulesetErrorSeverity.Error)
|
||||||
.apply { add(ex.message, RulesetErrorSeverity.Error) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ object Conditionals {
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
|
|
||||||
if (condition.type?.targetTypes?.any { it.modifierType == UniqueTarget.ModifierType.Other } == true)
|
if (condition.type?.targetTypes?.any { it.modifierType == UniqueTarget.ModifierType.Other } == true)
|
||||||
return true // not a filtering condition
|
return true // not a filtering condition, includes e.g. ModifierHiddenFromUsers
|
||||||
|
|
||||||
val relevantUnit by lazy {
|
val relevantUnit by lazy {
|
||||||
if (state.ourCombatant != null && state.ourCombatant is MapUnitCombatant) state.ourCombatant.unit
|
if (state.ourCombatant != null && state.ourCombatant is MapUnitCombatant) state.ourCombatant.unit
|
||||||
@ -109,7 +109,6 @@ object Conditionals {
|
|||||||
return when (condition.type) {
|
return when (condition.type) {
|
||||||
// These are 'what to do' and not 'when to do' conditionals
|
// These are 'what to do' and not 'when to do' conditionals
|
||||||
UniqueType.ConditionalTimedUnique -> true
|
UniqueType.ConditionalTimedUnique -> true
|
||||||
UniqueType.ModifierHiddenFromUsers -> true // allowed to be attached to any Unique to hide it, no-op otherwise
|
|
||||||
|
|
||||||
UniqueType.ConditionalChance -> stateBasedRandom.nextFloat() < condition.params[0].toFloat() / 100f
|
UniqueType.ConditionalChance -> stateBasedRandom.nextFloat() < condition.params[0].toFloat() / 100f
|
||||||
UniqueType.ConditionalEveryTurns -> checkOnGameInfo { turns % condition.params[0].toInt() == 0 }
|
UniqueType.ConditionalEveryTurns -> checkOnGameInfo { turns % condition.params[0].toInt() == 0 }
|
||||||
|
@ -7,7 +7,7 @@ package com.unciv.models.ruleset.unique
|
|||||||
* @param inheritsFrom means that all such uniques are acceptable as well. For example, all Global uniques are acceptable for Nations, Eras, etc.
|
* @param inheritsFrom means that all such uniques are acceptable as well. For example, all Global uniques are acceptable for Nations, Eras, etc.
|
||||||
*/
|
*/
|
||||||
enum class UniqueTarget(
|
enum class UniqueTarget(
|
||||||
val documentationString:String = "",
|
val documentationString: String = "",
|
||||||
val inheritsFrom: UniqueTarget? = null,
|
val inheritsFrom: UniqueTarget? = null,
|
||||||
val modifierType: ModifierType = ModifierType.None
|
val modifierType: ModifierType = ModifierType.None
|
||||||
) {
|
) {
|
||||||
@ -64,6 +64,7 @@ enum class UniqueTarget(
|
|||||||
TriggerCondition("Special conditionals that can be added to Triggerable uniques, to make them activate upon specific actions.", inheritsFrom = Global, modifierType = ModifierType.Other),
|
TriggerCondition("Special conditionals that can be added to Triggerable uniques, to make them activate upon specific actions.", inheritsFrom = Global, modifierType = ModifierType.Other),
|
||||||
UnitTriggerCondition("Special conditionals that can be added to UnitTriggerable uniques, to make them activate upon specific actions.", inheritsFrom = TriggerCondition, modifierType = ModifierType.Other),
|
UnitTriggerCondition("Special conditionals that can be added to UnitTriggerable uniques, to make them activate upon specific actions.", inheritsFrom = TriggerCondition, modifierType = ModifierType.Other),
|
||||||
UnitActionModifier("Modifiers that can be added to UnitAction uniques as conditionals", modifierType = ModifierType.Other),
|
UnitActionModifier("Modifiers that can be added to UnitAction uniques as conditionals", modifierType = ModifierType.Other),
|
||||||
|
MetaModifier("Modifiers that can be added to other uniques changing user experience, not their behavior", modifierType = ModifierType.Other),
|
||||||
;
|
;
|
||||||
|
|
||||||
/** Whether a UniqueType is allowed in the `<conditional or trigger>` part - or not.
|
/** Whether a UniqueType is allowed in the `<conditional or trigger>` part - or not.
|
||||||
|
@ -809,7 +809,7 @@ enum class UniqueType(
|
|||||||
HiddenAfterGreatProphet("Hidden after generating a Great Prophet", UniqueTarget.Ruins),
|
HiddenAfterGreatProphet("Hidden after generating a Great Prophet", UniqueTarget.Ruins),
|
||||||
HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers),
|
HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers),
|
||||||
HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.Displayable, flags = UniqueFlag.setOfHiddenToUsers),
|
HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.Displayable, flags = UniqueFlag.setOfHiddenToUsers),
|
||||||
ModifierHiddenFromUsers("hidden from users", UniqueTarget.Conditional),
|
ModifierHiddenFromUsers("hidden from users", UniqueTarget.MetaModifier),
|
||||||
Comment("Comment [comment]", *UniqueTarget.Displayable,
|
Comment("Comment [comment]", *UniqueTarget.Displayable,
|
||||||
docDescription = "Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent."),
|
docDescription = "Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent."),
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.unciv.models.ruleset.validation
|
package com.unciv.models.ruleset.validation
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.unciv.models.ruleset.Ruleset
|
||||||
|
import com.unciv.models.ruleset.unique.IHasUniques
|
||||||
|
|
||||||
class RulesetError(val text: String, val errorSeverityToReport: RulesetErrorSeverity)
|
class RulesetError(val text: String, val errorSeverityToReport: RulesetErrorSeverity)
|
||||||
|
|
||||||
@ -11,32 +13,61 @@ enum class RulesetErrorSeverity(val color: Color) {
|
|||||||
Error(Color.RED),
|
Error(Color.RED),
|
||||||
}
|
}
|
||||||
|
|
||||||
class RulesetErrorList : ArrayList<RulesetError>() {
|
/**
|
||||||
operator fun plusAssign(text: String) {
|
* A container collecting errors in a [Ruleset]
|
||||||
add(text, RulesetErrorSeverity.Error)
|
*
|
||||||
|
* While this is based a standard collection, please do not use the standard add, [plusAssign] or [addAll].
|
||||||
|
* Mod-controlled warning suppression is handled in [add] overloads that provide a source object, which can host suppression uniques.
|
||||||
|
* Bypassing these add methods means suppression is ignored. Thus using [addAll] is fine when the elements to add are all already checked.
|
||||||
|
*
|
||||||
|
* //todo This version prepares suppression, but does not actually implement it
|
||||||
|
*
|
||||||
|
* @param ruleset The ruleset being validated (needed to check modOptions for suppression uniques). Leave `null` only for validation results that need no suppression checks.
|
||||||
|
*/
|
||||||
|
class RulesetErrorList(
|
||||||
|
ruleset: Ruleset? = null
|
||||||
|
) : ArrayList<RulesetError>() {
|
||||||
|
/** Add an [element], preventing duplicates (in which case the highest severity wins).
|
||||||
|
*
|
||||||
|
* [sourceObject] is for future use and should be the originating object. When it is not known or not a [IHasUniques], pass `null`.
|
||||||
|
*/
|
||||||
|
fun add(element: RulesetError, sourceObject: IHasUniques?): Boolean {
|
||||||
|
// Suppression to be checked here
|
||||||
|
return addWithDuplicateCheck(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(text: String, errorSeverityToReport: RulesetErrorSeverity) {
|
/** Shortcut: Add a new [RulesetError] built from [text] and [errorSeverityToReport].
|
||||||
add(RulesetError(text, errorSeverityToReport))
|
*
|
||||||
}
|
* [sourceObject] is for future use and should be the originating object. When it is not known or not a [IHasUniques], pass `null`.
|
||||||
|
*/
|
||||||
|
fun add(text: String, errorSeverityToReport: RulesetErrorSeverity = RulesetErrorSeverity.Error, sourceObject: IHasUniques?) =
|
||||||
|
add(RulesetError(text, errorSeverityToReport), sourceObject)
|
||||||
|
|
||||||
override fun add(element: RulesetError): Boolean {
|
@Deprecated("No adding without explicit source object", ReplaceWith("add(element, sourceObject)"))
|
||||||
// Suppress duplicates due to the double run of some checks for invariant/specific,
|
override fun add(element: RulesetError) = super.add(element)
|
||||||
// Without changing collection type or making RulesetError obey the equality contract
|
|
||||||
val existing = firstOrNull { it.text == element.text }
|
|
||||||
?: return super.add(element)
|
|
||||||
if (existing.errorSeverityToReport >= element.errorSeverityToReport) return false
|
|
||||||
remove(existing)
|
|
||||||
return super.add(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/** Add all [elements] with duplicate check, but without suppression check */
|
||||||
override fun addAll(elements: Collection<RulesetError>): Boolean {
|
override fun addAll(elements: Collection<RulesetError>): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
for (element in elements)
|
for (element in elements)
|
||||||
if (add(element)) result = true
|
if (addWithDuplicateCheck(element)) result = true
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addWithDuplicateCheck(element: RulesetError) =
|
||||||
|
removeLowerSeverityDuplicate(element) && super.add(element)
|
||||||
|
|
||||||
|
/** @return `true` if the element is not present, or it was removed due to having a lower severity */
|
||||||
|
private fun removeLowerSeverityDuplicate(element: RulesetError): Boolean {
|
||||||
|
// Suppress duplicates due to the double run of some checks for invariant/specific,
|
||||||
|
// Without changing collection type or making RulesetError obey the equality contract
|
||||||
|
val existing = firstOrNull { it.text == element.text }
|
||||||
|
?: return true
|
||||||
|
if (existing.errorSeverityToReport >= element.errorSeverityToReport) return false
|
||||||
|
remove(existing)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun getFinalSeverity(): RulesetErrorSeverity {
|
fun getFinalSeverity(): RulesetErrorSeverity {
|
||||||
if (isEmpty()) return RulesetErrorSeverity.OK
|
if (isEmpty()) return RulesetErrorSeverity.OK
|
||||||
return this.maxOf { it.errorSeverityToReport }
|
return this.maxOf { it.errorSeverityToReport }
|
||||||
@ -60,4 +91,17 @@ class RulesetErrorList : ArrayList<RulesetError>() {
|
|||||||
// out of place. Prevent via kludge:
|
// out of place. Prevent via kludge:
|
||||||
it.text.replace('<','〈').replace('>','〉')
|
it.text.replace('<','〈').replace('>','〉')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(
|
||||||
|
text: String,
|
||||||
|
severity: RulesetErrorSeverity = RulesetErrorSeverity.Error,
|
||||||
|
ruleset: Ruleset? = null,
|
||||||
|
sourceObject: IHasUniques? = null
|
||||||
|
): RulesetErrorList {
|
||||||
|
val result = RulesetErrorList(ruleset)
|
||||||
|
result.add(text, severity, sourceObject)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getNonBaseRulesetErrorList(tryFixUnknownUniques: Boolean): RulesetErrorList {
|
private fun getNonBaseRulesetErrorList(tryFixUnknownUniques: Boolean): RulesetErrorList {
|
||||||
val lines = RulesetErrorList()
|
val lines = RulesetErrorList(ruleset)
|
||||||
|
|
||||||
// When not checking the entire ruleset, we can only really detect ruleset-invariant errors in uniques
|
// When not checking the entire ruleset, we can only really detect ruleset-invariant errors in uniques
|
||||||
addModOptionsErrors(lines, tryFixUnknownUniques)
|
addModOptionsErrors(lines, tryFixUnknownUniques)
|
||||||
@ -62,7 +62,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
|
|
||||||
uniqueValidator.populateFilteringUniqueHashsets()
|
uniqueValidator.populateFilteringUniqueHashsets()
|
||||||
|
|
||||||
val lines = RulesetErrorList()
|
val lines = RulesetErrorList(ruleset)
|
||||||
addModOptionsErrors(lines, tryFixUnknownUniques)
|
addModOptionsErrors(lines, tryFixUnknownUniques)
|
||||||
uniqueValidator.checkUniques(ruleset.globalUniques, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(ruleset.globalUniques, lines, true, tryFixUnknownUniques)
|
||||||
|
|
||||||
@ -106,11 +106,12 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
UniqueType.ModIsAudioVisualOnly,
|
UniqueType.ModIsAudioVisualOnly,
|
||||||
UniqueType.ModIsNotAudioVisual
|
UniqueType.ModIsNotAudioVisual
|
||||||
)
|
)
|
||||||
|
// modOptions is a valid sourceObject, but unnecessary
|
||||||
if (ruleset.modOptions.uniqueObjects.count { it.type in audioVisualUniqueTypes } > 1)
|
if (ruleset.modOptions.uniqueObjects.count { it.type in audioVisualUniqueTypes } > 1)
|
||||||
lines += "A mod should only specify one of the 'can/should/cannot be used as permanent audiovisual mod' options."
|
lines.add("A mod should only specify one of the 'can/should/cannot be used as permanent audiovisual mod' options.", sourceObject = null)
|
||||||
if (!ruleset.modOptions.isBaseRuleset) return
|
if (!ruleset.modOptions.isBaseRuleset) return
|
||||||
for (unique in ruleset.modOptions.getMatchingUniques(UniqueType.ModRequires)) {
|
for (unique in ruleset.modOptions.getMatchingUniques(UniqueType.ModRequires)) {
|
||||||
lines += "Mod option '${unique.text}' is invalid for a base ruleset."
|
lines.add("Mod option '${unique.text}' is invalid for a base ruleset.", sourceObject = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,32 +133,34 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addDifficultyErrors(lines: RulesetErrorList) {
|
private fun addDifficultyErrors(lines: RulesetErrorList) {
|
||||||
|
// A Difficulty is not a IHasUniques, so not suitable as sourceObject
|
||||||
for (difficulty in ruleset.difficulties.values) {
|
for (difficulty in ruleset.difficulties.values) {
|
||||||
for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits)
|
for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits)
|
||||||
if (unitName != Constants.eraSpecificUnit && !ruleset.units.containsKey(unitName))
|
if (unitName != Constants.eraSpecificUnit && !ruleset.units.containsKey(unitName))
|
||||||
lines += "Difficulty ${difficulty.name} contains starting unit $unitName which does not exist!"
|
lines.add("Difficulty ${difficulty.name} contains starting unit $unitName which does not exist!", sourceObject = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addVictoryTypeErrors(lines: RulesetErrorList) {
|
private fun addVictoryTypeErrors(lines: RulesetErrorList) {
|
||||||
|
// Victory and Milestone aren't IHasUniques and are unsuitable as sourceObject
|
||||||
for (victoryType in ruleset.victories.values) {
|
for (victoryType in ruleset.victories.values) {
|
||||||
for (requiredUnit in victoryType.requiredSpaceshipParts)
|
for (requiredUnit in victoryType.requiredSpaceshipParts)
|
||||||
if (!ruleset.units.contains(requiredUnit))
|
if (!ruleset.units.contains(requiredUnit))
|
||||||
lines.add(
|
lines.add(
|
||||||
"Victory type ${victoryType.name} requires adding the non-existant unit $requiredUnit to the capital to win!",
|
"Victory type ${victoryType.name} requires adding the non-existant unit $requiredUnit to the capital to win!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, sourceObject = null
|
||||||
)
|
)
|
||||||
for (milestone in victoryType.milestoneObjects)
|
for (milestone in victoryType.milestoneObjects)
|
||||||
if (milestone.type == null)
|
if (milestone.type == null)
|
||||||
lines.add(
|
lines.add(
|
||||||
"Victory type ${victoryType.name} has milestone ${milestone.uniqueDescription} that is of an unknown type!",
|
"Victory type ${victoryType.name} has milestone ${milestone.uniqueDescription} that is of an unknown type!",
|
||||||
RulesetErrorSeverity.Error
|
RulesetErrorSeverity.Error, sourceObject = null
|
||||||
)
|
)
|
||||||
for (victory in ruleset.victories.values)
|
for (victory in ruleset.victories.values)
|
||||||
if (victory.name != victoryType.name && victory.milestones == victoryType.milestones)
|
if (victory.name != victoryType.name && victory.milestones == victoryType.milestones)
|
||||||
lines.add(
|
lines.add(
|
||||||
"Victory types ${victoryType.name} and ${victory.name} have the same requirements!",
|
"Victory types ${victoryType.name} and ${victory.name} have the same requirements!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, sourceObject = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,12 +186,12 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
if (!ruleset.unitPromotions.containsKey(prereq))
|
if (!ruleset.unitPromotions.containsKey(prereq))
|
||||||
lines.add(
|
lines.add(
|
||||||
"${promotion.name} requires promotion $prereq which does not exist!",
|
"${promotion.name} requires promotion $prereq which does not exist!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, promotion
|
||||||
)
|
)
|
||||||
for (unitType in promotion.unitTypes) checkUnitType(unitType) {
|
for (unitType in promotion.unitTypes) checkUnitType(unitType) {
|
||||||
lines.add(
|
lines.add(
|
||||||
"${promotion.name} references unit type $unitType, which does not exist!",
|
"${promotion.name} references unit type $unitType, which does not exist!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, promotion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
uniqueValidator.checkUniques(promotion, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(promotion, lines, true, tryFixUnknownUniques)
|
||||||
@ -202,10 +205,10 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
) {
|
) {
|
||||||
for (reward in ruleset.ruinRewards.values) {
|
for (reward in ruleset.ruinRewards.values) {
|
||||||
@Suppress("KotlinConstantConditions") // data is read from json, so any assumptions may be wrong
|
@Suppress("KotlinConstantConditions") // data is read from json, so any assumptions may be wrong
|
||||||
if (reward.weight < 0) lines += "${reward.name} has a negative weight, which is not allowed!"
|
if (reward.weight < 0) lines.add("${reward.name} has a negative weight, which is not allowed!", sourceObject = reward)
|
||||||
for (difficulty in reward.excludedDifficulties)
|
for (difficulty in reward.excludedDifficulties)
|
||||||
if (!ruleset.difficulties.containsKey(difficulty))
|
if (!ruleset.difficulties.containsKey(difficulty))
|
||||||
lines += "${reward.name} references difficulty ${difficulty}, which does not exist!"
|
lines.add("${reward.name} references difficulty ${difficulty}, which does not exist!", sourceObject = reward)
|
||||||
uniqueValidator.checkUniques(reward, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(reward, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,18 +221,18 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
if (policy.requires != null)
|
if (policy.requires != null)
|
||||||
for (prereq in policy.requires!!)
|
for (prereq in policy.requires!!)
|
||||||
if (!ruleset.policies.containsKey(prereq))
|
if (!ruleset.policies.containsKey(prereq))
|
||||||
lines += "${policy.name} requires policy $prereq which does not exist!"
|
lines.add("${policy.name} requires policy $prereq which does not exist!", sourceObject = policy)
|
||||||
uniqueValidator.checkUniques(policy, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(policy, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (branch in ruleset.policyBranches.values)
|
for (branch in ruleset.policyBranches.values)
|
||||||
if (branch.era !in ruleset.eras)
|
if (branch.era !in ruleset.eras)
|
||||||
lines += "${branch.name} requires era ${branch.era} which does not exist!"
|
lines.add("${branch.name} requires era ${branch.era} which does not exist!", sourceObject = branch)
|
||||||
|
|
||||||
|
|
||||||
for (policy in ruleset.policyBranches.values.flatMap { it.policies + it })
|
for (policy in ruleset.policyBranches.values.flatMap { it.policies + it })
|
||||||
if (policy != ruleset.policies[policy.name])
|
if (policy != ruleset.policies[policy.name])
|
||||||
lines += "More than one policy with the name ${policy.name} exists!"
|
lines.add("More than one policy with the name ${policy.name} exists!", sourceObject = policy)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,9 +246,9 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
uniqueValidator.checkUniques(nation, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(nation, lines, true, tryFixUnknownUniques)
|
||||||
|
|
||||||
if (nation.cityStateType != null && nation.cityStateType !in ruleset.cityStateTypes)
|
if (nation.cityStateType != null && nation.cityStateType !in ruleset.cityStateTypes)
|
||||||
lines += "${nation.name} is of city-state type ${nation.cityStateType} which does not exist!"
|
lines.add("${nation.name} is of city-state type ${nation.cityStateType} which does not exist!", sourceObject = nation)
|
||||||
if (nation.favoredReligion != null && nation.favoredReligion !in ruleset.religions)
|
if (nation.favoredReligion != null && nation.favoredReligion !in ruleset.religions)
|
||||||
lines += "${nation.name} has ${nation.favoredReligion} as their favored religion, which does not exist!"
|
lines.add("${nation.name} has ${nation.favoredReligion} as their favored religion, which does not exist!", sourceObject = nation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +258,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
) {
|
) {
|
||||||
for (belief in ruleset.beliefs.values) {
|
for (belief in ruleset.beliefs.values) {
|
||||||
if (belief.type == BeliefType.Any || belief.type == BeliefType.None)
|
if (belief.type == BeliefType.Any || belief.type == BeliefType.None)
|
||||||
lines += "${belief.name} type is {belief.type}, which is not allowed!"
|
lines.add("${belief.name} type is ${belief.type}, which is not allowed!", sourceObject = belief)
|
||||||
uniqueValidator.checkUniques(belief, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(belief, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,9 +266,9 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
private fun addSpeedErrors(lines: RulesetErrorList) {
|
private fun addSpeedErrors(lines: RulesetErrorList) {
|
||||||
for (speed in ruleset.speeds.values) {
|
for (speed in ruleset.speeds.values) {
|
||||||
if (speed.modifier < 0f)
|
if (speed.modifier < 0f)
|
||||||
lines += "Negative speed modifier for game speed ${speed.name}"
|
lines.add("Negative speed modifier for game speed ${speed.name}", sourceObject = speed)
|
||||||
if (speed.yearsPerTurn.isEmpty())
|
if (speed.yearsPerTurn.isEmpty())
|
||||||
lines += "Empty turn increment list for game speed ${speed.name}"
|
lines.add("Empty turn increment list for game speed ${speed.name}", sourceObject = speed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +277,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
tryFixUnknownUniques: Boolean
|
tryFixUnknownUniques: Boolean
|
||||||
) {
|
) {
|
||||||
if (ruleset.eras.isEmpty()) {
|
if (ruleset.eras.isEmpty()) {
|
||||||
lines += "Eras file is empty! This will likely lead to crashes. Ask the mod maker to update this mod!"
|
lines.add("Eras file is empty! This will likely lead to crashes. Ask the mod maker to update this mod!", sourceObject = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val allDifficultiesStartingUnits = hashSetOf<String>()
|
val allDifficultiesStartingUnits = hashSetOf<String>()
|
||||||
@ -287,38 +290,38 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
for (era in ruleset.eras.values) {
|
for (era in ruleset.eras.values) {
|
||||||
for (wonder in era.startingObsoleteWonders)
|
for (wonder in era.startingObsoleteWonders)
|
||||||
if (wonder !in ruleset.buildings)
|
if (wonder !in ruleset.buildings)
|
||||||
lines += "Nonexistent wonder $wonder obsoleted when starting in ${era.name}!"
|
lines.add("Nonexistent wonder $wonder obsoleted when starting in ${era.name}!", sourceObject = era)
|
||||||
for (building in era.settlerBuildings)
|
for (building in era.settlerBuildings)
|
||||||
if (building !in ruleset.buildings)
|
if (building !in ruleset.buildings)
|
||||||
lines += "Nonexistent building $building built by settlers when starting in ${era.name}"
|
lines.add("Nonexistent building $building built by settlers when starting in ${era.name}", sourceObject = era)
|
||||||
// todo the whole 'starting unit' thing needs to be redone, there's no reason we can't have a single list containing all the starting units.
|
// todo the whole 'starting unit' thing needs to be redone, there's no reason we can't have a single list containing all the starting units.
|
||||||
if (era.startingSettlerUnit !in ruleset.units
|
if (era.startingSettlerUnit !in ruleset.units
|
||||||
&& ruleset.units.values.none { it.isCityFounder() }
|
&& ruleset.units.values.none { it.isCityFounder() }
|
||||||
)
|
)
|
||||||
lines += "Nonexistent unit ${era.startingSettlerUnit} marked as starting unit when starting in ${era.name}"
|
lines.add("Nonexistent unit ${era.startingSettlerUnit} marked as starting unit when starting in ${era.name}", sourceObject = era)
|
||||||
if (era.startingWorkerCount != 0 && era.startingWorkerUnit !in ruleset.units
|
if (era.startingWorkerCount != 0 && era.startingWorkerUnit !in ruleset.units
|
||||||
&& ruleset.units.values.none { it.hasUnique(UniqueType.BuildImprovements) }
|
&& ruleset.units.values.none { it.hasUnique(UniqueType.BuildImprovements) }
|
||||||
)
|
)
|
||||||
lines += "Nonexistent unit ${era.startingWorkerUnit} marked as starting unit when starting in ${era.name}"
|
lines.add("Nonexistent unit ${era.startingWorkerUnit} marked as starting unit when starting in ${era.name}", sourceObject = era)
|
||||||
|
|
||||||
val grantsStartingMilitaryUnit = era.startingMilitaryUnitCount != 0
|
val grantsStartingMilitaryUnit = era.startingMilitaryUnitCount != 0
|
||||||
|| allDifficultiesStartingUnits.contains(Constants.eraSpecificUnit)
|
|| allDifficultiesStartingUnits.contains(Constants.eraSpecificUnit)
|
||||||
if (grantsStartingMilitaryUnit && era.startingMilitaryUnit !in ruleset.units)
|
if (grantsStartingMilitaryUnit && era.startingMilitaryUnit !in ruleset.units)
|
||||||
lines += "Nonexistent unit ${era.startingMilitaryUnit} marked as starting unit when starting in ${era.name}"
|
lines.add("Nonexistent unit ${era.startingMilitaryUnit} marked as starting unit when starting in ${era.name}", sourceObject = era)
|
||||||
if (era.researchAgreementCost < 0 || era.startingSettlerCount < 0 || era.startingWorkerCount < 0 || era.startingMilitaryUnitCount < 0 || era.startingGold < 0 || era.startingCulture < 0)
|
if (era.researchAgreementCost < 0 || era.startingSettlerCount < 0 || era.startingWorkerCount < 0 || era.startingMilitaryUnitCount < 0 || era.startingGold < 0 || era.startingCulture < 0)
|
||||||
lines += "Unexpected negative number found while parsing era ${era.name}"
|
lines.add("Unexpected negative number found while parsing era ${era.name}", sourceObject = era)
|
||||||
if (era.settlerPopulation <= 0)
|
if (era.settlerPopulation <= 0)
|
||||||
lines += "Population in cities from settlers must be strictly positive! Found value ${era.settlerPopulation} for era ${era.name}"
|
lines.add("Population in cities from settlers must be strictly positive! Found value ${era.settlerPopulation} for era ${era.name}", sourceObject = era)
|
||||||
|
|
||||||
if (era.allyBonus.isNotEmpty())
|
if (era.allyBonus.isNotEmpty())
|
||||||
lines.add(
|
lines.add(
|
||||||
"Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
|
"Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
|
||||||
RulesetErrorSeverity.WarningOptionsOnly
|
RulesetErrorSeverity.WarningOptionsOnly, era
|
||||||
)
|
)
|
||||||
if (era.friendBonus.isNotEmpty())
|
if (era.friendBonus.isNotEmpty())
|
||||||
lines.add(
|
lines.add(
|
||||||
"Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
|
"Era ${era.name} contains city-state bonuses. City-state bonuses are now defined in CityStateType.json",
|
||||||
RulesetErrorSeverity.WarningOptionsOnly
|
RulesetErrorSeverity.WarningOptionsOnly, era
|
||||||
)
|
)
|
||||||
|
|
||||||
uniqueValidator.checkUniques(era, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(era, lines, true, tryFixUnknownUniques)
|
||||||
@ -332,20 +335,20 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
for (tech in ruleset.technologies.values) {
|
for (tech in ruleset.technologies.values) {
|
||||||
for (prereq in tech.prerequisites) {
|
for (prereq in tech.prerequisites) {
|
||||||
if (!ruleset.technologies.containsKey(prereq))
|
if (!ruleset.technologies.containsKey(prereq))
|
||||||
lines += "${tech.name} requires tech $prereq which does not exist!"
|
lines.add("${tech.name} requires tech $prereq which does not exist!", sourceObject = tech)
|
||||||
|
|
||||||
if (tech.prerequisites.any { it != prereq && getPrereqTree(it).contains(prereq) }) {
|
if (tech.prerequisites.any { it != prereq && getPrereqTree(it).contains(prereq) }) {
|
||||||
lines.add(
|
lines.add(
|
||||||
"No need to add $prereq as a prerequisite of ${tech.name} - it is already implicit from the other prerequisites!",
|
"No need to add $prereq as a prerequisite of ${tech.name} - it is already implicit from the other prerequisites!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, tech
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPrereqTree(prereq).contains(tech.name))
|
if (getPrereqTree(prereq).contains(tech.name))
|
||||||
lines += "Techs ${tech.name} and $prereq require each other!"
|
lines.add("Techs ${tech.name} and $prereq require each other!", sourceObject = tech)
|
||||||
}
|
}
|
||||||
if (tech.era() !in ruleset.eras)
|
if (tech.era() !in ruleset.eras)
|
||||||
lines += "Unknown era ${tech.era()} referenced in column of tech ${tech.name}"
|
lines.add("Unknown era ${tech.era()} referenced in column of tech ${tech.name}", sourceObject = tech)
|
||||||
uniqueValidator.checkUniques(tech, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(tech, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,25 +358,25 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
tryFixUnknownUniques: Boolean
|
tryFixUnknownUniques: Boolean
|
||||||
) {
|
) {
|
||||||
if (ruleset.terrains.values.none { it.type == TerrainType.Land && !it.impassable })
|
if (ruleset.terrains.values.none { it.type == TerrainType.Land && !it.impassable })
|
||||||
lines += "No passable land terrains exist!"
|
lines.add("No passable land terrains exist!", sourceObject = null)
|
||||||
|
|
||||||
for (terrain in ruleset.terrains.values) {
|
for (terrain in ruleset.terrains.values) {
|
||||||
for (baseTerrainName in terrain.occursOn) {
|
for (baseTerrainName in terrain.occursOn) {
|
||||||
val baseTerrain = ruleset.terrains[baseTerrainName]
|
val baseTerrain = ruleset.terrains[baseTerrainName]
|
||||||
if (baseTerrain == null)
|
if (baseTerrain == null)
|
||||||
lines += "${terrain.name} occurs on terrain $baseTerrainName which does not exist!"
|
lines.add("${terrain.name} occurs on terrain $baseTerrainName which does not exist!", sourceObject = terrain)
|
||||||
else if (baseTerrain.type == TerrainType.NaturalWonder)
|
else if (baseTerrain.type == TerrainType.NaturalWonder)
|
||||||
lines.add("${terrain.name} occurs on natural wonder $baseTerrainName: Unsupported.", RulesetErrorSeverity.WarningOptionsOnly)
|
lines.add("${terrain.name} occurs on natural wonder $baseTerrainName: Unsupported.", RulesetErrorSeverity.WarningOptionsOnly, terrain)
|
||||||
}
|
}
|
||||||
if (terrain.type == TerrainType.NaturalWonder) {
|
if (terrain.type == TerrainType.NaturalWonder) {
|
||||||
if (terrain.turnsInto == null)
|
if (terrain.turnsInto == null)
|
||||||
lines += "Natural Wonder ${terrain.name} is missing the turnsInto attribute!"
|
lines.add("Natural Wonder ${terrain.name} is missing the turnsInto attribute!", sourceObject = terrain)
|
||||||
val baseTerrain = ruleset.terrains[terrain.turnsInto]
|
val baseTerrain = ruleset.terrains[terrain.turnsInto]
|
||||||
if (baseTerrain == null)
|
if (baseTerrain == null)
|
||||||
lines += "${terrain.name} turns into terrain ${terrain.turnsInto} which does not exist!"
|
lines.add("${terrain.name} turns into terrain ${terrain.turnsInto} which does not exist!", sourceObject = terrain)
|
||||||
else if (!baseTerrain.type.isBaseTerrain)
|
else if (!baseTerrain.type.isBaseTerrain)
|
||||||
// See https://github.com/hackedpassword/Z2/blob/main/HybridTileTech.md for a clever exploit
|
// See https://github.com/hackedpassword/Z2/blob/main/HybridTileTech.md for a clever exploit
|
||||||
lines.add("${terrain.name} turns into terrain ${terrain.turnsInto} which is not a base terrain!", RulesetErrorSeverity.Warning)
|
lines.add("${terrain.name} turns into terrain ${terrain.turnsInto} which is not a base terrain!", RulesetErrorSeverity.Warning, terrain)
|
||||||
}
|
}
|
||||||
uniqueValidator.checkUniques(terrain, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(terrain, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
@ -385,10 +388,10 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
) {
|
) {
|
||||||
for (improvement in ruleset.tileImprovements.values) {
|
for (improvement in ruleset.tileImprovements.values) {
|
||||||
if (improvement.techRequired != null && !ruleset.technologies.containsKey(improvement.techRequired!!))
|
if (improvement.techRequired != null && !ruleset.technologies.containsKey(improvement.techRequired!!))
|
||||||
lines += "${improvement.name} requires tech ${improvement.techRequired} which does not exist!"
|
lines.add("${improvement.name} requires tech ${improvement.techRequired} which does not exist!", sourceObject = improvement)
|
||||||
for (terrain in improvement.terrainsCanBeBuiltOn)
|
for (terrain in improvement.terrainsCanBeBuiltOn)
|
||||||
if (!ruleset.terrains.containsKey(terrain) && terrain != "Land" && terrain != "Water")
|
if (!ruleset.terrains.containsKey(terrain) && terrain != "Land" && terrain != "Water")
|
||||||
lines += "${improvement.name} can be built on terrain $terrain which does not exist!"
|
lines.add("${improvement.name} can be built on terrain $terrain which does not exist!", sourceObject = improvement)
|
||||||
if (improvement.terrainsCanBeBuiltOn.isEmpty()
|
if (improvement.terrainsCanBeBuiltOn.isEmpty()
|
||||||
&& !improvement.hasUnique(UniqueType.CanOnlyImproveResource)
|
&& !improvement.hasUnique(UniqueType.CanOnlyImproveResource)
|
||||||
&& !improvement.hasUnique(UniqueType.Unbuildable)
|
&& !improvement.hasUnique(UniqueType.Unbuildable)
|
||||||
@ -398,7 +401,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
) {
|
) {
|
||||||
lines.add(
|
lines.add(
|
||||||
"${improvement.name} has an empty `terrainsCanBeBuiltOn`, isn't allowed to only improve resources and isn't unbuildable! Support for this will soon end. Either give this the unique \"Unbuildable\", \"Can only be built to improve a resource\" or add \"Land\", \"Water\" or any other value to `terrainsCanBeBuiltOn`.",
|
"${improvement.name} has an empty `terrainsCanBeBuiltOn`, isn't allowed to only improve resources and isn't unbuildable! Support for this will soon end. Either give this the unique \"Unbuildable\", \"Can only be built to improve a resource\" or add \"Land\", \"Water\" or any other value to `terrainsCanBeBuiltOn`.",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, improvement
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for (unique in improvement.uniqueObjects
|
for (unique in improvement.uniqueObjects
|
||||||
@ -407,7 +410,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
val params = Stats.parse(unique.params[0])
|
val params = Stats.parse(unique.params[0])
|
||||||
if (params.values.any { it < 0 }) lines.add(
|
if (params.values.any { it < 0 }) lines.add(
|
||||||
"${improvement.name} cannot have a negative value for a pillage yield!",
|
"${improvement.name} cannot have a negative value for a pillage yield!",
|
||||||
RulesetErrorSeverity.Error
|
RulesetErrorSeverity.Error, improvement
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,7 +419,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
if (hasPillageUnique && improvement.hasUnique(UniqueType.Unpillagable, StateForConditionals.IgnoreConditionals)) {
|
if (hasPillageUnique && improvement.hasUnique(UniqueType.Unpillagable, StateForConditionals.IgnoreConditionals)) {
|
||||||
lines.add(
|
lines.add(
|
||||||
"${improvement.name} has both an `Unpillagable` unique type and a `PillageYieldRandom` or `PillageYieldFixed` unique type!",
|
"${improvement.name} has both an `Unpillagable` unique type and a `PillageYieldRandom` or `PillageYieldFixed` unique type!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, improvement
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
uniqueValidator.checkUniques(improvement, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(improvement, lines, true, tryFixUnknownUniques)
|
||||||
@ -429,26 +432,27 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
) {
|
) {
|
||||||
for (resource in ruleset.tileResources.values) {
|
for (resource in ruleset.tileResources.values) {
|
||||||
if (resource.revealedBy != null && !ruleset.technologies.containsKey(resource.revealedBy!!))
|
if (resource.revealedBy != null && !ruleset.technologies.containsKey(resource.revealedBy!!))
|
||||||
lines += "${resource.name} revealed by tech ${resource.revealedBy} which does not exist!"
|
lines.add("${resource.name} revealed by tech ${resource.revealedBy} which does not exist!", sourceObject = resource)
|
||||||
if (resource.improvement != null && !ruleset.tileImprovements.containsKey(resource.improvement!!))
|
if (resource.improvement != null && !ruleset.tileImprovements.containsKey(resource.improvement!!))
|
||||||
lines += "${resource.name} improved by improvement ${resource.improvement} which does not exist!"
|
lines.add("${resource.name} improved by improvement ${resource.improvement} which does not exist!", sourceObject = resource)
|
||||||
for (improvement in resource.improvedBy)
|
for (improvement in resource.improvedBy)
|
||||||
if (!ruleset.tileImprovements.containsKey(improvement))
|
if (!ruleset.tileImprovements.containsKey(improvement))
|
||||||
lines += "${resource.name} improved by improvement $improvement which does not exist!"
|
lines.add("${resource.name} improved by improvement $improvement which does not exist!", sourceObject = resource)
|
||||||
for (terrain in resource.terrainsCanBeFoundOn)
|
for (terrain in resource.terrainsCanBeFoundOn)
|
||||||
if (!ruleset.terrains.containsKey(terrain))
|
if (!ruleset.terrains.containsKey(terrain))
|
||||||
lines += "${resource.name} can be found on terrain $terrain which does not exist!"
|
lines.add("${resource.name} can be found on terrain $terrain which does not exist!", sourceObject = resource)
|
||||||
uniqueValidator.checkUniques(resource, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(resource, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addSpecialistErrors(lines: RulesetErrorList) {
|
private fun addSpecialistErrors(lines: RulesetErrorList) {
|
||||||
|
// Specialist is not a IHasUniques and unsuitable as sourceObject
|
||||||
for (specialist in ruleset.specialists.values) {
|
for (specialist in ruleset.specialists.values) {
|
||||||
for (gpp in specialist.greatPersonPoints)
|
for (gpp in specialist.greatPersonPoints)
|
||||||
if (gpp.key !in ruleset.units)
|
if (gpp.key !in ruleset.units)
|
||||||
lines.add(
|
lines.add(
|
||||||
"Specialist ${specialist.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
|
"Specialist ${specialist.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, sourceObject = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,17 +466,17 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
|
|
||||||
for (requiredTech: String in building.requiredTechs())
|
for (requiredTech: String in building.requiredTechs())
|
||||||
if (!ruleset.technologies.containsKey(requiredTech))
|
if (!ruleset.technologies.containsKey(requiredTech))
|
||||||
lines += "${building.name} requires tech $requiredTech which does not exist!"
|
lines.add("${building.name} requires tech $requiredTech which does not exist!", sourceObject = building)
|
||||||
for (specialistName in building.specialistSlots.keys)
|
for (specialistName in building.specialistSlots.keys)
|
||||||
if (!ruleset.specialists.containsKey(specialistName))
|
if (!ruleset.specialists.containsKey(specialistName))
|
||||||
lines += "${building.name} provides specialist $specialistName which does not exist!"
|
lines.add("${building.name} provides specialist $specialistName which does not exist!", sourceObject = building)
|
||||||
for (resource in building.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys)
|
for (resource in building.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys)
|
||||||
if (!ruleset.tileResources.containsKey(resource))
|
if (!ruleset.tileResources.containsKey(resource))
|
||||||
lines += "${building.name} requires resource $resource which does not exist!"
|
lines.add("${building.name} requires resource $resource which does not exist!", sourceObject = building)
|
||||||
if (building.replaces != null && !ruleset.buildings.containsKey(building.replaces!!))
|
if (building.replaces != null && !ruleset.buildings.containsKey(building.replaces!!))
|
||||||
lines += "${building.name} replaces ${building.replaces} which does not exist!"
|
lines.add("${building.name} replaces ${building.replaces} which does not exist!", sourceObject = building)
|
||||||
if (building.requiredBuilding != null && !ruleset.buildings.containsKey(building.requiredBuilding!!))
|
if (building.requiredBuilding != null && !ruleset.buildings.containsKey(building.requiredBuilding!!))
|
||||||
lines += "${building.name} requires ${building.requiredBuilding} which does not exist!"
|
lines.add("${building.name} requires ${building.requiredBuilding} which does not exist!", sourceObject = building)
|
||||||
uniqueValidator.checkUniques(building, lines, true, tryFixUnknownUniques)
|
uniqueValidator.checkUniques(building, lines, true, tryFixUnknownUniques)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,7 +497,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
tryFixUnknownUniques: Boolean
|
tryFixUnknownUniques: Boolean
|
||||||
) {
|
) {
|
||||||
if (ruleset.units.values.none { it.isCityFounder() })
|
if (ruleset.units.values.none { it.isCityFounder() })
|
||||||
lines += "No city-founding units in ruleset!"
|
lines.add("No city-founding units in ruleset!", sourceObject = null)
|
||||||
|
|
||||||
for (unit in ruleset.units.values) {
|
for (unit in ruleset.units.values) {
|
||||||
checkUnitRulesetInvariant(unit, lines)
|
checkUnitRulesetInvariant(unit, lines)
|
||||||
@ -523,12 +527,12 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addPromotionErrorRulesetInvariant(promotion: Promotion, lines: RulesetErrorList) {
|
private fun addPromotionErrorRulesetInvariant(promotion: Promotion, lines: RulesetErrorList) {
|
||||||
if (promotion.row < -1) lines += "Promotion ${promotion.name} has invalid row value: ${promotion.row}"
|
if (promotion.row < -1) lines.add("Promotion ${promotion.name} has invalid row value: ${promotion.row}", sourceObject = promotion)
|
||||||
if (promotion.column < 0) lines += "Promotion ${promotion.name} has invalid column value: ${promotion.column}"
|
if (promotion.column < 0) lines.add("Promotion ${promotion.name} has invalid column value: ${promotion.column}", sourceObject = promotion)
|
||||||
if (promotion.row == -1) return
|
if (promotion.row == -1) return
|
||||||
for (otherPromotion in ruleset.unitPromotions.values)
|
for (otherPromotion in ruleset.unitPromotions.values)
|
||||||
if (promotion != otherPromotion && promotion.column == otherPromotion.column && promotion.row == otherPromotion.row)
|
if (promotion != otherPromotion && promotion.column == otherPromotion.column && promotion.row == otherPromotion.row)
|
||||||
lines += "Promotions ${promotion.name} and ${otherPromotion.name} have the same position: ${promotion.row}/${promotion.column}"
|
lines.add("Promotions ${promotion.name} and ${otherPromotion.name} have the same position: ${promotion.row}/${promotion.column}", sourceObject = promotion)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addNationErrorsRulesetInvariant(
|
private fun addNationErrorsRulesetInvariant(
|
||||||
@ -543,23 +547,20 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
|
|
||||||
private fun addNationErrorRulesetInvariant(nation: Nation, lines: RulesetErrorList) {
|
private fun addNationErrorRulesetInvariant(nation: Nation, lines: RulesetErrorList) {
|
||||||
if (nation.cities.isEmpty() && !nation.isSpectator && !nation.isBarbarian) {
|
if (nation.cities.isEmpty() && !nation.isSpectator && !nation.isBarbarian) {
|
||||||
lines += "${nation.name} can settle cities, but has no city names!"
|
lines.add("${nation.name} can settle cities, but has no city names!", sourceObject = nation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast
|
// https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast
|
||||||
val constrastRatio = nation.getContrastRatio()
|
val constrastRatio = nation.getContrastRatio()
|
||||||
if (constrastRatio < 3) {
|
if (constrastRatio < 3) {
|
||||||
val suggestedColors = getSuggestedColors(nation)
|
val (newInnerColor, newOuterColor) = getSuggestedColors(nation)
|
||||||
val newOuterColor = suggestedColors.outerColor
|
|
||||||
val newInnerColor = suggestedColors.innerColor
|
|
||||||
|
|
||||||
var text = "${nation.name}'s colors do not contrast enough - it is unreadable!"
|
var text = "${nation.name}'s colors do not contrast enough - it is unreadable!"
|
||||||
text += "\nSuggested colors: "
|
text += "\nSuggested colors: "
|
||||||
text += "\n\t\t\"outerColor\": [${(newOuterColor.r * 255).toInt()}, ${(newOuterColor.g * 255).toInt()}, ${(newOuterColor.b * 255).toInt()}],"
|
text += "\n\t\t\"outerColor\": [${(newOuterColor.r * 255).toInt()}, ${(newOuterColor.g * 255).toInt()}, ${(newOuterColor.b * 255).toInt()}],"
|
||||||
text += "\n\t\t\"innerColor\": [${(newInnerColor.r * 255).toInt()}, ${(newInnerColor.g * 255).toInt()}, ${(newInnerColor.b * 255).toInt()}],"
|
text += "\n\t\t\"innerColor\": [${(newInnerColor.r * 255).toInt()}, ${(newInnerColor.g * 255).toInt()}, ${(newInnerColor.b * 255).toInt()}],"
|
||||||
|
|
||||||
lines.add(text, RulesetErrorSeverity.WarningOptionsOnly)
|
lines.add(text, RulesetErrorSeverity.WarningOptionsOnly, nation)
|
||||||
lines.add(text, RulesetErrorSeverity.WarningOptionsOnly)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,37 +610,38 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
)
|
)
|
||||||
lines.add(
|
lines.add(
|
||||||
"${building.name} is buildable and therefore should either have an explicit cost or reference an existing tech!",
|
"${building.name} is buildable and therefore should either have an explicit cost or reference an existing tech!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, building
|
||||||
)
|
)
|
||||||
|
|
||||||
for (gpp in building.greatPersonPoints)
|
for (gpp in building.greatPersonPoints)
|
||||||
if (gpp.key !in ruleset.units)
|
if (gpp.key !in ruleset.units)
|
||||||
lines.add(
|
lines.add(
|
||||||
"Building ${building.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
|
"Building ${building.name} has greatPersonPoints for ${gpp.key}, which is not a unit in the ruleset!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, building
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTechColumnErrorsRulesetInvariant(lines: RulesetErrorList) {
|
private fun addTechColumnErrorsRulesetInvariant(lines: RulesetErrorList) {
|
||||||
|
// TechColumn is not a IHasUniques and unsuitable as sourceObject
|
||||||
for (techColumn in ruleset.techColumns) {
|
for (techColumn in ruleset.techColumns) {
|
||||||
if (techColumn.columnNumber < 0)
|
if (techColumn.columnNumber < 0)
|
||||||
lines += "Tech Column number ${techColumn.columnNumber} is negative"
|
lines.add("Tech Column number ${techColumn.columnNumber} is negative", sourceObject = null)
|
||||||
if (techColumn.buildingCost == -1)
|
if (techColumn.buildingCost == -1)
|
||||||
lines.add(
|
lines.add(
|
||||||
"Tech Column number ${techColumn.columnNumber} has no explicit building cost",
|
"Tech Column number ${techColumn.columnNumber} has no explicit building cost",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, sourceObject = null
|
||||||
)
|
)
|
||||||
if (techColumn.wonderCost == -1)
|
if (techColumn.wonderCost == -1)
|
||||||
lines.add(
|
lines.add(
|
||||||
"Tech Column number ${techColumn.columnNumber} has no explicit wonder cost",
|
"Tech Column number ${techColumn.columnNumber} has no explicit wonder cost",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, sourceObject = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (tech in ruleset.technologies.values) {
|
for (tech in ruleset.technologies.values) {
|
||||||
for (otherTech in ruleset.technologies.values) {
|
for (otherTech in ruleset.technologies.values) {
|
||||||
if (tech != otherTech && otherTech.column?.columnNumber == tech.column?.columnNumber && otherTech.row == tech.row)
|
if (tech != otherTech && otherTech.column?.columnNumber == tech.column?.columnNumber && otherTech.row == tech.row)
|
||||||
lines += "${tech.name} is in the same row and column as ${otherTech.name}!"
|
lines.add("${tech.name} is in the same row and column as ${otherTech.name}!", sourceObject = tech)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -673,23 +675,23 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
private fun checkUnitRulesetInvariant(unit: BaseUnit, lines: RulesetErrorList) {
|
private fun checkUnitRulesetInvariant(unit: BaseUnit, lines: RulesetErrorList) {
|
||||||
for (upgradesTo in unit.getUpgradeUnits(StateForConditionals.IgnoreConditionals)) {
|
for (upgradesTo in unit.getUpgradeUnits(StateForConditionals.IgnoreConditionals)) {
|
||||||
if (upgradesTo == unit.name || (upgradesTo == unit.replaces))
|
if (upgradesTo == unit.name || (upgradesTo == unit.replaces))
|
||||||
lines += "${unit.name} upgrades to itself!"
|
lines.add("${unit.name} upgrades to itself!", sourceObject = unit)
|
||||||
}
|
}
|
||||||
if (unit.isMilitary() && unit.strength == 0) // Should only match ranged units with 0 strength
|
if (unit.isMilitary() && unit.strength == 0) // Should only match ranged units with 0 strength
|
||||||
lines += "${unit.name} is a military unit but has no assigned strength!"
|
lines.add("${unit.name} is a military unit but has no assigned strength!", sourceObject = unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collects all RulesetSpecific checks for a BaseUnit */
|
/** Collects all RulesetSpecific checks for a BaseUnit */
|
||||||
private fun checkUnitRulesetSpecific(unit: BaseUnit, lines: RulesetErrorList) {
|
private fun checkUnitRulesetSpecific(unit: BaseUnit, lines: RulesetErrorList) {
|
||||||
for (requiredTech: String in unit.requiredTechs())
|
for (requiredTech: String in unit.requiredTechs())
|
||||||
if (!ruleset.technologies.containsKey(requiredTech))
|
if (!ruleset.technologies.containsKey(requiredTech))
|
||||||
lines += "${unit.name} requires tech $requiredTech which does not exist!"
|
lines.add("${unit.name} requires tech $requiredTech which does not exist!", sourceObject = unit)
|
||||||
for (obsoleteTech: String in unit.techsAtWhichNoLongerAvailable())
|
for (obsoleteTech: String in unit.techsAtWhichNoLongerAvailable())
|
||||||
if (!ruleset.technologies.containsKey(obsoleteTech))
|
if (!ruleset.technologies.containsKey(obsoleteTech))
|
||||||
lines += "${unit.name} obsoletes at tech $obsoleteTech which does not exist!"
|
lines.add("${unit.name} obsoletes at tech $obsoleteTech which does not exist!", sourceObject = unit)
|
||||||
for (upgradesTo in unit.getUpgradeUnits(StateForConditionals.IgnoreConditionals))
|
for (upgradesTo in unit.getUpgradeUnits(StateForConditionals.IgnoreConditionals))
|
||||||
if (!ruleset.units.containsKey(upgradesTo))
|
if (!ruleset.units.containsKey(upgradesTo))
|
||||||
lines += "${unit.name} upgrades to unit $upgradesTo which does not exist!"
|
lines.add("${unit.name} upgrades to unit $upgradesTo which does not exist!", sourceObject = unit)
|
||||||
|
|
||||||
// Check that we don't obsolete ourselves before we can upgrade
|
// Check that we don't obsolete ourselves before we can upgrade
|
||||||
for (obsoleteTech: String in unit.techsAtWhichAutoUpgradeInProduction())
|
for (obsoleteTech: String in unit.techsAtWhichAutoUpgradeInProduction())
|
||||||
@ -702,20 +704,20 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
lines.add(
|
lines.add(
|
||||||
"${unit.name} is supposed to automatically upgrade at tech ${obsoleteTech}," +
|
"${unit.name} is supposed to automatically upgrade at tech ${obsoleteTech}," +
|
||||||
" and therefore $requiredTech for its upgrade ${upgradedUnit.name} may not yet be researched!",
|
" and therefore $requiredTech for its upgrade ${upgradedUnit.name} may not yet be researched!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, unit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (resource in unit.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys)
|
for (resource in unit.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys)
|
||||||
if (!ruleset.tileResources.containsKey(resource))
|
if (!ruleset.tileResources.containsKey(resource))
|
||||||
lines += "${unit.name} requires resource $resource which does not exist!"
|
lines.add("${unit.name} requires resource $resource which does not exist!", sourceObject = unit)
|
||||||
if (unit.replaces != null && !ruleset.units.containsKey(unit.replaces!!))
|
if (unit.replaces != null && !ruleset.units.containsKey(unit.replaces!!))
|
||||||
lines += "${unit.name} replaces ${unit.replaces} which does not exist!"
|
lines.add("${unit.name} replaces ${unit.replaces} which does not exist!", sourceObject = unit)
|
||||||
for (promotion in unit.promotions)
|
for (promotion in unit.promotions)
|
||||||
if (!ruleset.unitPromotions.containsKey(promotion))
|
if (!ruleset.unitPromotions.containsKey(promotion))
|
||||||
lines += "${unit.name} contains promotion $promotion which does not exist!"
|
lines.add("${unit.name} contains promotion $promotion which does not exist!", sourceObject = unit)
|
||||||
checkUnitType(unit.unitType) {
|
checkUnitType(unit.unitType) {
|
||||||
lines += "${unit.name} is of type ${unit.unitType}, which does not exist!"
|
lines.add("${unit.name} is of type ${unit.unitType}, which does not exist!", sourceObject = unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should ignore conditionals here - there are condition implementations on this out there that require a game state (and will test false without)
|
// We should ignore conditionals here - there are condition implementations on this out there that require a game state (and will test false without)
|
||||||
@ -726,7 +728,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
unit.isCivilian() &&
|
unit.isCivilian() &&
|
||||||
!unit.isGreatPersonOfType("War")) {
|
!unit.isGreatPersonOfType("War")) {
|
||||||
lines.add("${unit.name} can place improvement $improvementName which has no stats, preventing unit automation!",
|
lines.add("${unit.name} can place improvement $improvementName which has no stats, preventing unit automation!",
|
||||||
RulesetErrorSeverity.WarningOptionsOnly)
|
RulesetErrorSeverity.WarningOptionsOnly, unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -763,31 +765,31 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
allFallbacks.add(config.fallbackTileSet!!)
|
allFallbacks.add(config.fallbackTileSet!!)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
// Our fromJsonFile wrapper already intercepts Exceptions and gives them a generalized message, so go a level deeper for useful details (like "unmatched brace")
|
// Our fromJsonFile wrapper already intercepts Exceptions and gives them a generalized message, so go a level deeper for useful details (like "unmatched brace")
|
||||||
lines.add("Tileset config '${file.name()}' cannot be loaded (${ex.cause?.message})", RulesetErrorSeverity.Warning)
|
lines.add("Tileset config '${file.name()}' cannot be loaded (${ex.cause?.message})", RulesetErrorSeverity.Warning, sourceObject = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Folder should not contain subdirectories, non-json files, or be empty
|
// Folder should not contain subdirectories, non-json files, or be empty
|
||||||
if (folderContentBad)
|
if (folderContentBad)
|
||||||
lines.add("The Mod tileset config folder contains non-json files or subdirectories", RulesetErrorSeverity.Warning)
|
lines.add("The Mod tileset config folder contains non-json files or subdirectories", RulesetErrorSeverity.Warning, sourceObject = null)
|
||||||
if (configTilesets.isEmpty())
|
if (configTilesets.isEmpty())
|
||||||
lines.add("The Mod tileset config folder contains no json files", RulesetErrorSeverity.Warning)
|
lines.add("The Mod tileset config folder contains no json files", RulesetErrorSeverity.Warning, sourceObject = null)
|
||||||
|
|
||||||
// There should be atlas images corresponding to each json name
|
// There should be atlas images corresponding to each json name
|
||||||
val atlasTilesets = getTilesetNamesFromAtlases()
|
val atlasTilesets = getTilesetNamesFromAtlases()
|
||||||
val configOnlyTilesets = configTilesets - atlasTilesets
|
val configOnlyTilesets = configTilesets - atlasTilesets
|
||||||
if (configOnlyTilesets.isNotEmpty())
|
if (configOnlyTilesets.isNotEmpty())
|
||||||
lines.add("Mod has no graphics for configured tilesets: ${configOnlyTilesets.joinToString()}", RulesetErrorSeverity.Warning)
|
lines.add("Mod has no graphics for configured tilesets: ${configOnlyTilesets.joinToString()}", RulesetErrorSeverity.Warning, sourceObject = null)
|
||||||
|
|
||||||
// For all atlas images matching "TileSets/*" there should be a json
|
// For all atlas images matching "TileSets/*" there should be a json
|
||||||
val atlasOnlyTilesets = atlasTilesets - configTilesets
|
val atlasOnlyTilesets = atlasTilesets - configTilesets
|
||||||
if (atlasOnlyTilesets.isNotEmpty())
|
if (atlasOnlyTilesets.isNotEmpty())
|
||||||
lines.add("Mod has no configuration for tileset graphics: ${atlasOnlyTilesets.joinToString()}", RulesetErrorSeverity.Warning)
|
lines.add("Mod has no configuration for tileset graphics: ${atlasOnlyTilesets.joinToString()}", RulesetErrorSeverity.Warning, sourceObject = null)
|
||||||
|
|
||||||
// All fallbacks should exist (default added because TileSetCache is not loaded when running as unit test)
|
// All fallbacks should exist (default added because TileSetCache is not loaded when running as unit test)
|
||||||
val unknownFallbacks = allFallbacks - TileSetCache.keys - Constants.defaultFallbackTileset
|
val unknownFallbacks = allFallbacks - TileSetCache.keys - Constants.defaultFallbackTileset
|
||||||
if (unknownFallbacks.isNotEmpty())
|
if (unknownFallbacks.isNotEmpty())
|
||||||
lines.add("Fallback tileset invalid: ${unknownFallbacks.joinToString()}", RulesetErrorSeverity.Warning)
|
lines.add("Fallback tileset invalid: ${unknownFallbacks.joinToString()}", RulesetErrorSeverity.Warning, sourceObject = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTilesetNamesFromAtlases(): Set<String> {
|
private fun getTilesetNamesFromAtlases(): Set<String> {
|
||||||
@ -810,7 +812,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
|||||||
fun recursiveCheck(history: HashSet<Promotion>, promotion: Promotion, level: Int) {
|
fun recursiveCheck(history: HashSet<Promotion>, promotion: Promotion, level: Int) {
|
||||||
if (promotion in history) {
|
if (promotion in history) {
|
||||||
lines.add("Circular Reference in Promotions: ${history.joinToString("→") { it.name }}→${promotion.name}",
|
lines.add("Circular Reference in Promotions: ${history.joinToString("→") { it.name }}→${promotion.name}",
|
||||||
RulesetErrorSeverity.Warning)
|
RulesetErrorSeverity.Warning, promotion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (level > 99) return
|
if (level > 99) return
|
||||||
|
@ -60,30 +60,29 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
tryFixUnknownUniques: Boolean,
|
tryFixUnknownUniques: Boolean,
|
||||||
uniqueContainer: IHasUniques?,
|
uniqueContainer: IHasUniques?,
|
||||||
reportRulesetSpecificErrors: Boolean
|
reportRulesetSpecificErrors: Boolean
|
||||||
): List<RulesetError> {
|
): RulesetErrorList {
|
||||||
val prefix by lazy { (if (uniqueContainer is IRulesetObject) "${uniqueContainer.originRuleset}: " else "") +
|
val prefix by lazy { getUniqueContainerPrefix(uniqueContainer) + "\"${unique.text}\"" }
|
||||||
(if (uniqueContainer == null) "The" else "(${uniqueContainer.getUniqueTarget().name}) ${uniqueContainer.name}'s") }
|
if (unique.type == null) return checkUntypedUnique(unique, tryFixUnknownUniques, uniqueContainer, prefix)
|
||||||
if (unique.type == null) return checkUntypedUnique(unique, tryFixUnknownUniques, prefix)
|
|
||||||
|
|
||||||
val rulesetErrors = RulesetErrorList()
|
val rulesetErrors = RulesetErrorList(ruleset)
|
||||||
|
|
||||||
if (uniqueContainer != null && !unique.type.canAcceptUniqueTarget(uniqueContainer.getUniqueTarget()))
|
if (uniqueContainer != null && !unique.type.canAcceptUniqueTarget(uniqueContainer.getUniqueTarget()))
|
||||||
rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" is not allowed on its target type", RulesetErrorSeverity.Warning))
|
rulesetErrors.add("$prefix is not allowed on its target type", RulesetErrorSeverity.Warning, uniqueContainer)
|
||||||
|
|
||||||
val typeComplianceErrors = getComplianceErrors(unique)
|
val typeComplianceErrors = getComplianceErrors(unique)
|
||||||
for (complianceError in typeComplianceErrors) {
|
for (complianceError in typeComplianceErrors) {
|
||||||
if (!reportRulesetSpecificErrors && complianceError.errorSeverity == UniqueType.UniqueParameterErrorSeverity.RulesetSpecific)
|
if (!reportRulesetSpecificErrors && complianceError.errorSeverity == UniqueType.UniqueParameterErrorSeverity.RulesetSpecific)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," +
|
rulesetErrors.add("$prefix 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()
|
complianceError.errorSeverity.getRulesetErrorSeverity(), uniqueContainer
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (conditional in unique.conditionals) {
|
for (conditional in unique.conditionals) {
|
||||||
addConditionalErrors(conditional, rulesetErrors, prefix, unique, reportRulesetSpecificErrors)
|
addConditionalErrors(conditional, rulesetErrors, prefix, unique, uniqueContainer, reportRulesetSpecificErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unique.type in MapUnitCache.UnitMovementUniques
|
if (unique.type in MapUnitCache.UnitMovementUniques
|
||||||
@ -91,14 +90,17 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
)
|
)
|
||||||
// (Stay silent if the only conditional is `<for [All] units>` - as in G&K Denmark)
|
// (Stay silent if the only conditional is `<for [All] units>` - as in G&K Denmark)
|
||||||
// Not necessarily even a problem, but yes something mod maker should be aware of
|
// Not necessarily even a problem, but yes something mod maker should be aware of
|
||||||
rulesetErrors.add("$prefix unique \"${unique.text}\" contains a conditional on a unit movement unique. " +
|
rulesetErrors.add(
|
||||||
|
"$prefix contains a conditional on a unit movement unique. " +
|
||||||
"Due to performance considerations, this unique is cached on the unit," +
|
"Due to performance considerations, this unique is cached on the unit," +
|
||||||
" and the conditional may not always limit the unique correctly.", RulesetErrorSeverity.OK)
|
" and the conditional may not always limit the unique correctly.",
|
||||||
|
RulesetErrorSeverity.OK, uniqueContainer
|
||||||
|
)
|
||||||
|
|
||||||
if (reportRulesetSpecificErrors)
|
if (reportRulesetSpecificErrors)
|
||||||
// If we don't filter these messages will be listed twice as this function is called twice on most objects
|
// If we don't filter these messages will be listed twice as this function is called twice on most objects
|
||||||
// The tests are RulesetInvariant in nature, but RulesetSpecific is called for _all_ objects, invariant is not.
|
// The tests are RulesetInvariant in nature, but RulesetSpecific is called for _all_ objects, invariant is not.
|
||||||
addDeprecationAnnotationErrors(unique, prefix, rulesetErrors)
|
addDeprecationAnnotationErrors(unique, prefix, rulesetErrors, uniqueContainer)
|
||||||
|
|
||||||
return rulesetErrors
|
return rulesetErrors
|
||||||
}
|
}
|
||||||
@ -108,40 +110,41 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
rulesetErrors: RulesetErrorList,
|
rulesetErrors: RulesetErrorList,
|
||||||
prefix: String,
|
prefix: String,
|
||||||
unique: Unique,
|
unique: Unique,
|
||||||
|
uniqueContainer: IHasUniques?,
|
||||||
reportRulesetSpecificErrors: Boolean
|
reportRulesetSpecificErrors: Boolean
|
||||||
) {
|
) {
|
||||||
if (unique.hasFlag(UniqueFlag.NoConditionals)) {
|
if (unique.hasFlag(UniqueFlag.NoConditionals)) {
|
||||||
rulesetErrors.add(
|
rulesetErrors.add(
|
||||||
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
"$prefix contains the conditional \"${conditional.text}\"," +
|
||||||
" but the unique does not accept conditionals!",
|
" but the unique does not accept conditionals!",
|
||||||
RulesetErrorSeverity.Error
|
RulesetErrorSeverity.Error, uniqueContainer
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conditional.type == null) {
|
if (conditional.type == null) {
|
||||||
rulesetErrors.add(
|
rulesetErrors.add(
|
||||||
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
"$prefix contains the conditional \"${conditional.text}\"," +
|
||||||
" which is of an unknown type!",
|
" which is of an unknown type!",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, uniqueContainer
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None })
|
if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None })
|
||||||
rulesetErrors.add(
|
rulesetErrors.add(
|
||||||
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
"$prefix 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, uniqueContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
if (conditional.type.targetTypes.contains(UniqueTarget.UnitActionModifier)
|
if (conditional.type.targetTypes.contains(UniqueTarget.UnitActionModifier)
|
||||||
&& unique.type!!.targetTypes.none { UniqueTarget.UnitAction.canAcceptUniqueTarget(it) }
|
&& unique.type!!.targetTypes.none { UniqueTarget.UnitAction.canAcceptUniqueTarget(it) }
|
||||||
)
|
)
|
||||||
rulesetErrors.add(
|
rulesetErrors.add(
|
||||||
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
"$prefix contains the conditional \"${conditional.text}\"," +
|
||||||
" which as a UnitActionModifier is only allowed on UnitAction uniques.",
|
" which as a UnitActionModifier is only allowed on UnitAction uniques.",
|
||||||
RulesetErrorSeverity.Warning
|
RulesetErrorSeverity.Warning, uniqueContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
val conditionalComplianceErrors =
|
val conditionalComplianceErrors =
|
||||||
@ -152,12 +155,10 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
rulesetErrors.add(
|
rulesetErrors.add(
|
||||||
RulesetError(
|
"$prefix contains the conditional \"${conditional.text}\"." +
|
||||||
"$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(), uniqueContainer
|
||||||
complianceError.errorSeverity.getRulesetErrorSeverity()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,19 +166,20 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
private fun addDeprecationAnnotationErrors(
|
private fun addDeprecationAnnotationErrors(
|
||||||
unique: Unique,
|
unique: Unique,
|
||||||
prefix: String,
|
prefix: String,
|
||||||
rulesetErrors: RulesetErrorList
|
rulesetErrors: RulesetErrorList,
|
||||||
|
uniqueContainer: IHasUniques?
|
||||||
) {
|
) {
|
||||||
val deprecationAnnotation = unique.getDeprecationAnnotation()
|
val deprecationAnnotation = unique.getDeprecationAnnotation()
|
||||||
if (deprecationAnnotation != null) {
|
if (deprecationAnnotation != null) {
|
||||||
val replacementUniqueText = unique.getReplacementText(ruleset)
|
val replacementUniqueText = unique.getReplacementText(ruleset)
|
||||||
val deprecationText =
|
val deprecationText =
|
||||||
"$prefix unique \"${unique.text}\" is deprecated ${deprecationAnnotation.message}," +
|
"$prefix 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
|
||||||
else RulesetErrorSeverity.Warning // User visible
|
else RulesetErrorSeverity.Warning // User visible
|
||||||
|
|
||||||
rulesetErrors.add(deprecationText, severity)
|
rulesetErrors.add(deprecationText, severity, uniqueContainer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +187,7 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
private fun getComplianceErrors(
|
private fun getComplianceErrors(
|
||||||
unique: Unique,
|
unique: Unique,
|
||||||
): List<UniqueComplianceError> {
|
): List<UniqueComplianceError> {
|
||||||
if (unique.type==null) return emptyList()
|
if (unique.type == null) return emptyList()
|
||||||
val errorList = ArrayList<UniqueComplianceError>()
|
val errorList = ArrayList<UniqueComplianceError>()
|
||||||
for ((index, param) in unique.params.withIndex()) {
|
for ((index, param) in unique.params.withIndex()) {
|
||||||
val acceptableParamTypes = unique.type.parameterTypeMap[index]
|
val acceptableParamTypes = unique.type.parameterTypeMap[index]
|
||||||
@ -215,24 +217,26 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
return severity
|
return severity
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkUntypedUnique(unique: Unique, tryFixUnknownUniques: Boolean, prefix: String ): List<RulesetError> {
|
private fun checkUntypedUnique(unique: Unique, tryFixUnknownUniques: Boolean, uniqueContainer: IHasUniques?, prefix: String): RulesetErrorList {
|
||||||
// Malformed conditional is always bad
|
// Malformed conditional is always bad
|
||||||
if (unique.text.count { it == '<' } != unique.text.count { it == '>' })
|
if (unique.text.count { it == '<' } != unique.text.count { it == '>' })
|
||||||
return listOf(RulesetError(
|
return RulesetErrorList.of(
|
||||||
"$prefix unique \"${unique.text}\" contains mismatched conditional braces!",
|
"$prefix contains mismatched conditional braces!",
|
||||||
RulesetErrorSeverity.Warning))
|
RulesetErrorSeverity.Warning, ruleset, uniqueContainer
|
||||||
|
)
|
||||||
|
|
||||||
// Support purely filtering Uniques without actual implementation
|
// Support purely filtering Uniques without actual implementation
|
||||||
if (isFilteringUniqueAllowed(unique)) return emptyList()
|
if (isFilteringUniqueAllowed(unique)) return RulesetErrorList()
|
||||||
if (tryFixUnknownUniques) {
|
if (tryFixUnknownUniques) {
|
||||||
val fixes = tryFixUnknownUnique(unique, prefix)
|
val fixes = tryFixUnknownUnique(unique, uniqueContainer, prefix)
|
||||||
if (fixes.isNotEmpty()) return fixes
|
if (fixes.isNotEmpty()) return fixes
|
||||||
}
|
}
|
||||||
|
|
||||||
return listOf(RulesetError(
|
return RulesetErrorList.of(
|
||||||
"$prefix unique \"${unique.text}\" not found in Unciv's unique types, and is not used as a filtering unique.",
|
"$prefix not found in Unciv's unique types, and is not used as a filtering unique.",
|
||||||
if (unique.params.isEmpty()) RulesetErrorSeverity.OK else RulesetErrorSeverity.Warning
|
if (unique.params.isEmpty()) RulesetErrorSeverity.OK else RulesetErrorSeverity.Warning,
|
||||||
))
|
ruleset, uniqueContainer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isFilteringUniqueAllowed(unique: Unique): Boolean {
|
private fun isFilteringUniqueAllowed(unique: Unique): Boolean {
|
||||||
@ -242,7 +246,7 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
return unique.text in allUniqueParameters // referenced at least once from elsewhere
|
return unique.text in allUniqueParameters // referenced at least once from elsewhere
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryFixUnknownUnique(unique: Unique, prefix: String): List<RulesetError> {
|
private fun tryFixUnknownUnique(unique: Unique, uniqueContainer: IHasUniques?, prefix: String): RulesetErrorList {
|
||||||
val similarUniques = UniqueType.values().filter {
|
val similarUniques = UniqueType.values().filter {
|
||||||
getRelativeTextDistance(
|
getRelativeTextDistance(
|
||||||
it.placeholderText,
|
it.placeholderText,
|
||||||
@ -253,13 +257,15 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
similarUniques.filter { it.placeholderText == unique.placeholderText }
|
similarUniques.filter { it.placeholderText == unique.placeholderText }
|
||||||
return when {
|
return when {
|
||||||
// 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() -> RulesetErrorList.of(
|
||||||
"$prefix unique \"${unique.text}\" looks like it should be fine, but for some reason isn't recognized.",
|
"$prefix looks like it should be fine, but for some reason isn't recognized.",
|
||||||
RulesetErrorSeverity.OK))
|
RulesetErrorSeverity.OK,
|
||||||
|
ruleset, uniqueContainer
|
||||||
|
)
|
||||||
|
|
||||||
similarUniques.isNotEmpty() -> {
|
similarUniques.isNotEmpty() -> {
|
||||||
val text =
|
val text =
|
||||||
"$prefix unique \"${unique.text}\" looks like it may be a misspelling of:\n" +
|
"$prefix 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())
|
||||||
@ -268,9 +274,16 @@ class UniqueValidator(val ruleset: Ruleset) {
|
|||||||
if (uniqueType.getDeprecationAnnotation() != null) text += " (Deprecated)"
|
if (uniqueType.getDeprecationAnnotation() != null) text += " (Deprecated)"
|
||||||
return@joinToString text
|
return@joinToString text
|
||||||
}.prependIndent("\t")
|
}.prependIndent("\t")
|
||||||
listOf(RulesetError(text, RulesetErrorSeverity.OK))
|
RulesetErrorList.of(text, RulesetErrorSeverity.OK, ruleset, uniqueContainer)
|
||||||
}
|
}
|
||||||
else -> emptyList()
|
else -> RulesetErrorList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal fun getUniqueContainerPrefix(uniqueContainer: IHasUniques?) =
|
||||||
|
(if (uniqueContainer is IRulesetObject) "${uniqueContainer.originRuleset}: " else "") +
|
||||||
|
(if (uniqueContainer == null) "The" else "(${uniqueContainer.getUniqueTarget().name}) ${uniqueContainer.name}'s") +
|
||||||
|
" unique "
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import com.badlogic.gdx.utils.Align
|
|||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
import com.unciv.models.ruleset.unique.Unique
|
import com.unciv.models.ruleset.unique.Unique
|
||||||
import com.unciv.models.ruleset.validation.RulesetError
|
|
||||||
import com.unciv.models.ruleset.validation.RulesetErrorSeverity
|
import com.unciv.models.ruleset.validation.RulesetErrorSeverity
|
||||||
import com.unciv.models.ruleset.validation.UniqueValidator
|
import com.unciv.models.ruleset.validation.UniqueValidator
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
@ -101,8 +100,8 @@ class ModCheckTab(
|
|||||||
else RulesetCache.checkCombinedModLinks(linkedSetOf(mod.name), base, tryFixUnknownUniques = true)
|
else RulesetCache.checkCombinedModLinks(linkedSetOf(mod.name), base, tryFixUnknownUniques = true)
|
||||||
modLinks.sortByDescending { it.errorSeverityToReport }
|
modLinks.sortByDescending { it.errorSeverityToReport }
|
||||||
val noProblem = !modLinks.isNotOK()
|
val noProblem = !modLinks.isNotOK()
|
||||||
if (modLinks.isNotEmpty()) modLinks += RulesetError("", RulesetErrorSeverity.OK)
|
if (modLinks.isNotEmpty()) modLinks.add("", RulesetErrorSeverity.OK, sourceObject = null)
|
||||||
if (noProblem) modLinks += RulesetError("No problems found.".tr(), RulesetErrorSeverity.OK)
|
if (noProblem) modLinks.add("No problems found.".tr(), RulesetErrorSeverity.OK, sourceObject = null)
|
||||||
|
|
||||||
launchOnGLThread {
|
launchOnGLThread {
|
||||||
// When the options popup is already closed before this postRunnable is run,
|
// When the options popup is already closed before this postRunnable is run,
|
||||||
|
@ -2170,9 +2170,6 @@ Due to performance considerations, this unique is cached, thus conditionals may
|
|||||||
|
|
||||||
Applicable to: Conditional
|
Applicable to: Conditional
|
||||||
|
|
||||||
??? example "<hidden from users>"
|
|
||||||
Applicable to: Conditional
|
|
||||||
|
|
||||||
## TriggerCondition uniques
|
## TriggerCondition uniques
|
||||||
!!! note ""
|
!!! note ""
|
||||||
|
|
||||||
@ -2306,6 +2303,14 @@ Due to performance considerations, this unique is cached, thus conditionals may
|
|||||||
??? example "<after which this unit is consumed>"
|
??? example "<after which this unit is consumed>"
|
||||||
Applicable to: UnitActionModifier
|
Applicable to: UnitActionModifier
|
||||||
|
|
||||||
|
## MetaModifier uniques
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
Modifiers that can be added to other uniques changing user experience, not their behavior
|
||||||
|
|
||||||
|
??? example "<hidden from users>"
|
||||||
|
Applicable to: MetaModifier
|
||||||
|
|
||||||
|
|
||||||
*[amount]: This indicates a whole number, possibly with a + or - sign, such as `2`, `+13`, or `-3`.
|
*[amount]: This indicates a whole number, possibly with a + or - sign, such as `2`, `+13`, or `-3`.
|
||||||
*[baseTerrain]: The name of any terrain that is a base terrain according to the json file.
|
*[baseTerrain]: The name of any terrain that is a base terrain according to the json file.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user