diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 0ae90d28ee..ab39544443 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -4,8 +4,8 @@ package com.unciv.build object BuildConfig { const val kotlinVersion = "2.1.20" const val appName = "Unciv" - const val appCodeNumber = 1151 - const val appVersion = "4.17.11" + const val appCodeNumber = 1152 + const val appVersion = "4.17.11-patch1" const val gdxVersion = "1.13.1" const val ktorVersion = "2.3.13" diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index b1c8835f6a..8be643ade7 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -487,7 +487,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci companion object { //region AUTOMATICALLY GENERATED VERSION DATA - DO NOT CHANGE THIS REGION, INCLUDING THIS COMMENT - val VERSION = Version("4.17.11", 1151) + val VERSION = Version("4.17.11-patch1", 1152) //endregion /** Global reference to the one Gdx.Game instance created by the platform launchers - do not use without checking [isCurrentInitialized] first. */ diff --git a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt index 6b674336d0..ce0eaf127a 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt @@ -559,7 +559,7 @@ class CityStateFunctions(val civInfo: Civilization) { .filter { !it.hasUnique(UniqueType.ResearchableMultipleTimes) && civInfo.tech.canBeResearched(it.name) } for (tech in researchableTechs) { val aliveMajorCivs = civInfo.gameInfo.getAliveMajorCivs() - if (aliveMajorCivs.count { it.tech.isResearched(tech.name) } >= aliveMajorCivs.size / 2) + if (aliveMajorCivs.count { it.tech.isResearched(tech.name) } > aliveMajorCivs.size / 2) civInfo.tech.addTechnology(tech.name) } return diff --git a/core/src/com/unciv/logic/map/tile/Tile.kt b/core/src/com/unciv/logic/map/tile/Tile.kt index 3f292e8deb..d5cffa96f2 100644 --- a/core/src/com/unciv/logic/map/tile/Tile.kt +++ b/core/src/com/unciv/logic/map/tile/Tile.kt @@ -19,7 +19,6 @@ import com.unciv.logic.map.mapgenerator.MapResourceSetting import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.mapunit.UnitTurnManager import com.unciv.logic.map.mapunit.movement.UnitMovement -import com.unciv.models.UnitAction import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.Terrain @@ -631,28 +630,13 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { return min(distance, wrappedDistance).toInt() } - - fun canBeSettled(unitCanFoundUnique: Unique?=null): Boolean { + + @Readonly + fun canBeSettled(): Boolean { val modConstants = tileMap.gameInfo.ruleset.modOptions.constants - var addedDistanceBeweenContinents: Int - var canSettleInTileWithUnique = false - if (unitCanFoundUnique != null) { - canSettleInTileWithUnique = (isWater || isImpassible()) && - unitCanFoundUnique.getModifiers(UniqueType.ConditionalInTiles).none{ - matchesFilter(it.params[0]) - } - } - /* - Putting the ! to make sure the player/Ai doesn't place cities too near each other. - Because when .none return False when one element has a match. - */ - - addedDistanceBeweenContinents = if (!canSettleInTileWithUnique) 1 else 0 - return when { - canSettleInTileWithUnique -> false - getTilesInDistance(modConstants.minimalCityDistanceOnDifferentContinents+ - addedDistanceBeweenContinents) + isWater || isImpassible() -> false + getTilesInDistance(modConstants.minimalCityDistanceOnDifferentContinents) .any { it.isCityCenter() && it.getContinent() != getContinent() } -> false getTilesInDistance(modConstants.minimalCityDistance) .any { it.isCityCenter() && it.getContinent() == getContinent() } -> false diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 15905fb574..f43f3f6dda 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -291,12 +291,11 @@ class Ruleset { personalities.clear() events.clear() } - + @Readonly fun allRulesetObjects(): Sequence = RulesetFile.entries.asSequence().flatMap { it.getRulesetObjects(this) } @Readonly fun allUniques(): Sequence = RulesetFile.entries.asSequence().flatMap { it.getUniques(this) } @Readonly fun allICivilopediaText(): Sequence = allRulesetObjects() + events.values.flatMap { it.choices } - fun load(folderHandle: FileHandle) { // Note: Most files are loaded using createHashmap, which sets originRuleset automatically. // For other files containing IRulesetObject's we'll have to remember to do so manually - e.g. Tech. diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index 93317ab4f1..2a05f980da 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -187,7 +187,6 @@ class Terrain : RulesetStatsObject() { type.name -> true "Natural Wonder" -> type == TerrainType.NaturalWonder "Terrain Feature" -> type == TerrainType.TerrainFeature - "Impassable" -> impassable else -> false } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 408656a8f2..1132a97ed8 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -805,8 +805,7 @@ enum class UniqueType( /////// tile conditionals ConditionalNeighborTiles("with [nonNegativeAmount] to [nonNegativeAmount] neighboring [tileFilter] tiles", UniqueTarget.Conditional), - ConditionalInTiles("in [tileFilter] tiles", UniqueTarget.Conditional, - docDescription = "Can be used with FoundCity and FoundPuppetCity to only found cities in Water/Impassible tiles."), + ConditionalInTiles("in [tileFilter] tiles", UniqueTarget.Conditional), ConditionalInTilesNot("in tiles without [tileFilter]", UniqueTarget.Conditional), ConditionalNearTiles("within [positiveAmount] tiles of a [tileFilter]", UniqueTarget.Conditional), diff --git a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt index 13eb92a51a..b934bb9860 100644 --- a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt +++ b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt @@ -15,8 +15,6 @@ import com.unciv.models.ruleset.unique.UniqueParameterType import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.expressions.Expressions -import yairm210.purity.annotations.Cache -import yairm210.purity.annotations.LocalState import yairm210.purity.annotations.Readonly class UniqueValidator(val ruleset: Ruleset) { @@ -66,7 +64,7 @@ class UniqueValidator(val ruleset: Ruleset) { UniqueType.ConditionalNotAdjacentTo ) - @Readonly + @Readonly @Suppress("purity") fun checkUnique( unique: Unique, tryFixUnknownUniques: Boolean, @@ -76,7 +74,7 @@ class UniqueValidator(val ruleset: Ruleset) { val prefix by lazy { getUniqueContainerPrefix(uniqueContainer) + "\"${unique.text}\"" } if (unique.type == null) return checkUntypedUnique(unique, tryFixUnknownUniques, uniqueContainer, prefix, reportRulesetSpecificErrors) - @LocalState val rulesetErrors = RulesetErrorList(ruleset) + val rulesetErrors = RulesetErrorList(ruleset) if (uniqueContainer != null && !(unique.type.canAcceptUniqueTarget(uniqueContainer.getUniqueTarget()) || @@ -109,14 +107,14 @@ class UniqueValidator(val ruleset: Ruleset) { complianceError.errorSeverity.getRulesetErrorSeverity(), uniqueContainer, unique ) - rulesetErrors += getExpressionParseErrors(complianceError, uniqueContainer, unique) + addExpressionParseErrors(complianceError, rulesetErrors, uniqueContainer, unique) } for (conditional in unique.modifiers) { - rulesetErrors += getConditionalErrors(conditional, prefix, unique, uniqueContainer, reportRulesetSpecificErrors) + addConditionalErrors(conditional, rulesetErrors, prefix, unique, uniqueContainer, reportRulesetSpecificErrors) } - rulesetErrors += getUniqueTypeSpecificErrors(prefix, unique, uniqueContainer, reportRulesetSpecificErrors) + addUniqueTypeSpecificErrors(rulesetErrors, prefix, unique, uniqueContainer, reportRulesetSpecificErrors) val conditionals = unique.modifiers.filter { it.type?.canAcceptUniqueTarget(UniqueTarget.Conditional) == true } if (conditionals.size > 1){ @@ -146,19 +144,18 @@ class UniqueValidator(val ruleset: Ruleset) { if (reportRulesetSpecificErrors) // 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. - rulesetErrors += addDeprecationAnnotationErrors(unique, prefix, uniqueContainer) + addDeprecationAnnotationErrors(unique, prefix, rulesetErrors, uniqueContainer) return rulesetErrors } - @Readonly - private fun getExpressionParseErrors( + private fun addExpressionParseErrors( complianceError: UniqueComplianceError, + rulesetErrors: RulesetErrorList, uniqueContainer: IHasUniques?, unique: Unique - ): RulesetErrorList { - @LocalState val rulesetErrors = RulesetErrorList(ruleset) - if (!complianceError.acceptableParameterTypes.contains(UniqueParameterType.Countable)) return rulesetErrors + ) { + if (!complianceError.acceptableParameterTypes.contains(UniqueParameterType.Countable)) return val parseError = Expressions.getParsingError(complianceError.parameterName) if (parseError != null) { @@ -170,7 +167,7 @@ class UniqueValidator(val ruleset: Ruleset) { val text = "\"${complianceError.parameterName}\" could not be parsed as an expression due to:" + " ${parseError.message}. \n$parameterWithErrorLocationMarked" rulesetErrors.add(text, RulesetErrorSeverity.WarningOptionsOnly, uniqueContainer, unique) - return rulesetErrors + return } val countableErrors = Expressions.getCountableErrors(complianceError.parameterName, ruleset) @@ -179,7 +176,6 @@ class UniqueValidator(val ruleset: Ruleset) { " ${countableErrors.joinToString(", ")}" rulesetErrors.add(text, RulesetErrorSeverity.WarningOptionsOnly, uniqueContainer, unique) } - return rulesetErrors } private val resourceUniques = setOf(UniqueType.ProvidesResources, UniqueType.ConsumesResources, @@ -192,37 +188,21 @@ class UniqueValidator(val ruleset: Ruleset) { UniqueType.ConditionalWhenBelowAmountStatResource, ) - @Readonly - private fun getUniqueTypeSpecificErrors( - prefix: String, unique: Unique, uniqueContainer: IHasUniques?, reportRulesetSpecificErrors: Boolean - ): RulesetErrorList { - @LocalState val rulesetErrors = RulesetErrorList(ruleset) - when (unique.type) { - UniqueType.RuinsUpgrade -> { - if (reportRulesetSpecificErrors && !anyAncientRuins) - rulesetErrors.add("$prefix is pointless - there are no ancient ruins", RulesetErrorSeverity.Warning, uniqueContainer, unique) - } - else -> {} - } - return rulesetErrors - } - - @Readonly - private fun getConditionalErrors( + private fun addConditionalErrors( conditional: Unique, + rulesetErrors: RulesetErrorList, prefix: String, unique: Unique, uniqueContainer: IHasUniques?, reportRulesetSpecificErrors: Boolean - ): RulesetErrorList { - @LocalState val rulesetErrors = RulesetErrorList(ruleset) + ) { if (unique.hasFlag(UniqueFlag.NoConditionals)) { rulesetErrors.add( "$prefix contains the conditional \"${conditional.text}\"," + " but the unique does not accept conditionals!", RulesetErrorSeverity.Error, uniqueContainer, unique ) - return rulesetErrors + return } if (conditional.type == null) { @@ -241,7 +221,7 @@ class UniqueValidator(val ruleset: Ruleset) { text, RulesetErrorSeverity.Warning, uniqueContainer, unique ) - return rulesetErrors + return } if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None }) @@ -296,21 +276,30 @@ class UniqueValidator(val ruleset: Ruleset) { complianceError.errorSeverity.getRulesetErrorSeverity(), uniqueContainer, unique ) - rulesetErrors += getExpressionParseErrors(complianceError, uniqueContainer, unique) + addExpressionParseErrors(complianceError, rulesetErrors, uniqueContainer, unique) } - addDeprecationAnnotationErrors(conditional, "$prefix contains modifier \"${conditional.text}\" which", uniqueContainer) - - return rulesetErrors + addDeprecationAnnotationErrors(conditional, "$prefix contains modifier \"${conditional.text}\" which", rulesetErrors, uniqueContainer) + } + + private fun addUniqueTypeSpecificErrors( + rulesetErrors: RulesetErrorList, prefix: String, unique: Unique, uniqueContainer: IHasUniques?, reportRulesetSpecificErrors: Boolean + ) { + when(unique.type) { + UniqueType.RuinsUpgrade -> { + if (reportRulesetSpecificErrors && !anyAncientRuins) + rulesetErrors.add("$prefix is pointless - there are no ancient ruins", RulesetErrorSeverity.Warning, uniqueContainer, unique) + } + else -> return + } } - @Readonly private fun addDeprecationAnnotationErrors( unique: Unique, prefix: String, + rulesetErrors: RulesetErrorList, uniqueContainer: IHasUniques? - ): RulesetErrorList { - @LocalState val rulesetErrors = RulesetErrorList(ruleset) + ) { val deprecationAnnotation = unique.getDeprecationAnnotation() if (deprecationAnnotation != null) { val replacementUniqueText = unique.getReplacementText(ruleset) @@ -325,13 +314,12 @@ class UniqueValidator(val ruleset: Ruleset) { } // Check for deprecated Countables - if (unique.type == null) return rulesetErrors + if (unique.type == null) return val countables = unique.type.parameterTypeMap.withIndex() .filter { UniqueParameterType.Countable in it.value } .map { unique.params[it.index] } .mapNotNull { Countables.getMatching(it, ruleset) } - for (countable in countables) { val deprecation = countable.getDeprecationAnnotation() ?: continue // This is less flexible than unique.getReplacementText(ruleset) @@ -343,18 +331,14 @@ class UniqueValidator(val ruleset: Ruleset) { else RulesetErrorSeverity.ErrorOptionsOnly // User visible in new game and red in options rulesetErrors.add(text, severity, uniqueContainer, unique) } - - return rulesetErrors } /** Maps uncompliant parameters to their required types */ - @Readonly private fun getComplianceErrors( unique: Unique, ): List { if (unique.type == null) return emptyList() - @LocalState val errorList = ArrayList() - + val errorList = ArrayList() for ((index, param) in unique.params.withIndex()) { // Trying to catch the error at #11404 if (unique.type.parameterTypeMap.size != unique.params.size) { @@ -379,13 +363,11 @@ class UniqueValidator(val ruleset: Ruleset) { return errorList } - @Cache private val paramTypeErrorSeverityCache = HashMap>() - @Readonly + private val paramTypeErrorSeverityCache = HashMap>() private fun getParamTypeErrorSeverityCached(uniqueParameterType: UniqueParameterType, param: String): UniqueType.UniqueParameterErrorSeverity? { if (!paramTypeErrorSeverityCache.containsKey(uniqueParameterType)) paramTypeErrorSeverityCache[uniqueParameterType] = hashMapOf() - - @LocalState val uniqueParamCache = paramTypeErrorSeverityCache[uniqueParameterType]!! + val uniqueParamCache = paramTypeErrorSeverityCache[uniqueParameterType]!! if (uniqueParamCache.containsKey(param)) return uniqueParamCache[param] @@ -394,7 +376,6 @@ class UniqueValidator(val ruleset: Ruleset) { return severity } - @Readonly private fun checkUntypedUnique( unique: Unique, tryFixUnknownUniques: Boolean, @@ -410,7 +391,7 @@ class UniqueValidator(val ruleset: Ruleset) { ) // Support purely filtering Uniques without actual implementation - if (isFilteringUniqueAllowed(unique, reportRulesetSpecificErrors)) return RulesetErrorList(ruleset) + if (isFilteringUniqueAllowed(unique, reportRulesetSpecificErrors)) return RulesetErrorList() if (tryFixUnknownUniques) { val fixes = tryFixUnknownUnique(unique, uniqueContainer, prefix) @@ -424,7 +405,6 @@ class UniqueValidator(val ruleset: Ruleset) { ) } - @Readonly private fun isFilteringUniqueAllowed(unique: Unique, reportRulesetSpecificErrors: Boolean): Boolean { // Isolate this decision, to allow easy change of approach // This says: Must have no conditionals or parameters, and is used in any "filtering" parameter of another Unique @@ -433,7 +413,6 @@ class UniqueValidator(val ruleset: Ruleset) { return unique.text in allUniqueParameters // referenced at least once from elsewhere } - @Readonly private fun tryFixUnknownUnique(unique: Unique, uniqueContainer: IHasUniques?, prefix: String): RulesetErrorList { val similarUniques = UniqueType.entries.filter { getRelativeTextDistance( @@ -464,14 +443,13 @@ class UniqueValidator(val ruleset: Ruleset) { }.prependIndent("\t") RulesetErrorList.of(text, RulesetErrorSeverity.OK, ruleset, uniqueContainer, unique) } - else -> RulesetErrorList(ruleset) + else -> RulesetErrorList() } } companion object { const val whichDoesNotFitParameterType = "which does not fit parameter type" - @Readonly internal fun getUniqueContainerPrefix(uniqueContainer: IHasUniques?) = (if (uniqueContainer is IRulesetObject) "${uniqueContainer.originRuleset}: " else "") + (if (uniqueContainer == null) "The" else "(${uniqueContainer.getUniqueTarget().name}) ${uniqueContainer.name}'s") + diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt index 313612b528..140566627f 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt @@ -45,11 +45,11 @@ object UnitActionsFromUniques { UnitActionModifiers.getUsableUnitActionUniques(unit, UniqueType.FoundPuppetCity).firstOrNull() ?: return null - + if (tile.isWater || tile.isImpassible()) return null // Spain should still be able to build Conquistadors in a one city challenge - but can't settle them if (unit.civ.isOneCityChallenger() && unit.civ.hasEverOwnedOriginalCapital) return null - if (!unit.hasMovement() || !tile.canBeSettled(unique)) + if (!unit.hasMovement() || !tile.canBeSettled()) return UnitAction(UnitActionType.FoundCity, 80f, action = null) val hasActionModifiers = unique.modifiers.any { it.type?.targetTypes?.contains( diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 09f9412c47..09e940f20b 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -3535,8 +3535,6 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Conditional ??? example "<in [tileFilter] tiles>" - Can be used with FoundCity and FoundPuppetCity to only found cities in Water/Impassible tiles. - Example: "<in [Farm] tiles>" Applicable to: Conditional diff --git a/mkdocs.yml b/mkdocs.yml index a175cc9c57..3f5fcbc833 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,6 +23,7 @@ theme: repo_name: yairm210/unciv repo_url: https://github.com/yairm210/unciv +site_url: https://yairm210.github.io/Unciv markdown_extensions: # For adminitions