Improve completeness and consistency of Technology descriptions (#8860)

This commit is contained in:
SomeTroglodyte 2023-03-11 19:04:12 +01:00 committed by GitHub
parent 2f00aa2af1
commit de12f671a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 414 additions and 349 deletions

View File

@ -1,18 +1,11 @@
package com.unciv.models.ruleset.tech
import com.unciv.GUI
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetObject
import com.unciv.models.ruleset.RulesetStatsObject
import com.unciv.models.ruleset.unique.UniqueFlag
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.translations.tr
import com.unciv.ui.components.Fonts
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.objectdescriptions.TechnologyDescriptions
class Technology: RulesetObject() {
@ -29,261 +22,14 @@ class Technology: RulesetObject() {
fun isContinuallyResearchable() = hasUnique(UniqueType.ResearchableMultipleTimes)
/**
* Textual description used in TechPickerScreen and AlertPopup(AlertType.TechResearched)
*/
fun getDescription(ruleset: Ruleset): String {
val lineList = ArrayList<String>() // more readable than StringBuilder, with same performance for our use-case
for (unique in uniques) lineList += unique.tr()
for (improvement in ruleset.tileImprovements.values) {
for (unique in improvement.getMatchingUniques(UniqueType.Stats)) {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += "[${unique.params[0]}] from every [${improvement.name}]"
}
for (unique in improvement.getMatchingUniques(UniqueType.ImprovementStatsOnTile)) {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += "[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles"
}
}
val viewingCiv = GUI.getViewingPlayer()
val enabledUnits = getEnabledUnits(ruleset, viewingCiv)
if (enabledUnits.any()) {
lineList += "{Units enabled}: "
for (unit in enabledUnits)
lineList += "" + unit.name.tr() + " (" + unit.getShortDescription() + ")"
}
val enabledBuildings = getEnabledBuildings(ruleset, viewingCiv)
val regularBuildings = enabledBuildings.filter { !it.isAnyWonder() }
if (regularBuildings.any()) {
lineList += "{Buildings enabled}: "
for (building in regularBuildings)
lineList += "" + building.name.tr() + " (" + building.getShortDescription() + ")"
}
val wonders = enabledBuildings.filter { it.isAnyWonder() }
if (wonders.any()) {
lineList += "{Wonders enabled}: "
for (wonder in wonders)
lineList += "" + wonder.name.tr() + " (" + wonder.getShortDescription() + ")"
}
for (obj in getObsoletedObjects(ruleset, viewingCiv))
lineList += "[${obj.name}] obsoleted"
for (resource in ruleset.tileResources.values.asSequence().filter { it.revealedBy == name }
.map { it.name })
lineList += "Reveals [$resource] on the map"
val tileImprovements = ruleset.tileImprovements.values.filter { it.techRequired == name }
if (tileImprovements.isNotEmpty())
lineList += "{Tile improvements enabled}: " + tileImprovements.joinToString { it.name.tr() }
return lineList.joinToString("\n") { it.tr() }
}
/**
* Returns a Sequence of [Building]s enabled by this Technology, filtered for [civInfo]'s uniques,
* nuclear weapons and religion settings, and without those expressly hidden from Civilopedia.
*/
// Used for Civilopedia, Alert and Picker, so if any of these decide to ignore the "Will not be displayed in Civilopedia" unique this needs refactoring
fun getEnabledBuildings(ruleset: Ruleset, civInfo: Civilization?) = getFilteredBuildings(ruleset, civInfo)
{ it.requiredTech == name }
/**
* Returns a Sequence of [Building]s obsoleted by this Technology, filtered for [civInfo]'s uniques,
* nuclear weapons and religion settings, and without those expressly hidden from Civilopedia.
*/
// Used for Civilopedia, Alert and Picker, so if any of these decide to ignore the "Will not be displayed in Civilopedia" unique this needs refactoring
fun getObsoletedObjects(ruleset: Ruleset, civInfo: Civilization?): Sequence<RulesetStatsObject> =
(getFilteredBuildings(ruleset, civInfo){true}
+ ruleset.tileResources.values.asSequence()
+ ruleset.tileImprovements.values.filter {
it.uniqueTo == null || it.uniqueTo == civInfo?.civName
}).filter { obj: RulesetStatsObject ->
obj.getMatchingUniques(UniqueType.ObsoleteWith).any { it.params[0] == name }
}
// Helper: common filtering for both getEnabledBuildings and getObsoletedBuildings, difference via predicate parameter
private fun getFilteredBuildings(
ruleset: Ruleset,
civInfo: Civilization?,
predicate: (Building)->Boolean
): Sequence<Building> {
val (nuclearWeaponsEnabled, religionEnabled) = getNukeAndReligionSwitches(civInfo)
return ruleset.buildings.values.asSequence()
.filter {
predicate(it) // expected to be the most selective, thus tested first
&& (it.uniqueTo == civInfo?.civName || it.uniqueTo == null && civInfo?.getEquivalentBuilding(it) == it)
&& (nuclearWeaponsEnabled || !it.hasUnique(UniqueType.EnablesNuclearWeapons))
&& (religionEnabled || !it.hasUnique(UniqueType.HiddenWithoutReligion))
&& !it.hasUnique(UniqueType.HiddenFromCivilopedia)
}
}
private fun getNukeAndReligionSwitches(civInfo: Civilization?): Pair<Boolean, Boolean> {
if (civInfo == null) return true to true
return civInfo.gameInfo.run { gameParameters.nuclearWeaponsEnabled to isReligionEnabled() }
}
/**
* Returns a Sequence of [BaseUnit]s enabled by this Technology, filtered for [civInfo]'s uniques,
* nuclear weapons and religion settings, and without those expressly hidden from Civilopedia.
*/
// Used for Civilopedia, Alert and Picker, so if any of these decide to ignore the "Will not be displayed in Civilopedia"/HiddenFromCivilopedia unique this needs refactoring
fun getEnabledUnits(ruleset: Ruleset, civInfo: Civilization?): Sequence<BaseUnit> {
val (nuclearWeaponsEnabled, religionEnabled) = getNukeAndReligionSwitches(civInfo)
return ruleset.units.values.asSequence()
.filter {
it.requiredTech == name
&& (it.uniqueTo == civInfo?.civName || it.uniqueTo == null && civInfo?.getEquivalentUnit(it) == it)
&& (nuclearWeaponsEnabled || !it.isNuclearWeapon())
&& (religionEnabled || !it.hasUnique(UniqueType.HiddenWithoutReligion))
&& !it.hasUnique(UniqueType.HiddenFromCivilopedia)
}
}
/** Get improvements related to this tech by a unique */
private fun getSeeAlsoObjects(ruleset: Ruleset) =
// This is a band-aid to clarify the relation Offshore platform - Refrigeration. A generic variant
// covering all mentions in uniques in all ruleset objects would be possible here but - overkill for now.
ruleset.tileImprovements.values
.asSequence()
.filter { improvement ->
improvement.uniqueObjects.any {
unique -> unique.conditionals.any {
(it.isOfType(UniqueType.ConditionalTech) || it.isOfType(UniqueType.ConditionalNoTech))
&& it.params[0] == name
}
}
}
/** Get Civilization-specific description for TechPicker or AlertType.TechResearched */
fun getDescription(viewingCiv: Civilization) =
TechnologyDescriptions.getDescription(this, viewingCiv)
override fun makeLink() = "Technology/$name"
override fun getCivilopediaTextLines(ruleset: Ruleset): List<FormattedLine> {
val lineList = ArrayList<FormattedLine>()
val eraColor = ruleset.eras[era()]?.getHexColor() ?: ""
lineList += FormattedLine(era(), header = 3, color = eraColor)
lineList += FormattedLine()
lineList += FormattedLine("{Cost}: $cost${Fonts.science}")
if (prerequisites.isNotEmpty()) {
lineList += FormattedLine()
if (prerequisites.size == 1)
prerequisites.first().let { lineList += FormattedLine("Required tech: [$it]", link = "Technology/$it") }
else {
lineList += FormattedLine("Requires all of the following:")
prerequisites.forEach {
lineList += FormattedLine(it, link = "Technology/$it")
}
}
}
val leadsTo = ruleset.technologies.values.filter { name in it.prerequisites }
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())
}
}
}
if (uniques.isNotEmpty()) {
lineList += FormattedLine()
uniqueObjects.forEach {
if (!it.hasFlag(UniqueFlag.HiddenToUsers))
lineList += FormattedLine(it)
}
}
for (improvement in ruleset.tileImprovements.values)
for (unique in improvement.uniqueObjects) {
if (unique.isOfType(UniqueType.Stats)) {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}]",
link = improvement.makeLink())
} else if (unique.placeholderText == "[] on [] tiles") {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles",
link = improvement.makeLink())
}
}
val viewingCiv = if (GUI.isWorldLoaded()) GUI.getViewingPlayer() else null
val enabledUnits = getEnabledUnits(ruleset, viewingCiv)
if (enabledUnits.any()) {
lineList += FormattedLine()
lineList += FormattedLine("{Units enabled}:")
for (unit in enabledUnits)
lineList += FormattedLine(unit.name.tr() + " (" + unit.getShortDescription() + ")", link = unit.makeLink())
}
val enabledBuildings = getEnabledBuildings(ruleset, viewingCiv)
.partition { it.isAnyWonder() }
if (enabledBuildings.first.isNotEmpty()) {
lineList += FormattedLine()
lineList += FormattedLine("{Wonders enabled}:")
for (wonder in enabledBuildings.first)
lineList += FormattedLine(wonder.name.tr() + " (" + wonder.getShortDescription() + ")", link = wonder.makeLink())
}
if (enabledBuildings.second.isNotEmpty()) {
lineList += FormattedLine()
lineList += FormattedLine("{Buildings enabled}:")
for (building in enabledBuildings.second)
lineList += FormattedLine(building.name.tr() + " (" + building.getShortDescription() + ")", link = building.makeLink())
}
val obsoletedObjects = getObsoletedObjects(ruleset, viewingCiv)
if (obsoletedObjects.any()) {
lineList += FormattedLine()
obsoletedObjects.forEach {
lineList += FormattedLine("[${it.name}] obsoleted", link = it.makeLink())
}
}
val revealedResources = ruleset.tileResources.values.asSequence().filter { it.revealedBy == name }
if (revealedResources.any()) {
lineList += FormattedLine()
revealedResources.forEach {
lineList += FormattedLine("Reveals [${it.name}] on the map", link = it.makeLink())
}
}
val tileImprovements = ruleset.tileImprovements.values.asSequence().filter { it.techRequired == name }
if (tileImprovements.any()) {
lineList += FormattedLine()
lineList += FormattedLine("{Tile improvements enabled}:")
tileImprovements.forEach {
lineList += FormattedLine(it.name, link = it.makeLink())
}
}
val seeAlsoObjects = getSeeAlsoObjects(ruleset)
if (seeAlsoObjects.any()) {
lineList += FormattedLine()
lineList += FormattedLine("{See also}:")
seeAlsoObjects.forEach {
lineList += FormattedLine(it.name, link = it.makeLink())
}
}
return lineList
}
override fun getCivilopediaTextLines(ruleset: Ruleset) =
TechnologyDescriptions.getCivilopediaTextLines(this, ruleset)
fun matchesFilter(filter: String): Boolean {
return when (filter) {

View File

@ -16,6 +16,7 @@ import com.unciv.ui.components.extensions.filterAndLogic
import com.unciv.ui.components.extensions.getNeedMoreAmountString
import com.unciv.ui.components.extensions.toPercent
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.objectdescriptions.BaseUnitDescriptions
import kotlin.math.pow
// This is BaseUnit because Unit is already a base Kotlin class and to avoid mixing the two up

View File

@ -3,7 +3,7 @@ package com.unciv.models.ruleset.unit
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetObject
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unit.BaseUnitDescriptions.getUnitTypeCivilopediaTextLines
import com.unciv.ui.objectdescriptions.BaseUnitDescriptions.getUnitTypeCivilopediaTextLines
enum class UnitLayer { // The layer in which the unit moves

View File

@ -1,9 +1,12 @@
package com.unciv.models.ruleset.unit
package com.unciv.ui.objectdescriptions
import com.unciv.logic.city.City
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.unique.UniqueFlag
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.UnitMovementType
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.screens.civilopediascreen.FormattedLine

View File

@ -0,0 +1,370 @@
package com.unciv.ui.objectdescriptions
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetStatsObject
import com.unciv.models.ruleset.tech.Technology
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueFlag
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.translations.tr
import com.unciv.ui.components.Fonts
import com.unciv.ui.components.extensions.center
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.civilopediascreen.ICivilopediaText
import com.unciv.ui.screens.pickerscreens.TechButton
object TechnologyDescriptions {
//region Methods called from Technology
/**
* Textual description used in TechPickerScreen and AlertPopup(AlertType.TechResearched) -
* Civilization always known and description tailored to
*/
fun getDescription(technology: Technology, viewingCiv: Civilization): String = technology.run {
val ruleset = viewingCiv.gameInfo.ruleset
val lineList = ArrayList<String>() // more readable than StringBuilder, with same performance for our use-case
for (unique in uniques) lineList += unique
lineList.addAll(
getAffectedImprovements(name, ruleset)
.filter { it.improvement.uniqueTo == null || it.improvement.uniqueTo == viewingCiv.civName }
.map { it.getText() }
)
val enabledUnits = getEnabledUnits(name, ruleset, viewingCiv)
if (enabledUnits.any()) {
lineList += "{Units enabled}: "
for (unit in enabledUnits)
lineList += "" + unit.name.tr() + " (" + unit.getShortDescription() + ")"
}
val (wonders, regularBuildings) = getEnabledBuildings(name, ruleset, viewingCiv)
.partition { it.isAnyWonder() }
if (regularBuildings.isNotEmpty()) {
lineList += "{Buildings enabled}: "
for (building in regularBuildings)
lineList += "" + building.name.tr() + " (" + building.getShortDescription() + ")"
}
if (wonders.isNotEmpty()) {
lineList += "{Wonders enabled}: "
for (wonder in wonders)
lineList += "" + wonder.name.tr() + " (" + wonder.getShortDescription() + ")"
}
for (obj in getObsoletedObjects(name, ruleset, viewingCiv))
lineList += "[${obj.name}] obsoleted"
val resourcesRevealed = ruleset.tileResources.values.asSequence()
.filter { it.revealedBy == name }
.map { it.name }
for (resource in resourcesRevealed)
lineList += "Reveals [$resource] on the map"
val tileImprovements = ruleset.tileImprovements.values.asSequence()
.filter { it.techRequired == name }
.filter { it.uniqueTo == null || it.uniqueTo == viewingCiv.civName }
.toList()
if (tileImprovements.isNotEmpty())
lineList += "{Tile improvements enabled}: " + tileImprovements.joinToString { it.name.tr() }
return lineList.joinToString("\n") { it.tr() }
}
/**
* Gets icons to display on a [TechButton] - all should be also described in [getDescription]
*/
fun getTechEnabledIcons(tech: Technology, viewingCiv: Civilization, techIconSize: Float) = sequence {
val ruleset = viewingCiv.gameInfo.ruleset
val techName = tech.name
val civName = viewingCiv.civName
for (unit in getEnabledUnits(techName, ruleset, viewingCiv)) {
yield(ImageGetter.getConstructionPortrait(unit.name, techIconSize))
}
for (building in getEnabledBuildings(techName, ruleset, viewingCiv)) {
yield(ImageGetter.getConstructionPortrait(building.name, techIconSize))
}
yieldAll(
getObsoletedObjects(techName, ruleset, viewingCiv)
.mapNotNull { it.getObsoletedIcon(techIconSize) }
)
for (resource in ruleset.tileResources.values.filter { it.revealedBy == techName }) {
yield(ImageGetter.getResourcePortrait(resource.name, techIconSize))
}
for (improvement in ruleset.tileImprovements.values.asSequence()
.filter { it.techRequired == techName }
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
yield(ImageGetter.getImprovementPortrait(improvement.name, techIconSize))
}
for (improvement in ruleset.tileImprovements.values.asSequence()
.filter { it.uniqueObjects.any { u -> u.allParams.contains(techName) } }
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
yield(ImageGetter.getUniquePortrait(improvement.name, techIconSize))
}
for (unique in tech.uniqueObjects) {
yield(
when {
unique.isOfType(UniqueType.EnablesCivWideStatProduction) ->
ImageGetter.getConstructionPortrait(unique.params[0], techIconSize)
else ->
ImageGetter.getUniquePortrait(unique.text, techIconSize)
}
)
}
}
/**
* Implementation of [ICivilopediaText.getCivilopediaTextLines]
*/
fun getCivilopediaTextLines(technology: Technology, ruleset: Ruleset): List<FormattedLine> = technology.run {
val lineList = ArrayList<FormattedLine>()
val eraColor = ruleset.eras[era()]?.getHexColor() ?: ""
lineList += FormattedLine(era(), header = 3, color = eraColor)
lineList += FormattedLine()
lineList += FormattedLine("{Cost}: $cost${Fonts.science}")
if (prerequisites.isNotEmpty()) {
lineList += FormattedLine()
if (prerequisites.size == 1)
prerequisites.first().let { lineList += FormattedLine("Required tech: [$it]", link = "Technology/$it") }
else {
lineList += FormattedLine("Requires all of the following:")
prerequisites.forEach {
lineList += FormattedLine(it, link = "Technology/$it")
}
}
}
val leadsTo = ruleset.technologies.values.filter { name in it.prerequisites }
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())
}
}
}
if (uniques.isNotEmpty()) {
lineList += FormattedLine()
uniqueObjects.forEach {
if (!it.hasFlag(UniqueFlag.HiddenToUsers))
lineList += FormattedLine(it)
}
}
val affectedImprovements = getAffectedImprovements(name, ruleset)
if (affectedImprovements.any()) {
lineList += FormattedLine()
for (entry in affectedImprovements) {
lineList += FormattedLine(entry.getText(), link = entry.improvement.makeLink())
}
}
val enabledUnits = getEnabledUnits(name, ruleset, null)
if (enabledUnits.any()) {
lineList += FormattedLine()
lineList += FormattedLine("{Units enabled}:")
for (unit in enabledUnits)
lineList += FormattedLine(unit.name.tr() + " (" + unit.getShortDescription() + ")", link = unit.makeLink())
}
val (wonders, regularBuildings) = getEnabledBuildings(name, ruleset, null)
.partition { it.isAnyWonder() }
if (wonders.isNotEmpty()) {
lineList += FormattedLine()
lineList += FormattedLine("{Wonders enabled}:")
for (wonder in wonders)
lineList += FormattedLine(wonder.name.tr() + " (" + wonder.getShortDescription() + ")", link = wonder.makeLink())
}
if (regularBuildings.isNotEmpty()) {
lineList += FormattedLine()
lineList += FormattedLine("{Buildings enabled}:")
for (building in regularBuildings)
lineList += FormattedLine(building.name.tr() + " (" + building.getShortDescription() + ")", link = building.makeLink())
}
val obsoletedObjects = getObsoletedObjects(name, ruleset, null).toList()
if (obsoletedObjects.isNotEmpty()) {
lineList += FormattedLine()
obsoletedObjects.forEach {
lineList += FormattedLine("[${it.name}] obsoleted", link = it.makeLink())
}
}
val revealedResources = ruleset.tileResources.values.asSequence().filter { it.revealedBy == name }
if (revealedResources.any()) {
lineList += FormattedLine()
revealedResources.forEach {
lineList += FormattedLine("Reveals [${it.name}] on the map", link = it.makeLink())
}
}
val tileImprovements = ruleset.tileImprovements.values.asSequence().filter { it.techRequired == name }
if (tileImprovements.any()) {
lineList += FormattedLine()
lineList += FormattedLine("{Tile improvements enabled}:")
tileImprovements.forEach {
lineList += FormattedLine(it.name, link = it.makeLink())
}
}
val seeAlsoObjects = getSeeAlsoObjects(ruleset)
if (seeAlsoObjects.isNotEmpty()) {
lineList += FormattedLine()
lineList += FormattedLine("{See also}:")
seeAlsoObjects.forEach {
lineList += FormattedLine(it.name, link = it.makeLink())
}
}
return lineList
}
//endregion
//region Helpers
/**
* Returns a Sequence of [Building]s enabled by this Technology, filtered for [civInfo]'s uniques,
* nuclear weapons and religion settings, and without those expressly hidden from Civilopedia.
*/
// Used for Civilopedia, Alert and Picker, so if any of these decide to ignore the "Will not be displayed in Civilopedia" unique this needs refactoring
private fun getEnabledBuildings(techName: String, ruleset: Ruleset, civInfo: Civilization?) =
getFilteredBuildings(ruleset, civInfo) { it.requiredTech == techName }
/**
* Returns a Sequence of [RulesetStatsObject]s obsoleted by this Technology, filtered for [civInfo]'s uniques,
* nuclear weapons and religion settings, and without those expressly hidden from Civilopedia.
*/
// Used for Civilopedia, Alert and Picker, so if any of these decide to ignore the "Will not be displayed in Civilopedia" unique this needs refactoring
private fun getObsoletedObjects(techName: String, ruleset: Ruleset, civInfo: Civilization?): Sequence<RulesetStatsObject> =
(
getFilteredBuildings(ruleset, civInfo) { true }
+ ruleset.tileResources.values.asSequence()
+ ruleset.tileImprovements.values.filter {
it.uniqueTo == null || it.uniqueTo == civInfo?.civName
}
).filter { obj: RulesetStatsObject ->
obj.getMatchingUniques(UniqueType.ObsoleteWith).any { it.params[0] == techName }
}
/** Readability - for the 'obsoleted' in [getTechEnabledIcons] */
private fun RulesetStatsObject.getObsoletedIcon(techIconSize: Float) =
when (this) {
is Building -> ImageGetter.getConstructionPortrait(name, techIconSize)
is TileResource -> ImageGetter.getResourcePortrait(name, techIconSize)
is TileImprovement -> ImageGetter.getImprovementPortrait(name, techIconSize)
else -> null
}?.also {
val closeImage = ImageGetter.getRedCross(techIconSize / 2, 1f)
closeImage.center(it)
it.addActor(closeImage)
}
/** Common filtering for both [getEnabledBuildings] and [getObsoletedObjects], difference via predicate parameter */
private fun getFilteredBuildings(
ruleset: Ruleset,
civInfo: Civilization?,
predicate: (Building) -> Boolean
): Sequence<Building> {
val (nuclearWeaponsEnabled, religionEnabled) = getNukeAndReligionSwitches(civInfo)
return ruleset.buildings.values.asSequence()
.filter {
predicate(it) // expected to be the most selective, thus tested first
&& (it.uniqueTo == civInfo?.civName || it.uniqueTo == null && civInfo?.getEquivalentBuilding(it) == it)
&& (nuclearWeaponsEnabled || !it.hasUnique(UniqueType.EnablesNuclearWeapons))
&& (religionEnabled || !it.hasUnique(UniqueType.HiddenWithoutReligion))
&& !it.hasUnique(UniqueType.HiddenFromCivilopedia)
}
}
private fun getNukeAndReligionSwitches(civInfo: Civilization?): Pair<Boolean, Boolean> {
if (civInfo == null) return true to true
return civInfo.gameInfo.run { gameParameters.nuclearWeaponsEnabled to isReligionEnabled() }
}
/**
* Returns a Sequence of [BaseUnit]s enabled by this Technology, filtered for [civInfo]'s uniques,
* nuclear weapons and religion settings, and without those expressly hidden from Civilopedia.
*/
// Used for Civilopedia, Alert and Picker, so if any of these decide to ignore the "Will not be displayed in Civilopedia"/HiddenFromCivilopedia unique this needs refactoring
private fun getEnabledUnits(techName: String, ruleset: Ruleset, civInfo: Civilization?): Sequence<BaseUnit> {
val (nuclearWeaponsEnabled, religionEnabled) = getNukeAndReligionSwitches(civInfo)
return ruleset.units.values.asSequence()
.filter {
it.requiredTech == techName
&& (it.uniqueTo == civInfo?.civName || it.uniqueTo == null && civInfo?.getEquivalentUnit(it) == it)
&& (nuclearWeaponsEnabled || !it.isNuclearWeapon())
&& (religionEnabled || !it.hasUnique(UniqueType.HiddenWithoutReligion))
&& !it.hasUnique(UniqueType.HiddenFromCivilopedia)
}
}
/** Tests whether a Unique means bonus Stats enabled by [techName] */
private fun Unique.isImprovementStatsEnabledByTech(techName: String) =
(isOfType(UniqueType.Stats) || isOfType(UniqueType.ImprovementStatsOnTile)) &&
conditionals.any {
it.isOfType(UniqueType.ConditionalTech) && it.params[0] == techName
}
/** Tests whether a Unique Conditional is enabling or disabling its parent by a tech */
private fun Unique.isTechConditional() =
isOfType(UniqueType.ConditionalTech) ||
isOfType(UniqueType.ConditionalNoTech)
/** Tests whether a Unique is enabled or disabled by [techName] */
private fun Unique.isRelatedToTech(techName: String) =
conditionals.any {
it.isTechConditional() && it.params[0] == techName
}
/** Used by [getAffectedImprovements] only */
private data class ImprovementAndUnique(val improvement: TileImprovement, val unique: Unique) {
fun getText() = "[${unique.params[0]}] from every [${improvement.name}]" +
(if (unique.isOfType(UniqueType.Stats)) "" else " on [${unique.params[1]}] tiles")
}
/** Yields Improvements with bonus Stats enabled by [techName] including the Unique doing it */
private fun getAffectedImprovements(techName: String, ruleset: Ruleset): Sequence<ImprovementAndUnique> =
ruleset.tileImprovements.values.asSequence()
.flatMap { improvement ->
improvement.uniqueObjects.asSequence()
.filter { it.isImprovementStatsEnabledByTech(techName) }
.map { ImprovementAndUnique(improvement, it) }
}
/** Get other objects related to this tech by a unique,
* e.g. Petra/Archaeology or Work Boats/Astronomy
* but not including the Improvement Stats increases already listed */
private fun Technology.getSeeAlsoObjects(ruleset: Ruleset) =
ruleset.allRulesetObjects()
.filter { iRulesetObject ->
iRulesetObject.uniqueObjects.any {
it.isRelatedToTech(name) && !it.isImprovementStatsEnabledByTech(name)
}
}.toList()
//endregion
}

View File

@ -1,16 +1,12 @@
package com.unciv.ui.screens.pickerscreens
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.logic.civilization.managers.TechManager
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.objectdescriptions.TechnologyDescriptions
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.extensions.addBorder
@ -21,29 +17,38 @@ import com.unciv.ui.components.extensions.darken
import com.unciv.ui.components.extensions.setFontSize
import com.unciv.ui.components.extensions.toLabel
class TechButton(techName:String, private val techManager: TechManager, isWorldScreen: Boolean = true) : Table(
BaseScreen.skin) {
val text = "".toLabel().apply {
class TechButton(
techName: String,
private val techManager: TechManager,
isWorldScreen: Boolean = true
) : Table(BaseScreen.skin) {
internal val text = "".toLabel().apply {
wrap = false
setFontSize(14)
setAlignment(Align.left)
setEllipsis(true)
}
val turns = "".toLabel().apply {
internal val turns = "".toLabel().apply {
setFontSize(14)
setAlignment(Align.right)
}
var bg = Image(BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangleMidShape))
private val backgroundImage: Image // Table.background is the border
init {
touchable = Touchable.enabled
background = BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangleMidShape,
tintColor = Color.WHITE.cpy().darken(0.3f))
bg.toBack()
addActor(bg)
val path = "TechPickerScreen/TechButton"
val default = BaseScreen.skinStrings.roundedEdgeRectangleMidShape
backgroundImage = Image(BaseScreen.skinStrings.getUiBackground(path, default))
background = BaseScreen.skinStrings.getUiBackground(path, default, Color.WHITE.darken(0.3f))
pad(0f).padBottom(5f).padTop(5f).padLeft(5f).padRight(0f)
backgroundImage.toBack()
addActor(backgroundImage)
pad(5f, 5f, 5f, 0f)
add(ImageGetter.getTechIconPortrait(techName, 46f))
.padRight(5f).padLeft(2f).left()
@ -73,15 +78,15 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
add(rightSide).expandX().left()
pack()
bg.setSize(width-3f, height-3f)
bg.align = Align.center
bg.center(this)
backgroundImage.setSize(width - 3f, height - 3f)
backgroundImage.align = Align.center
backgroundImage.center(this)
pack()
}
fun setButtonColor(color: Color) {
bg.color = color
backgroundImage.color = color
pack()
}
@ -92,74 +97,15 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
BaseScreen.skinStrings.roundedEdgeRectangleSmallShape,
tintColor = Color.BLACK.cpy().apply { a = 0.7f }
)
techEnabledIcons.pad(0f).padLeft(10f).padTop(2f).padBottom(2f)
techEnabledIcons.pad(2f, 10f, 2f, 0f)
techEnabledIcons.defaults().padRight(5f)
val techIconSize = 30f
val civName = techManager.civInfo.civName
val ruleset = techManager.civInfo.gameInfo.ruleset
val civ = techManager.civInfo
val tech = civ.gameInfo.ruleset.technologies[techName]!!
val tech = ruleset.technologies[techName]!!
val icons = ArrayList<Group>()
for (unit in tech.getEnabledUnits(ruleset, techManager.civInfo)) {
icons.add(ImageGetter.getConstructionPortrait(unit.name, techIconSize))
}
for (building in tech.getEnabledBuildings(ruleset, techManager.civInfo)) {
icons.add(ImageGetter.getConstructionPortrait(building.name, techIconSize))
}
for (obj in tech.getObsoletedObjects(ruleset, techManager.civInfo)) {
val obsoletedIcon = when (obj) {
is Building -> ImageGetter.getConstructionPortrait(obj.name, techIconSize)
is TileResource -> ImageGetter.getResourcePortrait(obj.name, techIconSize)
is TileImprovement -> ImageGetter.getImprovementPortrait(obj.name, techIconSize)
else -> continue
}.also {
val closeImage = ImageGetter.getRedCross(techIconSize / 2, 1f)
closeImage.center(it)
it.addActor(closeImage)
}
icons.add(obsoletedIcon)
}
for (resource in ruleset.tileResources.values.filter { it.revealedBy == techName }) {
icons.add(ImageGetter.getResourcePortrait(resource.name, techIconSize))
}
for (improvement in ruleset.tileImprovements.values.asSequence()
.filter { it.techRequired == techName }
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
icons.add(ImageGetter.getImprovementPortrait(improvement.name, techIconSize))
}
for (improvement in ruleset.tileImprovements.values.asSequence()
.filter { it.uniqueObjects.any { u -> u.allParams.contains(techName) } }
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
icons.add(ImageGetter.getUniquePortrait(improvement.name, techIconSize))
}
for (unique in tech.uniques) {
icons.add(
when (unique) {
UniqueType.EnablesCivWideStatProduction.text.replace("civWideStat", "Gold" )
-> ImageGetter.getConstructionPortrait("Gold", techIconSize)
UniqueType.EnablesCivWideStatProduction.text.replace("civWideStat", "Science" )
-> ImageGetter.getConstructionPortrait("Science", techIconSize)
else -> ImageGetter.getUniquePortrait(unique, techIconSize)
}
)
}
for (i in 0..4) {
val icon = icons.getOrNull(i)
if (icon != null)
techEnabledIcons.add(icon)
}
TechnologyDescriptions.getTechEnabledIcons(tech, civ, techIconSize = 30f)
.take(5)
.forEach { techEnabledIcons.add(it) }
rightSide.add(techEnabledIcons)
.colspan(2)

View File

@ -9,7 +9,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.GUI
import com.unciv.UncivGame
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.managers.TechManager
import com.unciv.models.UncivSound
@ -370,7 +369,7 @@ class TechPickerScreen(
val previousSelectedTech = selectedTech
selectedTech = tech
descriptionLabel.setText(tech?.getDescription(civInfo.gameInfo.ruleset))
descriptionLabel.setText(tech?.getDescription(civInfo))
if (!switchFromWorldScreen)
return

View File

@ -229,7 +229,7 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
val centerTable = Table()
centerTable.add(tech.quote.toLabel().apply { wrap = true }).width(worldScreen.stage.width / 3)
centerTable.add(ImageGetter.getTechIconPortrait(tech.name, 100f)).pad(20f)
val descriptionScroll = ScrollPane(tech.getDescription(gameBasics).toLabel().apply { wrap = true })
val descriptionScroll = ScrollPane(tech.getDescription(worldScreen.viewingCiv).toLabel().apply { wrap = true })
centerTable.add(descriptionScroll).width(worldScreen.stage.width / 3).maxHeight(worldScreen.stage.height / 2)
add(centerTable).row()
add(getCloseButton(Constants.close))