Allow for replacement improvements (#11369)

* Allow for replacement improvements

* imports

* Forgot the most important change, lol

* Docs

* Replacement description, validation, and filter

* Move more into ImprovementDescriptions

* Whoops, forgot to yield

* Fix some copy-paste artifacts

* New translations

* Fix double see also

* Add space for translation engine
This commit is contained in:
SeventhM 2024-04-04 13:39:44 -07:00 committed by GitHub
parent 0caf0cb4fa
commit 3ea1e4a539
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 264 additions and 139 deletions

View File

@ -1538,6 +1538,7 @@ Unique to [civName], replaces [unitName] =
Unique to [civName] =
Tutorials =
Cost =
Turns to build =
May contain [listOfResources] =
May contain: =
Can upgrade from [unit] =
@ -1557,6 +1558,7 @@ Improvements that provide this resource =
Buildings that require this resource worked near the city =
Units that consume this resource =
Can be built on =
Cannot be built on =
or [terrainType] =
Can be constructed by =
Can be created instantly by =

View File

@ -275,7 +275,7 @@ object Automation {
): Boolean {
if (construction !is Building) return true
if (!construction.hasCreateOneImprovementUnique()) return true // redundant but faster???
val improvement = construction.getImprovementToCreate(city.getRuleset()) ?: return true
val improvement = construction.getImprovementToCreate(city.getRuleset(), civInfo) ?: return true
return city.getTiles().any {
it.improvementFunctions.canBuildImprovement(improvement, civInfo)
}

View File

@ -655,7 +655,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
tile: Tile? = null
): Boolean {
// Support UniqueType.CreatesOneImprovement: it is active when getImprovementToCreate returns an improvement
val improvementToPlace = (construction as? Building)?.getImprovementToCreate(city.getRuleset())
val improvementToPlace = (construction as? Building)?.getImprovementToCreate(city.getRuleset(), city.civ)
if (improvementToPlace != null) {
// If active without a predetermined tile to place the improvement on, automate a tile
val finalTile = tile
@ -716,7 +716,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
/** Support for [UniqueType.CreatesOneImprovement] - if an Improvement-creating Building was auto-queued, auto-choose a tile: */
val building = getCurrentConstruction() as? Building ?: return
val improvement = building.getImprovementToCreate(city.getRuleset()) ?: return
val improvement = building.getImprovementToCreate(city.getRuleset(), city.civ) ?: return
if (getTileForImprovement(improvement.name) != null) return
val newTile = Automation.getTileForConstructionImprovement(city, improvement) ?: return
newTile.improvementFunctions.markForCreatesOneImprovement(improvement.name)
@ -778,7 +778,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
// UniqueType.CreatesOneImprovement support
val construction = getConstruction(constructionName)
if (construction is Building) {
val improvement = construction.getImprovementToCreate(city.getRuleset())
val improvement = construction.getImprovementToCreate(city.getRuleset(), city.civ)
if (improvement != null) {
getTileForImprovement(improvement.name)?.stopWorkingOnImprovement()
}
@ -846,7 +846,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
* (skip if none found), then un-mark the tile and place the improvement unless [removeOnly] is set.
*/
private fun applyCreateOneImprovement(building: Building, removeOnly: Boolean = false) {
val improvement = building.getImprovementToCreate(city.getRuleset())
val improvement = building.getImprovementToCreate(city.getRuleset(), city.civ)
?: return
val tileForImprovement = getTileForImprovement(improvement.name) ?: return
tileForImprovement.stopWorkingOnImprovement() // clears mark
@ -866,7 +866,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
val ruleset = city.getRuleset()
val indexToRemove = constructionQueue.withIndex().firstNotNullOfOrNull {
val construction = getConstruction(it.value)
val buildingImprovement = (construction as? Building)?.getImprovementToCreate(ruleset)?.name
val buildingImprovement = (construction as? Building)?.getImprovementToCreate(ruleset, city.civ)?.name
it.index.takeIf { buildingImprovement == improvement }
} ?: return

View File

@ -45,6 +45,7 @@ import com.unciv.models.ruleset.nation.Personality
import com.unciv.models.ruleset.tech.Era
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.TemporaryUnique
@ -544,6 +545,22 @@ class Civilization : IsPartOfGameInfoSerialization {
return baseBuilding
}
fun getEquivalentTileImprovement(tileImprovementName: String): TileImprovement {
val tileImprovement = gameInfo.ruleset.tileImprovements[tileImprovementName]
?: throw UncivShowableException("Improvement $tileImprovementName doesn't seem to exist!")
return getEquivalentTileImprovement(tileImprovement)
}
fun getEquivalentTileImprovement(tileImprovement: TileImprovement): TileImprovement {
if (tileImprovement.replaces != null)
return getEquivalentTileImprovement(tileImprovement.replaces!!)
for (improvement in cache.uniqueImprovements)
if (improvement.replaces == tileImprovement.name)
return improvement
return tileImprovement
}
fun getEquivalentUnit(baseUnitName: String): BaseUnit {
val baseUnit = gameInfo.ruleset.units[baseUnitName]
?: throw UncivShowableException("Unit $baseUnitName doesn't seem to exist!")

View File

@ -14,6 +14,7 @@ import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
@ -35,6 +36,9 @@ class CivInfoTransientCache(val civInfo: Civilization) {
@Transient
val uniqueUnits = hashSetOf<BaseUnit>()
@Transient
val uniqueImprovements = hashSetOf<TileImprovement>()
@Transient
val uniqueBuildings = hashSetOf<Building>()
@ -64,6 +68,10 @@ class CivInfoTransientCache(val civInfo: Civilization) {
}
}
for (improvement in ruleset.tileImprovements.values)
if (improvement.uniqueTo == civInfo.civName)
uniqueImprovements.add(improvement)
for (unit in ruleset.units.values) {
if (unit.uniqueTo == civInfo.civName) {
uniqueUnits.add(unit)

View File

@ -20,6 +20,7 @@ enum class ImprovementBuildingProblem(
/** `true` if the ImprovementPicker should report this problem */
val reportable: Boolean = false
) {
Replaced(permanent = true),
WrongCiv(permanent = true),
MissingTech(reportable = true),
Unbuildable(permanent = true),
@ -45,6 +46,8 @@ class TileInfoImprovementFunctions(val tile: Tile) {
if (improvement.uniqueTo != null && improvement.uniqueTo != civInfo.civName)
yield(ImprovementBuildingProblem.WrongCiv)
if (civInfo.cache.uniqueImprovements.any { it.replaces == improvement.name })
yield(ImprovementBuildingProblem.Replaced)
if (improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!))
yield(ImprovementBuildingProblem.MissingTech)
if (improvement.getMatchingUniques(UniqueType.Unbuildable, StateForConditionals.IgnoreConditionals)

View File

@ -551,6 +551,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
}
return _getImprovementToCreate
}
fun getImprovementToCreate(ruleset: Ruleset, civInfo: Civilization): TileImprovement? {
val improvement = getImprovementToCreate(ruleset) ?: return null
return civInfo.getEquivalentTileImprovement(improvement)
}
fun isSellable() = !isAnyWonder() && !hasUnique(UniqueType.Unsellable)

View File

@ -13,6 +13,7 @@ import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.colorFromRGB
import com.unciv.ui.objectdescriptions.BaseUnitDescriptions
import com.unciv.ui.objectdescriptions.BuildingDescriptions
import com.unciv.ui.objectdescriptions.ImprovementDescriptions
import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia
import com.unciv.ui.screens.civilopediascreen.FormattedLine
@ -246,17 +247,15 @@ class Nation : RulesetObject() {
yield(FormattedLine(separator = true))
yield(FormattedLine(improvement.name, link = "Improvement/${improvement.name}"))
yield(FormattedLine(improvement.cloneStats().toString(), indent = 1)) // = (improvement as Stats).toString minus import plus copy overhead
if (improvement.terrainsCanBeBuiltOn.isNotEmpty()) {
improvement.terrainsCanBeBuiltOn.withIndex().forEach {
yield(
FormattedLine(if (it.index == 0) "{Can be built on} {${it.value}}" else "or [${it.value}]",
link = "Terrain/${it.value}", indent = if (it.index == 0) 1 else 2)
)
}
}
for (unique in improvement.uniqueObjects) {
if (unique.isHiddenToUsers()) continue
yield(FormattedLine(unique, indent = 1))
if (improvement.replaces != null && ruleset.tileImprovements.containsKey(improvement.replaces!!)) {
val originalImprovement = ruleset.tileImprovements[improvement.replaces!!]!!
yield(FormattedLine("Replaces [${originalImprovement.name}]", link = originalImprovement.makeLink(), indent=1))
yieldAll(ImprovementDescriptions.getDifferences(ruleset, originalImprovement, improvement))
yield(FormattedLine())
} else if (improvement.replaces != null) {
yield(FormattedLine("Replaces [${improvement.replaces}], which is not found in the ruleset!", indent=1))
} else {
yieldAll(improvement.getShortDecription())
}
}
}

View File

@ -1,28 +1,24 @@
package com.unciv.models.ruleset.tile
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.MultiFilter
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.RoadStatus
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetStatsObject
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.toPercent
import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines
import com.unciv.ui.objectdescriptions.uniquesToDescription
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia
import com.unciv.ui.objectdescriptions.ImprovementDescriptions
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import kotlin.math.roundToInt
class TileImprovement : RulesetStatsObject() {
var replaces: String? = null
var terrainsCanBeBuiltOn: Collection<String> = ArrayList()
var techRequired: String? = null
var uniqueTo: String? = null
@ -44,29 +40,8 @@ class TileImprovement : RulesetStatsObject() {
// In some weird cases it was possible for something to take 0 turns, leading to it instead never finishing
}
fun getDescription(ruleset: Ruleset): String {
val lines = ArrayList<String>()
val statsDesc = cloneStats().toString()
if (statsDesc.isNotEmpty()) lines += statsDesc
if (!terrainsCanBeBuiltOn.isEmpty()) {
val terrainsCanBeBuiltOnString: ArrayList<String> = arrayListOf()
for (i in terrainsCanBeBuiltOn) {
terrainsCanBeBuiltOnString.add(i.tr())
}
lines += "Can be built on".tr() + terrainsCanBeBuiltOnString.joinToString(", ", " ") //language can be changed when setting changes.
}
for (resource: TileResource in ruleset.tileResources.values.filter { it.isImprovedBy(name) }) {
if (resource.improvementStats == null) continue
val statsString = resource.improvementStats.toString()
lines += "[${statsString}] <in [${resource.name}] tiles>".tr()
}
if (techRequired != null) lines += "Required tech: [$techRequired]".tr()
uniquesToDescription(lines)
return lines.joinToString("\n")
}
fun getDescription(ruleset: Ruleset): String = ImprovementDescriptions.getDescription(this, ruleset)
fun getShortDecription() = ImprovementDescriptions.getShortDescription(this)
fun isGreatImprovement() = hasUnique(UniqueType.GreatImprovement)
fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name }
@ -100,6 +75,7 @@ class TileImprovement : RulesetStatsObject() {
private fun matchesSingleFilter(filter: String): Boolean {
return when (filter) {
name -> true
replaces -> true
in Constants.all -> true
"Improvement" -> true // For situations involving tileFilter
"All Road" -> isRoad()
@ -111,95 +87,10 @@ class TileImprovement : RulesetStatsObject() {
override fun makeLink() = "Improvement/$name"
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val textList = ArrayList<FormattedLine>()
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> =
ImprovementDescriptions.getCivilopediaTextLines(this, ruleset)
val statsDesc = cloneStats().toString()
if (statsDesc.isNotEmpty()) textList += FormattedLine(statsDesc)
if (uniqueTo != null) {
textList += FormattedLine()
textList += FormattedLine("Unique to [$uniqueTo]", link="Nation/$uniqueTo")
}
val constructorUnits = getConstructorUnits(ruleset)
val creatingUnits = getCreatingUnits(ruleset)
val creatorExists = constructorUnits.isNotEmpty() || creatingUnits.isNotEmpty()
if (creatorExists && terrainsCanBeBuiltOn.isNotEmpty()) {
textList += FormattedLine()
if (terrainsCanBeBuiltOn.size == 1) {
with (terrainsCanBeBuiltOn.first()) {
textList += FormattedLine("{Can be built on} {$this}", link="Terrain/$this")
}
} else {
textList += FormattedLine("{Can be built on}:")
terrainsCanBeBuiltOn.forEach {
textList += FormattedLine(it, link="Terrain/$it", indent=1)
}
}
}
var addedLineBeforeResourceBonus = false
for (resource in ruleset.tileResources.values) {
if (resource.improvementStats == null || !resource.isImprovedBy(name)) continue
if (!addedLineBeforeResourceBonus) {
addedLineBeforeResourceBonus = true
textList += FormattedLine()
}
val statsString = resource.improvementStats.toString()
// Line intentionally modeled as UniqueType.Stats + ConditionalInTiles
textList += FormattedLine("[${statsString}] <in [${resource.name}] tiles>", link = resource.makeLink())
}
if (techRequired != null) {
textList += FormattedLine()
textList += FormattedLine("Required tech: [$techRequired]", link="Technology/$techRequired")
}
uniquesToCivilopediaTextLines(textList)
// Be clearer when one needs to chop down a Forest first... A "Can be built on Plains" is clear enough,
// but a "Can be built on Land" is not - how is the user to know Forest is _not_ Land?
if (creatorExists &&
!isEmpty() && // Has any Stats
!hasUnique(UniqueType.NoFeatureRemovalNeeded) &&
!hasUnique(UniqueType.RemovesFeaturesIfBuilt) &&
terrainsCanBeBuiltOn.none { it in ruleset.terrains }
)
textList += FormattedLine("Needs removal of terrain features to be built")
if (isAncientRuinsEquivalent() && ruleset.ruinRewards.isNotEmpty()) {
val difficulty = if (!UncivGame.isCurrentInitialized() || UncivGame.Current.gameInfo == null)
"Prince" // most factors == 1
else UncivGame.Current.gameInfo!!.gameParameters.difficulty
val religionEnabled = showReligionInCivilopedia(ruleset)
textList += FormattedLine()
textList += FormattedLine("The possible rewards are:")
ruleset.ruinRewards.values.asSequence()
.filter { reward ->
difficulty !in reward.excludedDifficulties &&
(religionEnabled || !reward.hasUnique(UniqueType.HiddenWithoutReligion))
}
.forEach { reward ->
textList += FormattedLine(reward.name, starred = true, color = reward.color)
textList += reward.civilopediaText
}
}
if (creatorExists)
textList += FormattedLine()
for (unit in constructorUnits)
textList += FormattedLine("{Can be constructed by} {$unit}", unit.makeLink())
for (unit in creatingUnits)
textList += FormattedLine("{Can be created instantly by} {$unit}", unit.makeLink())
textList += Belief.getCivilopediaTextMatching(name, ruleset)
return textList
}
private fun getConstructorUnits(ruleset: Ruleset): List<BaseUnit> {
fun getConstructorUnits(ruleset: Ruleset): List<BaseUnit> {
//todo Why does this have to be so complicated? A unit's "Can build [Land] improvements on tiles"
// creates the _justified_ expectation that an improvement it can build _will_ have
// `matchesFilter("Land")==true` - but that's not the case.
@ -251,7 +142,7 @@ class TileImprovement : RulesetStatsObject() {
}.toList()
}
private fun getCreatingUnits(ruleset: Ruleset): List<BaseUnit> {
fun getCreatingUnits(ruleset: Ruleset): List<BaseUnit> {
return ruleset.units.values.asSequence()
.filter { unit ->
unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly, StateForConditionals.IgnoreConditionals)

View File

@ -401,13 +401,15 @@ class RulesetValidator(val ruleset: Ruleset) {
for (improvement in ruleset.tileImprovements.values) {
if (improvement.techRequired != null && !ruleset.technologies.containsKey(improvement.techRequired!!))
lines.add("${improvement.name} requires tech ${improvement.techRequired} which does not exist!", sourceObject = improvement)
if (improvement.replaces != null && !ruleset.tileImprovements.containsKey(improvement.replaces))
lines.add("${improvement.name} replaces ${improvement.replaces} which does not exist!", sourceObject = improvement)
for (terrain in improvement.terrainsCanBeBuiltOn)
if (!ruleset.terrains.containsKey(terrain) && terrain != "Land" && terrain != "Water")
lines.add("${improvement.name} can be built on terrain $terrain which does not exist!", sourceObject = improvement)
if (improvement.terrainsCanBeBuiltOn.isEmpty()
&& !improvement.hasUnique(UniqueType.CanOnlyImproveResource)
&& !improvement.hasUnique(UniqueType.Unbuildable)
&& !improvement.name.startsWith(Constants.remove)
&& improvement !in ruleset.tileRemovals
&& improvement.name !in RoadStatus.values().map { it.removeAction }
&& improvement.name != Constants.cancelImprovementOrder
) {

View File

@ -0,0 +1,197 @@
package com.unciv.ui.objectdescriptions
import com.unciv.UncivGame
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
import com.unciv.ui.screens.civilopediascreen.FormattedLine
object ImprovementDescriptions {
/**
* Lists differences: how a nation-unique Improvement compares to its replacement.
*
* Result as indented, non-linking [FormattedLine]s
*
* @param originalImprovement The "standard" Improvement
* @param replacementImprovement The "uniqueTo" Improvement
*/
fun getDifferences(
ruleset: Ruleset, originalImprovement: TileImprovement, replacementImprovement: TileImprovement
): Sequence<FormattedLine> = sequence {
for ((key, value) in replacementImprovement)
if (value != originalImprovement[key])
yield(FormattedLine( key.name.tr() + " " +"[${value.toInt()}] vs [${originalImprovement[key].toInt()}]".tr(), indent=1))
for (terrain in replacementImprovement.terrainsCanBeBuiltOn)
if (terrain !in originalImprovement.terrainsCanBeBuiltOn)
yield(FormattedLine("Can be built on [${terrain}]", link = ruleset.terrains[terrain]?.makeLink() ?: "", indent = 1))
for (terrain in originalImprovement.terrainsCanBeBuiltOn)
if (terrain !in replacementImprovement.terrainsCanBeBuiltOn)
yield(FormattedLine("Cannot be built on [${terrain}]", link = ruleset.terrains[terrain]?.makeLink() ?: "", indent = 1))
if (replacementImprovement.turnsToBuild != originalImprovement.turnsToBuild)
yield(FormattedLine("{Turns to build} ".tr() + "[${replacementImprovement.turnsToBuild}] vs [${originalImprovement.turnsToBuild}]".tr(), indent=1))
val newAbilityPredicate: (Unique)->Boolean = { it.text in originalImprovement.uniques || it.isHiddenToUsers() }
for (unique in replacementImprovement.uniqueObjects.filterNot(newAbilityPredicate))
yield(FormattedLine(unique.text, indent=1)) // FormattedLine(unique) would look worse - no indent and autolinking could distract
val lostAbilityPredicate: (Unique)->Boolean = { it.text in replacementImprovement.uniques || it.isHiddenToUsers() }
for (unique in originalImprovement.uniqueObjects.filterNot(lostAbilityPredicate)) {
// Need double translation of the "ability" here - unique texts may contain square brackets
yield(FormattedLine("Lost ability (vs [${originalImprovement.name}]): [${unique.text.tr()}]", indent=1))
}
}
fun getCivilopediaTextLines(improvement: TileImprovement, ruleset: Ruleset): List<FormattedLine> {
val textList = ArrayList<FormattedLine>()
val statsDesc = improvement.cloneStats().toString()
if (statsDesc.isNotEmpty()) textList += FormattedLine(statsDesc)
if (improvement.uniqueTo != null) {
textList += FormattedLine()
textList += FormattedLine("Unique to [${improvement.uniqueTo}]", link="Nation/${improvement.uniqueTo}")
}
if (improvement.replaces != null) {
val replaceImprovement = ruleset.tileImprovements[improvement.replaces]
textList += FormattedLine("Replaces [${improvement.replaces}]", link=replaceImprovement?.makeLink() ?: "", indent = 1)
}
val constructorUnits = improvement.getConstructorUnits(ruleset)
val creatingUnits = improvement.getCreatingUnits(ruleset)
val creatorExists = constructorUnits.isNotEmpty() || creatingUnits.isNotEmpty()
if (creatorExists && improvement.terrainsCanBeBuiltOn.isNotEmpty()) {
textList += FormattedLine()
if (improvement.terrainsCanBeBuiltOn.size == 1) {
with (improvement.terrainsCanBeBuiltOn.first()) {
textList += FormattedLine("{Can be built on} {$this}", link="Terrain/$this")
}
} else {
textList += FormattedLine("{Can be built on}:")
improvement.terrainsCanBeBuiltOn.forEach {
textList += FormattedLine(it, link="Terrain/$it", indent=1)
}
}
}
var addedLineBeforeResourceBonus = false
for (resource in ruleset.tileResources.values) {
if (resource.improvementStats == null || !resource.isImprovedBy(improvement.name)) continue
if (!addedLineBeforeResourceBonus) {
addedLineBeforeResourceBonus = true
textList += FormattedLine()
}
val statsString = resource.improvementStats.toString()
// Line intentionally modeled as UniqueType.Stats + ConditionalInTiles
textList += FormattedLine("[${statsString}] <in [${resource.name}] tiles>", link = resource.makeLink())
}
if (improvement.techRequired != null) {
textList += FormattedLine()
textList += FormattedLine("Required tech: [${improvement.techRequired}]", link="Technology/${improvement.techRequired}")
}
improvement.uniquesToCivilopediaTextLines(textList)
// Be clearer when one needs to chop down a Forest first... A "Can be built on Plains" is clear enough,
// but a "Can be built on Land" is not - how is the user to know Forest is _not_ Land?
if (creatorExists &&
!improvement.isEmpty() && // Has any Stats
!improvement.hasUnique(UniqueType.NoFeatureRemovalNeeded) &&
!improvement.hasUnique(UniqueType.RemovesFeaturesIfBuilt) &&
improvement.terrainsCanBeBuiltOn.none { it in ruleset.terrains }
)
textList += FormattedLine("Needs removal of terrain features to be built")
if (improvement.isAncientRuinsEquivalent() && ruleset.ruinRewards.isNotEmpty()) {
val difficulty = if (!UncivGame.isCurrentInitialized() || UncivGame.Current.gameInfo == null)
"Prince" // most factors == 1
else UncivGame.Current.gameInfo!!.gameParameters.difficulty
val religionEnabled = CivilopediaScreen.showReligionInCivilopedia(ruleset)
textList += FormattedLine()
textList += FormattedLine("The possible rewards are:")
ruleset.ruinRewards.values.asSequence()
.filter { reward ->
difficulty !in reward.excludedDifficulties &&
(religionEnabled || !reward.hasUnique(UniqueType.HiddenWithoutReligion))
}
.forEach { reward ->
textList += FormattedLine(reward.name, starred = true, color = reward.color)
textList += reward.civilopediaText
}
}
if (creatorExists)
textList += FormattedLine()
for (unit in constructorUnits)
textList += FormattedLine("{Can be constructed by} {$unit}", unit.makeLink())
for (unit in creatingUnits)
textList += FormattedLine("{Can be created instantly by} {$unit}", unit.makeLink())
val seeAlso = ArrayList<FormattedLine>()
for (alsoImprovement in ruleset.tileImprovements.values) {
if (alsoImprovement.replaces == improvement.name)
seeAlso += FormattedLine(alsoImprovement.name, link = alsoImprovement.makeLink(), indent = 1)
}
seeAlso += Belief.getCivilopediaTextMatching(improvement.name, ruleset, false)
if (seeAlso.isNotEmpty()) {
textList += FormattedLine()
textList += FormattedLine("{See also}:")
textList += seeAlso
}
return textList
}
fun getDescription(improvement: TileImprovement, ruleset: Ruleset): String {
val lines = ArrayList<String>()
val statsDesc = improvement.cloneStats().toString()
if (statsDesc.isNotEmpty()) lines += statsDesc
if (improvement.uniqueTo != null) lines += "Unique to [${improvement.uniqueTo}]".tr()
if (improvement.replaces != null) lines += "Replaces [${improvement.replaces}]".tr()
if (!improvement.terrainsCanBeBuiltOn.isEmpty()) {
val terrainsCanBeBuiltOnString: ArrayList<String> = arrayListOf()
for (i in improvement.terrainsCanBeBuiltOn) {
terrainsCanBeBuiltOnString.add(i.tr())
}
lines += "Can be built on".tr() + terrainsCanBeBuiltOnString.joinToString(", ", " ") //language can be changed when setting changes.
}
for (resource: TileResource in ruleset.tileResources.values.filter { it.isImprovedBy(improvement.name) }) {
if (resource.improvementStats == null) continue
val statsString = resource.improvementStats.toString()
lines += "[${statsString}] <in [${resource.name}] tiles>".tr()
}
if (improvement.techRequired != null) lines += "Required tech: [${improvement.techRequired}]".tr()
improvement.uniquesToDescription(lines)
return lines.joinToString("\n")
}
fun getShortDescription(improvement: TileImprovement) = sequence {
if (improvement.terrainsCanBeBuiltOn.isNotEmpty()) {
improvement.terrainsCanBeBuiltOn.withIndex().forEach {
yield(
FormattedLine(
if (it.index == 0) "{Can be built on} {${it.value}}" else "or [${it.value}]",
link = "Terrain/${it.value}", indent = if (it.index == 0) 1 else 2
)
)
}
}
for (unique in improvement.uniqueObjects) {
if (unique.isHiddenToUsers()) continue
yield(FormattedLine(unique, indent = 1))
}
}
}

View File

@ -660,7 +660,8 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
return cityScreen.startPickTileForCreatesOneImprovement(construction, stat, true)
// Buying a UniqueType.CreatesOneImprovement building from queue must pass down
// the already selected tile, otherwise a new one is chosen from Automation code.
val improvement = construction.getImprovementToCreate(cityScreen.city.getRuleset())!!
val improvement = construction.getImprovementToCreate(
cityScreen.city.getRuleset(), cityScreen.city.civ)!!
val tileForImprovement = cityScreen.city.cityConstructions.getTileForImprovement(improvement.name)
askToBuyConstruction(construction, stat, tileForImprovement)
}

View File

@ -454,7 +454,7 @@ class CityScreen(
fun selectConstruction(newConstruction: IConstruction) {
selectedConstruction = newConstruction
if (newConstruction is Building && newConstruction.hasCreateOneImprovementUnique()) {
val improvement = newConstruction.getImprovementToCreate(city.getRuleset())
val improvement = newConstruction.getImprovementToCreate(city.getRuleset(), city.civ)
selectedQueueEntryTargetTile = if (improvement == null) null
else city.cityConstructions.getTileForImprovement(improvement.name)
} else {
@ -472,7 +472,7 @@ class CityScreen(
fun clearSelection() = selectTile(null)
fun startPickTileForCreatesOneImprovement(construction: Building, stat: Stat, isBuying: Boolean) {
val improvement = construction.getImprovementToCreate(city.getRuleset()) ?: return
val improvement = construction.getImprovementToCreate(city.getRuleset(), city.civ) ?: return
pickTileData = PickTileForImprovementData(construction, improvement, isBuying, stat)
updateTileGroups()
ToastPopup("Please select a tile for this building's [${improvement.name}]", this)

View File

@ -45,6 +45,7 @@ Each improvement has the following structure:
| name | String | Required | [^A] |
| terrainsCanBeBuiltOn | List of Strings | empty | Terrains that this improvement can be built on [^B]. Removable terrain features will need to be removed before building an improvement [^C]. Must be in [Terrains.json](#terrainsjson) |
| techRequired | String | none | The name of the technology required to build this improvement |
| replaces | String | none | The name of a improvement that should be replaced by this improvement. Must be in [TileImprovements.json](#TileImprovementsjson) |
| uniqueTo | String | none | The name of the nation this improvement is unique for |
| [`<stats>`](#stats) | Integer | 0 | Per-turn bonus yield for the tile |
| turnsToBuild | Integer | -1 | Number of turns a worker spends building this. If -1, the improvement is unbuildable [^D]. If 0, the improvement is always built in one turn |