diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 12c806e698..d9cb51832b 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -29,7 +29,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { private var percentStatBonus: Stats? = null var specialistSlots: Counter? = null fun newSpecialists(): Counter { - if (specialistSlots == null) return Counter() + if (specialistSlots == null) return Counter() // Could have old specialist values of "gold", "science" etc - change them to the new specialist names val counter = Counter() for ((entry, amount) in specialistSlots!!) { @@ -90,18 +90,24 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { return infoList.joinToString("; ") { it.tr() } } - fun getUniquesStrings(): ArrayList { + private fun getUniquesStrings() = sequence { val tileBonusHashmap = HashMap>() - val finalUniques = ArrayList() - for (unique in uniqueObjects) - if (unique.placeholderText == "[] from [] tiles []" && unique.params[2] == "in this city") { + for (unique in uniqueObjects) when { + unique.placeholderText == "[] from [] tiles []" && unique.params[2] == "in this city" -> { val stats = unique.params[0] if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList() tileBonusHashmap[stats]!!.add(unique.params[1]) - } else if (unique.placeholderText != "Consumes [] []") finalUniques += unique.text + } + unique.placeholderText == "Consumes [] []" -> Unit // skip these, + else -> yield(unique.text) + } for ((key, value) in tileBonusHashmap) - finalUniques += "[stats] from [tileFilter] tiles in this city".fillPlaceholders(key, value.joinToString { it.tr() }) - return finalUniques + yield( "[stats] from [tileFilter] tiles in this city" + .fillPlaceholders( key, + // A single tileFilter will be properly translated later due to being within [] + // advantage to not translate prematurely: FormatLine.formatUnique will recognize it + if (value.size == 1) value[0] else value.joinToString { it.tr() } + )) } fun getDescription(cityInfo: CityInfo?, ruleset: Ruleset): String { @@ -118,7 +124,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { stringBuilder.appendLine("Provides a free [$providesFreeBuilding] in the city".tr()) if (uniques.isNotEmpty()) { if (replacementTextForUniques != "") stringBuilder.appendLine(replacementTextForUniques) - else stringBuilder.appendLine(getUniquesStrings().asSequence().map { it.tr() }.joinToString("\n")) + else stringBuilder.appendLine(getUniquesStrings().map { it.tr() }.joinToString("\n")) } if (!stats.isEmpty()) stringBuilder.appendLine(stats.toString()) @@ -180,8 +186,7 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { fun getStatPercentageBonuses(cityInfo: CityInfo?): Stats { val stats = if (percentStatBonus == null) Stats() else percentStatBonus!!.clone() - val civInfo = cityInfo?.civInfo - if (civInfo == null) return stats // initial stats + val civInfo = cityInfo?.civInfo ?: return stats // initial stats val baseBuildingName = getBaseBuilding(civInfo.gameInfo.ruleSet).name @@ -262,7 +267,8 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { if (replacementTextForUniques.isNotEmpty()) textList += FormattedLine(replacementTextForUniques) else - for (unique in getUniquesStrings()) textList += FormattedLine(unique) + for (unique in getUniquesStrings()) + textList += FormattedLine(Unique(unique)) } if (!stats.isEmpty()) { @@ -504,8 +510,8 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { for ((resource, amount) in getResourceRequirements()) if (civInfo.getCivResourcesByName()[resource]!! < amount) { - if (amount == 1) return "Consumes 1 [$resource]" // Again, to preserve existing translations - else return "Consumes [$amount] [$resource]" + return if (amount == 1) "Consumes 1 [$resource]" // Again, to preserve existing translations + else "Consumes [$amount] [$resource]" } if (requiredNearbyImprovedResources != null) { @@ -599,14 +605,14 @@ class Building : NamedStats(), IConstruction, ICivilopediaText { } fun getBaseBuilding(ruleset: Ruleset): Building { - if (replaces == null) return this - else return ruleset.buildings[replaces!!]!! + return if (replaces == null) this + else ruleset.buildings[replaces!!]!! } fun getImprovement(ruleset: Ruleset): TileImprovement? { val improvementUnique = uniqueObjects - .firstOrNull { it.placeholderText == "Creates a [] improvement on a specific tile" } - if (improvementUnique == null) return null + .firstOrNull { it.placeholderText == "Creates a [] improvement on a specific tile" } + ?: return null return ruleset.tileImprovements[improvementUnique.params[0]] } diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 41d1680031..686371be91 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -103,14 +103,8 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() { textList += FormattedLine(replacementTextForUniques) } else if (uniques.isNotEmpty()) { textList += FormattedLine() - for (uniqueObject in uniqueObjects.sortedBy { it.text }) { - if (uniqueObject.placeholderText == "Can construct []") { - val improvement = uniqueObject.params[0] - textList += FormattedLine(uniqueObject.text, link="Improvement/$improvement") - } else { - textList += FormattedLine(uniqueObject.text) - } - } + for (uniqueObject in uniqueObjects.sortedBy { it.text }) + textList += FormattedLine(uniqueObject) } val resourceRequirements = getResourceRequirements() diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt index 393eadde06..2539ddc52a 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt @@ -5,7 +5,9 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align +import com.unciv.UncivGame import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.Unique import com.unciv.models.stats.INamed import com.unciv.ui.utils.* import kotlin.math.max @@ -66,6 +68,9 @@ class FormattedLine ( // from json in the primary constructor parameters above. Everything else should be a fun(), // have no backing field, be `by lazy` or use @Transient, Thank you. + /** Looks for linkable ruleset objects in [Unique] parameters and returns a linked [FormattedLine] if successful, a plain one otherwise */ + constructor(unique: Unique) : this(unique.text, getUniqueLink(unique)) + /** Link types that can be used for [FormattedLine.link] */ enum class LinkType { None, @@ -133,6 +138,43 @@ class FormattedLine ( const val iconPad = 5f /** Padding distance per [indent] level */ const val indentPad = 30f + + // Helper for constructor(Unique) + private fun getUniqueLink(unique: Unique): String { + for (parameter in unique.params) { + val category = allObjectNamesCategoryMap[parameter] ?: continue + return category.name + "/" + parameter + } + return "" + } + // Cache to quickly match Categories to names. Takes a few ms to build on a slower desktop and will use just a few 10k bytes. + private val allObjectNamesCategoryMap: HashMap by lazy { + //val startTime = System.nanoTime() + val ruleSet = UncivGame.Current.gameInfo.ruleSet + // order these with the categories that should take precedence in case of name conflicts (e.g. Railroad) _last_ + val allObjectMapsSequence = sequence { + yield(CivilopediaCategories.Difficulty to ruleSet.difficulties) + yield(CivilopediaCategories.Promotion to ruleSet.unitPromotions) + yield(CivilopediaCategories.Policy to ruleSet.policies) + yield(CivilopediaCategories.Terrain to ruleSet.terrains) + yield(CivilopediaCategories.Improvement to ruleSet.tileImprovements) + yield(CivilopediaCategories.Resource to ruleSet.tileResources) + yield(CivilopediaCategories.Nation to ruleSet.nations) + yield(CivilopediaCategories.Unit to ruleSet.units) + yield(CivilopediaCategories.Technology to ruleSet.technologies) + yield(CivilopediaCategories.Building to ruleSet.buildings.filter { !it.value.isAnyWonder() }) + yield(CivilopediaCategories.Wonder to ruleSet.buildings.filter { it.value.isAnyWonder() }) + } + val result = HashMap() + allObjectMapsSequence.filter { !it.first.hide } + .flatMap { pair -> pair.second.keys.asSequence().map { key -> pair.first to key } } + .forEach { + result[it.second] = it.first + //println(" ${it.second} is a ${it.first}") + } + //println("allObjectNamesCategoryMap took ${System.nanoTime()-startTime}ns to initialize") + result + } } /** Extension: determines if a [String] looks like a link understood by the OS */