Civilopedia phase 8 - Nations and Promotions (#4657)

* Civilopedia phase 8 - Nations and Promotions

* Civilopedia phase 8 - Nations and Promotions - AS insubordination

* Civilopedia phase 8 - Nations and Promotions - OR template

* Civilopedia phase 8 - Nations and Promotions - proofread

* Civilopedia phase 8 - Nations and Promotions - revert OR
This commit is contained in:
SomeTroglodyte
2021-07-30 14:55:17 +02:00
committed by GitHub
parent 4bcf821879
commit 130c318ae0
12 changed files with 311 additions and 52 deletions

View File

@ -575,7 +575,7 @@ open class TileInfo {
}
fun toMarkup(viewingCiv: CivilizationInfo?): ArrayList<FormattedLine> {
val lineList = ArrayList<FormattedLine>() // more readable than StringBuilder, with same performance for our use-case
val lineList = ArrayList<FormattedLine>()
val isViewableToPlayer = viewingCiv == null || UncivGame.Current.viewEntireMapForDebug
|| viewingCiv.viewableTiles.contains(this)

View File

@ -3,8 +3,14 @@
import com.badlogic.gdx.graphics.Color
import com.unciv.Constants
import com.unciv.logic.civilization.CityStateType
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.stats.INamed
import com.unciv.models.translations.Translations
import com.unciv.models.translations.squareBraceRegex
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaText
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.colorFromRGB
@ -15,7 +21,7 @@ enum class VictoryType {
Scientific
}
class Nation : INamed {
class Nation : INamed, ICivilopediaText {
override lateinit var name: String
var leaderName = ""
@ -49,6 +55,8 @@ class Nation : INamed {
var adjective = ArrayList<String>()
*/
override var civilopediaText = listOf<FormattedLine>()
@Transient
private lateinit var outerColorObject: Color
fun getOuterColor(): Color = outerColorObject
@ -88,14 +96,10 @@ class Nation : INamed {
lateinit var cities: ArrayList<String>
fun getUniqueString(ruleset: Ruleset, forPickerScreen: Boolean = true): String {
/** Used only by NewGame Nation picker */
fun getUniqueString(ruleset: Ruleset): String {
val textList = ArrayList<String>()
if (leaderName.isNotEmpty() && !forPickerScreen) {
textList += getLeaderDisplayName().tr()
textList += ""
}
if (uniqueName != "") textList += uniqueName.tr() + ":"
if (uniqueText != "") {
textList += " " + uniqueText.tr()
@ -190,4 +194,152 @@ class Nation : INamed {
textList += " " + unique.tr()
}
}
override fun makeLink() = "Nation/$name"
override fun replacesCivilopediaDescription() = true
override fun hasCivilopediaTextLines() = true
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val textList = ArrayList<FormattedLine>()
if (leaderName.isNotEmpty()) {
textList += FormattedLine(extraImage = "LeaderIcons/$leaderName", imageSize = 200f)
textList += FormattedLine(getLeaderDisplayName(), centered = true, header = 3)
textList += FormattedLine()
}
if (uniqueName != "")
textList += FormattedLine("{$uniqueName}:", header = 4)
if (uniqueText != "") {
textList += FormattedLine(uniqueText, indent = 1)
} else {
uniqueObjects.forEach {
textList += FormattedLine(it)
}
textList += FormattedLine()
}
if (startBias.isNotEmpty()) {
startBias.withIndex().forEach {
// can be "Avoid []"
val link = if ('[' !in it.value) it.value
else squareBraceRegex.find(it.value)!!.groups[1]!!.value
textList += FormattedLine(
(if (it.index == 0) "[Start bias:] " else "") + it.value.tr(), // extra tr because tr cannot nest {[]}
link = "Terrain/$link",
indent = if (it.index == 0) 0 else 1)
}
textList += FormattedLine()
}
addUniqueBuildingsText(textList, ruleset)
addUniqueUnitsText(textList, ruleset)
addUniqueImprovementsText(textList, ruleset)
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) {
if (building.uniqueTo != name || "Will not be displayed in Civilopedia" in building.uniques) continue
textList += FormattedLine("{${building.name}} -", link=building.makeLink())
if (building.replaces != null && ruleset.buildings.containsKey(building.replaces!!)) {
val originalBuilding = ruleset.buildings[building.replaces!!]!!
textList += FormattedLine("Replaces [${originalBuilding.name}]", link=originalBuilding.makeLink(), indent=1)
val originalBuildingStatMap = originalBuilding.toHashMap()
for (stat in building.toHashMap())
if (stat.value != originalBuildingStatMap[stat.key])
textList += FormattedLine(
stat.key.toString().tr() + " " +
"[${stat.value.toInt()}] vs [${originalBuildingStatMap[stat.key]!!.toInt()}]".tr(),
indent=1)
for (unique in building.uniques.filter { it !in originalBuilding.uniques })
textList += FormattedLine(unique, indent=1)
if (building.maintenance != originalBuilding.maintenance)
textList += FormattedLine("{Maintenance} ".tr() + "[${building.maintenance}] vs [${originalBuilding.maintenance}]".tr(), indent=1)
if (building.cost != originalBuilding.cost)
textList += FormattedLine("{Cost} ".tr() + "[${building.cost}] vs [${originalBuilding.cost}]".tr(), indent=1)
if (building.cityStrength != originalBuilding.cityStrength)
textList += FormattedLine("{City strength} ".tr() + "[${building.cityStrength}] vs [${originalBuilding.cityStrength}]".tr(), indent=1)
if (building.cityHealth != originalBuilding.cityHealth)
textList += FormattedLine("{City health} ".tr() + "[${building.cityHealth}] vs [${originalBuilding.cityHealth}]".tr(), indent=1)
textList += FormattedLine()
} else if (building.replaces != null) {
textList += FormattedLine("Replaces [${building.replaces}], which is not found in the ruleset!", indent=1)
} else {
textList += FormattedLine(building.getShortDescription(ruleset), indent=1)
}
}
}
@JvmName("addUniqueUnitsText1")
private fun addUniqueUnitsText(textList: ArrayList<FormattedLine>, ruleset: Ruleset) {
for (unit in ruleset.units.values) {
if (unit.uniqueTo != name || "Will not be displayed in Civilopedia" in unit.uniques) continue
textList += FormattedLine("{${unit.name}} -", link="Unit/${unit.name}")
if (unit.replaces != null && ruleset.units.containsKey(unit.replaces!!)) {
val originalUnit = ruleset.units[unit.replaces!!]!!
textList += FormattedLine("Replaces [${originalUnit.name}]", link="Unit/${originalUnit.name}", indent=1)
if (unit.cost != originalUnit.cost)
textList += FormattedLine("{Cost} ".tr() + "[${unit.cost}] vs [${originalUnit.cost}]".tr(), indent=1)
if (unit.strength != originalUnit.strength)
textList += FormattedLine("${Fonts.strength} " + "[${unit.strength}] vs [${originalUnit.strength}]".tr(), indent=1)
if (unit.rangedStrength != originalUnit.rangedStrength)
textList += FormattedLine("${Fonts.rangedStrength} " + "[${unit.rangedStrength}] vs [${originalUnit.rangedStrength}]".tr(), indent=1)
if (unit.range != originalUnit.range)
textList += FormattedLine("${Fonts.range} " + "[${unit.range}] vs [${originalUnit.range}]".tr(), indent=1)
if (unit.movement != originalUnit.movement)
textList += FormattedLine("${Fonts.movement} " + "[${unit.movement}] vs [${originalUnit.movement}]".tr(), indent=1)
for (resource in originalUnit.getResourceRequirements().keys)
if (!unit.getResourceRequirements().containsKey(resource)) {
textList += FormattedLine("[$resource] not required", link="Resource/$resource", indent=1)
}
// This does not use the auto-linking FormattedLine(Unique) for two reasons:
// would look a little chaotic as unit uniques unlike most uniques are a HashSet and thus do not preserve order
// No .copy() factory on FormattedLine and no FormattedLine(Unique, all other val's) constructor either
for (unique in unit.uniques.filterNot { it in originalUnit.uniques })
textList += FormattedLine(unique, indent=1)
for (unique in originalUnit.uniques.filterNot { it in unit.uniques })
textList += FormattedLine("Lost ability".tr() + " (" + "vs [${originalUnit.name}]".tr() + "): " +
unique.tr(), indent=1)
for (promotion in unit.promotions.filter { it !in originalUnit.promotions }) {
val effect = ruleset.unitPromotions[promotion]!!.effect
// "{$promotion} ({$effect})" won't work as effect may contain [] and tr() does not support that kind of nesting
textList += FormattedLine(
if (effect.isEmpty()) promotion
else "${promotion.tr()} (${effect.tr()})",
link = "Promotion/$promotion", indent = 1 )
}
} else if (unit.replaces != null) {
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)
}
}
textList += FormattedLine()
}
}
@JvmName("addUniqueImprovementsText1")
private fun addUniqueImprovementsText(textList: ArrayList<FormattedLine>, ruleset: Ruleset) {
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
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)
}
}
for (unique in improvement.uniques)
textList += FormattedLine(unique, indent=1)
}
}
}

View File

@ -94,7 +94,7 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
stats += "$rangedStrength${Fonts.rangedStrength}"
stats += "$range${Fonts.range}"
}
if (movement != 0) stats += "$movement${Fonts.movement}"
if (movement != 0 && !movesLikeAirUnits()) stats += "$movement${Fonts.movement}"
if (cost != 0) stats += "{Cost}: $cost"
if (stats.isNotEmpty())
textList += FormattedLine(stats.joinToString(", "))

View File

@ -4,45 +4,136 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
class Promotion : INamed{
class Promotion : INamed, ICivilopediaText {
override lateinit var name: String
var prerequisites = listOf<String>()
var effect = ""
var unitTypes = listOf<String>() // The json parser wouldn't agree to deserialize this as a list of UnitTypes. =(
var uniques = ArrayList<String>()
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } + Unique(effect) }
private fun uniquesWithEffect() = sequence {
if (effect.isNotEmpty()) yield(effect)
yieldAll(uniques)
}
val uniqueObjects: List<Unique> by lazy { uniquesWithEffect().map { Unique(it) }.toList() }
fun getDescription(promotionsForUnitType: Collection<Promotion>, forCivilopedia:Boolean=false, ruleSet:Ruleset? = null):String {
// we translate it before it goes in to get uniques like "vs units in rough terrain" and after to get "vs city
val stringBuilder = StringBuilder()
override var civilopediaText = listOf<FormattedLine>()
for (unique in uniques + effect) {
stringBuilder.appendLine(unique.tr())
/** Used to describe a Promotion on the PromotionPickerScreen */
fun getDescription(promotionsForUnitType: Collection<Promotion>):String {
val textList = ArrayList<String>()
for (unique in uniquesWithEffect()) {
textList += unique.tr()
}
if(prerequisites.isNotEmpty()) {
if (prerequisites.isNotEmpty()) {
val prerequisitesString: ArrayList<String> = arrayListOf()
for (i in prerequisites.filter { promotionsForUnitType.any { promotion -> promotion.name == it } }) {
prerequisitesString.add(i.tr())
}
stringBuilder.appendLine("{Requires}: ".tr() + prerequisitesString.joinToString(" OR ".tr()))
textList += "{Requires}: ".tr() + prerequisitesString.joinToString(" OR ".tr())
}
if(forCivilopedia){
if (unitTypes.isNotEmpty()) {
val unitTypesString = unitTypes.joinToString(", ") { it.tr() }
stringBuilder.appendLine("Available for [$unitTypesString]".tr())
}
return textList.joinToString("\n")
}
if (ruleSet!=null) {
val freeforUnits = ruleSet.units.filter { it.value.promotions.contains(name) }
if (freeforUnits.isNotEmpty()) {
val freeforString = freeforUnits.map { it.value.name }.joinToString(", ") { it.tr() }
stringBuilder.appendLine("Free for [$freeforString]".tr())
override fun makeLink() = "Promotion/$name"
override fun hasCivilopediaTextLines() = true
override fun replacesCivilopediaDescription() = true
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val textList = ArrayList<FormattedLine>()
for (unique in uniqueObjects) {
textList += FormattedLine(unique)
}
val filteredPrerequisites = prerequisites.mapNotNull {
ruleset.unitPromotions[it]
}
if (filteredPrerequisites.isNotEmpty()) {
textList += FormattedLine()
if (filteredPrerequisites.size == 1) {
filteredPrerequisites[0].let {
textList += FormattedLine("Requires [${it.name}]", link = it.makeLink())
}
} else {
textList += FormattedLine("Requires at least one of the following:")
filteredPrerequisites.forEach {
textList += FormattedLine(it.name, link = it.makeLink())
}
}
}
return stringBuilder.toString()
if (unitTypes.isNotEmpty()) {
textList += FormattedLine()
// This separates the linkable (corresponding to a BaseUnit name) unitFilter entries
// from the others - `first` collects those for which the predicate is `true`.
val types = unitTypes.partition { it in ruleset.units }
if (unitTypes.size == 1) {
if (types.first.isNotEmpty())
types.first.first().let {
textList += FormattedLine("Available for [${it.tr()}]", link = "Unit/$it")
}
else
textList += FormattedLine("Available for [${types.second.first().tr()}]")
} else {
textList += FormattedLine("Available for:")
types.first.forEach {
textList += FormattedLine(it, indent = 1, link = "Unit/$it")
}
types.second.forEach {
textList += FormattedLine(it, indent = 1)
}
}
}
val freeForUnits = ruleset.units.filter { it.value.promotions.contains(name) }.map { it.key }
if (freeForUnits.isNotEmpty()) {
textList += FormattedLine()
if (freeForUnits.size == 1) {
freeForUnits[0].let {
textList += FormattedLine("Free for [$it]", link = "Unit/$it")
}
} else {
textList += FormattedLine("Free for:")
freeForUnits.forEach {
textList += FormattedLine(it, link = "Unit/$it")
}
}
}
val grantors = ruleset.buildings.values.filter {
building -> building.uniqueObjects.any {
it.placeholderText == "All newly-trained [] units [] receive the [] promotion"
&& it.params[2] == name
}
} + ruleset.terrains.values.filter {
// once that unique is parameterized, this will be the efficient order of checks
terrain -> terrain.uniques.any {
it == "Grants Rejuvenation (all healing effects doubled) to adjacent military land units for the rest of the game"
&& name == "Rejuvenation"
}
}
if (grantors.isNotEmpty()) {
textList += FormattedLine()
if (grantors.size == 1) {
grantors[0].let {
textList += FormattedLine("Granted by [${it.name}]", link = it.makeLink())
}
} else {
textList += FormattedLine("Granted by:")
grantors.forEach {
textList += FormattedLine(it.name, link = it.makeLink(), indent = 1)
}
}
}
return textList
}
}
}

View File

@ -242,7 +242,7 @@ class CivilopediaScreen(
.map {
CivilopediaEntry(
it.name,
it.getUniqueString(ruleset, false),
"",
CivilopediaCategories.Nation.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)
@ -260,7 +260,7 @@ class CivilopediaScreen(
.map {
CivilopediaEntry(
it.name,
it.getDescription(ruleset.unitPromotions.values, true, ruleset),
"",
CivilopediaCategories.Promotion.getImage?.invoke(it.name, imageSize),
(it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() }
)

View File

@ -138,6 +138,8 @@ class FormattedLine (
const val iconPad = 5f
/** Padding distance per [indent] level */
const val indentPad = 30f
/** Where indent==1 will be, measured as icon count */
const val indentOneAtNumIcons = 3
// Helper for constructor(Unique)
private fun getUniqueLink(unique: Unique): String {
@ -259,7 +261,9 @@ class FormattedLine (
centered -> -usedWidth
indent == 0 && iconCount == 0 -> 0f
indent == 0 -> iconPad
else -> (indent-1) * indentPad + 3 * minIconSize + 4 * iconPad - usedWidth
noLinkImages -> indent * indentPad - usedWidth
else -> (indent-1) * indentPad +
indentOneAtNumIcons * (minIconSize + iconPad) + iconPad - usedWidth
}
val label = if (fontSize == defaultSize && labelColor == defaultColor) textToDisplay.toLabel()
else textToDisplay.toLabel(labelColor,fontSize)