Civilopedia Phase X (#5003)

* Civilopedia Phase X - Show Policies

* Civilopedia Phase X - Show City States

* Civilopedia - Loop-driven init and Cleanup

* Civilopedia - City States

* Civilopedia Phase X - Remove spurious comments
This commit is contained in:
SomeTroglodyte 2021-08-27 15:24:23 +02:00 committed by GitHub
parent 30e5ac3665
commit bf2ee91b67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 333 additions and 283 deletions

View File

@ -161,7 +161,6 @@ Declare Protection of [cityStateName]? =
Build [improvementName] on [resourceName] (200 Gold) =
Gift Improvement =
Cultured =
Maritime =
Mercantile =
@ -175,9 +174,12 @@ Personality =
Influence =
Reach 30 for friendship. =
Reach highest influence above 60 for alliance. =
When Friends: =
When Allies: =
The unique luxury is one of: =
# Trades
Trade =
Offer trade =
Retract offer =
@ -989,9 +991,10 @@ Adopt free policy =
Unlocked at =
Gain 2 free technologies =
All policies adopted =
# Religions
Policy branch: [branchName] =
# Religions
Choose an Icon and name for your Religion =
Choose a [beliefType] belief! =
Found [religionName] =

View File

@ -1,10 +1,10 @@
package com.unciv.logic.civilization
enum class CityStateType {
Cultured,
Maritime,
Mercantile,
Militaristic
enum class CityStateType(val color: String = "") {
Cultured("#8b60ff"),
Maritime("#38ff70"),
Mercantile("#ffd800"),
Militaristic("#ff0000")
}
enum class CityStatePersonality {

View File

@ -2,6 +2,7 @@ package com.unciv.logic.civilization
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.metadata.BASE_GAME_DURATION_TURNS
import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.stats.Stat
import com.unciv.models.stats.StatMap
@ -221,7 +222,7 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
if (civInfo.hasUnique("Provides 1 happiness per 2 additional social policies adopted")) {
if (!statMap.containsKey("Policies")) statMap["Policies"] = 0f
statMap["Policies"] = statMap["Policies"]!! +
civInfo.policies.getAdoptedPolicies().count { !it.endsWith("Complete") } / 2
civInfo.policies.getAdoptedPolicies().count { !Policy.isBranchCompleteByName(it) } / 2
}
var happinessPerNaturalWonder = 1f

View File

@ -204,7 +204,7 @@ class CivilizationInfo {
fun isAlive(): Boolean = !isDefeated()
fun hasEverBeenFriendWith(otherCiv: CivilizationInfo): Boolean = getDiplomacyManager(otherCiv).everBeenFriends()
fun hasMetCivTerritory(otherCiv: CivilizationInfo): Boolean = otherCiv.getCivTerritory().any { it in exploredTiles }
fun getCompletedPolicyBranchesCount(): Int = policies.adoptedPolicies.count { it.endsWith("Complete") }
fun getCompletedPolicyBranchesCount(): Int = policies.adoptedPolicies.count { Policy.isBranchCompleteByName(it) }
private fun getCivTerritory() = cities.asSequence().flatMap { it.tiles.asSequence() }
fun victoryType(): VictoryType {
@ -490,7 +490,7 @@ class CivilizationInfo {
RankingType.Force -> units.sumBy { it.baseUnit.strength }
RankingType.Happiness -> getHappiness()
RankingType.Technologies -> tech.researchedTechnologies.size
RankingType.Culture -> policies.adoptedPolicies.count { !it.endsWith("Complete") }
RankingType.Culture -> policies.adoptedPolicies.count { !Policy.isBranchCompleteByName(it) }
}
}
@ -520,7 +520,7 @@ class CivilizationInfo {
policies.civInfo = this
if (policies.adoptedPolicies.size > 0 && policies.numberOfAdoptedPolicies == 0)
policies.numberOfAdoptedPolicies = policies.adoptedPolicies.count { !it.endsWith("Complete") }
policies.numberOfAdoptedPolicies = policies.adoptedPolicies.count { !Policy.isBranchCompleteByName(it) }
policies.setTransients()
questManager.civInfo = this

View File

@ -2,6 +2,7 @@ package com.unciv.logic.civilization
import com.unciv.logic.map.MapSize
import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.Policy.PolicyBranchType
import com.unciv.models.ruleset.UniqueMap
import com.unciv.models.ruleset.UniqueTriggerActivation
import com.unciv.models.translations.equalsPlaceholderText
@ -124,7 +125,7 @@ class PolicyManager {
*/
fun isAdoptable(policy: Policy, checkEra: Boolean = true): Boolean {
if (isAdopted(policy.name)) return false
if (policy.name.endsWith("Complete")) return false
if (policy.policyBranchType == PolicyBranchType.BranchComplete) return false
if (!getAdoptedPolicies().containsAll(policy.requires!!)) return false
if (checkEra && civInfo.gameInfo.ruleSet.getEraNumber(policy.branch.era) > civInfo.getEraNumber()) return false
if (policy.uniqueObjects.any { it.placeholderText == "Incompatible with []" && adoptedPolicies.contains(it.params[0]) }) return false

View File

@ -2,8 +2,10 @@ package com.unciv.models.ruleset
import com.unciv.UncivGame
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
import java.text.Collator
import java.util.ArrayList
class Belief : INamed, ICivilopediaText, IHasUniques {
@ -15,12 +17,16 @@ class Belief : INamed, ICivilopediaText, IHasUniques {
override var civilopediaText = listOf<FormattedLine>()
override fun makeLink() = "Belief/$name"
override fun getCivilopediaTextHeader() = FormattedLine(name, icon = makeLink(), header = 2, color = if (type == BeliefType.None) "#e34a2b" else "")
override fun replacesCivilopediaDescription() = true
override fun hasCivilopediaTextLines() = true
override fun getSortGroup(ruleset: Ruleset) = type.ordinal
override fun getIconName() = if (type == BeliefType.None) "Religion" else type.name
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val textList = ArrayList<FormattedLine>()
textList += FormattedLine("{Type}: $type", color=type.color )
if (type != BeliefType.None)
textList += FormattedLine("{Type}: $type", color = type.color )
uniqueObjects.forEach {
textList += FormattedLine(it)
}
@ -49,7 +55,17 @@ class Belief : INamed, ICivilopediaText, IHasUniques {
val matchingBeliefs = getBeliefsMatching(name, ruleset)
if (matchingBeliefs.none()) return@sequence
if (withSeeAlso) { yield(FormattedLine()); yield(FormattedLine("{See also}:")) }
yieldAll(matchingBeliefs.map { FormattedLine(it.name, link=it.makeLink(), indent = 1) })
yieldAll(matchingBeliefs.map { FormattedLine(it.name, link = it.makeLink(), indent = 1) })
}
fun getCivilopediaReligionEntry(ruleset: Ruleset) = Belief().apply {
name = "Religions"
val lines = ArrayList<FormattedLine>()
lines += FormattedLine(separator = true)
ruleset.religions.sortedWith(compareBy(Collator.getInstance(), { it.tr() })).forEach {
lines += FormattedLine(it, icon = "Belief/$it")
}
civilopediaText = lines
}
}
}

View File

@ -31,9 +31,27 @@ class Era : INamed {
repeat(startingMilitaryUnitCount) {startingUnits.add(startingMilitaryUnit)}
return startingUnits
}
fun getColor(): Color {
if (iconRGB == null) return Color.WHITE.cpy()
return colorFromRGB(iconRGB!![0], iconRGB!![1], iconRGB!![2])
}
fun getHexColor() = "#" + getColor().toString().substring(0,6)
companion object {
// User for CS bonuses in case the Eras file is missing (legacy mods)
fun getLegacyCityStateBonusEra(eraNumber: Int) = Era().apply {
val cultureBonus = if(eraNumber in 0..1) 3 else if (eraNumber in 2..3) 6 else 13
val happinessBonus = if(eraNumber in 0..1) 2 else 3
friendBonus[CityStateType.Militaristic.name] = arrayListOf("Provides military units every [20] turns")
friendBonus[CityStateType.Cultured.name] = arrayListOf("Provides [$cultureBonus] [Culture] per turn")
friendBonus[CityStateType.Mercantile.name] = arrayListOf("Provides [$happinessBonus] Happiness")
friendBonus[CityStateType.Maritime.name] = arrayListOf("Provides [2] [Food] [in capital]")
allyBonus[CityStateType.Militaristic.name] = arrayListOf("Provides military units every [17] turns")
allyBonus[CityStateType.Cultured.name] = arrayListOf("Provides [${cultureBonus*2}] [Culture] per turn")
allyBonus[CityStateType.Mercantile.name] = arrayListOf("Provides [$happinessBonus] Happiness", "Provides a unique luxury")
allyBonus[CityStateType.Maritime.name] = arrayListOf("Provides [2] [Food] [in capital]", "Provides [1] [Food] [in all cities]")
}
}
}

View File

@ -2,6 +2,7 @@
import com.badlogic.gdx.graphics.Color
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.CityStateType
import com.unciv.models.stats.INamed
import com.unciv.models.translations.squareBraceRegex
@ -195,8 +196,15 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
override fun makeLink() = "Nation/$name"
override fun replacesCivilopediaDescription() = true
override fun hasCivilopediaTextLines() = true
override fun getSortGroup(ruleset: Ruleset) = when {
isCityState() -> 1
isBarbarian() -> 9
else -> 0
}
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
if (isCityState()) return getCityStateInfo(ruleset)
val textList = ArrayList<FormattedLine>()
if (leaderName.isNotEmpty()) {
@ -235,6 +243,50 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
return textList
}
private fun getCityStateInfo(ruleset: Ruleset): List<FormattedLine> {
val textList = ArrayList<FormattedLine>()
textList += FormattedLine("Type: [$cityStateType]", header = 4, color = cityStateType!!.color)
val viewingCiv = UncivGame.Current.gameInfo.currentPlayerCiv
val era = viewingCiv.getEraObject() ?: Era.getLegacyCityStateBonusEra(viewingCiv.getEraNumber())
var showResources = false
val friendBonus = era.friendBonus[cityStateType!!.name]
if (friendBonus != null && friendBonus.isNotEmpty()) {
textList += FormattedLine()
textList += FormattedLine("When Friends: ")
friendBonus.forEach {
textList += FormattedLine(Unique(it), indent = 1)
if (it == "Provides a unique luxury") showResources = true
}
}
val allyBonus = era.allyBonus[cityStateType!!.name]
if (allyBonus != null && allyBonus.isNotEmpty()) {
textList += FormattedLine()
textList += FormattedLine("When Allies: ")
allyBonus.forEach {
textList += FormattedLine(Unique(it), indent = 1)
if (it == "Provides a unique luxury") showResources = true
}
}
if (showResources) {
val allMercantileResources = ruleset.tileResources.values
.filter { it.unique == "Can only be created by Mercantile City-States" }
if (allMercantileResources.isNotEmpty()) {
textList += FormattedLine()
textList += FormattedLine("The unique luxury is one of:")
allMercantileResources.forEach {
textList += FormattedLine(it.name, it.makeLink(), indent = 1)
}
}
}
// personality is not a nation property, it gets assigned to the civ randomly
return textList
}
@JvmName("addUniqueBuildingsText1") // These overloads are too similar - but I hope to remove the other one soon
private fun addUniqueBuildingsText(textList: ArrayList<FormattedLine>, ruleset: Ruleset) {
for (building in ruleset.buildings.values) {
@ -305,10 +357,10 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
link = "Promotion/$promotion", indent = 1 )
}
} else if (unit.replaces != null) {
textList += FormattedLine("Replaces [${unit.replaces}], which is not found in the ruleset!", indent=1)
textList += FormattedLine("Replaces [${unit.replaces}], which is not found in the ruleset!", indent = 1)
} else {
textList += unit.getCivilopediaTextLines(ruleset).map {
FormattedLine(it.text, link=it.link, indent = it.indent + 1, color=it.color)
FormattedLine(it.text, link = it.link, indent = it.indent + 1, color = it.color)
}
}
@ -321,16 +373,16 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
for (improvement in ruleset.tileImprovements.values) {
if (improvement.uniqueTo != name ) continue
textList += FormattedLine(improvement.name, link="Improvement/${improvement.name}")
textList += FormattedLine(improvement.clone().toString(), indent=1) // = (improvement as Stats).toString minus import plus copy overhead
textList += FormattedLine(improvement.name, link = "Improvement/${improvement.name}")
textList += FormattedLine(improvement.clone().toString(), indent = 1) // = (improvement as Stats).toString minus import plus copy overhead
if (improvement.terrainsCanBeBuiltOn.isNotEmpty()) {
improvement.terrainsCanBeBuiltOn.withIndex().forEach {
textList += 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)
link = "Terrain/${it.value}", indent = if (it.index == 0) 1 else 2)
}
}
for (unique in improvement.uniques)
textList += FormattedLine(unique, indent=1)
textList += FormattedLine(unique, indent = 1)
}
}

View File

@ -1,8 +1,11 @@
package com.unciv.models.ruleset
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
open class Policy : INamed, IHasUniques {
open class Policy : INamed, IHasUniques, ICivilopediaText {
lateinit var branch: PolicyBranch // not in json - added in gameBasics
override lateinit var name: String
@ -12,6 +15,94 @@ open class Policy : INamed, IHasUniques {
var column: Int = 0
var requires: ArrayList<String>? = null
override var civilopediaText = listOf<FormattedLine>()
/** 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.
* To keep the hardcoding in one place, this is public and should be used instead of duplicating it.
*/
fun isBranchCompleteByName(name: String) = name.endsWith(branchCompleteSuffix)
}
override fun toString() = name
/** Used in PolicyPickerScreen to display Policy properties */
fun getDescription(): String {
val policyText = ArrayList<String>()
policyText += name
policyText += uniques
if (policyBranchType != PolicyBranchType.BranchComplete) {
policyText += if (requires!!.isNotEmpty())
"Requires [" + requires!!.joinToString { it.tr() } + "]"
else
"{Unlocked at} {${branch.era}}"
}
return policyText.joinToString("\n") { it.tr() }
}
override fun makeLink() = "Policy/$name"
override fun replacesCivilopediaDescription() = true
override fun hasCivilopediaTextLines() = true
override fun getSortGroup(ruleset: Ruleset) =
ruleset.getEraNumber(branch.era) * 10000 +
ruleset.policyBranches.keys.indexOf(branch.name) * 100 +
policyBranchType.ordinal
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val lineList = ArrayList<FormattedLine>()
lineList += if (this is PolicyBranch) {
val eraColor = ruleset.eras[era]?.getHexColor() ?: ""
FormattedLine("{Unlocked at} {${branch.era}}", header = 4, color = eraColor)
} else {
FormattedLine("Policy branch: [${branch.name}]", link = branch.makeLink())
}
if (policyBranchType != PolicyBranchType.BranchComplete && requires != null && requires!!.isNotEmpty()) {
lineList += FormattedLine()
if (requires!!.size == 1)
requires!!.first().let { lineList += FormattedLine("Requires: [$it]", link = "Policy/$it") }
else {
lineList += FormattedLine("Requires all of the following:")
requires!!.forEach {
lineList += FormattedLine(it, link = "Policy/$it")
}
}
}
val leadsTo = ruleset.policies.values.filter {
it.requires != null && name in it.requires!!
&& it.policyBranchType != PolicyBranchType.BranchComplete
}
if (leadsTo.isNotEmpty()) {
lineList += FormattedLine()
if (leadsTo.size == 1)
leadsTo.first().let { lineList += FormattedLine("Leads to [${it.name}]", link = it.makeLink()) }
else {
lineList += FormattedLine("Leads to:")
leadsTo.forEach {
lineList += FormattedLine(it.name, link = it.makeLink(), indent = 1)
}
}
}
if (uniques.isNotEmpty()) {
lineList += FormattedLine()
for (unique in uniqueObjects) lineList += FormattedLine(unique)
}
return lineList
}
}

View File

@ -4,12 +4,12 @@ import com.unciv.models.stats.INamed
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
class RuinReward : INamed, ICivilopediaText {
class RuinReward : INamed, ICivilopediaText, IHasUniques {
override lateinit var name: String // Displayed in Civilopedia!
val notification: String = ""
val uniques: List<String> = listOf()
override var uniques = ArrayList<String>()
@delegate:Transient // Defense in depth against mad modders
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
override val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
val excludedDifficulties: List<String> = listOf()
val weight: Int = 1
val color: String = "" // For Civilopedia

View File

@ -206,7 +206,7 @@ class Ruleset {
if (policy.requires == null) policy.requires = arrayListOf(branch.name)
policies[policy.name] = policy
}
branch.policies.last().name = branch.name + " Complete"
branch.policies.last().name = branch.name + Policy.branchCompleteSuffix
}
}

View File

@ -150,7 +150,8 @@ class Technology: INamed, ICivilopediaText, IHasUniques {
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val lineList = ArrayList<FormattedLine>()
lineList += FormattedLine(era(), header = 3, color = "#8080ff")
val eraColor = ruleset.eras[era()]?.getHexColor() ?: ""
lineList += FormattedLine(era(), header = 3, color = eraColor)
lineList += FormattedLine()
lineList += FormattedLine("{Cost}: $cost${Fonts.science}")

View File

@ -127,21 +127,12 @@ class Terrain : NamedStats(), ICivilopediaText {
if (defenceBonus != 0f)
textList += FormattedLine("{Defence bonus}: ${(defenceBonus * 100).toInt()}%")
val seeAlso = (
//todo: Could vastly be simplified using upcoming INonPerpetualConstruction
ruleset.buildings.values.asSequence()
.filter {
building -> building.uniqueObjects.any {
unique -> unique.params.any { it == name }
}
} +
ruleset.units.values.asSequence()
.filter {
unit -> unit.uniqueObjects.any {
unique -> unique.params.any { it == name }
}
val seeAlso = (ruleset.buildings.values.asSequence() + ruleset.units.values.asSequence())
.filter {
construction -> construction.uniqueObjects.any {
unique -> unique.params.any { it == name }
}
).map { FormattedLine(it.name, it.makeLink(), indent=1) } +
}.map { FormattedLine(it.name, it.makeLink(), indent=1) } +
Belief.getCivilopediaTextMatching(name, ruleset, false)
if (seeAlso.any()) {
textList += FormattedLine()

View File

@ -3,7 +3,6 @@ package com.unciv.models.ruleset.tile
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.RuinsManager.RuinsManager
import com.unciv.models.ruleset.Belief
import com.unciv.logic.map.RoadStatus
import com.unciv.models.ruleset.IHasUniques

View File

@ -200,10 +200,10 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
var productionCost = cost.toFloat()
if (civInfo.isCityState())
productionCost *= 1.5f
if (civInfo.isPlayerCivilization())
productionCost *= civInfo.getDifficulty().unitCostModifier
else
productionCost *= civInfo.gameInfo.getDifficulty().aiUnitCostModifier
productionCost *= if (civInfo.isPlayerCivilization())
civInfo.getDifficulty().unitCostModifier
else
civInfo.gameInfo.getDifficulty().aiUnitCostModifier
productionCost *= civInfo.gameInfo.gameParameters.gameSpeed.modifier
return productionCost.toInt()
}
@ -298,8 +298,8 @@ class BaseUnit : INamed, INonPerpetualConstruction, 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 (uniques.contains(Constants.settlerUnique) && civInfo.isCityState()) return "No settler for city-states"
@ -322,7 +322,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
override fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean): Boolean {
val civInfo = cityConstructions.cityInfo.civInfo
val unit = civInfo.placeUnitNearTile(cityConstructions.cityInfo.location, name)
if (unit == null) return false // couldn't place the unit, so there's actually no unit =(
?: return false // couldn't place the unit, so there's actually no unit =(
//movement penalty
if (wasBought && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought"))

View File

@ -16,7 +16,7 @@ enum class Stat(
Culture(NotificationIcon.Culture, UncivSound.Paper, Fonts.culture),
Happiness(NotificationIcon.Happiness, UncivSound.Click, Fonts.happiness),
Faith(NotificationIcon.Faith, UncivSound.Choir, Fonts.faith);
companion object {
val statsUsableToBuy = listOf(Gold, Food, Science, Culture, Faith)
}

View File

@ -17,6 +17,7 @@ import java.io.File
/** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */
object CivilopediaImageGetters {
private const val policyIconFolder = "PolicyIcons"
private const val policyInnerSize = 0.25f
// Todo: potential synergy with map editor
fun terrainImage(terrain: Terrain, ruleset: Ruleset, imageSize: Float): Actor {
@ -48,7 +49,7 @@ object CivilopediaImageGetters {
val construction = { name: String, size: Float ->
ImageGetter.getConstructionImage(name)
.surroundWithCircle(size, color = Color.WHITE)
.surroundWithCircle(size)
}
val improvement = { name: String, size: Float ->
ImageGetter.getImprovementIcon(name, size)
@ -59,8 +60,16 @@ object CivilopediaImageGetters {
else ImageGetter.getNationIndicator(nation, size)
}
val policy = { name: String, size: Float ->
ImageGetter.getImage(policyIconFolder + File.separator + name)
.apply { setSize(size,size) }
// policy branch start and complete have no icons but are linked -> nonexistence must be passed down
val fileName = policyIconFolder + File.separator + name
if (ImageGetter.imageExists(fileName))
ImageGetter.getImage(fileName)
.apply {
setSize(size * policyInnerSize,size * policyInnerSize)
color = Color.BROWN
}
.surroundWithCircle(size)
else null
}
val resource = { name: String, size: Float ->
ImageGetter.getResourceImage(name, size)
@ -97,6 +106,7 @@ object CivilopediaImageGetters {
/** Enum used as keys for Civilopedia "pages" (categories).
*
* Note names are singular on purpose - a "link" allows both key and label
* Order of values determines ordering of the categories in the Civilopedia top bar
*
* @param label Translatable caption for the Civilopedia button
*/
@ -114,10 +124,10 @@ enum class CivilopediaCategories (
Nation ("Nations", false, CivilopediaImageGetters.nation ),
Technology ("Technologies", false, CivilopediaImageGetters.technology ),
Promotion ("Promotions", false, CivilopediaImageGetters.promotion ),
Policy ("Policies", true, CivilopediaImageGetters.policy ),
Policy ("Policies", false, CivilopediaImageGetters.policy ),
Belief("Religions and Beliefs", false, CivilopediaImageGetters.belief ),
Tutorial ("Tutorials", false, null ),
Difficulty ("Difficulty levels", false, null ),
Belief("Religions and Beliefs", false, CivilopediaImageGetters.belief)
;
companion object {

View File

@ -6,6 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.VictoryType
@ -77,7 +78,7 @@ class CivilopediaScreen(
/** Select a specified category
* @param name Category name or label
*/
fun selectCategory(name: String) {
private fun selectCategory(name: String) {
val category = CivilopediaCategories.fromLink(name)
?: return // silently ignore unknown category names in links
selectCategory(category)
@ -86,7 +87,7 @@ class CivilopediaScreen(
/** Select a specified category - unselects entry, rebuilds left side buttons.
* @param category Category key
*/
fun selectCategory(category: CivilopediaCategories) {
private fun selectCategory(category: CivilopediaCategories) {
currentCategory = category
entrySelectTable.clear()
entryIndex.clear()
@ -173,139 +174,53 @@ class CivilopediaScreen(
onBackButtonClicked { UncivGame.Current.setWorldScreen() }
val hideReligionItems = !game.gameInfo.hasReligionEnabled()
val noCulturalVictory = VictoryType.Cultural !in game.gameInfo.gameParameters.victoryTypes
categoryToEntries[CivilopediaCategories.Building] = ruleset.buildings.values
.filter { shouldBeDisplayed(it.uniqueObjects)
&& !it.isAnyWonder() }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Building.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Wonder] = ruleset.buildings.values
.filter { shouldBeDisplayed(it.uniqueObjects)
&& it.isAnyWonder() }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Wonder.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Resource] = ruleset.tileResources.values
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Resource.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Terrain] = ruleset.terrains.values
.filter { shouldBeDisplayed(it.uniqueObjects) }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Terrain.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Improvement] = ruleset.tileImprovements.values
.filter { shouldBeDisplayed(it.uniqueObjects) }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Improvement.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Unit] = ruleset.units.values
.filter { shouldBeDisplayed(it.uniqueObjects) }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Unit.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Nation] = ruleset.nations.values
.filter { shouldBeDisplayed(it.uniqueObjects) && it.isMajorCiv() }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Nation.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Technology] = ruleset.technologies.values
.filter { shouldBeDisplayed(it.uniqueObjects) }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Technology.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
categoryToEntries[CivilopediaCategories.Promotion] = ruleset.unitPromotions.values
.filter { shouldBeDisplayed(it.uniqueObjects) }
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Promotion.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
}
fun shouldBeDisplayed(uniqueObjects: List<Unique>): Boolean {
val uniques = uniqueObjects.map { it.placeholderText }
categoryToEntries[CivilopediaCategories.Tutorial] = tutorialController.getCivilopediaTutorials()
.map {
CivilopediaEntry(
it.name,
"",
// CivilopediaCategories.Tutorial.getImage?.invoke(it.name, imageSize)
flavour = it
)
}
return Constants.hideFromCivilopediaUnique !in uniques
&& !(hideReligionItems && Constants.hiddenWithoutReligionUnique in uniques)
&& !(uniqueObjects.filter { unique -> unique.placeholderText == "Hidden when [] Victory is disabled"}.any {
unique -> !game.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0] ))
})
// Deprecated since 3.15.14
&& !(noCulturalVictory && "Hidden when cultural victory is disabled" in uniques)
//
}
categoryToEntries[CivilopediaCategories.Difficulty] = ruleset.difficulties.values
.map {
CivilopediaEntry(
it.name,
"",
// CivilopediaCategories.Difficulty.getImage?.invoke(it.name, imageSize)
flavour = (it as? ICivilopediaText)
)
}
fun getCategoryIterator(category: CivilopediaCategories): Collection<ICivilopediaText> =
when (category) {
CivilopediaCategories.Building -> ruleset.buildings.values.filter { !it.isAnyWonder() }
CivilopediaCategories.Wonder -> ruleset.buildings.values.filter { it.isAnyWonder() }
CivilopediaCategories.Resource -> ruleset.tileResources.values
CivilopediaCategories.Terrain -> ruleset.terrains.values
CivilopediaCategories.Improvement -> ruleset.tileImprovements.values
CivilopediaCategories.Unit -> ruleset.units.values
CivilopediaCategories.Nation -> ruleset.nations.values.filter { !it.isSpectator() }
CivilopediaCategories.Technology -> ruleset.technologies.values
CivilopediaCategories.Promotion -> ruleset.unitPromotions.values
CivilopediaCategories.Policy -> ruleset.policies.values
CivilopediaCategories.Tutorial -> tutorialController.getCivilopediaTutorials()
CivilopediaCategories.Difficulty -> ruleset.difficulties.values
CivilopediaCategories.Belief -> (ruleset.beliefs.values.asSequence() +
Belief.getCivilopediaReligionEntry(ruleset)).toList()
}
for (loopCategory in CivilopediaCategories.values()) {
if (loopCategory.hide) continue
if (hideReligionItems && loopCategory == CivilopediaCategories.Belief) continue
categoryToEntries[loopCategory] =
getCategoryIterator(loopCategory)
.filter { it.getUniquesAsObjects()?.let { uniques -> shouldBeDisplayed(uniques) } ?: true }
.map { CivilopediaEntry(
(it as INamed).name, "",
loopCategory.getImage?.invoke(it.getIconName(), imageSize),
it.takeUnless { ct -> ct.isCivilopediaTextEmpty() },
sortBy = it.getSortGroup(ruleset)
) }
}
if (!hideReligionItems)
categoryToEntries[CivilopediaCategories.Belief] = (
ruleset.beliefs.values.asSequence()
.map {
CivilopediaEntry(
it.name,
"",
CivilopediaCategories.Belief.getImage?.invoke(it.type.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() },
sortBy = it.type.ordinal
)
} + CivilopediaEntry(
"Religions",
"",
CivilopediaCategories.Belief.getImage?.invoke("Religion", imageSize),
getReligionText(),
sortBy = -1
)
).toList()
val buttonTable = Table()
buttonTable.pad(15f)
buttonTable.defaults().pad(10f)
@ -364,31 +279,6 @@ class CivilopediaScreen(
selectEntry(link, noScrollAnimation = true)
}
private fun getReligionText(): ICivilopediaText {
val lines = ArrayList<FormattedLine>()
lines += FormattedLine("Religions", header=2, color="#e34a2b")
lines += FormattedLine(separator=true)
ruleset.religions.sortedWith(compareBy(Collator.getInstance(), { it.tr() })).forEach {
lines += FormattedLine(it, icon="Belief/$it")
}
return SimpleCivilopediaText(lines, true)
}
private fun shouldBeDisplayed(uniqueObjects: List<Unique>): Boolean {
val uniques = uniqueObjects.map { it.placeholderText }
val hideReligionItems = !game.gameInfo.hasReligionEnabled()
val noCulturalVictory = VictoryType.Cultural !in game.gameInfo.gameParameters.victoryTypes
return Constants.hideFromCivilopediaUnique !in uniques
&& !(hideReligionItems && Constants.hiddenWithoutReligionUnique in uniques)
&& !(uniqueObjects.filter { unique -> unique.placeholderText == "Hidden when [] Victory is disabled"}.any {
unique -> !game.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0] ))
})
// Deprecated since 3.15.14
&& !(noCulturalVictory && "Hidden when cultural victory is disabled" in uniques)
//
}
override fun resize(width: Int, height: Int) {
if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) {
game.setScreen(CivilopediaScreen(game.worldScreen.gameInfo.ruleSet, currentCategory, currentEntry))

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.UncivGame
import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.IHasUniques
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.Unique
@ -141,7 +142,7 @@ class FormattedLine (
/** Padding distance per [indent] level */
const val indentPad = 30f
/** Where indent==1 will be, measured as icon count */
const val indentOneAtNumIcons = 3
const val indentOneAtNumIcons = 2
private var rulesetCachedInNameMap: Ruleset? = null
// 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.
@ -378,11 +379,6 @@ object MarkupRenderer {
}
}
/** Storage class for interface [ICivilopediaText] for use as base class */
@Deprecated("As of 3.16.1, use ICivilopediaText directly please")
abstract class CivilopediaText : ICivilopediaText {
override var civilopediaText = listOf<FormattedLine>()
}
/** Storage class for instantiation of the simplest form containing only the lines collection */
open class SimpleCivilopediaText(
override var civilopediaText: List<FormattedLine>,
@ -401,7 +397,7 @@ open class SimpleCivilopediaText(
/** Addon common to most ruleset game objects managing civilopedia display
*
* ### Usage:
* 1. Let [Ruleset] object implement this (e.g. by inheriting class [CivilopediaText] or adding var [civilopediaText] itself)
* 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.
@ -484,4 +480,20 @@ interface ICivilopediaText {
/** Create the correct string for a Civilopedia link */
fun makeLink(): String
/** This just marshals access to the uniques so they can be queried as part of the ICivilopediaText interface.
* Used exclusively by CivilopediaScreen, named to avoid JVM signature confusion
* (a getUniqueObjects exists in IHasUniques and most civilopedia objects will implement that interface)
*/
fun getUniquesAsObjects() = (this as? IHasUniques)?.uniqueObjects
/** 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

@ -9,6 +9,7 @@ import com.unciv.models.Tutorial
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.PolicyBranch
import com.unciv.models.ruleset.Policy.PolicyBranchType
import com.unciv.models.translations.tr
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.WorldScreen
@ -110,7 +111,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
|| worldScreen.viewingCiv.isSpectator() // viewingCiv var points to selectedCiv in case of spectator
|| viewingCiv.isDefeated()
|| viewingCiv.policies.isAdopted(policy.name)
|| policy.name.endsWith("Complete")
|| policy.policyBranchType == PolicyBranchType.BranchComplete
|| !viewingCiv.policies.isAdoptable(policy)
|| !viewingCiv.policies.canAdoptPolicy()) {
rightSideButton.disable()
@ -123,17 +124,8 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
game.setScreen(PolicyPickerScreen(worldScreen))
}
pickedPolicy = policy
val policyText = mutableListOf<String>()
policyText += policy.name
policyText += policy.uniques
if (!policy.name.endsWith("Complete")) {
if (policy.requires!!.isNotEmpty())
policyText += "Requires [" + policy.requires!!.joinToString { it.tr() } + "]"
else
policyText += "{Unlocked at} {" + policy.branch.era + "}"
}
descriptionLabel.setText(policyText.joinToString("\n") { it.tr() })
descriptionLabel.setText(policy.getDescription())
}
/**
@ -151,7 +143,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
var currentColumn = 1
val branchTable = Table()
for (policy in branch.policies) {
if (policy.name.endsWith("Complete")) continue
if (policy.policyBranchType == PolicyBranchType.BranchComplete) continue
if (policy.row > currentRow) {
branchTable.row().pad(2.5f)
currentRow++

View File

@ -16,6 +16,7 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.models.ruleset.Era
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Quest
import com.unciv.models.ruleset.tile.ResourceType
@ -158,67 +159,38 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
else -> "Reach highest influence above 60 for alliance."
}
diplomacyTable.add(getRelationshipTable(otherCivDiplomacyManager)).row()
if (nextLevelString != "") {
if (nextLevelString.isNotEmpty()) {
diplomacyTable.add(nextLevelString.toLabel()).row()
}
var friendBonusText = "When Friends: ".tr()
val eraInfo = viewingCiv.getEraObject()
val friendBonuses =
if (eraInfo == null) null
else eraInfo.friendBonus[otherCiv.cityStateType.name]
friendBonusText +=
if (friendBonuses != null) {
friendBonuses.joinToString(separator = ", ") { it.tr() }
} else {
// Deprecated, assume Civ V values for compatibility
val cultureBonus = if(viewingCiv.getEraNumber() in 0..1) "3" else if (viewingCiv.getEraNumber() in 2..3) "6" else "13"
val happinessBonus = if(viewingCiv.getEraNumber() in 0..1) "2" else "3"
when (otherCiv.cityStateType) {
CityStateType.Militaristic -> "Provides military units every [20] turns".tr()
CityStateType.Cultured -> ("Provides [" + cultureBonus + "] [Culture] per turn").tr()
CityStateType.Mercantile -> ("Provides [" + happinessBonus + "] Happiness").tr()
CityStateType.Maritime -> "Provides [2] [Food] [in capital]".tr()
}
}
val eraInfo = viewingCiv.getEraObject() ?: Era.getLegacyCityStateBonusEra(viewingCiv.getEraNumber())
var friendBonusText = "{When Friends:} ".tr()
val friendBonuses = eraInfo.friendBonus[otherCiv.cityStateType.name]
friendBonusText += friendBonuses?.joinToString(separator = ", ") { it.tr() } ?: ""
var allyBonusText = "When Allies: "
val allyBonuses =
if (eraInfo == null) null
else eraInfo.allyBonus[otherCiv.cityStateType.name]
if (allyBonuses != null) {
allyBonusText += allyBonuses.joinToString(separator = ", ") { it.tr() }
} else {
// Deprecated, assume Civ V values for compatibility
val cultureBonus = if(viewingCiv.getEraNumber() in 0..1) "6" else if (viewingCiv.getEraNumber() in 2..3) "12" else "26"
val happinessBonus = if(viewingCiv.getEraNumber() in 0..1) "2" else "3"
allyBonusText += when (otherCiv.cityStateType) {
CityStateType.Militaristic -> "Provides military units every [20] turns".tr()
CityStateType.Cultured -> ("Provides [" + cultureBonus + "] [Culture] per turn").tr()
CityStateType.Mercantile -> ("Provides [" + happinessBonus + "] Happiness").tr() + ", " + "Provides a unique luxury".tr()
CityStateType.Maritime -> "Provides [2] [Food] [in capital]".tr() + ", " + "Provides [1] [Food] [in all cities]".tr()
}
}
var allyBonusText = "{When Allies:} ".tr()
val allyBonuses = eraInfo.allyBonus[otherCiv.cityStateType.name]
allyBonusText += allyBonuses?.joinToString(separator = ", ") { it.tr() } ?: ""
val friendBonusLabelColor: Color
if (otherCivDiplomacyManager.relationshipLevel() >= RelationshipLevel.Friend) {
friendBonusLabelColor = Color.GREEN
val relationLevel = otherCivDiplomacyManager.relationshipLevel()
if (relationLevel >= RelationshipLevel.Friend) {
// RelationshipChange = Ally -> Friend or Friend -> Favorable
val turnsToRelationshipChange = otherCivDiplomacyManager.getTurnsToRelationshipChange()
diplomacyTable.add("Relationship changes in another [$turnsToRelationshipChange] turns".toLabel())
.row()
} else
friendBonusLabelColor = Color.GRAY
}
val friendBonusLabelColor = if (relationLevel >= RelationshipLevel.Friend) Color.GREEN else Color.GRAY
val friendBonusLabel = friendBonusText.toLabel(friendBonusLabelColor)
.apply { setAlignment(Align.center) }
diplomacyTable.add(friendBonusLabel).row()
val allyBonusLabelColor = if (otherCivDiplomacyManager.relationshipLevel() == RelationshipLevel.Ally) Color.GREEN else Color.GRAY
val allyBonusLabelColor = if (relationLevel == RelationshipLevel.Ally) Color.GREEN else Color.GRAY
val allyBonusLabel = allyBonusText.toLabel(allyBonusLabelColor)
.apply { setAlignment(Align.center) }
diplomacyTable.add(allyBonusLabel).row()
return diplomacyTable
}

View File

@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.tr
@ -202,11 +203,11 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
policyVictoryColumn.add("Branches completed".toLabel()).row()
policyVictoryColumn.addSeparator()
data class civToBranchesCompleted(val civ: CivilizationInfo, val branchesCompleted: Int)
data class CivToBranchesCompleted(val civ: CivilizationInfo, val branchesCompleted: Int)
val civsToBranchesCompleted =
majorCivs.map { civToBranchesCompleted(it, it.policies.adoptedPolicies.count { pol -> pol.endsWith("Complete") }) }
.sortedByDescending { it.branchesCompleted }
val civsToBranchesCompleted = majorCivs.map {
CivToBranchesCompleted(it, it.policies.adoptedPolicies.count { pol -> Policy.isBranchCompleteByName(pol) })
}.sortedByDescending { it.branchesCompleted }
for (entry in civsToBranchesCompleted) {
val civToBranchesHaveCompleted = EmpireOverviewScreen.getCivGroup(entry.civ, " - " + entry.branchesCompleted, playerCivInfo)