From c33e7ae6d25f5a1cb869d76de2618ccc406816ff Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Tue, 3 Aug 2021 08:36:58 +0200 Subject: [PATCH] Civilopedia phase 9 - Technologies (#4737) --- .../jsons/translations/German.properties | 5 +- .../jsons/translations/template.properties | 3 + .../logic/civilization/CivilizationInfo.kt | 18 +- .../unciv/models/ruleset/tech/Technology.kt | 233 +++++++++++++----- .../unciv/ui/civilopedia/CivilopediaScreen.kt | 2 +- .../com/unciv/ui/pickerscreens/TechButton.kt | 11 +- 6 files changed, 201 insertions(+), 71 deletions(-) diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index a62d69c7ae..7cdfe4768a 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -49,6 +49,9 @@ Requires [PolicyOrNationalWonder] = Benötigt [PolicyOrNationalWonder] Cannot be purchased = Kann nicht gekauft werden See also = Siehe auch Requires at least one of the following: = Benötigt eine der folgenden Vorraussetzungen: +Requires all of the following: = Benötigt folgende Vorraussetzungen: +Leads to [techName] = [techName] kann nun erforscht werden +Leads to: = Ermöglicht die Erforschung von: Current construction = Aktuelle Produktion Construction queue = Produktionswarteschlange @@ -3356,7 +3359,7 @@ Increases embarked movement +1 = Bewegung eingeschiffter Einheiten +1 Enables embarked units to enter ocean tiles = Erlaubt es, eingeschifften Einheiten Ozeanfelder zu überqueren 'Joyfully to the breeze royal Odysseus spread his sail, and with his rudder skillfully he steered.' - Homer = 'Freudig zur Brise spreizte der königliche Odysseus sein Segel, und mit seinem Ruder steuerte er geschickt.' - Homer Acoustics = Akustik -'Their rising all at once was as the sound of thunder heard remote' - Milton = 'Ihr Aufstieg auf einmal war, als der Klang des Donners entfernt hörte.' - Milton +'Their rising all at once was as the sound of thunder heard remote' - Milton = 'Ihr gleichzeitiges Aufstehen klang wie aus der Ferne gehörtes Donnergrollen.' - Milton Banking = Bankwesen 'Happiness: a good bank account, a good cook and a good digestion' - Jean Jacques Rousseau = 'Fröhlichkeit: ein gutes Bankkonto, ein guter Koch und eine gute Verdauung.' - Jean Jacques Rousseau Printing Press = Druckpresse diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 758c618922..8b95351b12 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -50,6 +50,9 @@ Requires [PolicyOrNationalWonder] = Cannot be purchased = See also = Requires at least one of the following: = +Requires all of the following: = +Leads to [techName] = +Leads to: = Current construction = Construction queue = diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 75459ac70f..4c8a4b42b6 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -305,9 +305,10 @@ class CivilizationInfo { return tech.currentTechnology() == null && cities.isNotEmpty() } - - fun getEquivalentBuilding(buildingName: String): Building { - val baseBuilding = gameInfo.ruleSet.buildings[buildingName]!!.getBaseBuilding(gameInfo.ruleSet) + fun getEquivalentBuilding(buildingName: String) = getEquivalentBuilding(gameInfo.ruleSet.buildings[buildingName]!!) + fun getEquivalentBuilding(baseBuilding: Building): Building { + if (baseBuilding.replaces != null) + return getEquivalentBuilding(baseBuilding.replaces!!) for (building in gameInfo.ruleSet.buildings.values) if (building.replaces == baseBuilding.name && building.uniqueTo == civName) @@ -317,12 +318,15 @@ class CivilizationInfo { fun getEquivalentUnit(baseUnitName: String): BaseUnit { val baseUnit = gameInfo.ruleSet.units[baseUnitName] - @Suppress("FoldInitializerAndIfToElvis") - if (baseUnit == null) throw UncivShowableException("Unit $baseUnitName doesn't seem to exist!") - if (baseUnit.replaces != null) return getEquivalentUnit(baseUnit.replaces!!) // Equivalent of unique unit is the equivalent of the replaced unit + ?: throw UncivShowableException("Unit $baseUnitName doesn't seem to exist!") + return getEquivalentUnit(baseUnit) + } + fun getEquivalentUnit(baseUnit: BaseUnit): BaseUnit { + if (baseUnit.replaces != null) + return getEquivalentUnit(baseUnit.replaces!!) // Equivalent of unique unit is the equivalent of the replaced unit for (unit in gameInfo.ruleSet.units.values) - if (unit.replaces == baseUnitName && unit.uniqueTo == civName) + if (unit.replaces == baseUnit.name && unit.uniqueTo == civName) return unit return baseUnit } diff --git a/core/src/com/unciv/models/ruleset/tech/Technology.kt b/core/src/com/unciv/models/ruleset/tech/Technology.kt index 3c8d2f0311..1bf11d2ece 100644 --- a/core/src/com/unciv/models/ruleset/tech/Technology.kt +++ b/core/src/com/unciv/models/ruleset/tech/Technology.kt @@ -7,11 +7,15 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Unique import com.unciv.models.translations.tr import com.unciv.models.ruleset.unit.BaseUnit +import com.unciv.models.stats.INamed +import com.unciv.ui.civilopedia.ICivilopediaText +import com.unciv.ui.civilopedia.FormattedLine +import com.unciv.ui.utils.Fonts import java.util.* -class Technology { +class Technology: INamed, ICivilopediaText { - lateinit var name: String + override lateinit var name: String var cost: Int = 0 var prerequisites = HashSet() @@ -22,6 +26,20 @@ class Technology { var row: Int = 0 var quote = "" + override var civilopediaText = listOf() + + + // Debug helper + override fun toString() = name + + fun era(): String = column!!.era + + fun isContinuallyResearchable() = uniques.contains("Can be continually researched") + + + /** + * Textual description used in TechPickerScreen and AlertPopup(AlertType.TechResearched) + */ fun getDescription(ruleset: Ruleset): String { val lineList = ArrayList() // more readable than StringBuilder, with same performance for our use-case for (unique in uniques) lineList += unique.tr() @@ -35,8 +53,8 @@ class Technology { } val viewingCiv = UncivGame.Current.worldScreen.viewingCiv - val enabledUnits = getEnabledUnits(viewingCiv).filter { "Will not be displayed in Civilopedia" !in it.uniques } - if (enabledUnits.isNotEmpty()) { + val enabledUnits = getEnabledUnits(viewingCiv) + if (enabledUnits.any()) { lineList += "{Units enabled}: " for (unit in enabledUnits) lineList += " * " + unit.name.tr() + " (" + unit.getShortDescription() + ")" @@ -44,32 +62,23 @@ class Technology { val enabledBuildings = getEnabledBuildings(viewingCiv) - val regularBuildings = enabledBuildings.filter { - !it.isAnyWonder() - && "Will not be displayed in Civilopedia" !in it.uniques - && !(!viewingCiv.gameInfo.hasReligionEnabled() && it.uniques.contains("Hidden when religion is disabled")) - } - if (regularBuildings.isNotEmpty()) { + val regularBuildings = enabledBuildings.filter { !it.isAnyWonder() } + if (regularBuildings.any()) { lineList += "{Buildings enabled}: " for (building in regularBuildings) lineList += "* " + building.name.tr() + " (" + building.getShortDescription(ruleset) + ")" } - val wonders = enabledBuildings.filter { - it.isAnyWonder() - && "Will not be displayed in Civilopedia" !in it.uniques - } - if (wonders.isNotEmpty()) { + val wonders = enabledBuildings.filter { it.isAnyWonder() } + if (wonders.any()) { lineList += "{Wonders enabled}: " for (wonder in wonders) lineList += " * " + wonder.name.tr() + " (" + wonder.getShortDescription(ruleset) + ")" } - for (building in getObsoletedBuildings(viewingCiv) - .filter { "Will not be displayed in Civilopedia" !in it.uniques }) + for (building in getObsoletedBuildings(viewingCiv)) lineList += "[${building.name}] obsoleted" - for (resource in ruleset.tileResources.values.asSequence().filter { it.revealedBy == name } .map { it.name }) lineList += "Reveals [$resource] on the map" @@ -81,47 +90,161 @@ class Technology { return lineList.joinToString("\n") { it.tr() } } - fun getEnabledBuildings(civInfo: CivilizationInfo): List { - var enabledBuildings = civInfo.gameInfo.ruleSet.buildings.values.filter { - it.requiredTech == name && - (it.uniqueTo == null || it.uniqueTo == civInfo.civName) - } - val replacedBuildings = enabledBuildings.mapNotNull { it.replaces } - enabledBuildings = enabledBuildings.filter { it.name !in replacedBuildings } + /** + * 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(civInfo: CivilizationInfo) = getFilteredBuildings(civInfo) + { it.requiredTech == name } - if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled) - enabledBuildings = enabledBuildings.filterNot { it.name == "Manhattan Project" } + /** + * 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 getObsoletedBuildings(civInfo: CivilizationInfo) = getFilteredBuildings(civInfo) + { it.uniqueObjects.any { unique -> unique.placeholderText == "Obsolete with []" && unique.params[0] == name } } + + // Helper: common filtering for both getEnabledBuildings and getObsoletedBuildings, difference via predicate parameter + private fun getFilteredBuildings(civInfo: CivilizationInfo, predicate: (Building)->Boolean): Sequence { + val nuclearWeaponsEnabled = civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled + val religionEnabled = civInfo.gameInfo.hasReligionEnabled() + + return civInfo.gameInfo.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 || "Enables nuclear weapon" !in it.uniques) + && (religionEnabled || "Hidden when religion is disabled" !in it.uniques) + && "Will not be displayed in Civilopedia" !in it.uniques + } + } + + /** + * 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" unique this needs refactoring + fun getEnabledUnits(civInfo: CivilizationInfo): Sequence { + val nuclearWeaponsEnabled = civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled + val religionEnabled = civInfo.gameInfo.hasReligionEnabled() + + return civInfo.gameInfo.ruleSet.units.values.asSequence() + .filter { + it.requiredTech == name + && (it.uniqueTo == civInfo.civName || it.uniqueTo==null && civInfo.getEquivalentUnit(it) == it) + && (nuclearWeaponsEnabled || it.uniqueObjects.none { unique -> unique.placeholderText == "Nuclear weapon of Strength []" }) + && (religionEnabled || "Hidden when religion is disabled" !in it.uniques) + && "Will not be displayed in Civilopedia" !in it.uniques + } + } + + + override fun makeLink() = "Technology/$name" + override fun hasCivilopediaTextLines() = true + override fun replacesCivilopediaDescription() = true + + override fun getCivilopediaTextLines(ruleset: Ruleset): List { + val lineList = ArrayList() + + lineList += FormattedLine(era(), header = 3, color = "#8080ff") + 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 (!civInfo.gameInfo.hasReligionEnabled()) - enabledBuildings = enabledBuildings.filterNot { it.uniques.contains("Hidden when religion is disabled") } - - return enabledBuildings - } - - fun getObsoletedBuildings(civInfo: CivilizationInfo): Sequence { - val obsoletedBuildings = civInfo.gameInfo.ruleSet.buildings.values.asSequence() - .filter { it.uniqueObjects.any { it.placeholderText=="Obsolete with []" && it.params[0]==name } } - return obsoletedBuildings.filter { civInfo.getEquivalentBuilding(it.name)==it } - } - - fun getEnabledUnits(civInfo: CivilizationInfo): List { - var enabledUnits = civInfo.gameInfo.ruleSet.units.values.filter { - it.requiredTech == name && - (it.uniqueTo == null || it.uniqueTo == civInfo.civName) + if (uniques.isNotEmpty()) { + lineList += FormattedLine() + for (unique in uniqueObjects) lineList += FormattedLine(unique) } - val replacedUnits = civInfo.gameInfo.ruleSet.units.values.filter { it.uniqueTo == civInfo.civName } - .mapNotNull { it.replaces } - enabledUnits = enabledUnits.filter { it.name !in replacedUnits } - if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled) - enabledUnits = enabledUnits.filterNot { it.uniques.contains("Requires Manhattan Project") } + var wantEmpty = true + for (improvement in ruleset.tileImprovements.values) + for (unique in improvement.uniqueObjects) { + if (unique.placeholderText == "[] once [] is discovered" && unique.params.last() == name) { + if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false } + lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}]", + link = improvement.makeLink()) + } else if (unique.placeholderText == "[] on [] tiles once [] is discovered" && unique.params.last() == name) { + if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false } + lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles", + link = improvement.makeLink()) + } + } - return enabledUnits + val viewingCiv = UncivGame.Current.worldScreen.viewingCiv + val enabledUnits = getEnabledUnits(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(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(ruleset) + ")", 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(ruleset) + ")", link = building.makeLink()) + } + + val obsoletedBuildings = getObsoletedBuildings(viewingCiv) + if (obsoletedBuildings.any()) { + lineList += FormattedLine() + obsoletedBuildings.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()) + } + } + + return lineList } - - override fun toString() = name - - fun era(): String = column!!.era - - fun isContinuallyResearchable() = uniques.contains("Can be continually researched") } diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt index 529c9eee39..3c1c40cb66 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt @@ -253,7 +253,7 @@ class CivilopediaScreen( .map { CivilopediaEntry( it.name, - it.getDescription(ruleset), + "", CivilopediaCategories.Technology.getImage?.invoke(it.name, imageSize), (it as? ICivilopediaText).takeUnless { ct -> ct==null || ct.isCivilopediaTextEmpty() } ) diff --git a/core/src/com/unciv/ui/pickerscreens/TechButton.kt b/core/src/com/unciv/ui/pickerscreens/TechButton.kt index e8dc59c989..cda8408e86 100644 --- a/core/src/com/unciv/ui/pickerscreens/TechButton.kt +++ b/core/src/com/unciv/ui/pickerscreens/TechButton.kt @@ -51,16 +51,13 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS val tech = ruleset.technologies[techName]!! - for (unit in tech.getEnabledUnits(techManager.civInfo) - .filter { "Will not be displayed in Civilopedia" !in it.uniques }) + for (unit in tech.getEnabledUnits(techManager.civInfo)) techEnabledIcons.add(ImageGetter.getConstructionImage(unit.name).surroundWithCircle(techIconSize)) - for (building in tech.getEnabledBuildings(techManager.civInfo) - .filter { "Will not be displayed in Civilopedia" !in it.uniques }) + for (building in tech.getEnabledBuildings(techManager.civInfo)) techEnabledIcons.add(ImageGetter.getConstructionImage(building.name).surroundWithCircle(techIconSize)) - for (building in tech.getObsoletedBuildings(techManager.civInfo) - .filter { "Will not be displayed in Civilopedia" !in it.uniques }) + for (building in tech.getObsoletedBuildings(techManager.civInfo)) techEnabledIcons.add(ImageGetter.getConstructionImage(building.name).surroundWithCircle(techIconSize).apply { val closeImage = ImageGetter.getImage("OtherIcons/Close") closeImage.setSize(techIconSize / 2, techIconSize / 2) @@ -89,4 +86,4 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS else rightSide.add(techEnabledIcons) .minWidth(225f) } -} \ No newline at end of file +}