From 3722fab38df50d79d827c345f5580dfc8318fec2 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Wed, 8 Sep 2021 20:24:26 +0200 Subject: [PATCH] From the indstrial era onwards, things change in religion (#5095) * Improved redability * From the industrial era onwards, religion goes into 'second phase' * Fixed tests * Fixed formula for buying great prophets starting from the industrial era * Added `getMatchingUniques`, `hasUnique` to `IHasUniques`, cleaned up some code * Fix compilation errors --- .../assets/jsons/Civ V - Vanilla/Eras.json | 30 +++++++-- core/src/com/unciv/logic/GameInfo.kt | 5 +- core/src/com/unciv/logic/city/CityInfo.kt | 41 +++++++----- .../src/com/unciv/logic/city/IConstruction.kt | 64 +++++++++---------- .../logic/civilization/CivilizationInfo.kt | 14 ++-- .../logic/civilization/ReligionManager.kt | 31 +++++++-- .../civilization/RuinsManager/RuinsManager.kt | 4 +- core/src/com/unciv/models/ruleset/Era.kt | 4 +- .../com/unciv/models/ruleset/IHasUniques.kt | 4 ++ .../com/unciv/models/ruleset/RuinReward.kt | 1 + .../com/unciv/models/ruleset/tile/Terrain.kt | 7 +- .../models/ruleset/tile/TileImprovement.kt | 1 - .../com/unciv/models/ruleset/unit/BaseUnit.kt | 31 +++++++-- .../com/unciv/models/ruleset/unit/UnitType.kt | 7 +- .../logic/map/UnitMovementAlgorithmsTests.kt | 14 ++-- 15 files changed, 174 insertions(+), 84 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Eras.json b/android/assets/jsons/Civ V - Vanilla/Eras.json index 53810b3363..c06358bb56 100644 --- a/android/assets/jsons/Civ V - Vanilla/Eras.json +++ b/android/assets/jsons/Civ V - Vanilla/Eras.json @@ -137,7 +137,11 @@ "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Militaristic": ["Provides military units every [17] turns"] }, - "iconRGB": [63, 81, 182] + "iconRGB": [63, 81, 182], + "uniques": ["May not generate great prophet equivalents naturally", + "May buy [Great Prophet] units for [200] [Faith] [in all cities in which the majority religion is a major religion] at an increasing price ([100])", + "Starting in this era disables religion" + ] }, { "name": "Modern era", @@ -168,7 +172,11 @@ "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Militaristic": ["Provides military units every [17] turns"] }, - "iconRGB": [33, 150, 243] + "iconRGB": [33, 150, 243], + "uniques": ["May not generate great prophet equivalents naturally", + "May buy [Great Prophet] units for [200] [Faith] [in all cities in which the majority religion is a major religion] at an increasing price ([100])", + "Starting in this era disables religion" + ] }, { "name": "Atomic era", @@ -200,7 +208,11 @@ "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Militaristic": ["Provides military units every [17] turns"] }, - "iconRGB": [0, 150, 136] + "iconRGB": [0, 150, 136], + "uniques": ["May not generate great prophet equivalents naturally", + "May buy [Great Prophet] units for [200] [Faith] [in all cities in which the majority religion is a major religion] at an increasing price ([100])", + "Starting in this era disables religion" + ] }, { "name": "Information era", @@ -236,7 +248,11 @@ "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Militaristic": ["Provides military units every [17] turns"] }, - "iconRGB": [76, 176, 81] + "iconRGB": [76, 176, 81], + "uniques": ["May not generate great prophet equivalents naturally", + "May buy [Great Prophet] units for [200] [Faith] [in all cities in which the majority religion is a major religion] at an increasing price ([100])", + "Starting in this era disables religion" + ] }, { // Technically, this Era doesn't exist in the original game. // But as it is _really_ usefull to have for testing, I'd like to keep it. @@ -271,6 +287,10 @@ "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Militaristic": ["Provides military units every [17] turns"] }, - "iconRGB": [76, 176, 81] + "iconRGB": [76, 176, 81], + "uniques": ["May not generate great prophet equivalents naturally", + "May buy [Great Prophet] units for [200] [Faith] [in all cities in which the majority religion is a major religion] at an increasing price ([100])", + "Starting in this era disables religion" + ] } ] \ No newline at end of file diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 90313195ea..55adf51036 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -433,7 +433,10 @@ class GameInfo { } - fun hasReligionEnabled() = gameParameters.religionEnabled || ruleSet.hasReligion() // Temporary function to check whether religion should be used for this game + fun hasReligionEnabled() = + // Temporary function to check whether religion should be used for this game + (gameParameters.religionEnabled || ruleSet.hasReligion()) + && (ruleSet.eras.isEmpty() || !ruleSet.eras[gameParameters.startingEra]!!.hasUnique("Starting in this era disables religion")) } // reduced variant only for load preview diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 7603fe9fed..2d2eabc258 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -43,15 +43,15 @@ class CityInfo { lateinit var tilesInRange: HashSet @Transient - var hasJustBeenConquered = - false // this is so that military units can enter the city, even before we decide what to do with it + // This is so that military units can enter the city, even before we decide what to do with it + var hasJustBeenConquered = false var location: Vector2 = Vector2.Zero var id: String = UUID.randomUUID().toString() var name: String = "" var foundingCiv = "" - var previousOwner = - "" // This is so that cities in resistance that re recaptured aren't in resistance anymore + // This is so that cities in resistance that are recaptured aren't in resistance anymore + var previousOwner = "" var turnAcquired = 0 var health = 200 var resistanceCounter = 0 @@ -245,8 +245,10 @@ class CityInfo { fun isCapital(): Boolean = cityConstructions.builtBuildings.contains(capitalCityIndicator()) fun isCoastal(): Boolean = centerTileInfo.isCoastalTile() fun capitalCityIndicator(): String { - val indicatorBuildings = getRuleset().buildings.values.asSequence() + val indicatorBuildings = getRuleset().buildings.values + .asSequence() .filter { it.uniques.contains("Indicates the capital city") } + val civSpecificBuilding = indicatorBuildings.firstOrNull { it.uniqueTo == civInfo.civName } if (civSpecificBuilding != null) return civSpecificBuilding.name else return indicatorBuildings.first().name @@ -294,8 +296,9 @@ class CityInfo { val resource = getRuleset().tileResources[unique.params[1]] if (resource != null) { cityResources.add( - resource, unique.params[0].toInt() - * civInfo.getResourceModifier(resource), "Tiles" + resource, + unique.params[0].toInt() * civInfo.getResourceModifier(resource), + "Tiles" ) } } @@ -596,11 +599,15 @@ class CityInfo { */ private fun triggerCitiesSettledNearOtherCiv() { val citiesWithin6Tiles = - civInfo.gameInfo.civilizations.filter { it.isMajorCiv() && it != civInfo } + civInfo.gameInfo.civilizations + .filter { it.isMajorCiv() && it != civInfo } .flatMap { it.cities } .filter { it.getCenterTile().aerialDistanceTo(getCenterTile()) <= 6 } - val civsWithCloseCities = citiesWithin6Tiles.map { it.civInfo }.distinct() - .filter { it.knows(civInfo) && it.exploredTiles.contains(location) } + val civsWithCloseCities = + citiesWithin6Tiles + .map { it.civInfo } + .distinct() + .filter { it.knows(civInfo) && it.exploredTiles.contains(location) } for (otherCiv in civsWithCloseCities) otherCiv.getDiplomacyManager(civInfo).setFlag(DiplomacyFlags.SettledCitiesNearUs, 30) } @@ -627,13 +634,13 @@ class CityInfo { "in all cities with a garrison" -> getCenterTile().militaryUnit != null "in all cities in which the majority religion is a major religion" -> religion.getMajorityReligionName() != null - && religion.getMajorityReligion()!!.isMajorReligion() + && religion.getMajorityReligion()!!.isMajorReligion() "in all cities in which the majority religion is an enhanced religion" -> religion.getMajorityReligionName() != null - && religion.getMajorityReligion()!!.isEnhancedReligion() + && religion.getMajorityReligion()!!.isEnhancedReligion() "in non-enemy foreign cities" -> viewingCiv != civInfo - && !civInfo.isAtWarWith(viewingCiv) + && !civInfo.isAtWarWith(viewingCiv) "in foreign cities" -> viewingCiv != civInfo "in annexed cities" -> foundingCiv != civInfo.civName && !isPuppet "in holy cities" -> religion.religionThisIsTheHolyCityOf != null @@ -715,9 +722,11 @@ class CityInfo { if (!tilesList.contains(tile)) cityPositionList.add(tile) - return cityPositionList.asSequence() - .map { it.getOwner()?.civName }.filterNotNull() - .distinct().toList() + return cityPositionList + .asSequence() + .mapNotNull { it.getOwner()?.civName } + .distinct() + .toList() } fun getImprovableTiles(): Sequence = getTiles() diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index 74ca4d1a5c..1f7d2b74d2 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -24,13 +24,6 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious. - fun getMatchingUniques(uniqueTemplate: String): Sequence { - return uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate } - } - fun hasUnique(uniqueTemplate: String): Boolean { - return uniqueObjects.any { it.placeholderText == uniqueTemplate } - } - fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean { if (stat in listOf(Stat.Production, Stat.Happiness)) return false if ("Cannot be purchased" in uniques) return false @@ -82,41 +75,46 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { class RejectionReasons(): HashSet() { - private val techPolicyEraWonderRequirements = hashSetOf( - RejectionReason.Obsoleted, - RejectionReason.RequiresTech, - RejectionReason.RequiresPolicy, - RejectionReason.MorePolicyBranches, - RejectionReason.RequiresBuildingInSomeCity - ) + fun filterTechPolicyEraWonderRequirements(): HashSet { return filterNot { it in techPolicyEraWonderRequirements }.toHashSet() } - - private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf( - RejectionReason.Obsoleted, - RejectionReason.WonderAlreadyBuilt, - RejectionReason.NationalWonderAlreadyBuilt, - RejectionReason.CannotBeBuiltWith, - RejectionReason.ReachedBuildCap - ) + fun hasAReasonToBeRemovedFromQueue(): Boolean { return any { it in reasonsToDefinitivelyRemoveFromQueue } } - - private val orderOfErrorMessages = listOf( - RejectionReason.WonderBeingBuiltElsewhere, - RejectionReason.NationalWonderBeingBuiltElsewhere, - RejectionReason.RequiresBuildingInAllCities, - RejectionReason.RequiresBuildingInThisCity, - RejectionReason.RequiresBuildingInSomeCity, - RejectionReason.PopulationRequirement, - RejectionReason.ConsumesResources, - RejectionReason.CanOnlyBePurchased - ) + fun getMostImportantRejectionReason(): String? { return orderOfErrorMessages.firstOrNull { it in this }?.errorMessage } + + // Used for constant variables in the functions above + companion object { + private val techPolicyEraWonderRequirements = hashSetOf( + RejectionReason.Obsoleted, + RejectionReason.RequiresTech, + RejectionReason.RequiresPolicy, + RejectionReason.MorePolicyBranches, + RejectionReason.RequiresBuildingInSomeCity + ) + private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf( + RejectionReason.Obsoleted, + RejectionReason.WonderAlreadyBuilt, + RejectionReason.NationalWonderAlreadyBuilt, + RejectionReason.CannotBeBuiltWith, + RejectionReason.ReachedBuildCap + ) + private val orderOfErrorMessages = listOf( + RejectionReason.WonderBeingBuiltElsewhere, + RejectionReason.NationalWonderBeingBuiltElsewhere, + RejectionReason.RequiresBuildingInAllCities, + RejectionReason.RequiresBuildingInThisCity, + RejectionReason.RequiresBuildingInSomeCity, + RejectionReason.PopulationRequirement, + RejectionReason.ConsumesResources, + RejectionReason.CanOnlyBePurchased + ) + } } diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 15ddbe7b96..5d68fd5ce3 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -293,11 +293,15 @@ class CivilizationInfo { temporaryUniques .asSequence() .filter { it.first.placeholderText == uniqueTemplate }.map { it.first } + - if (religionManager.religion != null) - religionManager.religion!!.getFounderUniques() - .asSequence() - .filter { it.placeholderText == uniqueTemplate } - else sequenceOf() + getEra().getMatchingUniques(uniqueTemplate) + .asSequence() + + ( + if (religionManager.religion != null) + religionManager.religion!!.getFounderUniques() + .asSequence() + .filter { it.placeholderText == uniqueTemplate } + else sequenceOf() + ) } //region Units diff --git a/core/src/com/unciv/logic/civilization/ReligionManager.kt b/core/src/com/unciv/logic/civilization/ReligionManager.kt index 960af42cf0..188c8a980f 100644 --- a/core/src/com/unciv/logic/civilization/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/ReligionManager.kt @@ -27,8 +27,11 @@ class ReligionManager { // But the other one should still be _somewhere_. So our only option is to have the GameInfo // contain the master list, and the ReligionManagers retrieve it from there every time the game loads. - var greatProphetsEarned = 0 - private set + // Deprecated since 3.16.13 + @Deprecated("Replace by adding to `civInfo.boughtConstructionsWithGloballyIncreasingPrice`") + var greatProphetsEarned = 0 + private set + // var religionState = ReligionState.None private set @@ -47,7 +50,6 @@ class ReligionManager { clone.shouldChoosePantheonBelief = shouldChoosePantheonBelief clone.storedFaith = storedFaith clone.religionState = religionState - clone.greatProphetsEarned = greatProphetsEarned return clone } @@ -62,6 +64,13 @@ class ReligionManager { religion = civInfo.gameInfo.religions.values.firstOrNull { it.foundingCivName == civInfo.civName } + + // greatProphetsEarned deprecated since 3.16.13, replacement code + if (greatProphetsEarned != 0) { + civInfo.boughtConstructionsWithGloballyIncreasingPrice[getGreatProphetEquivalent()!!] = greatProphetsEarned + greatProphetsEarned = 0 + } + // } fun startTurn() { @@ -106,6 +115,8 @@ class ReligionManager { // https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/ // Game files (globaldefines.xml) fun faithForNextGreatProphet(): Int { + val greatProphetsEarned = civInfo.boughtConstructionsWithGloballyIncreasingPrice[getGreatProphetEquivalent()!!] ?: 0 + var faithCost = (200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2f) * civInfo.gameInfo.gameParameters.gameSpeed.modifier @@ -120,22 +131,28 @@ class ReligionManager { if (religion == null || religionState == ReligionState.None) return false // First get a pantheon, then we'll talk about a real religion if (storedFaith < faithForNextGreatProphet()) return false if (!civInfo.isMajorCiv()) return false - // In the base game, great prophets shouldn't generate anymore starting from the industrial era - // This is difficult to implement in the current codebase, probably requires an additional variable in eras.json + if (civInfo.hasUnique("May not generate great prophet equivalents naturally")) return false return true } + + fun getGreatProphetEquivalent(): String? { + return civInfo.gameInfo.ruleSet.units.values.firstOrNull { it.hasUnique("May found a religion") }?.name + } private fun generateProphet() { + val prophetUnitName = getGreatProphetEquivalent() ?: return // No prophet units in this mod + val prophetSpawnChange = (5f + storedFaith - faithForNextGreatProphet()) / 100f if (Random(civInfo.gameInfo.turns).nextFloat() < prophetSpawnChange) { val birthCity = if (religionState <= ReligionState.Pantheon) civInfo.getCapital() else civInfo.cities.firstOrNull { it.religion.religionThisIsTheHolyCityOf == religion!!.name } - val prophet = civInfo.addUnit("Great Prophet", birthCity) ?: return + val prophet = civInfo.addUnit(prophetUnitName, birthCity) ?: return prophet.religion = religion!!.name storedFaith -= faithForNextGreatProphet() - greatProphetsEarned += 1 + civInfo.boughtConstructionsWithGloballyIncreasingPrice[prophetUnitName] = + (civInfo.boughtConstructionsWithGloballyIncreasingPrice[prophetUnitName] ?: 0) + 1 } } diff --git a/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt b/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt index c3f4d2064e..e9c8873803 100644 --- a/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt +++ b/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt @@ -42,7 +42,9 @@ class RuinsManager { for (possibleReward in possibleRewards) { if (civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties) continue if (Constants.hiddenWithoutReligionUnique in possibleReward.uniques && !civInfo.gameInfo.hasReligionEnabled()) continue - if ("Hidden after generating a Great Prophet" in possibleReward.uniques && civInfo.religionManager.greatProphetsEarned > 0) continue + if ("Hidden after generating a Great Prophet" in possibleReward.uniques + && civInfo.boughtConstructionsWithGloballyIncreasingPrice[civInfo.religionManager.getGreatProphetEquivalent()] ?: 0 > 0 + ) continue if (possibleReward.uniqueObjects.any { unique -> unique.placeholderText == "Only available after [] turns" && unique.params[0].toInt() < civInfo.gameInfo.turns diff --git a/core/src/com/unciv/models/ruleset/Era.kt b/core/src/com/unciv/models/ruleset/Era.kt index 84cf17150b..cc84f38655 100644 --- a/core/src/com/unciv/models/ruleset/Era.kt +++ b/core/src/com/unciv/models/ruleset/Era.kt @@ -5,7 +5,7 @@ import com.unciv.logic.civilization.CityStateType import com.unciv.models.stats.INamed import com.unciv.ui.utils.colorFromRGB -class Era : INamed { +class Era : INamed, IHasUniques { override var name: String = "" var eraNumber: Int = -1 var researchAgreementCost = 300 @@ -25,6 +25,8 @@ class Era : INamed { var friendBonus = HashMap>() var allyBonus = HashMap>() var iconRGB: List? = null + override var uniques: ArrayList = arrayListOf() + override val uniqueObjects: List by lazy { uniques.map { Unique(it) } } fun getStartingUnits(): List { val startingUnits = mutableListOf() diff --git a/core/src/com/unciv/models/ruleset/IHasUniques.kt b/core/src/com/unciv/models/ruleset/IHasUniques.kt index f2564cbb07..55e6b16ed7 100644 --- a/core/src/com/unciv/models/ruleset/IHasUniques.kt +++ b/core/src/com/unciv/models/ruleset/IHasUniques.kt @@ -6,4 +6,8 @@ package com.unciv.models.ruleset interface IHasUniques { var uniques: ArrayList // Can not be a hashset as that would remove doubles val uniqueObjects: List + + fun getMatchingUniques(uniqueTemplate: String) = uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate } + + fun hasUnique(uniqueTemplate: String) = uniqueObjects.any { it.placeholderText == uniqueTemplate } } diff --git a/core/src/com/unciv/models/ruleset/RuinReward.kt b/core/src/com/unciv/models/ruleset/RuinReward.kt index be1ab9bedc..0df9e9fcdb 100644 --- a/core/src/com/unciv/models/ruleset/RuinReward.kt +++ b/core/src/com/unciv/models/ruleset/RuinReward.kt @@ -10,6 +10,7 @@ class RuinReward : INamed, ICivilopediaText, IHasUniques { override var uniques = ArrayList() @delegate:Transient // Defense in depth against mad modders override val uniqueObjects: List by lazy { uniques.map { Unique(it) } } + val excludedDifficulties: List = listOf() val weight: Int = 1 val color: String = "" // For Civilopedia diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index a0fbc3baec..1e9069db22 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -3,6 +3,7 @@ package com.unciv.models.ruleset.tile import com.badlogic.gdx.graphics.Color import com.unciv.Constants import com.unciv.models.ruleset.Belief +import com.unciv.models.ruleset.IHasUniques import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Unique import com.unciv.models.stats.NamedStats @@ -10,7 +11,7 @@ import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.ICivilopediaText import com.unciv.ui.utils.colorFromRGB -class Terrain : NamedStats(), ICivilopediaText { +class Terrain : NamedStats(), ICivilopediaText, IHasUniques { lateinit var type: TerrainType @@ -26,8 +27,8 @@ class Terrain : NamedStats(), ICivilopediaText { val turnsInto: String? = null /** Uniques (Properties such as Temp/humidity, Fresh water, elevation, rough, defense, Natural Wonder specials) */ - val uniques = ArrayList() - val uniqueObjects: List by lazy { uniques.map { Unique(it) } } + override var uniques = ArrayList() + override val uniqueObjects: List by lazy { uniques.map { Unique(it) } } /** Natural Wonder weight: probability to be picked */ var weight = 10 diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index 38023fdac1..2437a4d456 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -70,7 +70,6 @@ class TileImprovement : NamedStats(), ICivilopediaText, IHasUniques { return lines.joinToString("\n") } - fun hasUnique(unique: String) = uniques.contains(unique) fun isGreatImprovement() = hasUnique("Great Improvement") fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name } fun isAncientRuinsEquivalent() = hasUnique("Provides a random bonus when entered") diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index ca6842889b..b97ff85468 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -225,6 +225,15 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { } ) return true + // May buy [unitFilter] units for [amount] [Stat] [cityFilter] at an increasing price ([amount]) + if (cityInfo != null && cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] at an increasing price ([])") + .any { + matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + && it.params[2] == stat.name + } + ) return true + return super.canBePurchasedWithStat(cityInfo, stat) } @@ -237,12 +246,12 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { return ( sequenceOf(super.getBaseBuyCost(cityInfo, stat)).filterNotNull() // May buy [unitFilter] units for [amount] [Stat] starting from the [eraName] at an increasing price ([amount]) - + cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] starting from the [] at an increasing price ([])") + + (cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] starting from the [] at an increasing price ([])") .filter { matchesFilter(it.params[0]) - && cityInfo.matchesFilter(it.params[3]) - && cityInfo.civInfo.getEraNumber() >= ruleset.eras[it.params[4]]!!.eraNumber - && it.params[2] == stat.name + && cityInfo.matchesFilter(it.params[3]) + && cityInfo.civInfo.getEraNumber() >= ruleset.eras[it.params[4]]!!.eraNumber + && it.params[2] == stat.name }.map { getCostForConstructionsIncreasingInPrice( it.params[1].toInt(), @@ -250,6 +259,20 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0 ) } + ) + + (cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] at an increasing price ([])") + .filter { + matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + && it.params[2] == stat.name + }.map { + getCostForConstructionsIncreasingInPrice( + it.params[1].toInt(), + it.params[4].toInt(), + cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0 + ) + } + ) ).minOrNull() } diff --git a/core/src/com/unciv/models/ruleset/unit/UnitType.kt b/core/src/com/unciv/models/ruleset/unit/UnitType.kt index 79a4a1ef6c..ca2541f5ba 100644 --- a/core/src/com/unciv/models/ruleset/unit/UnitType.kt +++ b/core/src/com/unciv/models/ruleset/unit/UnitType.kt @@ -1,5 +1,6 @@ package com.unciv.models.ruleset.unit +import com.unciv.models.ruleset.IHasUniques import com.unciv.models.ruleset.Unique import com.unciv.models.stats.INamed @@ -16,13 +17,13 @@ enum class UnitMovementType { // The types of tiles the unit can by default ente Air // Only city tiles and carrying units } -class UnitType() : INamed { +class UnitType() : INamed, IHasUniques { override lateinit var name: String private var movementType: String? = null private val unitMovementType: UnitMovementType? by lazy { if (movementType == null) null else UnitMovementType.valueOf(movementType!!) } - val uniques: ArrayList = ArrayList() - val uniqueObjects: List by lazy { uniques.map { Unique(it) } } + override var uniques: ArrayList = ArrayList() + override val uniqueObjects: List by lazy { uniques.map { Unique(it) } } constructor(name: String, domain: String? = null) : this() { this.name = name diff --git a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt index c97b4c0f63..91679c782f 100644 --- a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt +++ b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt @@ -2,6 +2,7 @@ package com.unciv.logic.map import com.unciv.Constants +import com.unciv.logic.GameInfo import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.diplomacy.DiplomacyManager @@ -37,6 +38,8 @@ class UnitMovementAlgorithmsTests { name = "My nation" cities = arrayListOf("The Capital") } + civInfo.gameInfo = GameInfo() + civInfo.gameInfo.ruleSet = ruleSet unit.civInfo = civInfo @@ -117,10 +120,13 @@ class UnitMovementAlgorithmsTests { unit.baseUnit = BaseUnit().apply { unitType = type.key; ruleset = ruleSet } unit.updateUniques() - Assert.assertTrue("$type cannot be in Ice", ( - type.value.uniques.contains("Can enter ice tiles")) - || type.value.uniques.contains("Can pass through impassable tiles" - ) == unit.movement.canPassThrough(tile)) + Assert.assertTrue( + "$type cannot be in Ice", + unit.movement.canPassThrough(tile) == ( + type.value.uniques.contains("Can enter ice tiles") + || type.value.uniques.contains("Can pass through impassable tiles") + ) + ) } }