From 130c318ae007eb8e5d0b6b357627315f4134a715 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Fri, 30 Jul 2021 14:55:17 +0200 Subject: [PATCH] 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 --- .../jsons/Civ V - Vanilla/UnitPromotions.json | 3 +- .../Brazilian_Portuguese.properties | 2 +- .../jsons/translations/Dutch.properties | 2 +- .../jsons/translations/German.properties | 35 ++-- .../jsons/translations/Swedish.properties | 2 +- .../jsons/translations/template.properties | 6 + core/src/com/unciv/logic/map/TileInfo.kt | 2 +- core/src/com/unciv/models/ruleset/Nation.kt | 166 +++++++++++++++++- .../com/unciv/models/ruleset/unit/BaseUnit.kt | 2 +- .../unciv/models/ruleset/unit/Promotion.kt | 133 +++++++++++--- .../unciv/ui/civilopedia/CivilopediaScreen.kt | 4 +- .../unciv/ui/civilopedia/CivilopediaText.kt | 6 +- 12 files changed, 311 insertions(+), 52 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json b/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json index 6dce2cbb92..201498ddd9 100644 --- a/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json +++ b/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json @@ -152,8 +152,7 @@ { "name": "Medic II", "prerequisites": ["Medic"], - "uniques": ["[+5] HP when healing", "All adjacent units heal [5] HP when healing", - "[+5] HP when healing in [Foreign Land] tiles"], + "uniques": ["[+5] HP when healing", "[+5] HP when healing in [Foreign Land] tiles", "All adjacent units heal [5] HP when healing"], "unitTypes": ["Melee","Mounted","Scout"] }, diff --git a/android/assets/jsons/translations/Brazilian_Portuguese.properties b/android/assets/jsons/translations/Brazilian_Portuguese.properties index af0c04be3c..bf1be66a67 100644 --- a/android/assets/jsons/translations/Brazilian_Portuguese.properties +++ b/android/assets/jsons/translations/Brazilian_Portuguese.properties @@ -1146,7 +1146,7 @@ relevant = aplicável # Promotions Pick promotion = Escolher promoção - OR = Ou + OR = Ou units in open terrain = unidades em terreno aberto units in rough terrain = unidades em terreno acidentado Targeting II (air) = Alvejamento Aéreo II diff --git a/android/assets/jsons/translations/Dutch.properties b/android/assets/jsons/translations/Dutch.properties index 7665178399..2458572cfb 100644 --- a/android/assets/jsons/translations/Dutch.properties +++ b/android/assets/jsons/translations/Dutch.properties @@ -1123,7 +1123,7 @@ relevant = relevante # Promotions Pick promotion = Kies een promotie - OR = OF + OR = OF units in open terrain = eenheden in open terrein units in rough terrain = eenheden in ruw terrein Targeting II (air) = Gerichtheid II (lucht) diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index cba2a3797e..ed916362ad 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -48,6 +48,7 @@ Required tech: [requiredTech] = Benötigt Technologie: [requiredTech] 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: Current construction = Aktuelle Produktion Construction queue = Produktionswarteschlange @@ -852,6 +853,7 @@ Buildings that consume this resource = Gebäude welche diese Ressource verbrauch Buildings that require this resource worked near the city = Gebäude welche diese Ressource bewirtschaftet in der Nähe der Stadt benötigen Units that consume this resource = Einheiten welche diese Ressource verbrauchen Can be built on = Kann gebaut werden auf +or [terrainType] = oder [terrainType] Can be constructed by = Kann erbaut werden von Defence bonus = Verteidigungsbonus Movement cost = Bewegungskosten @@ -922,9 +924,13 @@ Gold reward for clearing barbarian camps = Gold-Belohnung für das Räumen von B # Other civilopedia things Nations = Nationen Available for [unitTypes] = Verfügbar für [unitTypes] +Available for: = Verfügbar für: Free promotion: = Freie Beförderung: Free promotions: = Freie Beförderungen: Free for [units] = Frei für [units] +Free for: = Frei für: +Granted by [param] = Von [param] erteilt +Granted by: = Erteilt von: [bonus] with [tech] = [bonus] mit [tech] Difficulty levels = Schwierigkeitsgrade @@ -978,7 +984,7 @@ Siege = Belagerung WaterCivilian = Wasser-Zivilist WaterMelee = Wassernahkampf WaterRanged = Wasserfernkampf -WaterSubmarine = Wasser-U-Boot +WaterSubmarine = U-Boote WaterAircraftCarrier = Flugzeugträger Fighter = Jagdflugzeug @@ -1196,7 +1202,8 @@ Paper Maker = Papiermacher 'Libraries are as the shrine where all the relics of the ancient saints, full of true virtue, and all that without delusion or imposture are preserved and reposed.' - Sir Francis Bacon = 'Bibliotheken sind wie der Schrein, wo alle Reliquien der Heiligen vergangener Tage, voll der wahren Tugend, ruhen und bewahrt werden, und zwar ganz ohne Irreführung und Betrug.' - Sir Francis Bacon Free Technology = Kostenlose Technologie -Provides a free [building] [cityFilter] = Stellt das Gebäude [building] [cityFilter] bereit + # Requires translation! +Provides a free [building] [cityFilter] = Stellt das Gebäude [building] [cityFilter] kostenlos bereit The Great Library = Die Große Bibliothek Circus = Zirkus @@ -2346,7 +2353,7 @@ You have defeated us... but our spirits will never be vanquished! We shall retur Greetings, stranger. I am Hiawatha, speaker for the Iroquois. We seek peace with all, but we do not shrink from war. = Seid gegrüßt, Fremder. Ich bin Hiawatha, Sprecher der Irokesen. Wir streben nach Frieden, doch wenn es sein muss, scheuen wir nicht den Krieg. Does this trade work for you, my friend? = Ist dieser Tausch für dich ok, mein Freund? The Great Warpath = Auf dem Großen Kriegspfad -All units move through Forest and Jungle Tiles in friendly territory as if they have roads. These tiles can be used to establish City Connections upon researching the Wheel. = Alle Einheiten bewegen sich durch verbündete Wald- und Dschungelfelder als hätten sie Straßen. Diese Felder können genutzt werden, um bei der Erforschung des Rades Stadtverbindungen herzustellen. +All units move through Forest and Jungle Tiles in friendly territory as if they have roads. These tiles can be used to establish City Connections upon researching the Wheel. = Alle Einheiten bewegen sich durch verbündete Wald- und Dschungelfelder als hätten sie Straßen. Diese Felder können genutzt werden, um nach Erforschung des Rades Stadtverbindungen herzustellen. Greetings, noble Hiawatha, leader of the mighty Iroquois nations! Long have your people lived near the great and holy lake Ontario in the land that has come to be known as the New York state in North America. In the mists of antiquity, the five peoples of Seneca, Onondaga, Mohawks, Cayugas and Oneida united into one nation, the Haudenosaunee, the Iroquois. With no written language, the wise men of your nation created the great law of peace, the model for many constitutions including that of the United States. For many years, your people battled great enemies, such as the Huron, and the French and English invaders. Tough outnumbered and facing weapons far more advanced than the ones your warriors wielded, the Iroquois survived and prospered, until they were finally overwhelmed by the mighty armies of the new United States. = Seid gegrüßt, edler Hiawatha, Häuptling der mächtigen Irokesenvölker! Seit langem lebt euer Volk in der Nähe des großen und heiligen Sees Ontario in dem Land, das als der Staat New York in Nordamerika bekannt geworden ist. In den Nebeln des Altertums vereinigten sich die fünf Völker der Seneca, Onondaga, Mohawks, Cayugas und Oneida zu einer Nation, den Haudenosaunee, den Irokesen. Ohne Schriftsprache schufen die Weisen eures Volkes das große Friedensgesetz, das Vorbild für viele Verfassungen, darunter auch die der Vereinigten Staaten. Viele Jahre lang kämpfte euer Volk gegen große Feinde, wie die Huronen und die französischen und englischen Invasoren. Obwohl sie zahlenmäßig unterlegen und mit Waffen konfrontiert waren, die weitaus fortschrittlicher waren als die, die eure Krieger trugen, überlebten die Irokesen und gediehen, bis sie schließlich von den mächtigen Armeen der neuen Vereinigten Staaten überwältigt wurden. Oh noble Hiawatha, listen to the cries of your people! They call out to you to lead them in peace and war, to rebuild the great longhouse and unite the tribes once again. Will you accept this challenge, great leader? Will you build a civilization that will stand the test of time? = Oh edler Hiawatha, höre auf die Schreie deines Volkes! Sie rufen nach Euch, um sie in Frieden und Krieg zu führen, um das große Langhaus wieder aufzubauen und die Stämme wieder zu vereinen. Werdet Ihr diese Herausforderung annehmen, großer Anführer? Könnt Ihr eine Zivilisation aufbauen, die dem Test der Zeit standhält? Onondaga = Onondaga @@ -2523,7 +2530,7 @@ I, Pho Kun Ramkhamhaeng, King of Siam, consider it a great honor that you have w Greetings. I believe this is a fair proposal for both parties. What do you think? = Ich grüße Euch. Ich glaube, dies ist ein fairer Vorschlag für beide Seiten. Was meint Ihr dazu? Welcome. = Willkommen. Father Governs Children = Vater und Kinder -Food and Culture from Friendly City-States are increased by 50% = Nahrung und Kultur aus freundlichen Stadtstaaten werden um 50% erhöht. +Food and Culture from Friendly City-States are increased by 50% = Nahrung und Kultur aus befreundeten Stadtstaaten werden um 50% erhöht. Greetings to you, Great King Ramkhamhaeng, leader of the glorious Siamese people! O mighty King, your people bow down before you in awe and fear! You are the ruler of Siam, an ancient country in the heart of Southeast Asia, a beautiful and mysterious land. Surrounded by foes, beset by bloody war and grinding poverty, the clever and loyal Siamese people have endured and triumphed. King Ramkhamhaeng, your empire was once part of the Khmer Empire, until the 13th century AD, when your ancestors revolted, forming the small Sukhothai kingdom. Through successful battle and cunning diplomacy, the tiny kingdom grew into a mighty empire, an empire which would dominate South East Asia for more than a century! = Sei gegrüßt, Großkönig Ramkhamhaeng, Führer des glorreichen siamesischen Volkes! O mächtiger König, euer Volk verneigt sich in Ehrfurcht und Furcht vor Euch! Ihr seid der Herrscher von Siam, einem alten Land im Herzen Südostasiens, einem schönen und geheimnisvollen Land. Umgeben von Feinden, bedrängt von blutigen Kriegen und drückender Armut, hat das kluge und treue siamesische Volk ausgehalten und triumphiert. König Ramkhamhaeng, euer Reich war einst Teil des Khmer-Reiches, bis zum 13. Jahrhundert n. Chr., als sich eure Vorfahren auflehnten und das kleine Königreich Sukhothai gründeten. Durch erfolgreiche Kämpfe und schlaue Diplomatie wuchs das winzige Königreich zu einem mächtigen Reich heran, ein Reich, das Südostasien für mehr als ein Jahrhundert beherrschen sollte! Oh, wise and puissant King Ramkhamhaeng, your people need you to once again lead them to greatness! Can you use your wits and strength of arms to protect your people and defeat your foes? Can you build a civilization that will stand the test of time? = Oh, weiser und mächtiger König Ramkhamhaeng, euer Volk braucht Euch, um es noch einmal zu Größe zu führen! Könnt Ihr Euren Verstand und Eure Waffenstärke nutzen, um euer Volk zu schützen und Eure Feinde zu besiegen? Könnt Ihr eine Zivilisation aufbauen, die dem Test der Zeit standhält? Sukhothai = Sukhothai @@ -3427,7 +3434,7 @@ Telecommunications = Telekommunikation 'The more we elaborate our means of communication, the less we communicate.' - J.B. Priestly = 'Je mehr wir unsere Kommunikationsmittel ausbauen, desto weniger kommunizieren wir.' – J.B. Priestly Mobile Tactics = Mobile Taktiken 'All men can see these tactics whereby I conquer, but what none can see is the strategy out of which victory is evolved.' - Sun Tzu = 'Alle Menschen können diese Taten sehen, mit denen ich siege, aber was niemand sehen kann, ist die Strategie, aus der sich der Sieg entwickelt.' - Sun Tzu -Advanced Ballistics = Erweiterte Ballistik +Advanced Ballistics = Fortgeschrittene Ballistik 'Our scientific power has outrun our spiritual power, we have guided missiles and misguided men.' – Martin Luther King Jr. = 'Unsere wissenschaftliche Macht hat unsere geistige Macht überholt, wir haben Raketen gelenkt und Menschen fehlgeleitet.' – Martin Luther King Jr. Satellites = Satelliten Reveals the entire map = Enthüllt die gesamte Weltkarte @@ -3687,7 +3694,7 @@ Barrage III = Sperrfeuer III Volley = Salve -Extended Range = erweiterte Reichweite +Extended Range = Erhöhte Reichweite [amount] Range = [amount] Reichweite Indirect Fire = Indirektes Feuer @@ -3728,7 +3735,7 @@ Medic = Sanitäter I All adjacent units heal [amount] HP when healing = Beim Heilen werden alle benachbarten Einheiten um [amount] LP geheilt Medic II = Sanitäter II -[amount] HP when healing in [tileFilter] tiles = [amount] LP beim Heilen auf [tileFilter] Feldern +[amount] HP when healing in [tileFilter] tiles = [amount] LP bei Heilung in [tileFilter] Feldern Scouting I = Spähen I [amount] Visibility Range = [amount] Sichtweite @@ -3753,12 +3760,12 @@ Boarding Party II = Enterkommando II Boarding Party III = Enterkommando III -Coastal Raider I = KüstenräuberIn I +Coastal Raider I = Küstenräuberei I Earn [amount]% of the damage done to [unitType] units as [stat] = Erhalte [amount]% des verursachten Schadens an [unitType] Einheiten als [stat] -Coastal Raider II = KüstenräuberIn II +Coastal Raider II = Küstenräuberei II -Coastal Raider III = KüstenräuberIn III +Coastal Raider III = Küstenräuberei III Targeting I = Gezielter Schlag I @@ -3787,7 +3794,7 @@ Flight Deck II = Flugdeck II Flight Deck III = Flugdeck III Supply = Versorgung -May heal outside of friendly territory = Kann außerhalb des eigenen Territoriums heilen +May heal outside of friendly territory = Darf auch außerhalb von befreundetem Territorium heilen Siege I = Belagerung I @@ -3810,7 +3817,7 @@ Air Targeting I = Luftangriff I Air Targeting II = Luftangriff II Sortie = Lufteinsatz -[amount] extra interceptions may be made per turn = [amount] zusätzliche Abfangungen können pro Runde durchgeführt werden +[amount] extra interceptions may be made per turn = Kann pro Runde [amount] mal zusätzlich Abfangen Operational Range = Einsatzreichweite @@ -3938,7 +3945,7 @@ Knight = Ritter Camel Archer = Kamel-Bogenschütze -Conquistador = Eroberer +Conquistador = Konquistador Defense bonus when embarked = Verteidigungsbonus wenn Eingeschifft Naresuan's Elephant = Naresuans Elefant @@ -4052,7 +4059,7 @@ Zero = Zero B17 = B-17 Paratrooper = Fallschirmjäger -May Paradrop up to [amount] tiles from inside friendly territory = Kann einen Fallschirmabsprung bis zu [amount] Felder von innerhalb des befreundeten Territoriums aus durchführen +May Paradrop up to [amount] tiles from inside friendly territory = Kann aus befreundetem Territorium heraus einen Fallschirmabsprung bis zu [amount] Felder weit durchführen Tank = Panzer diff --git a/android/assets/jsons/translations/Swedish.properties b/android/assets/jsons/translations/Swedish.properties index 359dd122f9..c9886d2c69 100644 --- a/android/assets/jsons/translations/Swedish.properties +++ b/android/assets/jsons/translations/Swedish.properties @@ -1018,7 +1018,7 @@ relevant = tillämpliga # Promotions Pick promotion = Välj befordring - OR = ELLER + OR = ELLER units in open terrain = enheter i öppen terräng units in rough terrain = enheter i kuperad terräng Targeting II (air) = Målsökning II (luft) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 48c353fc97..dfa189c68c 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -48,6 +48,7 @@ Required tech: [requiredTech] = Requires [PolicyOrNationalWonder] = Cannot be purchased = See also = +Requires at least one of the following: = Current construction = Construction queue = @@ -856,6 +857,7 @@ Buildings that consume this resource = Buildings that require this resource worked near the city = Units that consume this resource = Can be built on = +or [terrainType] = Can be constructed by = Defence bonus = Movement cost = @@ -927,9 +929,13 @@ Gold reward for clearing barbarian camps = # Other civilopedia things Nations = Available for [unitTypes] = +Available for: = Free promotion: = Free promotions: = Free for [units] = +Free for: = +Granted by [param] = +Granted by: = [bonus] with [tech] = Difficulty levels = diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index cdc3dd390a..30ac5f24be 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -575,7 +575,7 @@ open class TileInfo { } fun toMarkup(viewingCiv: CivilizationInfo?): ArrayList { - val lineList = ArrayList() // more readable than StringBuilder, with same performance for our use-case + val lineList = ArrayList() val isViewableToPlayer = viewingCiv == null || UncivGame.Current.viewEntireMapForDebug || viewingCiv.viewableTiles.contains(this) diff --git a/core/src/com/unciv/models/ruleset/Nation.kt b/core/src/com/unciv/models/ruleset/Nation.kt index cdb3bfe7ef..17096b4227 100644 --- a/core/src/com/unciv/models/ruleset/Nation.kt +++ b/core/src/com/unciv/models/ruleset/Nation.kt @@ -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() */ + override var civilopediaText = listOf() + @Transient private lateinit var outerColorObject: Color fun getOuterColor(): Color = outerColorObject @@ -88,14 +96,10 @@ class Nation : INamed { lateinit var cities: ArrayList - fun getUniqueString(ruleset: Ruleset, forPickerScreen: Boolean = true): String { + /** Used only by NewGame Nation picker */ + fun getUniqueString(ruleset: Ruleset): String { val textList = ArrayList() - 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 { + val textList = ArrayList() + + 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, 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, 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, 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) + } + } + } diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 52c3077ad9..927d00e6ad 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -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(", ")) diff --git a/core/src/com/unciv/models/ruleset/unit/Promotion.kt b/core/src/com/unciv/models/ruleset/unit/Promotion.kt index 629611b91e..f5bf181a6f 100644 --- a/core/src/com/unciv/models/ruleset/unit/Promotion.kt +++ b/core/src/com/unciv/models/ruleset/unit/Promotion.kt @@ -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() var effect = "" var unitTypes = listOf() // The json parser wouldn't agree to deserialize this as a list of UnitTypes. =( var uniques = ArrayList() - val uniqueObjects: List by lazy { uniques.map { Unique(it) } + Unique(effect) } + private fun uniquesWithEffect() = sequence { + if (effect.isNotEmpty()) yield(effect) + yieldAll(uniques) + } + val uniqueObjects: List by lazy { uniquesWithEffect().map { Unique(it) }.toList() } - fun getDescription(promotionsForUnitType: Collection, 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() - for (unique in uniques + effect) { - stringBuilder.appendLine(unique.tr()) + + /** Used to describe a Promotion on the PromotionPickerScreen */ + fun getDescription(promotionsForUnitType: Collection):String { + val textList = ArrayList() + + for (unique in uniquesWithEffect()) { + textList += unique.tr() } - if(prerequisites.isNotEmpty()) { + if (prerequisites.isNotEmpty()) { val prerequisitesString: ArrayList = 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 { + val textList = ArrayList() + + 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 } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt index f1e186baa7..0c7ebc46d4 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt @@ -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() } ) diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt index bfff4eed30..e159301e0e 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaText.kt @@ -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)