diff --git a/core/src/com/unciv/models/ruleset/Belief.kt b/core/src/com/unciv/models/ruleset/Belief.kt index a81bf2608c..3d9ddee347 100644 --- a/core/src/com/unciv/models/ruleset/Belief.kt +++ b/core/src/com/unciv/models/ruleset/Belief.kt @@ -2,9 +2,9 @@ package com.unciv.models.ruleset import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.translations.tr +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia import com.unciv.ui.screens.civilopediascreen.FormattedLine @@ -27,6 +27,7 @@ class Belief() : RulesetObject() { return getCivilopediaTextLines(false) } + // This special overload is called from Religion overview and Religion picker fun getCivilopediaTextLines(withHeader: Boolean): List { val textList = ArrayList() if (withHeader) { @@ -35,10 +36,7 @@ class Belief() : RulesetObject() { } if (type != BeliefType.None) textList += FormattedLine("{Type}: {$type}", color = type.color, centered = withHeader) - uniqueObjects.forEach { - if (!it.hasFlag(UniqueFlag.HiddenToUsers)) - textList += FormattedLine(it) - } + uniquesToCivilopediaTextLines(textList, leadingSeparator = null) return textList } diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index c1d3f28b3e..9d0131d0f4 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -9,7 +9,6 @@ import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.LocalUniqueCache import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueParameterType import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType @@ -21,6 +20,7 @@ import com.unciv.ui.components.extensions.getConsumesAmountString import com.unciv.ui.components.extensions.getNeedMoreAmountString import com.unciv.ui.components.extensions.toPercent import com.unciv.ui.components.fonts.Fonts +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.FormattedLine @@ -100,7 +100,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { * @param filterUniques If provided, include only uniques for which this function returns true. */ private fun getUniquesStringsWithoutDisablers(filterUniques: ((Unique) -> Boolean)? = null) = getUniquesStrings { - !it.hasFlag(UniqueFlag.HiddenToUsers) + !it.isHiddenToUsers() && filterUniques?.invoke(it) ?: true } @@ -262,15 +262,8 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { if (replacementTextForUniques.isNotEmpty()) { textList += FormattedLine(replacementTextForUniques) - } else if (uniques.isNotEmpty()) { - for (unique in uniqueObjects) { - if (unique.hasFlag(UniqueFlag.HiddenToUsers)) continue - if (unique.type == UniqueType.ConsumesResources) { - textList += FormattedLine(unique.text, link = "Resources/${unique.params[1]}", color = "#F42") - continue - } - textList += FormattedLine(unique) - } + } else { + uniquesToCivilopediaTextLines(textList, colorConsumesResources = true) } if (!stats.isEmpty()) { diff --git a/core/src/com/unciv/models/ruleset/Policy.kt b/core/src/com/unciv/models/ruleset/Policy.kt index 1f26c59dfe..4506821d18 100644 --- a/core/src/com/unciv/models/ruleset/Policy.kt +++ b/core/src/com/unciv/models/ruleset/Policy.kt @@ -1,11 +1,10 @@ package com.unciv.models.ruleset import com.unciv.models.ruleset.unique.StateForConditionals -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.models.translations.getPlaceholderText import com.unciv.models.translations.tr +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.FormattedLine open class Policy : RulesetObject() { @@ -18,12 +17,14 @@ open class Policy : RulesetObject() { /** Indicates whether a [Policy] is a [PolicyBranch] starting policy, a normal one, or the branch completion */ enum class PolicyBranchType {BranchStart, Member, BranchComplete} + /** Indicates whether this [Policy] is a [PolicyBranch] starting policy, a normal one, or the branch completion */ val policyBranchType: PolicyBranchType by lazy { when { this is PolicyBranch -> PolicyBranchType.BranchStart isBranchCompleteByName(name) -> PolicyBranchType.BranchComplete else -> PolicyBranchType.Member } } + companion object { const val branchCompleteSuffix = " Complete" /** Some tests to count policies by completion or not use only the String collection without instantiating them. @@ -34,16 +35,13 @@ open class Policy : RulesetObject() { /** Used in PolicyPickerScreen to display Policy properties */ fun getDescription(): String { - var text = uniques - .filter { - !it.getPlaceholderText().contains(UniqueType.OnlyAvailableWhen.placeholderText) && - !it.getPlaceholderText().contains(UniqueType.OneTimeGlobalAlert.placeholderText) + return (if (policyBranchType == PolicyBranchType.Member) name.tr() + "\n" else "") + + uniqueObjects.filterNot { + it.isHiddenToUsers() + || it.isOfType(UniqueType.OnlyAvailableWhen) + || it.isOfType(UniqueType.OneTimeGlobalAlert) } - .joinToString("\n", transform = { "• ${it.tr()}" }) - if (policyBranchType != PolicyBranchType.BranchStart - && policyBranchType != PolicyBranchType.BranchComplete) - text = name.tr() + "\n" + text - return text + .joinToString("\n") { "• ${it.text.tr()}" } } override fun makeLink() = "Policy/$name" @@ -126,17 +124,9 @@ open class Policy : RulesetObject() { lineList += FormattedLine(unit.name, link = unit.makeLink(), indent = 1) } - - if (uniques.isNotEmpty()) { - lineList += FormattedLine() - uniqueObjects.forEach { - if (!it.hasFlag(UniqueFlag.HiddenToUsers)) - lineList += FormattedLine(it) - } - } + uniquesToCivilopediaTextLines(lineList) return lineList } } - diff --git a/core/src/com/unciv/models/ruleset/Speed.kt b/core/src/com/unciv/models/ruleset/Speed.kt index 1e6d026c3a..76f8ed4b19 100644 --- a/core/src/com/unciv/models/ruleset/Speed.kt +++ b/core/src/com/unciv/models/ruleset/Speed.kt @@ -54,6 +54,7 @@ class Speed : RulesetObject(), IsPartOfGameInfoSerialization { const val DEFAULTFORSIMULATION: String = "Standard" } + // Note: Speed is IHasUniques, but no implementation reads them, thus no UniqueType accepts this target override fun getUniqueTarget(): UniqueTarget = UniqueTarget.Speed override fun makeLink(): String = "Speed/$name" diff --git a/core/src/com/unciv/models/ruleset/nation/Nation.kt b/core/src/com/unciv/models/ruleset/nation/Nation.kt index 6eeba3e906..1e5ea3a9d1 100644 --- a/core/src/com/unciv/models/ruleset/nation/Nation.kt +++ b/core/src/com/unciv/models/ruleset/nation/Nation.kt @@ -4,15 +4,15 @@ import com.badlogic.gdx.graphics.Color import com.unciv.Constants import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetObject -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.squareBraceRegex import com.unciv.models.translations.tr import com.unciv.ui.components.extensions.colorFromRGB +import com.unciv.ui.objectdescriptions.BaseUnitDescriptions +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia import com.unciv.ui.screens.civilopediascreen.FormattedLine -import com.unciv.ui.objectdescriptions.BaseUnitDescriptions import kotlin.math.pow class Nation : RulesetObject() { @@ -111,12 +111,9 @@ class Nation : RulesetObject() { if (uniqueText != "") { textList += FormattedLine(uniqueText, indent = 1) } else { - uniqueObjects.forEach { - if (!it.hasFlag(UniqueFlag.HiddenToUsers)) - textList += FormattedLine(it) - } - textList += FormattedLine() + uniquesToCivilopediaTextLines(textList, leadingSeparator = null) } + textList += FormattedLine() if (startBias.isNotEmpty()) { startBias.withIndex().forEach { diff --git a/core/src/com/unciv/models/ruleset/tech/Era.kt b/core/src/com/unciv/models/ruleset/tech/Era.kt index 12df54eabb..ac6467234c 100644 --- a/core/src/com/unciv/models/ruleset/tech/Era.kt +++ b/core/src/com/unciv/models/ruleset/tech/Era.kt @@ -10,6 +10,7 @@ import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.components.extensions.colorFromRGB import com.unciv.ui.components.fonts.Fonts +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.FormattedLine class Era : RulesetObject() { @@ -53,8 +54,7 @@ class Era : RulesetObject() { .filter { it.era() == name } .map { FormattedLine(it.name, it.makeLink()) }) - if (uniques.isNotEmpty()) yield(FormattedLine()) - yieldAll(uniqueObjects.asSequence().map { FormattedLine(it) }) + yieldAll(uniquesToCivilopediaTextLines()) val eraGatedObjects = getEraGatedObjects(ruleset).toList() if (eraGatedObjects.isEmpty()) return@sequence diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index 50a3b54d48..b23e884647 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -5,11 +5,11 @@ import com.unciv.Constants import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetStatsObject -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.screens.civilopediascreen.FormattedLine import com.unciv.ui.components.extensions.colorFromRGB +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines +import com.unciv.ui.screens.civilopediascreen.FormattedLine class Terrain : RulesetStatsObject() { @@ -118,10 +118,7 @@ class Terrain : RulesetStatsObject() { // For now, natural wonders show no "open terrain" - may change later if (turnsInto == null && displayAs(TerrainType.Land, ruleset) && !isRough()) textList += FormattedLine("Open terrain") // Rough is in uniques - uniqueObjects.forEach { - if (!it.hasFlag(UniqueFlag.HiddenToUsers)) - textList += FormattedLine(it) - } + uniquesToCivilopediaTextLines(textList, leadingSeparator = null) textList += FormattedLine() textList += if (impassable) FormattedLine(Constants.impassable, color="#A00") diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index f05df32bda..386703813f 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -14,6 +14,8 @@ 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.screens.civilopediascreen.FormattedLine import kotlin.math.roundToInt @@ -58,8 +60,7 @@ class TileImprovement : RulesetStatsObject() { } if (techRequired != null) lines += "Required tech: [$techRequired]".tr() - for (unique in uniques) - lines += unique.tr() + uniquesToDescription(lines) return lines.joinToString("\n") } @@ -145,11 +146,7 @@ class TileImprovement : RulesetStatsObject() { textList += FormattedLine("Required tech: [$techRequired]", link="Technology/$techRequired") } - if (uniques.isNotEmpty()) { - textList += FormattedLine() - for (unique in uniqueObjects) - textList += FormattedLine(unique) - } + 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? diff --git a/core/src/com/unciv/models/ruleset/tile/TileResource.kt b/core/src/com/unciv/models/ruleset/tile/TileResource.kt index b8261b2c0a..b5e62eadf7 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileResource.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileResource.kt @@ -6,10 +6,10 @@ 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.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stats +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.FormattedLine class TileResource : RulesetStatsObject() { @@ -43,13 +43,7 @@ class TileResource : RulesetStatsObject() { textList += FormattedLine("${resourceType.name} resource", header = 4, color = resourceType.color) textList += FormattedLine() - if (uniques.any()){ - textList += FormattedLine() - for (unique in uniqueObjects.sortedBy { it.text }) { - if (unique.hasFlag(UniqueFlag.HiddenToUsers)) continue - textList += FormattedLine(unique) - } - } + uniquesToCivilopediaTextLines(textList, sorted = true) textList += FormattedLine(cloneStats().toString()) diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt index a051aa1149..9f7f6f0513 100644 --- a/core/src/com/unciv/models/ruleset/unique/Unique.kt +++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt @@ -44,6 +44,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s val isLocalEffect = params.contains("in this city") || conditionals.any { it.type == UniqueType.ConditionalInThisCity } fun hasFlag(flag: UniqueFlag) = type != null && type.flags.contains(flag) + fun isHiddenToUsers() = hasFlag(UniqueFlag.HiddenToUsers) || conditionals.any { it.type == UniqueType.ConditionalHideUniqueFromUsers } fun hasTriggerConditional(): Boolean { if (conditionals.none()) return false @@ -204,6 +205,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s return when (condition.type) { // These are 'what to do' and not 'when to do' conditionals UniqueType.ConditionalTimedUnique -> true + UniqueType.ConditionalHideUniqueFromUsers -> true // allowed to be attached to any Unique to hide it, no-op otherwise UniqueType.ConditionalChance -> stateBasedRandom.nextFloat() < condition.params[0].toFloat() / 100f UniqueType.ConditionalBeforeTurns -> checkOnCiv { gameInfo.turns < condition.params[0].toInt() } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 17239f5605..036bcb7a45 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -777,6 +777,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: UniqueTarget.Unit, UniqueTarget.UnitType, UniqueTarget.Improvement, UniqueTarget.Tech, UniqueTarget.Terrain, UniqueTarget.Resource, UniqueTarget.Policy, UniqueTarget.Promotion, UniqueTarget.Nation, UniqueTarget.Ruins, flags = UniqueFlag.setOfHiddenToUsers), + ConditionalHideUniqueFromUsers("hidden from users", UniqueTarget.Conditional), // Declarative Mod compatibility (so far rudimentary): ModIncompatibleWith("Mod is incompatible with [modFilter]", UniqueTarget.ModOptions), diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 2ceba63022..7c932ffbdd 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -61,6 +61,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { lateinit var ruleset: Ruleset + /** Generate short description as comma-separated string for Technology description "Units enabled" and GreatPersonPickerScreen */ fun getShortDescription() = BaseUnitDescriptions.getShortDescription(this) /** Generate description as multi-line string for CityScreen addSelectedConstructionTable diff --git a/core/src/com/unciv/models/ruleset/unit/Promotion.kt b/core/src/com/unciv/models/ruleset/unit/Promotion.kt index 55234f206b..89ca42afd0 100644 --- a/core/src/com/unciv/models/ruleset/unit/Promotion.kt +++ b/core/src/com/unciv/models/ruleset/unit/Promotion.kt @@ -2,10 +2,11 @@ package com.unciv.models.ruleset.unit import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetObject -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines +import com.unciv.ui.objectdescriptions.uniquesToDescription import com.unciv.ui.screens.civilopediascreen.FormattedLine import com.unciv.ui.screens.pickerscreens.PromotionPickerScreen @@ -45,9 +46,7 @@ class Promotion : RulesetObject() { fun getDescription(promotionsForUnitType: Collection):String { val textList = ArrayList() - for (unique in uniques) { - textList += unique.tr() - } + uniquesToDescription(textList) if (prerequisites.isNotEmpty()) { val prerequisitesString: ArrayList = arrayListOf() @@ -64,10 +63,7 @@ class Promotion : RulesetObject() { override fun getCivilopediaTextLines(ruleset: Ruleset): List { val textList = ArrayList() - uniqueObjects.forEach { - if (!it.hasFlag(UniqueFlag.HiddenToUsers)) - textList += FormattedLine(it) - } + uniquesToCivilopediaTextLines(textList, leadingSeparator = null) val filteredPrerequisites = prerequisites.mapNotNull { ruleset.unitPromotions[it] diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index 551378679c..0d82684418 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -349,7 +349,7 @@ object TranslationFileWriter { } fun submitString(string: String, unique: Unique) { - if (unique.hasFlag(UniqueFlag.HiddenToUsers)) + if (unique.isHiddenToUsers()) return // We don't need to translate this at all, not user-visible val stringToTranslate = string.removeConditionals() diff --git a/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt b/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt index f9f110c272..9b8891716b 100644 --- a/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt +++ b/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt @@ -9,7 +9,6 @@ import com.unciv.models.ruleset.IRulesetObject import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique -import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.UnitMovementType @@ -25,6 +24,7 @@ import com.unciv.ui.screens.civilopediascreen.MarkupRenderer object BaseUnitDescriptions { + /** Generate short description as comma-separated string for Technology description "Units enabled" and GreatPersonPickerScreen */ fun getShortDescription(baseUnit: BaseUnit): String { val infoList = mutableListOf() if (baseUnit.strength != 0) infoList += "${baseUnit.strength}${Fonts.strength}" @@ -33,8 +33,7 @@ object BaseUnitDescriptions { for (promotion in baseUnit.promotions) infoList += promotion.tr() if (baseUnit.replacementTextForUniques != "") infoList += baseUnit.replacementTextForUniques - else for (unique in baseUnit.uniqueObjects) if (!unique.hasFlag(UniqueFlag.HiddenToUsers)) - infoList += unique.text.tr() + else baseUnit.uniquesToDescription(infoList) return infoList.joinToString() } @@ -59,11 +58,7 @@ object BaseUnitDescriptions { lines += "$strengthLine${baseUnit.movement}${Fonts.movement}" if (baseUnit.replacementTextForUniques != "") lines += baseUnit.replacementTextForUniques - else for (unique in baseUnit.uniqueObjects.filterNot { - it.type == UniqueType.Unbuildable - || it.type?.flags?.contains(UniqueFlag.HiddenToUsers) == true - }) - lines += unique.text.tr() + else baseUnit.uniquesToDescription(lines) { isOfType(UniqueType.Unbuildable) } if (baseUnit.promotions.isNotEmpty()) { val prefix = "Free promotion${if (baseUnit.promotions.size == 1) "" else "s"}:".tr() + " " @@ -106,16 +101,8 @@ object BaseUnitDescriptions { if (baseUnit.replacementTextForUniques.isNotEmpty()) { textList += FormattedLine() textList += FormattedLine(baseUnit.replacementTextForUniques) - } else if (baseUnit.uniques.isNotEmpty()) { - textList += FormattedLine() - for (unique in baseUnit.uniqueObjects.sortedBy { it.text }) { - if (unique.hasFlag(UniqueFlag.HiddenToUsers)) continue - if (unique.type == UniqueType.ConsumesResources) { - textList += FormattedLine(unique.text, link = "Resources/${unique.params[1]}", color = "#F42") - continue - } - textList += FormattedLine(unique) - } + } else { + baseUnit.uniquesToCivilopediaTextLines(textList, sorted = true, colorConsumesResources = true) } if (baseUnit.requiredResource != null) { @@ -252,13 +239,8 @@ object BaseUnitDescriptions { for (promotion in relevantPromotions) yield(FormattedLine(promotion.name, promotion.makeLink())) } - if (uniqueObjects.isNotEmpty()) { - yield(FormattedLine(separator = true)) - for (unique in uniqueObjects) { - if (unique.hasFlag(UniqueFlag.HiddenToUsers)) continue - yield(FormattedLine(unique)) - } - } + + yieldAll(uniquesToCivilopediaTextLines(leadingSeparator = true)) } return (if (name.startsWith("Domain: ")) getDomainLines() else getUnitTypeLines()).toList() } @@ -303,12 +285,12 @@ object BaseUnitDescriptions { if (betterUnit.replacementTextForUniques.isNotEmpty()) { yield(betterUnit.replacementTextForUniques to null) } else { - val newAbilityPredicate: (Unique)->Boolean = { it.text in originalUnit.uniques || it.hasFlag(UniqueFlag.HiddenToUsers) } + val newAbilityPredicate: (Unique)->Boolean = { it.text in originalUnit.uniques || it.isHiddenToUsers() } for (unique in betterUnit.uniqueObjects.filterNot(newAbilityPredicate)) yield(unique.text to null) } - val lostAbilityPredicate: (Unique)->Boolean = { it.text in betterUnit.uniques || it.hasFlag(UniqueFlag.HiddenToUsers) } + val lostAbilityPredicate: (Unique)->Boolean = { it.text in betterUnit.uniques || it.isHiddenToUsers() } for (unique in originalUnit.uniqueObjects.filterNot(lostAbilityPredicate)) { yield("Lost ability (vs [${originalUnit.name}]): [${unique.text}]" to null) } diff --git a/core/src/com/unciv/ui/objectdescriptions/DescriptionHelpers.kt b/core/src/com/unciv/ui/objectdescriptions/DescriptionHelpers.kt new file mode 100644 index 0000000000..3584b10528 --- /dev/null +++ b/core/src/com/unciv/ui/objectdescriptions/DescriptionHelpers.kt @@ -0,0 +1,74 @@ +package com.unciv.ui.objectdescriptions + +import com.unciv.models.ruleset.unique.IHasUniques +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.FormattedLine + +/** + * Appends user-visible Uniques as translated text to a [line collection][lineList]. + * + * Follows json order. + * @param exclude Predicate that can exclude Uniques by returning `true` (defaults to return `false`). + */ +fun IHasUniques.uniquesToDescription( + lineList: MutableCollection, + exclude: Unique.() -> Boolean = {false} +) { + for (unique in uniqueObjects) { + if (unique.isHiddenToUsers()) continue + if (unique.exclude()) continue + lineList += unique.text.tr() + } +} + +/** + * A Sequence of user-visible Uniques as [FormattedLine]s. + * + * @param leadingSeparator Tristate: If there are lines to display and this parameter is not `null`, a leading line is output, as separator or empty line. + * @param sorted If set, sorts alphabetically (**not** using a locale-specific Collator). Otherwise lists in json order. + * @param colorConsumesResources If set, ConsumesResources Uniques get a reddish color. + * @param exclude Predicate that can exclude Uniques by returning `true` (defaults to return `false`). + */ +fun IHasUniques.uniquesToCivilopediaTextLines( + leadingSeparator: Boolean? = false, + sorted: Boolean = false, + colorConsumesResources: Boolean = false, + exclude: Unique.() -> Boolean = {false} +) = sequence { + var orderedUniques = uniqueObjects.asSequence() + .filterNot { it.isHiddenToUsers() || it.exclude() } + if (sorted) orderedUniques = orderedUniques.sortedBy { it.text } + + for ((index, unique) in orderedUniques.withIndex()) { + if (leadingSeparator != null && index == 0) + yield(FormattedLine(separator = leadingSeparator)) + // Optionally special-case ConsumesResources to give it a reddish color. Also ensures link always points to the resource + // (the other constructor guesses the first object by name in the Unique parameters). + yield( + if (colorConsumesResources && unique.isOfType(UniqueType.ConsumesResources)) + FormattedLine(unique.text, link = "Resources/${unique.params[1]}", color = "#F42") + else FormattedLine(unique) + ) + } +} + +/** + * Appends user-visible Uniques as [FormattedLine]s to [lineList]. + * + * @param leadingSeparator Tristate: If there are lines to display and this parameter is not `null`, a leading line is output, as separator or empty line. + * @param sorted If set, sorts alphabetically (**not** using a locale-specific Collator). Otherwise lists in json order. + * @param colorConsumesResources If set, ConsumesResources Uniques get a reddish color. + * @param exclude Predicate that can exclude Uniques by returning `true` (defaults to return `false`). + */ +fun IHasUniques.uniquesToCivilopediaTextLines( + lineList: MutableCollection, + leadingSeparator: Boolean? = false, + sorted: Boolean = false, + colorConsumesResources: Boolean = false, + exclude: Unique.() -> Boolean = {false} +) { + uniquesToCivilopediaTextLines(leadingSeparator, sorted, colorConsumesResources, exclude) + .toCollection(lineList) +} diff --git a/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt b/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt index a8bf0794f4..0802ce1e4a 100644 --- a/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt +++ b/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt @@ -8,7 +8,6 @@ import com.unciv.models.ruleset.tech.Technology 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.UniqueFlag import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.tr @@ -38,7 +37,8 @@ object TechnologyDescriptions { if (pediaText.text.isEmpty() || pediaText.header != 0) continue lineList += pediaText.text } - for (unique in uniques) lineList += unique + + uniquesToDescription(lineList) lineList.addAll( getAffectedImprovements(name, ruleset) @@ -191,13 +191,7 @@ object TechnologyDescriptions { } } - if (uniques.isNotEmpty()) { - lineList += FormattedLine() - uniqueObjects.forEach { - if (!it.hasFlag(UniqueFlag.HiddenToUsers)) - lineList += FormattedLine(it) - } - } + uniquesToCivilopediaTextLines(lineList) val affectedImprovements = getAffectedImprovements(name, ruleset) if (affectedImprovements.any()) { diff --git a/core/src/com/unciv/ui/screens/civilopediascreen/ICivilopediaText.kt b/core/src/com/unciv/ui/screens/civilopediascreen/ICivilopediaText.kt index ab7aa8a4c0..c43f235bdf 100644 --- a/core/src/com/unciv/ui/screens/civilopediascreen/ICivilopediaText.kt +++ b/core/src/com/unciv/ui/screens/civilopediascreen/ICivilopediaText.kt @@ -6,6 +6,7 @@ import com.unciv.models.ruleset.IRulesetObject import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetObject import com.unciv.models.stats.INamed +import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines /** Addon common to most ruleset game objects managing civilopedia display * @@ -39,6 +40,7 @@ interface ICivilopediaText { * (And the info displayed should be about the **ruleset**, not the player situation) * * Default implementation is empty - no need to call super in overrides. + * Note that for inclusion of Uniques, two helpers named [uniquesToCivilopediaTextLines] exist (for Sequence or MutableCollection context). * * @param ruleset The current ruleset for the Civilopedia viewer * @return A list of [FormattedLine]s that will be inserted before diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index a5a65c631c..ff3775efaa 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -2028,6 +2028,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Conditional +??? example "<hidden from users>" + Applicable to: Conditional + ## TriggerCondition uniques !!! note ""