Civilopedia tweaks (#7051)

* Civilopedia Files Split

* Central showReligionInCivilopedia function

* ConstructImprovementConsumingUnit with Conditional for Prophet

* Show Units capable of building an Improvement

* "Needs removal of terrain features to be built"
This commit is contained in:
SomeTroglodyte
2022-06-19 02:29:07 +02:00
committed by GitHub
parent 672b804ac5
commit 9683e27526
14 changed files with 313 additions and 217 deletions

View File

@ -540,7 +540,7 @@
"uniques": ["[+50]% Strength <vs [Mounted] units>"], "uniques": ["[+50]% Strength <vs [Mounted] units>"],
"upgradesTo": "Lancer", "upgradesTo": "Lancer",
"obsoleteTech": "Gunpowder", "obsoleteTech": "Gunpowder",
"attackSound": "metalhit" "attackSound": "metalhit"
}, },
{ {
"name": "Landsknecht", "name": "Landsknecht",
@ -1623,10 +1623,10 @@
{ {
"name": "Great Prophet", "name": "Great Prophet",
"unitType": "Civilian", "unitType": "Civilian",
"uniques": ["Can construct [Holy site] if it hasn't used other actions yet", "Can [Spread Religion] [4] times", "uniques": ["Can construct [Holy site] <if it hasn't used other actions yet>", "Can [Spread Religion] [4] times",
"Removes other religions when spreading religion", "May found a religion", "May enhance a religion", "Removes other religions when spreading religion", "May found a religion", "May enhance a religion",
"May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]", "May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]",
"Unbuildable", "Religious Unit", "Hidden when religion is disabled", "Unbuildable", "Religious Unit", "Hidden when religion is disabled",
"Takes your religion over the one in their birth city"], "Takes your religion over the one in their birth city"],
"movement": 2, "movement": 2,
"religiousStrength": 1000 "religiousStrength": 1000
@ -1654,7 +1654,7 @@
"name": "Missionary", "name": "Missionary",
"unitType": "Civilian", "unitType": "Civilian",
"uniques": ["Can [Spread Religion] [2] times", "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there", "uniques": ["Can [Spread Religion] [2] times", "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there",
"Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]", "Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]",
"[-1] Sight", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"], "[-1] Sight", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"],
"movement": 4, "movement": 4,
"religiousStrength": 1000 "religiousStrength": 1000

View File

@ -408,7 +408,7 @@
"uniques": ["[+50]% Strength <vs [Mounted] units>"], "uniques": ["[+50]% Strength <vs [Mounted] units>"],
"upgradesTo": "Lancer", "upgradesTo": "Lancer",
"obsoleteTech": "Gunpowder", "obsoleteTech": "Gunpowder",
"attackSound": "metalhit" "attackSound": "metalhit"
}, },
{ {
"name": "Landsknecht", "name": "Landsknecht",
@ -1299,10 +1299,10 @@
{ {
"name": "Great Prophet", "name": "Great Prophet",
"unitType": "Civilian", "unitType": "Civilian",
"uniques": ["Can construct [Holy site] if it hasn't used other actions yet", "Can [Spread Religion] [4] times", "uniques": ["Can construct [Holy site] <if it hasn't used other actions yet>", "Can [Spread Religion] [4] times",
"Removes other religions when spreading religion", "May found a religion", "May enhance a religion", "Removes other religions when spreading religion", "May found a religion", "May enhance a religion",
"May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]", "May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]",
"Unbuildable", "Religious Unit", "Hidden when religion is disabled", "Unbuildable", "Religious Unit", "Hidden when religion is disabled",
"Takes your religion over the one in their birth city"], "Takes your religion over the one in their birth city"],
"movement": 2, "movement": 2,
"religiousStrength": 1000 "religiousStrength": 1000
@ -1330,7 +1330,7 @@
"name": "Missionary", "name": "Missionary",
"unitType": "Civilian", "unitType": "Civilian",
"uniques": ["Can [Spread Religion] [2] times", "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there", "uniques": ["Can [Spread Religion] [2] times", "May enter foreign tiles without open borders, but loses [250] religious strength each turn it ends there",
"Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]", "Can be purchased with [Faith] [in all cities in which the majority religion is a major religion]",
"[-1] Sight", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"], "[-1] Sight", "Unbuildable", "Religious Unit", "Hidden when religion is disabled"],
"movement": 4, "movement": 4,
"religiousStrength": 1000 "religiousStrength": 1000

View File

@ -1267,6 +1267,7 @@ Units that consume this resource =
Can be built on = Can be built on =
or [terrainType] = or [terrainType] =
Can be constructed by = Can be constructed by =
Can be created instantly by =
Defence bonus = Defence bonus =
Movement cost = Movement cost =
for = for =
@ -1348,6 +1349,8 @@ Peace deal duration: [amount] turns⏳ =
Start year: [comment] = Start year: [comment] =
Pillaging this improvement yields [stats] = Pillaging this improvement yields [stats] =
Pillaging this improvement yields approximately [stats] = Pillaging this improvement yields approximately [stats] =
Needs removal of terrain features to be built =
# Policies # Policies

View File

@ -5,6 +5,7 @@ import com.unciv.UncivGame
import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueFlag
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaScreen.Companion.showReligionInCivilopedia
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -46,10 +47,7 @@ class Belief() : RulesetObject() {
companion object { companion object {
// private but potentially reusable, therefore not folded into getCivilopediaTextMatching // private but potentially reusable, therefore not folded into getCivilopediaTextMatching
private fun getBeliefsMatching(name: String, ruleset: Ruleset): Sequence<Belief> { private fun getBeliefsMatching(name: String, ruleset: Ruleset): Sequence<Belief> {
if (!UncivGame.isCurrentInitialized()) return sequenceOf() if (!showReligionInCivilopedia(ruleset)) return sequenceOf()
val gameInfo = UncivGame.Current.gameInfo
if (gameInfo == null) return sequenceOf()
if (!gameInfo.isReligionEnabled()) return sequenceOf()
return ruleset.beliefs.asSequence().map { it.value } return ruleset.beliefs.asSequence().map { it.value }
.filter { belief -> belief.uniqueObjects.any { unique -> unique.params.any { it == name } } .filter { belief -> belief.uniqueObjects.any { unique -> unique.params.any { it == name } }
} }

View File

@ -10,6 +10,7 @@ import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.squareBraceRegex import com.unciv.models.translations.squareBraceRegex
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaScreen.Companion.showReligionInCivilopedia
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.extensions.colorFromRGB import com.unciv.ui.utils.extensions.colorFromRGB
@ -182,11 +183,12 @@ class Nation : RulesetObject() {
} }
private fun getUniqueBuildingsText(ruleset: Ruleset) = sequence { private fun getUniqueBuildingsText(ruleset: Ruleset) = sequence {
val religionEnabled = showReligionInCivilopedia(ruleset)
for (building in ruleset.buildings.values) { for (building in ruleset.buildings.values) {
when { when {
building.uniqueTo != name -> continue building.uniqueTo != name -> continue
building.hasUnique(UniqueType.HiddenFromCivilopedia) -> continue building.hasUnique(UniqueType.HiddenFromCivilopedia) -> continue
UncivGame.Current.gameInfo != null && !UncivGame.Current.gameInfo!!.isReligionEnabled() && building.hasUnique(UniqueType.HiddenWithoutReligion) -> continue // This seems consistent with existing behaviour of CivilopediaScreen's init.<locals>.shouldBeDisplayed(), and Technology().getEnabledUnits(). Otherwise there are broken links in the Civilopedia (E.G. to "Pyramid" and "Shrine", from "The Maya"). religionEnabled && building.hasUnique(UniqueType.HiddenWithoutReligion) -> continue
} }
yield(FormattedLine("{${building.name}} -", link=building.makeLink())) yield(FormattedLine("{${building.name}} -", link=building.makeLink()))
if (building.replaces != null && ruleset.buildings.containsKey(building.replaces!!)) { if (building.replaces != null && ruleset.buildings.containsKey(building.replaces!!)) {

View File

@ -11,7 +11,9 @@ import com.unciv.models.ruleset.RulesetStatsObject
import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaScreen.Companion.showReligionInCivilopedia
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.utils.extensions.toPercent import com.unciv.ui.utils.extensions.toPercent
import com.unciv.ui.worldscreen.unit.UnitActions import com.unciv.ui.worldscreen.unit.UnitActions
@ -131,12 +133,16 @@ class TileImprovement : RulesetStatsObject() {
val statsDesc = cloneStats().toString() val statsDesc = cloneStats().toString()
if (statsDesc.isNotEmpty()) textList += FormattedLine(statsDesc) if (statsDesc.isNotEmpty()) textList += FormattedLine(statsDesc)
if (uniqueTo!=null) { if (uniqueTo != null) {
textList += FormattedLine() textList += FormattedLine()
textList += FormattedLine("Unique to [$uniqueTo]", link="Nation/$uniqueTo") textList += FormattedLine("Unique to [$uniqueTo]", link="Nation/$uniqueTo")
} }
if (terrainsCanBeBuiltOn.isNotEmpty()) { val constructorUnits = getConstructorUnits(ruleset)
val creatingUnits = getCreatingUnits(ruleset)
val creatorExists = constructorUnits.isNotEmpty() || creatingUnits.isNotEmpty()
if (creatorExists && terrainsCanBeBuiltOn.isNotEmpty()) {
textList += FormattedLine() textList += FormattedLine()
if (terrainsCanBeBuiltOn.size == 1) { if (terrainsCanBeBuiltOn.size == 1) {
with (terrainsCanBeBuiltOn.first()) { with (terrainsCanBeBuiltOn.first()) {
@ -151,15 +157,15 @@ class TileImprovement : RulesetStatsObject() {
} }
var addedLineBeforeResourceBonus = false var addedLineBeforeResourceBonus = false
for (resource in ruleset.tileResources.values.filter { it.isImprovedBy(name) }) { for (resource in ruleset.tileResources.values) {
if (resource.improvementStats == null) continue if (resource.improvementStats == null || !resource.isImprovedBy(name)) continue
if (!addedLineBeforeResourceBonus) { if (!addedLineBeforeResourceBonus) {
addedLineBeforeResourceBonus = true addedLineBeforeResourceBonus = true
textList += FormattedLine() textList += FormattedLine()
} }
val statsString = resource.improvementStats.toString() val statsString = resource.improvementStats.toString()
// Line intentionally modeled as UniqueType.Stats + ConditionalInTiles
textList += FormattedLine("[${statsString}] <in [${resource.name}] tiles>", link = "Resource/${resource.name}") textList += FormattedLine("[${statsString}] <in [${resource.name}] tiles>", link = resource.makeLink())
} }
if (techRequired != null) { if (techRequired != null) {
@ -173,16 +179,21 @@ class TileImprovement : RulesetStatsObject() {
textList += FormattedLine(unique) textList += FormattedLine(unique)
} }
// 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()) { if (isAncientRuinsEquivalent() && ruleset.ruinRewards.isNotEmpty()) {
val difficulty: String val difficulty = if (!UncivGame.isCurrentInitialized() || UncivGame.Current.gameInfo == null)
val religionEnabled: Boolean "Prince" // most factors == 1
if (UncivGame.isCurrentInitialized() && UncivGame.Current.gameInfo != null) { else UncivGame.Current.gameInfo!!.gameParameters.difficulty
difficulty = UncivGame.Current.gameInfo!!.gameParameters.difficulty val religionEnabled = showReligionInCivilopedia(ruleset)
religionEnabled = UncivGame.Current.gameInfo!!.isReligionEnabled()
} else {
difficulty = "Prince" // most factors == 1
religionEnabled = true
}
textList += FormattedLine() textList += FormattedLine()
textList += FormattedLine("The possible rewards are:") textList += FormattedLine("The possible rewards are:")
ruleset.ruinRewards.values.asSequence() ruleset.ruinRewards.values.asSequence()
@ -196,18 +207,75 @@ class TileImprovement : RulesetStatsObject() {
} }
} }
val unit = ruleset.units.asSequence().firstOrNull { if (creatorExists)
entry -> entry.value.uniques.any {
it.startsWith("Can construct [$name]")
}
}?.key
if (unit != null) {
textList += FormattedLine() textList += FormattedLine()
textList += FormattedLine("{Can be constructed by} {$unit}", link="Unit/$unit") 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) textList += Belief.getCivilopediaTextMatching(name, ruleset)
return textList return textList
} }
private 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.
// A kludge, but for display purposes the test below is meaningful enough.
if (hasUnique(UniqueType.Unbuildable)) return emptyList()
val canOnlyFilters = getMatchingUniques(UniqueType.CanOnlyBeBuiltOnTile)
.map { it.params[0].run { if (this == "Coastal") "Land" else this } }.toSet()
val cannotFilters = getMatchingUniques(UniqueType.CannotBuildOnTile).map { it.params[0] }.toSet()
val resourcesImprovedByThis = ruleset.tileResources.values.filter { it.isImprovedBy(name) }
val expandedCanBeBuiltOn = sequence {
yieldAll(terrainsCanBeBuiltOn)
yieldAll(terrainsCanBeBuiltOn.asSequence().mapNotNull { ruleset.terrains[it] }.flatMap { it.occursOn.asSequence() })
if (hasUnique(UniqueType.CanOnlyImproveResource))
yieldAll(resourcesImprovedByThis.asSequence().flatMap { it.terrainsCanBeFoundOn })
if (name.startsWith(Constants.remove)) name.removePrefix(Constants.remove).apply {
yield(this)
ruleset.terrains[this]?.occursOn?.let { yieldAll(it) }
ruleset.tileImprovements[this]?.terrainsCanBeBuiltOn?.let { yieldAll(it) }
}
}.filter { it !in cannotFilters }.toMutableSet()
val terrainsCanBeBuiltOnTypes = sequence {
yieldAll(expandedCanBeBuiltOn.asSequence()
.mapNotNull { ruleset.terrains[it]?.type })
yieldAll(TerrainType.values().asSequence()
.filter { it.name in expandedCanBeBuiltOn })
}.filter { it.name !in cannotFilters }.toMutableSet()
if (canOnlyFilters.isNotEmpty() && canOnlyFilters.intersect(expandedCanBeBuiltOn).isEmpty()) {
expandedCanBeBuiltOn.clear()
if (terrainsCanBeBuiltOnTypes.none { it.name in canOnlyFilters })
terrainsCanBeBuiltOnTypes.clear()
}
fun matchesBuildImprovementsFilter(filter: String) =
matchesFilter(filter) ||
filter in expandedCanBeBuiltOn ||
terrainsCanBeBuiltOnTypes.any { it.name == filter }
return ruleset.units.values.asSequence()
.filter { unit ->
turnsToBuild != 0
&& unit.getMatchingUniques(UniqueType.BuildImprovements, StateForConditionals.IgnoreConditionals)
.any { matchesBuildImprovementsFilter(it.params[0]) }
|| unit.hasUnique(UniqueType.CreateWaterImprovements)
&& terrainsCanBeBuiltOnTypes.contains(TerrainType.Water)
}.toList()
}
private fun getCreatingUnits(ruleset: Ruleset): List<BaseUnit> {
return ruleset.units.values.asSequence()
.filter { unit ->
unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit, StateForConditionals.IgnoreConditionals)
.any { it.params[0] == name }
}.toList()
}
} }

View File

@ -197,6 +197,9 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
state.ourCombatant != null && state.ourCombatant.getHealth() > condition.params[0].toInt() state.ourCombatant != null && state.ourCombatant.getHealth() > condition.params[0].toInt()
UniqueType.ConditionalBelowHP -> UniqueType.ConditionalBelowHP ->
state.ourCombatant != null && state.ourCombatant.getHealth() < condition.params[0].toInt() state.ourCombatant != null && state.ourCombatant.getHealth() < condition.params[0].toInt()
UniqueType.ConditionalHasNotUsedOtherActions ->
state.unit != null &&
state.unit.run { religiousActionsUnitCanDo().all { abilityUsesLeft[it] == maxAbilityUses[it] } }
UniqueType.ConditionalInTiles -> UniqueType.ConditionalInTiles ->
relevantTile?.matchesFilter(condition.params[0], state.civInfo) == true relevantTile?.matchesFilter(condition.params[0], state.civInfo) == true

View File

@ -405,6 +405,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
FoundCity("Founds a new city", UniqueTarget.Unit), FoundCity("Founds a new city", UniqueTarget.Unit),
ConstructImprovementConsumingUnit("Can construct [improvementName]", UniqueTarget.Unit), ConstructImprovementConsumingUnit("Can construct [improvementName]", UniqueTarget.Unit),
@Deprecated("as of 4.1.7", ReplaceWith("Can construct [improvementName] <if it hasn't used other actions yet>"))
CanConstructIfNoOtherActions("Can construct [improvementName] if it hasn't used other actions yet", UniqueTarget.Unit), CanConstructIfNoOtherActions("Can construct [improvementName] if it hasn't used other actions yet", UniqueTarget.Unit),
BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit), BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit),
CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit), CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit),
@ -697,6 +698,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
ConditionalAdjacentUnit("when adjacent to a [mapUnitFilter] unit", UniqueTarget.Conditional), ConditionalAdjacentUnit("when adjacent to a [mapUnitFilter] unit", UniqueTarget.Conditional),
ConditionalAboveHP("when above [amount] HP", UniqueTarget.Conditional), ConditionalAboveHP("when above [amount] HP", UniqueTarget.Conditional),
ConditionalBelowHP("when below [amount] HP", UniqueTarget.Conditional), ConditionalBelowHP("when below [amount] HP", UniqueTarget.Conditional),
ConditionalHasNotUsedOtherActions("if it hasn't used other actions yet", UniqueTarget.Conditional),
/////// tile conditionals /////// tile conditionals
ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional), ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional),
@ -1075,4 +1077,3 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// I didn't put this is a companion object because APPARENTLY doing that means you can't use it in the init function. // I didn't put this is a companion object because APPARENTLY doing that means you can't use it in the init function.
val numberRegex = Regex("\\d+$") // Any number of trailing digits val numberRegex = Regex("\\d+$") // Any number of trailing digits

View File

@ -16,6 +16,7 @@ import com.unciv.ui.utils.KeyCharAndCode
import com.unciv.ui.utils.extensions.surroundWithCircle import com.unciv.ui.utils.extensions.surroundWithCircle
import java.io.File import java.io.File
/** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */ /** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */
object CivilopediaImageGetters { object CivilopediaImageGetters {
private const val policyIconFolder = "PolicyIcons" private const val policyIconFolder = "PolicyIcons"

View File

@ -183,9 +183,8 @@ class CivilopediaScreen(
val imageSize = 50f val imageSize = 50f
globalShortcuts.add(KeyCharAndCode.BACK) { game.popScreen() } globalShortcuts.add(KeyCharAndCode.BACK) { game.popScreen() }
val curGameInfo = game.gameInfo val religionEnabled = showReligionInCivilopedia(ruleset)
val religionEnabled = if (curGameInfo != null) curGameInfo.isReligionEnabled() else ruleset.beliefs.isNotEmpty() val victoryTypes = game.gameInfo?.gameParameters?.victoryTypes ?: ruleset.victories.keys
val victoryTypes = if (curGameInfo != null) curGameInfo.gameParameters.victoryTypes else ruleset.victories.keys
fun shouldBeDisplayed(obj: IHasUniques): Boolean { fun shouldBeDisplayed(obj: IHasUniques): Boolean {
return when { return when {
@ -319,4 +318,15 @@ class CivilopediaScreen(
} }
override fun recreate(): BaseScreen = CivilopediaScreen(ruleset, currentCategory, currentEntry) override fun recreate(): BaseScreen = CivilopediaScreen(ruleset, currentCategory, currentEntry)
companion object {
/** Test whether to show Religion-specific items, does not require a game to be running */
// Here we decide whether to show Religion in Civilopedia from Main Menu (no gameInfo loaded)
fun showReligionInCivilopedia(ruleset: Ruleset? = null) = when {
UncivGame.isCurrentInitialized() && UncivGame.Current.gameInfo != null ->
UncivGame.Current.gameInfo!!.isReligionEnabled()
ruleset != null -> ruleset.beliefs.isNotEmpty()
else -> true
}
}
} }

View File

@ -11,12 +11,8 @@ import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.stats.INamed
import com.unciv.ui.civilopedia.MarkupRenderer.render
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.utils.extensions.addSeparator
import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.utils.extensions.toLabel
import com.unciv.utils.Log import com.unciv.utils.Log
import kotlin.math.max import kotlin.math.max
@ -164,13 +160,10 @@ class FormattedLine (
} }
return "" return ""
} }
private fun getCurrentRuleset(): Ruleset { private fun getCurrentRuleset() = when {
val gameInfo = UncivGame.Current.gameInfo !UncivGame.isCurrentInitialized() -> Ruleset()
return when { UncivGame.Current.gameInfo == null -> RulesetCache[BaseRuleset.Civ_V_Vanilla.fullName]!!
!UncivGame.isCurrentInitialized() -> Ruleset() else -> UncivGame.Current.gameInfo!!.ruleSet
gameInfo == null -> RulesetCache[BaseRuleset.Civ_V_Vanilla.fullName]!!
else -> gameInfo.ruleSet
}
} }
private fun initNamesCategoryMap(ruleSet: Ruleset): HashMap<String, CivilopediaCategories> { private fun initNamesCategoryMap(ruleSet: Ruleset): HashMap<String, CivilopediaCategories> {
//val startTime = System.nanoTime() //val startTime = System.nanoTime()
@ -341,165 +334,3 @@ class FormattedLine (
} }
} }
} }
/** Makes [renderer][render] available outside [ICivilopediaText] */
object MarkupRenderer {
/** Height of empty line (`FormattedLine()`) - about half a normal text line, independent of font size */
private const val emptyLineHeight = 10f
/** Default cell padding of non-empty lines */
private const val defaultPadding = 2.5f
/** Padding above a [separator][FormattedLine.separator] line */
private const val separatorTopPadding = 10f
/** Padding below a [separator][FormattedLine.separator] line */
private const val separatorBottomPadding = 10f
/**
* Build a Gdx [Table] showing [formatted][FormattedLine] [content][lines].
*
* @param labelWidth Available width needed for wrapping labels and [centered][FormattedLine.centered] attribute.
* @param padding Default cell padding (default 2.5f) to control line spacing
* @param iconDisplay Flag to omit link or all images (but not linking itself if linkAction is supplied)
* @param linkAction Delegate to call for internal links. Leave null to suppress linking.
*/
fun render(
lines: Collection<FormattedLine>,
labelWidth: Float = 0f,
padding: Float = defaultPadding,
iconDisplay: FormattedLine.IconDisplay = FormattedLine.IconDisplay.All,
linkAction: ((id: String) -> Unit)? = null
): Table {
val skin = BaseScreen.skin
val table = Table(skin).apply { defaults().pad(padding).align(Align.left) }
for (line in lines) {
if (line.isEmpty()) {
table.add().padTop(emptyLineHeight).row()
continue
}
if (line.separator) {
table.addSeparator(line.displayColor, 1, if (line.size == Int.MIN_VALUE) 2f else line.size.toFloat())
.pad(separatorTopPadding, 0f, separatorBottomPadding, 0f)
continue
}
val actor = line.render(labelWidth, iconDisplay)
if (line.linkType == FormattedLine.LinkType.Internal && linkAction != null)
actor.onClick {
linkAction(line.link)
}
else if (line.linkType == FormattedLine.LinkType.External)
actor.onClick {
Gdx.net.openURI(line.link)
}
if (labelWidth == 0f)
table.add(actor).align(line.align).row()
else
table.add(actor).width(labelWidth).align(line.align).row()
}
return table.apply { pack() }
}
}
/** Storage class for instantiation of the simplest form containing only the lines collection */
open class SimpleCivilopediaText(
override var civilopediaText: List<FormattedLine>
) : ICivilopediaText {
constructor(strings: Sequence<String>) : this(
strings.map { FormattedLine(it) }.toList())
constructor(first: Sequence<FormattedLine>, strings: Sequence<String>) : this(
(first + strings.map { FormattedLine(it) }).toList())
override fun makeLink() = ""
}
/** Addon common to most ruleset game objects managing civilopedia display
*
* ### Usage:
* 1. Let [Ruleset] object implement this (by inheriting and implementing class [ICivilopediaText])
* 2. Add `"civilopediaText": ["",],` in the json for these objects
* 3. Optionally override [getCivilopediaTextHeader] to supply a different header line
* 4. Optionally override [getCivilopediaTextLines] to supply automatic stuff like tech prerequisites, uniques, etc.
* 4. Optionally override [assembleCivilopediaText] to handle assembly of the final set of lines yourself.
*/
interface ICivilopediaText {
/** List of strings supporting simple [formatting rules][FormattedLine] that [CivilopediaScreen] can render.
* May later be merged with automatic lines generated by the deriving class
* through overridden [getCivilopediaTextHeader] and/or [getCivilopediaTextLines] methods.
*/
var civilopediaText: List<FormattedLine>
/** Generate header line from object metadata.
* Default implementation will take [INamed.name] and render it in 150% normal font size with an icon from [makeLink].
* @return A [FormattedLine] that will be inserted on top
*/
fun getCivilopediaTextHeader(): FormattedLine? =
if (this is INamed) FormattedLine(name, icon = makeLink(), header = 2)
else null
/** Generate automatic lines from object metadata.
*
* This function ***MUST not rely*** on [UncivGame.Current.gameInfo][UncivGame.gameInfo]
* **or** [UncivGame.Current.worldScreen][UncivGame.worldScreen] being initialized,
* this should be able to run from the main menu.
* (And the info displayed should be about the **ruleset**, not the player situation)
*
* Default implementation is empty - no need to call super in overrides.
*
* @param ruleset The current ruleset for the Civilopedia viewer
* @return A list of [FormattedLine]s that will be inserted before
* the first line of [civilopediaText] having a [link][FormattedLine.link]
*/
fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> = listOf()
/** Build a Gdx [Table] showing our [formatted][FormattedLine] [content][civilopediaText]. */
fun renderCivilopediaText (labelWidth: Float, linkAction: ((id: String)->Unit)? = null): Table {
return MarkupRenderer.render(civilopediaText, labelWidth, linkAction = linkAction)
}
/** Assemble json-supplied lines with automatically generated ones.
*
* The default implementation will insert [getCivilopediaTextLines] before the first [linked][FormattedLine.link] [civilopediaText] line and [getCivilopediaTextHeader] on top.
*
* @param ruleset The current ruleset for the Civilopedia viewer
* @return A new CivilopediaText instance containing original [civilopediaText] lines merged with those from [getCivilopediaTextHeader] and [getCivilopediaTextLines] calls.
*/
fun assembleCivilopediaText(ruleset: Ruleset): ICivilopediaText {
val outerLines = civilopediaText.iterator()
val newLines = sequence {
var middleDone = false
var outerNotEmpty = false
val header = getCivilopediaTextHeader()
if (header != null) {
yield(header)
yield(FormattedLine(separator = true))
}
while (outerLines.hasNext()) {
val next = outerLines.next()
if (!middleDone && !next.isEmpty() && next.linkType != FormattedLine.LinkType.None) {
middleDone = true
if (outerNotEmpty) yield(FormattedLine())
yieldAll(getCivilopediaTextLines(ruleset))
yield(FormattedLine())
}
outerNotEmpty = true
yield(next)
}
if (!middleDone) {
if (outerNotEmpty) yield(FormattedLine())
yieldAll(getCivilopediaTextLines(ruleset))
}
}
return SimpleCivilopediaText(newLines.toList())
}
/** Create the correct string for a Civilopedia link */
fun makeLink(): String
/** Overrides alphabetical sorting in Civilopedia
* @param ruleset The current ruleset in case the function needs to do lookups
*/
fun getSortGroup(ruleset: Ruleset): Int = 0
/** Overrides Icon used for Civilopedia entry list (where you select the instance)
* This will still be passed to the category-specific image getter.
*/
fun getIconName() = if (this is INamed) name else ""
}

View File

@ -0,0 +1,101 @@
package com.unciv.ui.civilopedia
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.stats.INamed
import com.unciv.UncivGame // Kdoc only
/** Addon common to most ruleset game objects managing civilopedia display
*
* ### Usage:
* 1. Let [Ruleset] object implement this (by inheriting and implementing class [ICivilopediaText])
* 2. Add `"civilopediaText": ["",…],` in the json for these objects
* 3. Optionally override [getCivilopediaTextHeader] to supply a different header line
* 4. Optionally override [getCivilopediaTextLines] to supply automatic stuff like tech prerequisites, uniques, etc.
* 4. Optionally override [assembleCivilopediaText] to handle assembly of the final set of lines yourself.
*/
interface ICivilopediaText {
/** List of strings supporting simple [formatting rules][FormattedLine] that [CivilopediaScreen] can render.
* May later be merged with automatic lines generated by the deriving class
* through overridden [getCivilopediaTextHeader] and/or [getCivilopediaTextLines] methods.
*/
var civilopediaText: List<FormattedLine>
/** Generate header line from object metadata.
* Default implementation will take [INamed.name] and render it in 150% normal font size with an icon from [makeLink].
* @return A [FormattedLine] that will be inserted on top
*/
fun getCivilopediaTextHeader(): FormattedLine? =
if (this is INamed) FormattedLine(name, icon = makeLink(), header = 2)
else null
/** Generate automatic lines from object metadata.
*
* This function ***MUST not rely*** on [UncivGame.Current.gameInfo][UncivGame.gameInfo]
* **or** [UncivGame.Current.worldScreen][UncivGame.worldScreen] being initialized,
* this should be able to run from the main menu.
* (And the info displayed should be about the **ruleset**, not the player situation)
*
* Default implementation is empty - no need to call super in overrides.
*
* @param ruleset The current ruleset for the Civilopedia viewer
* @return A list of [FormattedLine]s that will be inserted before
* the first line of [civilopediaText] having a [link][FormattedLine.link]
*/
fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> = listOf()
/** Build a Gdx [Table] showing our [formatted][FormattedLine] [content][civilopediaText]. */
fun renderCivilopediaText (labelWidth: Float, linkAction: ((id: String)->Unit)? = null): Table {
return MarkupRenderer.render(civilopediaText, labelWidth, linkAction = linkAction)
}
/** Assemble json-supplied lines with automatically generated ones.
*
* The default implementation will insert [getCivilopediaTextLines] before the first [linked][FormattedLine.link] [civilopediaText] line and [getCivilopediaTextHeader] on top.
*
* @param ruleset The current ruleset for the Civilopedia viewer
* @return A new CivilopediaText instance containing original [civilopediaText] lines merged with those from [getCivilopediaTextHeader] and [getCivilopediaTextLines] calls.
*/
fun assembleCivilopediaText(ruleset: Ruleset): ICivilopediaText {
val outerLines = civilopediaText.iterator()
val newLines = sequence {
var middleDone = false
var outerNotEmpty = false
val header = getCivilopediaTextHeader()
if (header != null) {
yield(header)
yield(FormattedLine(separator = true))
}
while (outerLines.hasNext()) {
val next = outerLines.next()
if (!middleDone && !next.isEmpty() && next.linkType != FormattedLine.LinkType.None) {
middleDone = true
if (outerNotEmpty) yield(FormattedLine())
yieldAll(getCivilopediaTextLines(ruleset))
yield(FormattedLine())
}
outerNotEmpty = true
yield(next)
}
if (!middleDone) {
if (outerNotEmpty) yield(FormattedLine())
yieldAll(getCivilopediaTextLines(ruleset))
}
}
return SimpleCivilopediaText(newLines.toList())
}
/** Create the correct string for a Civilopedia link */
fun makeLink(): String
/** Overrides alphabetical sorting in Civilopedia
* @param ruleset The current ruleset in case the function needs to do lookups
*/
fun getSortGroup(ruleset: Ruleset): Int = 0
/** Overrides Icon used for Civilopedia entry list (where you select the instance)
* This will still be passed to the category-specific image getter.
*/
fun getIconName() = if (this is INamed) name else ""
}

View File

@ -0,0 +1,65 @@
package com.unciv.ui.civilopedia
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.ui.utils.BaseScreen
import com.unciv.ui.utils.extensions.addSeparator
import com.unciv.ui.utils.extensions.onClick
/** Makes [renderer][render] available outside [ICivilopediaText] */
object MarkupRenderer {
/** Height of empty line (`FormattedLine()`) - about half a normal text line, independent of font size */
private const val emptyLineHeight = 10f
/** Default cell padding of non-empty lines */
private const val defaultPadding = 2.5f
/** Padding above a [separator][FormattedLine.separator] line */
private const val separatorTopPadding = 10f
/** Padding below a [separator][FormattedLine.separator] line */
private const val separatorBottomPadding = 10f
/**
* Build a Gdx [Table] showing [formatted][FormattedLine] [content][lines].
*
* @param labelWidth Available width needed for wrapping labels and [centered][FormattedLine.centered] attribute.
* @param padding Default cell padding (default 2.5f) to control line spacing
* @param iconDisplay Flag to omit link or all images (but not linking itself if linkAction is supplied)
* @param linkAction Delegate to call for internal links. Leave null to suppress linking.
*/
fun render(
lines: Collection<FormattedLine>,
labelWidth: Float = 0f,
padding: Float = defaultPadding,
iconDisplay: FormattedLine.IconDisplay = FormattedLine.IconDisplay.All,
linkAction: ((id: String) -> Unit)? = null
): Table {
val skin = BaseScreen.skin
val table = Table(skin).apply { defaults().pad(padding).align(Align.left) }
for (line in lines) {
if (line.isEmpty()) {
table.add().padTop(emptyLineHeight).row()
continue
}
if (line.separator) {
table.addSeparator(line.displayColor, 1, if (line.size == Int.MIN_VALUE) 2f else line.size.toFloat())
.pad(separatorTopPadding, 0f, separatorBottomPadding, 0f)
continue
}
val actor = line.render(labelWidth, iconDisplay)
if (line.linkType == FormattedLine.LinkType.Internal && linkAction != null)
actor.onClick {
linkAction(line.link)
}
else if (line.linkType == FormattedLine.LinkType.External)
actor.onClick {
Gdx.net.openURI(line.link)
}
if (labelWidth == 0f)
table.add(actor).align(line.align).row()
else
table.add(actor).width(labelWidth).align(line.align).row()
}
return table.apply { pack() }
}
}

View File

@ -0,0 +1,13 @@
package com.unciv.ui.civilopedia
/** Storage class for instantiation of the simplest form containing only the lines collection */
open class SimpleCivilopediaText(
override var civilopediaText: List<FormattedLine>
) : ICivilopediaText {
constructor(strings: Sequence<String>) : this(
strings.map { FormattedLine(it) }.toList())
constructor(first: Sequence<FormattedLine>, strings: Sequence<String>) : this(
(first + strings.map { FormattedLine(it) }).toList())
override fun makeLink() = ""
}