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
This commit is contained in:
Xander Lenstra
2021-09-08 20:24:26 +02:00
committed by GitHub
parent 65695496f3
commit 3722fab38d
15 changed files with 174 additions and 84 deletions

View File

@ -137,7 +137,11 @@
"Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"],
"Militaristic": ["Provides military units every [17] turns"] "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", "name": "Modern era",
@ -168,7 +172,11 @@
"Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"],
"Militaristic": ["Provides military units every [17] turns"] "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", "name": "Atomic era",
@ -200,7 +208,11 @@
"Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"],
"Militaristic": ["Provides military units every [17] turns"] "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", "name": "Information era",
@ -236,7 +248,11 @@
"Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"],
"Militaristic": ["Provides military units every [17] turns"] "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. { // 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. // 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"], "Mercantile": ["Provides [3] Happiness", "Provides a unique luxury"],
"Militaristic": ["Provides military units every [17] turns"] "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"
]
} }
] ]

View File

@ -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 // reduced variant only for load preview

View File

@ -43,15 +43,15 @@ class CityInfo {
lateinit var tilesInRange: HashSet<TileInfo> lateinit var tilesInRange: HashSet<TileInfo>
@Transient @Transient
var hasJustBeenConquered = // This is so that military units can enter the city, even before we decide what to do with it
false // 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 location: Vector2 = Vector2.Zero
var id: String = UUID.randomUUID().toString() var id: String = UUID.randomUUID().toString()
var name: String = "" var name: String = ""
var foundingCiv = "" var foundingCiv = ""
var previousOwner = // This is so that cities in resistance that are recaptured aren't in resistance anymore
"" // This is so that cities in resistance that re recaptured aren't in resistance anymore var previousOwner = ""
var turnAcquired = 0 var turnAcquired = 0
var health = 200 var health = 200
var resistanceCounter = 0 var resistanceCounter = 0
@ -245,8 +245,10 @@ class CityInfo {
fun isCapital(): Boolean = cityConstructions.builtBuildings.contains(capitalCityIndicator()) fun isCapital(): Boolean = cityConstructions.builtBuildings.contains(capitalCityIndicator())
fun isCoastal(): Boolean = centerTileInfo.isCoastalTile() fun isCoastal(): Boolean = centerTileInfo.isCoastalTile()
fun capitalCityIndicator(): String { fun capitalCityIndicator(): String {
val indicatorBuildings = getRuleset().buildings.values.asSequence() val indicatorBuildings = getRuleset().buildings.values
.asSequence()
.filter { it.uniques.contains("Indicates the capital city") } .filter { it.uniques.contains("Indicates the capital city") }
val civSpecificBuilding = indicatorBuildings.firstOrNull { it.uniqueTo == civInfo.civName } val civSpecificBuilding = indicatorBuildings.firstOrNull { it.uniqueTo == civInfo.civName }
if (civSpecificBuilding != null) return civSpecificBuilding.name if (civSpecificBuilding != null) return civSpecificBuilding.name
else return indicatorBuildings.first().name else return indicatorBuildings.first().name
@ -294,8 +296,9 @@ class CityInfo {
val resource = getRuleset().tileResources[unique.params[1]] val resource = getRuleset().tileResources[unique.params[1]]
if (resource != null) { if (resource != null) {
cityResources.add( cityResources.add(
resource, unique.params[0].toInt() resource,
* civInfo.getResourceModifier(resource), "Tiles" unique.params[0].toInt() * civInfo.getResourceModifier(resource),
"Tiles"
) )
} }
} }
@ -596,11 +599,15 @@ class CityInfo {
*/ */
private fun triggerCitiesSettledNearOtherCiv() { private fun triggerCitiesSettledNearOtherCiv() {
val citiesWithin6Tiles = val citiesWithin6Tiles =
civInfo.gameInfo.civilizations.filter { it.isMajorCiv() && it != civInfo } civInfo.gameInfo.civilizations
.filter { it.isMajorCiv() && it != civInfo }
.flatMap { it.cities } .flatMap { it.cities }
.filter { it.getCenterTile().aerialDistanceTo(getCenterTile()) <= 6 } .filter { it.getCenterTile().aerialDistanceTo(getCenterTile()) <= 6 }
val civsWithCloseCities = citiesWithin6Tiles.map { it.civInfo }.distinct() val civsWithCloseCities =
.filter { it.knows(civInfo) && it.exploredTiles.contains(location) } citiesWithin6Tiles
.map { it.civInfo }
.distinct()
.filter { it.knows(civInfo) && it.exploredTiles.contains(location) }
for (otherCiv in civsWithCloseCities) for (otherCiv in civsWithCloseCities)
otherCiv.getDiplomacyManager(civInfo).setFlag(DiplomacyFlags.SettledCitiesNearUs, 30) 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 with a garrison" -> getCenterTile().militaryUnit != null
"in all cities in which the majority religion is a major religion" -> "in all cities in which the majority religion is a major religion" ->
religion.getMajorityReligionName() != null religion.getMajorityReligionName() != null
&& religion.getMajorityReligion()!!.isMajorReligion() && religion.getMajorityReligion()!!.isMajorReligion()
"in all cities in which the majority religion is an enhanced religion" -> "in all cities in which the majority religion is an enhanced religion" ->
religion.getMajorityReligionName() != null religion.getMajorityReligionName() != null
&& religion.getMajorityReligion()!!.isEnhancedReligion() && religion.getMajorityReligion()!!.isEnhancedReligion()
"in non-enemy foreign cities" -> "in non-enemy foreign cities" ->
viewingCiv != civInfo viewingCiv != civInfo
&& !civInfo.isAtWarWith(viewingCiv) && !civInfo.isAtWarWith(viewingCiv)
"in foreign cities" -> viewingCiv != civInfo "in foreign cities" -> viewingCiv != civInfo
"in annexed cities" -> foundingCiv != civInfo.civName && !isPuppet "in annexed cities" -> foundingCiv != civInfo.civName && !isPuppet
"in holy cities" -> religion.religionThisIsTheHolyCityOf != null "in holy cities" -> religion.religionThisIsTheHolyCityOf != null
@ -715,9 +722,11 @@ class CityInfo {
if (!tilesList.contains(tile)) if (!tilesList.contains(tile))
cityPositionList.add(tile) cityPositionList.add(tile)
return cityPositionList.asSequence() return cityPositionList
.map { it.getOwner()?.civName }.filterNotNull() .asSequence()
.distinct().toList() .mapNotNull { it.getOwner()?.civName }
.distinct()
.toList()
} }
fun getImprovableTiles(): Sequence<TileInfo> = getTiles() fun getImprovableTiles(): Sequence<TileInfo> = getTiles()

View File

@ -24,13 +24,6 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons
fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious. fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious.
fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> {
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 { fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean {
if (stat in listOf(Stat.Production, Stat.Happiness)) return false if (stat in listOf(Stat.Production, Stat.Happiness)) return false
if ("Cannot be purchased" in uniques) return false if ("Cannot be purchased" in uniques) return false
@ -82,41 +75,46 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
class RejectionReasons(): HashSet<RejectionReason>() { class RejectionReasons(): HashSet<RejectionReason>() {
private val techPolicyEraWonderRequirements = hashSetOf(
RejectionReason.Obsoleted,
RejectionReason.RequiresTech,
RejectionReason.RequiresPolicy,
RejectionReason.MorePolicyBranches,
RejectionReason.RequiresBuildingInSomeCity
)
fun filterTechPolicyEraWonderRequirements(): HashSet<RejectionReason> { fun filterTechPolicyEraWonderRequirements(): HashSet<RejectionReason> {
return filterNot { it in techPolicyEraWonderRequirements }.toHashSet() return filterNot { it in techPolicyEraWonderRequirements }.toHashSet()
} }
private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf(
RejectionReason.Obsoleted,
RejectionReason.WonderAlreadyBuilt,
RejectionReason.NationalWonderAlreadyBuilt,
RejectionReason.CannotBeBuiltWith,
RejectionReason.ReachedBuildCap
)
fun hasAReasonToBeRemovedFromQueue(): Boolean { fun hasAReasonToBeRemovedFromQueue(): Boolean {
return any { it in reasonsToDefinitivelyRemoveFromQueue } 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? { fun getMostImportantRejectionReason(): String? {
return orderOfErrorMessages.firstOrNull { it in this }?.errorMessage 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
)
}
} }

View File

@ -293,11 +293,15 @@ class CivilizationInfo {
temporaryUniques temporaryUniques
.asSequence() .asSequence()
.filter { it.first.placeholderText == uniqueTemplate }.map { it.first } + .filter { it.first.placeholderText == uniqueTemplate }.map { it.first } +
if (religionManager.religion != null) getEra().getMatchingUniques(uniqueTemplate)
religionManager.religion!!.getFounderUniques() .asSequence() +
.asSequence() (
.filter { it.placeholderText == uniqueTemplate } if (religionManager.religion != null)
else sequenceOf() religionManager.religion!!.getFounderUniques()
.asSequence()
.filter { it.placeholderText == uniqueTemplate }
else sequenceOf()
)
} }
//region Units //region Units

View File

@ -27,8 +27,11 @@ class ReligionManager {
// But the other one should still be _somewhere_. So our only option is to have the GameInfo // 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. // contain the master list, and the ReligionManagers retrieve it from there every time the game loads.
var greatProphetsEarned = 0 // Deprecated since 3.16.13
private set @Deprecated("Replace by adding to `civInfo.boughtConstructionsWithGloballyIncreasingPrice`")
var greatProphetsEarned = 0
private set
//
var religionState = ReligionState.None var religionState = ReligionState.None
private set private set
@ -47,7 +50,6 @@ class ReligionManager {
clone.shouldChoosePantheonBelief = shouldChoosePantheonBelief clone.shouldChoosePantheonBelief = shouldChoosePantheonBelief
clone.storedFaith = storedFaith clone.storedFaith = storedFaith
clone.religionState = religionState clone.religionState = religionState
clone.greatProphetsEarned = greatProphetsEarned
return clone return clone
} }
@ -62,6 +64,13 @@ class ReligionManager {
religion = civInfo.gameInfo.religions.values.firstOrNull { religion = civInfo.gameInfo.religions.values.firstOrNull {
it.foundingCivName == civInfo.civName it.foundingCivName == civInfo.civName
} }
// greatProphetsEarned deprecated since 3.16.13, replacement code
if (greatProphetsEarned != 0) {
civInfo.boughtConstructionsWithGloballyIncreasingPrice[getGreatProphetEquivalent()!!] = greatProphetsEarned
greatProphetsEarned = 0
}
//
} }
fun startTurn() { fun startTurn() {
@ -106,6 +115,8 @@ class ReligionManager {
// https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/ // https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/
// Game files (globaldefines.xml) // Game files (globaldefines.xml)
fun faithForNextGreatProphet(): Int { fun faithForNextGreatProphet(): Int {
val greatProphetsEarned = civInfo.boughtConstructionsWithGloballyIncreasingPrice[getGreatProphetEquivalent()!!] ?: 0
var faithCost = var faithCost =
(200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2f) * (200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2f) *
civInfo.gameInfo.gameParameters.gameSpeed.modifier 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 (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 (storedFaith < faithForNextGreatProphet()) return false
if (!civInfo.isMajorCiv()) return false if (!civInfo.isMajorCiv()) return false
// In the base game, great prophets shouldn't generate anymore starting from the industrial era if (civInfo.hasUnique("May not generate great prophet equivalents naturally")) return false
// This is difficult to implement in the current codebase, probably requires an additional variable in eras.json
return true return true
} }
fun getGreatProphetEquivalent(): String? {
return civInfo.gameInfo.ruleSet.units.values.firstOrNull { it.hasUnique("May found a religion") }?.name
}
private fun generateProphet() { private fun generateProphet() {
val prophetUnitName = getGreatProphetEquivalent() ?: return // No prophet units in this mod
val prophetSpawnChange = (5f + storedFaith - faithForNextGreatProphet()) / 100f val prophetSpawnChange = (5f + storedFaith - faithForNextGreatProphet()) / 100f
if (Random(civInfo.gameInfo.turns).nextFloat() < prophetSpawnChange) { if (Random(civInfo.gameInfo.turns).nextFloat() < prophetSpawnChange) {
val birthCity = val birthCity =
if (religionState <= ReligionState.Pantheon) civInfo.getCapital() if (religionState <= ReligionState.Pantheon) civInfo.getCapital()
else civInfo.cities.firstOrNull { it.religion.religionThisIsTheHolyCityOf == religion!!.name } 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 prophet.religion = religion!!.name
storedFaith -= faithForNextGreatProphet() storedFaith -= faithForNextGreatProphet()
greatProphetsEarned += 1 civInfo.boughtConstructionsWithGloballyIncreasingPrice[prophetUnitName] =
(civInfo.boughtConstructionsWithGloballyIncreasingPrice[prophetUnitName] ?: 0) + 1
} }
} }

View File

@ -42,7 +42,9 @@ class RuinsManager {
for (possibleReward in possibleRewards) { for (possibleReward in possibleRewards) {
if (civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties) continue if (civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties) continue
if (Constants.hiddenWithoutReligionUnique in possibleReward.uniques && !civInfo.gameInfo.hasReligionEnabled()) 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 -> if (possibleReward.uniqueObjects.any { unique ->
unique.placeholderText == "Only available after [] turns" unique.placeholderText == "Only available after [] turns"
&& unique.params[0].toInt() < civInfo.gameInfo.turns && unique.params[0].toInt() < civInfo.gameInfo.turns

View File

@ -5,7 +5,7 @@ import com.unciv.logic.civilization.CityStateType
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.colorFromRGB
class Era : INamed { class Era : INamed, IHasUniques {
override var name: String = "" override var name: String = ""
var eraNumber: Int = -1 var eraNumber: Int = -1
var researchAgreementCost = 300 var researchAgreementCost = 300
@ -25,6 +25,8 @@ class Era : INamed {
var friendBonus = HashMap<String, List<String>>() var friendBonus = HashMap<String, List<String>>()
var allyBonus = HashMap<String, List<String>>() var allyBonus = HashMap<String, List<String>>()
var iconRGB: List<Int>? = null var iconRGB: List<Int>? = null
override var uniques: ArrayList<String> = arrayListOf()
override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
fun getStartingUnits(): List<String> { fun getStartingUnits(): List<String> {
val startingUnits = mutableListOf<String>() val startingUnits = mutableListOf<String>()

View File

@ -6,4 +6,8 @@ package com.unciv.models.ruleset
interface IHasUniques { interface IHasUniques {
var uniques: ArrayList<String> // Can not be a hashset as that would remove doubles var uniques: ArrayList<String> // Can not be a hashset as that would remove doubles
val uniqueObjects: List<Unique> val uniqueObjects: List<Unique>
fun getMatchingUniques(uniqueTemplate: String) = uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate }
fun hasUnique(uniqueTemplate: String) = uniqueObjects.any { it.placeholderText == uniqueTemplate }
} }

View File

@ -10,6 +10,7 @@ class RuinReward : INamed, ICivilopediaText, IHasUniques {
override var uniques = ArrayList<String>() override var uniques = ArrayList<String>()
@delegate:Transient // Defense in depth against mad modders @delegate:Transient // Defense in depth against mad modders
override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } } override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
val excludedDifficulties: List<String> = listOf() val excludedDifficulties: List<String> = listOf()
val weight: Int = 1 val weight: Int = 1
val color: String = "" // For Civilopedia val color: String = "" // For Civilopedia

View File

@ -3,6 +3,7 @@ package com.unciv.models.ruleset.tile
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.IHasUniques
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.NamedStats 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.civilopedia.ICivilopediaText
import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.colorFromRGB
class Terrain : NamedStats(), ICivilopediaText { class Terrain : NamedStats(), ICivilopediaText, IHasUniques {
lateinit var type: TerrainType lateinit var type: TerrainType
@ -26,8 +27,8 @@ class Terrain : NamedStats(), ICivilopediaText {
val turnsInto: String? = null val turnsInto: String? = null
/** Uniques (Properties such as Temp/humidity, Fresh water, elevation, rough, defense, Natural Wonder specials) */ /** Uniques (Properties such as Temp/humidity, Fresh water, elevation, rough, defense, Natural Wonder specials) */
val uniques = ArrayList<String>() override var uniques = ArrayList<String>()
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } } override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
/** Natural Wonder weight: probability to be picked */ /** Natural Wonder weight: probability to be picked */
var weight = 10 var weight = 10

View File

@ -70,7 +70,6 @@ class TileImprovement : NamedStats(), ICivilopediaText, IHasUniques {
return lines.joinToString("\n") return lines.joinToString("\n")
} }
fun hasUnique(unique: String) = uniques.contains(unique)
fun isGreatImprovement() = hasUnique("Great Improvement") fun isGreatImprovement() = hasUnique("Great Improvement")
fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name } fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name }
fun isAncientRuinsEquivalent() = hasUnique("Provides a random bonus when entered") fun isAncientRuinsEquivalent() = hasUnique("Provides a random bonus when entered")

View File

@ -225,6 +225,15 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
} }
) return true ) 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) return super.canBePurchasedWithStat(cityInfo, stat)
} }
@ -237,12 +246,12 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
return ( return (
sequenceOf(super.getBaseBuyCost(cityInfo, stat)).filterNotNull() sequenceOf(super.getBaseBuyCost(cityInfo, stat)).filterNotNull()
// May buy [unitFilter] units for [amount] [Stat] starting from the [eraName] at an increasing price ([amount]) // 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 { .filter {
matchesFilter(it.params[0]) matchesFilter(it.params[0])
&& cityInfo.matchesFilter(it.params[3]) && cityInfo.matchesFilter(it.params[3])
&& cityInfo.civInfo.getEraNumber() >= ruleset.eras[it.params[4]]!!.eraNumber && cityInfo.civInfo.getEraNumber() >= ruleset.eras[it.params[4]]!!.eraNumber
&& it.params[2] == stat.name && it.params[2] == stat.name
}.map { }.map {
getCostForConstructionsIncreasingInPrice( getCostForConstructionsIncreasingInPrice(
it.params[1].toInt(), it.params[1].toInt(),
@ -250,6 +259,20 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0 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() ).minOrNull()
} }

View File

@ -1,5 +1,6 @@
package com.unciv.models.ruleset.unit package com.unciv.models.ruleset.unit
import com.unciv.models.ruleset.IHasUniques
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.INamed 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 Air // Only city tiles and carrying units
} }
class UnitType() : INamed { class UnitType() : INamed, IHasUniques {
override lateinit var name: String override lateinit var name: String
private var movementType: String? = null private var movementType: String? = null
private val unitMovementType: UnitMovementType? by lazy { if (movementType == null) null else UnitMovementType.valueOf(movementType!!) } private val unitMovementType: UnitMovementType? by lazy { if (movementType == null) null else UnitMovementType.valueOf(movementType!!) }
val uniques: ArrayList<String> = ArrayList() override var uniques: ArrayList<String> = ArrayList()
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } } override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
constructor(name: String, domain: String? = null) : this() { constructor(name: String, domain: String? = null) : this() {
this.name = name this.name = name

View File

@ -2,6 +2,7 @@
package com.unciv.logic.map package com.unciv.logic.map
import com.unciv.Constants import com.unciv.Constants
import com.unciv.logic.GameInfo
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.diplomacy.DiplomacyManager import com.unciv.logic.civilization.diplomacy.DiplomacyManager
@ -37,6 +38,8 @@ class UnitMovementAlgorithmsTests {
name = "My nation" name = "My nation"
cities = arrayListOf("The Capital") cities = arrayListOf("The Capital")
} }
civInfo.gameInfo = GameInfo()
civInfo.gameInfo.ruleSet = ruleSet
unit.civInfo = civInfo unit.civInfo = civInfo
@ -117,10 +120,13 @@ class UnitMovementAlgorithmsTests {
unit.baseUnit = BaseUnit().apply { unitType = type.key; ruleset = ruleSet } unit.baseUnit = BaseUnit().apply { unitType = type.key; ruleset = ruleSet }
unit.updateUniques() unit.updateUniques()
Assert.assertTrue("$type cannot be in Ice", ( Assert.assertTrue(
type.value.uniques.contains("Can enter ice tiles")) "$type cannot be in Ice",
|| type.value.uniques.contains("Can pass through impassable tiles" unit.movement.canPassThrough(tile) == (
) == unit.movement.canPassThrough(tile)) type.value.uniques.contains("Can enter ice tiles")
|| type.value.uniques.contains("Can pass through impassable tiles")
)
)
} }
} }