Fix ModOptions unique parameter types not checked and "uniquetype" ModOptionsConstants (#10930)

* Kill evil ModOptionsConstants

* UniqueFlag to EnumSet and add `NoConditionals`

* Linting or import reorder

* Fix ModOptions unique parameter types not checked

* ModOptions Unique to suppress validation warnings

* Silence spurious RulesetValidator complaints about Denmark

* Revert "ModOptions Unique to suppress validation warnings"
This commit is contained in:
SomeTroglodyte 2024-02-01 22:24:59 +01:00 committed by GitHub
parent 9e9ffa51d4
commit d25b1c8c41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 107 additions and 72 deletions

View File

@ -24,12 +24,12 @@ import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.civilization.managers.TechManager
import com.unciv.logic.civilization.managers.TurnManager
import com.unciv.logic.civilization.managers.VictoryManager
import com.unciv.logic.github.Github.repoNameToFolderName
import com.unciv.logic.map.CityDistanceData
import com.unciv.logic.map.TileMap
import com.unciv.logic.map.tile.Tile
import com.unciv.models.Religion
import com.unciv.models.metadata.GameParameters
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.Speed
@ -39,7 +39,6 @@ import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr
import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags
import com.unciv.logic.github.Github.repoNameToFolderName
import com.unciv.ui.screens.savescreens.Gzip
import com.unciv.ui.screens.worldscreen.status.NextTurnProgress
import com.unciv.utils.DebugUtils
@ -287,7 +286,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
fun isReligionEnabled(): Boolean {
val religionDisabledByRuleset = (ruleset.eras[gameParameters.startingEra]!!.hasUnique(UniqueType.DisablesReligion)
|| ruleset.modOptions.uniques.contains(ModOptionsConstants.disableReligion))
|| ruleset.modOptions.hasUnique(UniqueType.DisableReligion))
return !religionDisabledByRuleset
}

View File

@ -14,7 +14,6 @@ import com.unciv.logic.map.tile.Tile
import com.unciv.models.metadata.GameParameters
import com.unciv.models.metadata.GameSetupInfo
import com.unciv.models.metadata.Player
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unique.StateForConditionals
@ -483,7 +482,7 @@ object GameStarter {
settlerLikeUnits: Map<String, BaseUnit>
) {
// Adjust starting units for city states
if (civ.isCityState() && !gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.allowCityStatesSpawnUnits)) {
if (civ.isCityState() && !gameInfo.ruleset.modOptions.hasUnique(UniqueType.AllowCityStatesSpawnUnits)) {
val startingSettlers = startingUnits.filter { settlerLikeUnits.contains(it) }
startingUnits.clear()

View File

@ -17,7 +17,6 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.civilization.managers.EspionageManager
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.models.ruleset.MilestoneType
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.PolicyBranch
import com.unciv.models.ruleset.Victory
@ -41,7 +40,7 @@ object NextTurnAutomation {
TradeAutomation.respondToTradeRequests(civInfo)
if (civInfo.isMajorCiv()) {
if (!civInfo.gameInfo.ruleset.modOptions.hasUnique(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
if (!civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
DiplomacyAutomation.declareWar(civInfo)
DiplomacyAutomation.offerPeaceTreaty(civInfo)
DiplomacyAutomation.offerDeclarationOfFriendship(civInfo)
@ -467,10 +466,10 @@ object NextTurnAutomation {
val bestCity = civInfo.cities.filterNot { it.isPuppet }
// If we can build workers, then we want AT LEAST 2 improvements, OR a worker nearby.
// Otherwise, AI tries to produce settlers when it can hardly sustain itself
.filter {
.filter { city ->
!workersBuildableForThisCiv
|| it.getCenterTile().getTilesInDistance(2).count { it.improvement!=null } > 1
|| it.getCenterTile().getTilesInDistance(3).any { it.civilianUnit?.hasUnique(UniqueType.BuildImprovements)==true }
|| city.getCenterTile().getTilesInDistance(2).count { it.improvement!=null } > 1
|| city.getCenterTile().getTilesInDistance(3).any { it.civilianUnit?.hasUnique(UniqueType.BuildImprovements)==true }
}
.maxByOrNull { it.cityStats.currentCityStats.production }
?: return

View File

@ -18,7 +18,6 @@ import com.unciv.logic.map.tile.RoadStatus
import com.unciv.logic.map.tile.Tile
import com.unciv.models.Counter
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
@ -35,7 +34,6 @@ enum class CityFlags {
class City : IsPartOfGameInfoSerialization {
@Suppress("JoinDeclarationAndAssignment")
@Transient
lateinit var civ: Civilization
@ -307,8 +305,8 @@ class City : IsPartOfGameInfoSerialization {
fun canBeDestroyed(justCaptured: Boolean = false): Boolean {
if (civ.gameInfo.gameParameters.noCityRazing) return false
val allowRazeCapital = civ.gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.allowRazeCapital)
val allowRazeHolyCity = civ.gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.allowRazeHolyCity)
val allowRazeCapital = civ.gameInfo.ruleset.modOptions.hasUnique(UniqueType.AllowRazeCapital)
val allowRazeHolyCity = civ.gameInfo.ruleset.modOptions.hasUnique(UniqueType.AllowRazeHolyCity)
if (isOriginalCapital && !allowRazeCapital) return false
if (isHolyCity() && !allowRazeHolyCity) return false

View File

@ -5,7 +5,6 @@ import com.unciv.models.Counter
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.IConstruction
import com.unciv.models.ruleset.INonPerpetualConstruction
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
@ -523,7 +522,7 @@ class CityStats(val city: City) {
}
// AFTER we've gotten all the gold stats figured out, only THEN do we plonk that gold into Science
if (city.getRuleset().modOptions.uniques.contains(ModOptionsConstants.convertGoldToScience)) {
if (city.getRuleset().modOptions.hasUnique(UniqueType.ConvertGoldToScience)) {
val amountConverted = (newFinalStatList.values.sumOf { it.gold.toDouble() }
* city.civ.tech.goldPercentConvertedToScience).toInt().toFloat()
if (amountConverted > 0) // Don't want you converting negative gold to negative science yaknow

View File

@ -15,7 +15,6 @@ import com.unciv.logic.civilization.diplomacy.DiplomacyTurnManager.nextTurn
import com.unciv.logic.map.mapunit.UnitTurnManager
import com.unciv.logic.map.tile.Tile
import com.unciv.logic.trade.TradeEvaluation
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
import com.unciv.models.ruleset.unique.UniqueType
@ -42,7 +41,6 @@ class TurnManager(val civInfo: Civilization) {
if (civInfo.cities.isNotEmpty() && civInfo.gameInfo.ruleset.technologies.isNotEmpty())
civInfo.tech.updateResearchProgress()
civInfo.cache.updateCivResources() // If you offered a trade last turn, this turn it will have been accepted/declined
for (stockpiledResource in civInfo.getCivResourceSupply().filter { it.resource.isStockpiled() })
civInfo.resourceStockpiles.add(stockpiledResource.resource.name, stockpiledResource.amount)
@ -52,8 +50,7 @@ class TurnManager(val civInfo: Civilization) {
civInfo.updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence
// Do this after updateStatsForNextTurn but before cities.startTurn
if (civInfo.playerType == PlayerType.AI && civInfo.gameInfo.ruleset.modOptions.uniques.contains(
ModOptionsConstants.convertGoldToScience))
if (civInfo.playerType == PlayerType.AI && civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.ConvertGoldToScience))
NextTurnAutomation.automateGoldToSciencePercentage(civInfo)
// Generate great people at the start of the turn,

View File

@ -8,7 +8,6 @@ import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
@ -64,7 +63,7 @@ class TradeEvaluation {
}
fun getTradeAcceptability(trade: Trade, evaluator: Civilization, tradePartner: Civilization): Int {
val citiesAskedToSurrender = trade.ourOffers.filter { it.type == TradeType.City }.count()
val citiesAskedToSurrender = trade.ourOffers.count { it.type == TradeType.City }
val maxCitiesToSurrender = ceil(evaluator.cities.size.toFloat() / 5).toInt()
if (citiesAskedToSurrender > maxCitiesToSurrender) {
return Int.MIN_VALUE
@ -100,7 +99,7 @@ class TradeEvaluation {
return evaluateBuyCost(offer, civInfo, tradePartner)
}
fun evaluateBuyCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization): Int {
private fun evaluateBuyCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization): Int {
when (offer.type) {
TradeType.Gold -> return offer.amount
// GPT loses 1% of value for each 'future' turn, meaning: gold now is more valuable than gold in the future
@ -151,13 +150,13 @@ class TradeEvaluation {
val civToDeclareWarOn = civInfo.gameInfo.getCivilization(offer.name)
val threatToThem = Automation.threatAssessment(civInfo, civToDeclareWarOn)
if (!civInfo.isAtWarWith(civToDeclareWarOn)) return 0 // why should we pay you to go fight someone...?
return if (!civInfo.isAtWarWith(civToDeclareWarOn)) 0 // why should we pay you to go fight someone...?
else when (threatToThem) {
ThreatLevel.VeryLow -> return 0
ThreatLevel.Low -> return 0
ThreatLevel.Medium -> return 100
ThreatLevel.High -> return 500
ThreatLevel.VeryHigh -> return 1000
ThreatLevel.VeryLow -> 0
ThreatLevel.Low -> 0
ThreatLevel.Medium -> 100
ThreatLevel.High -> 500
ThreatLevel.VeryHigh -> 1000
}
}
TradeType.City -> {
@ -209,7 +208,7 @@ class TradeEvaluation {
return evaluateSellCost(offer, civInfo, tradePartner)
}
fun evaluateSellCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization): Int {
private fun evaluateSellCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization): Int {
when (offer.type) {
TradeType.Gold -> return offer.amount
TradeType.Gold_Per_Turn -> return offer.amount * offer.duration
@ -307,7 +306,7 @@ class TradeEvaluation {
* This returns how much one gold is worth now in comparison to starting out the game
* Gold is worth less as the civilization has a higher income
*/
fun getGoldInflation(civInfo: Civilization): Double {
private fun getGoldInflation(civInfo: Civilization): Double {
val modifier = 1000.0
val goldPerTurn = civInfo.stats.statsForNextTurn.gold.toDouble()
// To visualise the function, plug this into a 2d graphing calculator \frac{1000}{x^{1.2}+1.11*1000}
@ -371,7 +370,7 @@ class TradeEvaluation {
}
private fun introductionValue(ruleSet: Ruleset): Int {
val unique = ruleSet.modOptions.getMatchingUniques(ModOptionsConstants.tradeCivIntroductions).firstOrNull()
val unique = ruleSet.modOptions.getMatchingUniques(UniqueType.TradeCivIntroductions).firstOrNull()
?: return 0
return unique.params[0].toInt()
}

View File

@ -7,8 +7,6 @@ import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.diplomacy.CityStateFunctions
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueType
@ -59,7 +57,7 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili
val otherCivsWeKnow = civInfo.getKnownCivs()
.filter { it.civName != otherCivilization.civName && it.isMajorCiv() && !it.isDefeated() }
if (civInfo.gameInfo.ruleset.modOptions.hasUnique(ModOptionsConstants.tradeCivIntroductions)) {
if (civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.TradeCivIntroductions)) {
val civsWeKnowAndTheyDont = otherCivsWeKnow
.filter { !otherCivilization.diplomacy.containsKey(it.civName) && !it.isDefeated() }
for (thirdCiv in civsWeKnowAndTheyDont) {
@ -68,7 +66,7 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili
}
if (!civInfo.isCityState() && !otherCivilization.isCityState()
&& !civInfo.gameInfo.ruleset.modOptions.hasUnique(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
&& !civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
val civsWeBothKnow = otherCivsWeKnow
.filter { otherCivilization.diplomacy.containsKey(it.civName) }
val civsWeArentAtWarWith = civsWeBothKnow
@ -134,7 +132,7 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili
from.getDiplomacyManager(to)
.setFlag(DiplomacyFlags.ResearchAgreement, offer.duration)
}
if (offer.name == Constants.defensivePact) to.getDiplomacyManager(from).signDefensivePact(offer.duration);
if (offer.name == Constants.defensivePact) to.getDiplomacyManager(from).signDefensivePact(offer.duration)
}
TradeType.Introduction -> to.diplomacyFunctions.makeCivilizationsMeet(to.gameInfo.getCivilization(offer.name))
TradeType.WarDeclaration -> {

View File

@ -6,16 +6,6 @@ import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueMap
import com.unciv.models.ruleset.unique.UniqueTarget
object ModOptionsConstants {
const val diplomaticRelationshipsCannotChange = "Diplomatic relationships cannot change"
const val convertGoldToScience = "Can convert gold to science with sliders"
const val allowCityStatesSpawnUnits = "Allow City States to spawn with additional units"
const val tradeCivIntroductions = "Can trade civilization introductions for [] Gold"
const val disableReligion = "Disable religion"
const val allowRazeCapital = "Allow raze capital"
const val allowRazeHolyCity = "Allow raze holy city"
}
class ModOptions : IHasUniques {
//region Modder choices
var isBaseRuleset = false

View File

@ -1,9 +1,14 @@
package com.unciv.models.ruleset.unique
import java.util.EnumSet
enum class UniqueFlag {
HiddenToUsers,
NoConditionals,
;
companion object {
val setOfHiddenToUsers = listOf(HiddenToUsers)
val none: EnumSet<UniqueFlag> = EnumSet.noneOf(UniqueFlag::class.java)
val setOfHiddenToUsers: EnumSet<UniqueFlag> = EnumSet.of(HiddenToUsers)
val setOfNoConditionals: EnumSet<UniqueFlag> = EnumSet.of(NoConditionals)
}
}

View File

@ -588,9 +588,14 @@ enum class UniqueParameterType(
/** Mod declarative compatibility: Define Mod relations by their name. */
ModName("modFilter", "DeCiv Redux", """A Mod name, case-sensitive _or_ a simple wildcard filter beginning and ending in an Asterisk, case-insensitive""", "Mod name filter") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueParameterErrorSeverity? =
if ('-' !in parameterText && ('*' !in parameterText || parameterText.matches(Regex("""^\*[^*]+\*$""")))) null
else UniqueType.UniqueParameterErrorSeverity.RulesetInvariant
UniqueType.UniqueParameterErrorSeverity? = when {
BaseRuleset.values().any { it.fullName == parameterText } -> null // Only Builtin ruleset names can contain '-'
parameterText == "*Civ V -*" || parameterText == "*Civ V - *" -> null // Wildcard filter for builtin
'-' in parameterText -> UniqueType.UniqueParameterErrorSeverity.RulesetInvariant
parameterText.matches(Regex("""^\*[^*]+\*$""")) -> null
parameterText.startsWith('*') || parameterText.endsWith('*') -> UniqueType.UniqueParameterErrorSeverity.RulesetInvariant
else -> null
}
override fun getTranslationWriterStringsForOutput() = scanExistingValues(this)
},

View File

@ -5,6 +5,7 @@ import com.unciv.models.ruleset.validation.RulesetErrorSeverity
import com.unciv.models.ruleset.validation.RulesetValidator
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.getPlaceholderText
import java.util.EnumSet
// I didn't put this in a companion object because APPARENTLY doing that means you can't use it in the init function.
private val numberRegex = Regex("\\d+$") // Any number of trailing digits
@ -12,7 +13,7 @@ private val numberRegex = Regex("\\d+$") // Any number of trailing digits
enum class UniqueType(
val text: String,
vararg targets: UniqueTarget,
val flags: List<UniqueFlag> = emptyList(),
val flags: EnumSet<UniqueFlag> = UniqueFlag.none,
val docDescription: String? = null
) {
@ -807,17 +808,27 @@ enum class UniqueType(
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."),
// Formerly `ModOptionsConstants`
DiplomaticRelationshipsCannotChange("Diplomatic relationships cannot change", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
ConvertGoldToScience("Can convert gold to science with sliders", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
AllowCityStatesSpawnUnits("Allow City States to spawn with additional units", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
TradeCivIntroductions("Can trade civilization introductions for [positiveAmount] Gold", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
DisableReligion("Disable religion", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
AllowRazeCapital("Allow raze capital", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
AllowRazeHolyCity("Allow raze holy city", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
// Declarative Mod compatibility (see [ModCompatibility]):
// Note there is currently no display for these, but UniqueFlag.HiddenToUsers is not set.
// That means we auto-template and ask our translators for a translation that is currently unused.
//todo To think over - leave as is for future use or remove templates and translations by adding the flag?
ModIncompatibleWith("Mod is incompatible with [modFilter]", UniqueTarget.ModOptions,
ModIncompatibleWith("Mod is incompatible with [modFilter]", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals,
docDescription = "Specifies that your Mod is incompatible with another. Always treated symmetrically, and cannot be overridden by the Mod you are declaring as incompatible."),
ModRequires("Mod requires [modFilter]", UniqueTarget.ModOptions,
ModRequires("Mod requires [modFilter]", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals,
docDescription = "Specifies that your Extension Mod is only available if any other Mod matching the filter is active."),
ModIsAudioVisualOnly("Should only be used as permanent audiovisual mod", UniqueTarget.ModOptions),
ModIsAudioVisual("Can be used as permanent audiovisual mod", UniqueTarget.ModOptions),
ModIsNotAudioVisual("Cannot be used as permanent audiovisual mod", UniqueTarget.ModOptions),
ModIsAudioVisualOnly("Should only be used as permanent audiovisual mod", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
ModIsAudioVisual("Can be used as permanent audiovisual mod", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
ModIsNotAudioVisual("Cannot be used as permanent audiovisual mod", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
// endregion

View File

@ -39,7 +39,7 @@ class RulesetValidator(val ruleset: Ruleset) {
val lines = RulesetErrorList()
// When not checking the entire ruleset, we can only really detect ruleset-invariant errors in uniques
addModOptionsErrors(lines)
addModOptionsErrors(lines, tryFixUnknownUniques)
uniqueValidator.checkUniques(ruleset.globalUniques, lines, false, tryFixUnknownUniques)
addUnitErrorsRulesetInvariant(lines, tryFixUnknownUniques)
addTechErrorsRulesetInvariant(lines, tryFixUnknownUniques)
@ -63,7 +63,7 @@ class RulesetValidator(val ruleset: Ruleset) {
uniqueValidator.populateFilteringUniqueHashsets()
val lines = RulesetErrorList()
addModOptionsErrors(lines)
addModOptionsErrors(lines, tryFixUnknownUniques)
uniqueValidator.checkUniques(ruleset.globalUniques, lines, true, tryFixUnknownUniques)
addUnitErrorsBaseRuleset(lines, tryFixUnknownUniques)
@ -94,8 +94,12 @@ class RulesetValidator(val ruleset: Ruleset) {
return lines
}
private fun addModOptionsErrors(lines: RulesetErrorList) {
if (ruleset.name.isBlank()) return // These tests don't make sense for combined rulesets
private fun addModOptionsErrors(lines: RulesetErrorList, tryFixUnknownUniques: Boolean) {
// Basic Unique validation (type, target, parameters) should always run.
// Using reportRulesetSpecificErrors=true as ModOptions never should use Uniques depending on objects from a base ruleset anyway.
uniqueValidator.checkUniques(ruleset.modOptions, lines, reportRulesetSpecificErrors = true, tryFixUnknownUniques)
if (ruleset.name.isBlank()) return // The rest of these tests don't make sense for combined rulesets
val audioVisualUniqueTypes = setOf(
UniqueType.ModIsAudioVisual,
@ -825,6 +829,4 @@ class RulesetValidator(val ruleset: Ruleset) {
recursiveCheck(hashSetOf(), promotion, 0)
}
}
}

View File

@ -7,6 +7,7 @@ import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueComplianceError
import com.unciv.models.ruleset.unique.UniqueFlag
import com.unciv.models.ruleset.unique.UniqueParameterType
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
@ -84,7 +85,10 @@ class UniqueValidator(val ruleset: Ruleset) {
addConditionalErrors(conditional, rulesetErrors, prefix, unique, reportRulesetSpecificErrors)
}
if (unique.conditionals.any() && unique.type in MapUnitCache.UnitMovementUniques)
if (unique.type in MapUnitCache.UnitMovementUniques
&& unique.conditionals.any { it.type != UniqueType.ConditionalOurUnit || it.params[0] != "All" }
)
// (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
rulesetErrors.add("$prefix unique \"${unique.text}\" contains a conditional on a unit movement unique. " +
"Due to performance considerations, this unique is cached on the unit," +
@ -105,6 +109,15 @@ class UniqueValidator(val ruleset: Ruleset) {
unique: Unique,
reportRulesetSpecificErrors: Boolean
) {
if (unique.hasFlag(UniqueFlag.NoConditionals)) {
rulesetErrors.add(
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
" but the unique does not accept conditionals!",
RulesetErrorSeverity.Error
)
return
}
if (conditional.type == null) {
rulesetErrors.add(
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +

View File

@ -18,7 +18,6 @@ import com.unciv.logic.civilization.managers.AssignedQuest
import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Quest
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueType
@ -70,7 +69,7 @@ class CityStateDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) {
if (diplomacyScreen.isNotPlayersTurn() || viewingCiv.isAtWarWith(otherCiv)) demandTributeButton.disable()
val diplomacyManager = viewingCiv.getDiplomacyManager(otherCiv)
if (!viewingCiv.gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
if (!viewingCiv.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
if (viewingCiv.isAtWarWith(otherCiv))
diplomacyTable.add(getNegotiatePeaceCityStateButton(otherCiv, diplomacyManager)).row()
else diplomacyTable.add(diplomacyScreen.getDeclareWarButton(diplomacyManager, otherCiv)).row()

View File

@ -14,7 +14,7 @@ import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.disable
@ -48,7 +48,7 @@ class MajorCivDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) {
diplomacyTable.addSeparator()
val diplomaticRelationshipsCanChange =
!viewingCiv.gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)
!viewingCiv.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)
val diplomacyManager = viewingCiv.getDiplomacyManager(otherCiv)

View File

@ -5,15 +5,14 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.Constants
import com.unciv.GUI
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.stats.StatMap
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.components.widgets.UncivSlider
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.components.widgets.UncivSlider
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
@ -53,7 +52,7 @@ class StatsOverviewTab(
unhappinessTable.update()
goldAndSliderTable.add(goldTable).row()
if (gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.convertGoldToScience))
if (gameInfo.ruleset.modOptions.hasUnique(UniqueType.ConvertGoldToScience))
goldAndSliderTable.addGoldSlider()
update()

View File

@ -1805,6 +1805,29 @@ Due to performance considerations, this unique is cached, thus conditionals may
Applicable to: CityState
## ModOptions uniques
??? example "Diplomatic relationships cannot change"
Applicable to: ModOptions
??? example "Can convert gold to science with sliders"
Applicable to: ModOptions
??? example "Allow City States to spawn with additional units"
Applicable to: ModOptions
??? example "Can trade civilization introductions for [positiveAmount] Gold"
Example: "Can trade civilization introductions for [3] Gold"
Applicable to: ModOptions
??? example "Disable religion"
Applicable to: ModOptions
??? example "Allow raze capital"
Applicable to: ModOptions
??? example "Allow raze holy city"
Applicable to: ModOptions
??? example "Mod is incompatible with [modFilter]"
Specifies that your Mod is incompatible with another. Always treated symmetrically, and cannot be overridden by the Mod you are declaring as incompatible.
Example: "Mod is incompatible with [DeCiv Redux]"