Ancient Ruins, Civilopedia and Translations (#4907)

* Ancient Ruins, Civilopedia and Translations
- TranslationFileWriter can process Ruins.json
- Hide Religion / Civilopedia uniques hardcoded String moved to Constants
- Civilopedia display of Ruins pulls actual json rewards in code
- Manually curated rewards description removed

* Ancient Ruins, Civilopedia and Translations
- Old rewards descriptions moved to names, color
- Code now recreates old output closely
- civilopediaText allowed additionally
- TranslationFileWriter tweaked accordingly

* Ancient Ruins, Civilopedia and Translations - fix stargazers
This commit is contained in:
SomeTroglodyte 2021-08-21 19:57:39 +02:00 committed by GitHub
parent 1aea1d53b9
commit 13365e5ad6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 102 additions and 60 deletions

View File

@ -1,64 +1,70 @@
[
{
"name": "freeCulture",
"name": "discover cultural artifacts",
"notification": "We have discovered cultural artifacts in the ruins! (+20 culture)",
"uniques": ["Gain [20] [Culture]"]
"uniques": ["Gain [20] [Culture]"],
"color": "#cf8ff7"
},
{
"name": "joinWorker",
"name": "squatters willing to work for you",
"notification": "A [Worker] has joined us!",
"uniques": ["Free [Worker] found in the ruins"],
"excludedDifficulties": ["Prince", "King", "Emperor", "Immortal", "Deity"]
},
{
"name": "joinSettler",
"name": "squatters wishing to settle under your rule",
"notification": "A [Settler] has joined us!",
"uniques": ["Free [Settler] found in the ruins"],
"excludedDifficulties": ["Warlord","Prince","King","Emperor","Immortal","Deity"]
},
{
"name": "freeXP",
"name": "your exploring unit receives training",
"notification": "An ancient tribe trained us in their ways of combat!",
"uniques": ["This Unit gains [10] XP"]
},
{
"name": "freePop",
"name": "survivors (adds population to a city)",
"notification": "We have found survivors in the ruins! Population added to [cityName].",
"uniques": ["[+1] population in a random city"] // This can't be easily added to cityFilter, as it is non-deterministic
"uniques": ["[+1] population in a random city"], // This can't be easily added to cityFilter, as it is non-deterministic
"color": "#81c784"
},
{
"name": "freeGold",
"name": "a stash of gold",
"notification": "We have found a stash of [goldAmount] Gold in the ruins!",
"uniques": ["Gain [50]-[100] [Gold]"]
"uniques": ["Gain [50]-[100] [Gold]"],
"color": "#ffeb7f"
},
{
"name": "freeAncientTech",
"name": "discover a lost technology",
"notification": "We have discovered the lost technology of [techName] in the ruins!",
"uniques": ["[1] free random researchable Tech(s) from the [Ancient era]"]
"uniques": ["[1] free random researchable Tech(s) from the [Ancient era]"],
"color": "#7f7fff"
},
{
"name": "unitUpgrade",
"name": "advanced weaponry for your explorer",
"notification": "Our unit finds advanced weaponry hidden in the ruins!",
"uniques": ["This Unit upgrades for free including special upgrades"]
},
{
"name": "barbCampsRevealed",
"name": "reveal nearby Barbarian camps",
"notification": "You find evidence of Barbarian activity. Nearby Barbarian camps are revealed!",
"uniques": ["Reveal up to [All] [Barbarian encampment] within a [10] tile radius"]
},
{
"name": "crudelyDrawnMap",
"name": "find a crudely-drawn map",
"notification": "We have found a crudely-drawn map in the ruins!",
"uniques": ["From a randomly chosen tile [4] tiles away, reveal tiles up to [4] tiles away with [80]% chance"]
},
{
"name": "holySymbols",
"name": "discover holy symbols",
"notification": "We have found holy symbols in the ruins, giving us a deeper understanding of religion! (+[faithAmount] Faith)",
"uniques": ["Hidden when religion is disabled", "Gain enough Faith for a Pantheon"]
"uniques": ["Hidden when religion is disabled", "Gain enough Faith for a Pantheon"],
"color": "#CDDDF4"
},
{
"name": "prophecy",
"name": "an ancient prophecy",
"notification": "We have found an ancient prophecy in the ruins, greatly increasing our spiritual connection! (+[faithAmount] Faith)",
"uniques": ["Hidden when religion is disabled", "Gain enough Faith for [33]% of a Great Prophet", "Hidden after generating a Great Prophet"]
"uniques": ["Hidden when religion is disabled", "Gain enough Faith for [33]% of a Great Prophet", "Hidden after generating a Great Prophet"],
"color": "#CDDDF4"
}
]

View File

@ -237,19 +237,7 @@
{
"name": "Ancient ruins",
"uniques": ["Unpillagable", "Provides a random bonus when entered"],
"civilopediaText": [
{"text":"Ancient ruins provide a one-time random bonus when explored"},
{},
{"text":"The possible rewards are:"},
{"text":"a stash of gold", "indent":1, "starred":true, "color":"#ffeb7f"},
{"text":"survivors (adds population to a city)", "indent":1, "starred":true, "color":"#81c784"},
{"text":"discover a lost technology", "indent":1, "starred":true, "color":"#7f7fff"},
{"text":"a unit joins you", "indent":1, "starred":true},
{"text":"your exploring unit receives training", "indent":1, "starred":true}, // "link":"Tutorial/Experience"
{"text":"discover cultural artifacts", "indent":1, "starred":true, "color":"#cf8ff7"},
{"text":"find a crudely-drawn map", "indent":1, "starred":true}
]
"uniques": ["Unpillagable", "Provides a random bonus when entered"]
},
{
"name": "City ruins",

View File

@ -9,6 +9,8 @@ object Constants {
const val settler = "Settler"
const val settlerUnique = "Founds a new city"
const val eraSpecificUnit = "Era Starting Unit"
const val hiddenWithoutReligionUnique = "Hidden when religion is disabled"
const val hideFromCivilopediaUnique = "Will not be displayed in Civilopedia"
const val impassable = "Impassable"
const val ocean = "Ocean"

View File

@ -1,5 +1,6 @@
package com.unciv.logic.civilization.RuinsManager
import com.unciv.Constants
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.RuinReward
@ -40,7 +41,7 @@ class RuinsManager {
for (possibleReward in possibleRewards) {
if (civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties) continue
if ("Hidden when religion is disabled" in possibleReward.uniques && !civInfo.gameInfo.hasReligionEnabled()) continue
if (Constants.hiddenWithoutReligionUnique in possibleReward.uniques && !civInfo.gameInfo.hasReligionEnabled()) continue
if ("Hidden after generating a Great Prophet" in possibleReward.uniques && civInfo.religionManager.greatProphetsEarned > 0) continue
if (possibleReward.uniqueObjects.any { unique ->
unique.placeholderText == "Only available after [] turns"

View File

@ -1,5 +1,6 @@
package com.unciv.models.ruleset
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
@ -117,7 +118,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
.filterNot {
it.startsWith("Hidden ") && it.endsWith(" disabled") ||
it == "Unbuildable" ||
it == "Will not be displayed in Civilopedia"
it == Constants.hideFromCivilopediaUnique
}
/** used in CityScreen (CityInfoTable and ConstructionInfoTable) */
@ -468,7 +469,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
"Can only be built in annexed cities" -> if (construction.cityInfo.isPuppet || construction.cityInfo.foundingCiv == ""
|| construction.cityInfo.civInfo.civName == construction.cityInfo.foundingCiv) return unique.text
"Obsolete with []" -> if (civInfo.tech.isResearched(unique.params[0])) return unique.text
"Hidden when religion is disabled" -> if (!civInfo.gameInfo.hasReligionEnabled()) return unique.text
Constants.hiddenWithoutReligionUnique -> if (!civInfo.gameInfo.hasReligionEnabled()) return unique.text
}
if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo"

View File

@ -119,7 +119,7 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
private fun addUniqueBuildingsText(textList: ArrayList<String>, ruleset: Ruleset) {
for (building in ruleset.buildings.values
.filter { it.uniqueTo == name && "Will not be displayed in Civilopedia" !in it.uniques }) {
.filter { it.uniqueTo == name && Constants.hideFromCivilopediaUnique !in it.uniques }) {
if (building.replaces != null && ruleset.buildings.containsKey(building.replaces!!)) {
val originalBuilding = ruleset.buildings[building.replaces!!]!!
@ -148,7 +148,7 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
private fun addUniqueUnitsText(textList: ArrayList<String>, ruleset: Ruleset) {
for (unit in ruleset.units.values
.filter { it.uniqueTo == name && "Will not be displayed in Civilopedia" !in it.uniques }) {
.filter { it.uniqueTo == name && Constants.hideFromCivilopediaUnique !in it.uniques }) {
if (unit.replaces != null && ruleset.units.containsKey(unit.replaces!!)) {
val originalUnit = ruleset.units[unit.replaces!!]!!
textList += unit.name.tr() + " - " + "Replaces [${originalUnit.name}]".tr()
@ -239,7 +239,7 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
@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
if (building.uniqueTo != name || Constants.hideFromCivilopediaUnique 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!!]!!
@ -275,7 +275,7 @@ class Nation : INamed, ICivilopediaText, IHasUniques {
@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
if (unit.uniqueTo != name || Constants.hideFromCivilopediaUnique 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!!]!!

View File

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

View File

@ -1,5 +1,6 @@
package com.unciv.models.ruleset.tech
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.ruleset.Building
@ -117,8 +118,8 @@ class Technology: INamed, ICivilopediaText, IHasUniques {
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
&& (religionEnabled || Constants.hiddenWithoutReligionUnique !in it.uniques)
&& Constants.hideFromCivilopediaUnique !in it.uniques
}
}
@ -136,8 +137,8 @@ class Technology: INamed, ICivilopediaText, IHasUniques {
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
&& (religionEnabled || Constants.hiddenWithoutReligionUnique !in it.uniques)
&& Constants.hideFromCivilopediaUnique !in it.uniques
}
}

View File

@ -1,6 +1,9 @@
package com.unciv.models.ruleset.tile
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.RuinsManager.RuinsManager
import com.unciv.models.ruleset.Belief
import com.unciv.logic.map.RoadStatus
import com.unciv.models.ruleset.IHasUniques
@ -167,6 +170,22 @@ class TileImprovement : NamedStats(), ICivilopediaText, IHasUniques {
textList += FormattedLine(unique)
}
if (isAncientRuinsEquivalent() && ruleset.ruinRewards.isNotEmpty()) {
val difficulty = UncivGame.Current.gameInfo.gameParameters.difficulty
val religionEnabled = UncivGame.Current.gameInfo.hasReligionEnabled()
textList += FormattedLine()
textList += FormattedLine("The possible rewards are:")
ruleset.ruinRewards.values.asSequence()
.filter { reward ->
difficulty !in reward.excludedDifficulties &&
(religionEnabled || Constants.hiddenWithoutReligionUnique !in reward.uniques)
}
.forEach { reward ->
textList += FormattedLine(reward.name, starred = true, color = reward.color)
textList += reward.civilopediaText
}
}
val unit = ruleset.units.asSequence().firstOrNull {
entry -> entry.value.uniques.any {
it.startsWith("Can construct [$name]")

View File

@ -48,7 +48,7 @@ object TranslationFileWriter {
if (modFolder == null) { // base game
val templateFile = getFileHandle(modFolder, templateFileLocation) // read the template
if (templateFile.exists())
linesFromTemplates.addAll(templateFile.reader().readLines())
linesFromTemplates.addAll(templateFile.reader(TranslationFileReader.charset).readLines())
for (baseRuleset in BaseRuleset.values()) {
val generatedStringsFromBaseRuleset =
@ -250,7 +250,7 @@ object TranslationFileWriter {
val array = jsonParser.getFromJson(javaClass, jsonFile.path())
generatedStrings[filename] = mutableSetOf()
val resultStrings = generatedStrings[filename]
val resultStrings = generatedStrings[filename]!!
fun submitString(item: Any) {
val string = item.toString()
@ -285,8 +285,15 @@ object TranslationFileWriter {
stringToTranslate = stringToTranslate.replaceFirst(parameter, parameterName)
}
} else if (string.contains('{')) {
val matches = curlyBraceRegex.findAll(string)
if (matches.any()) {
// Ignore outer string, only translate the parts within `{}`
matches.forEach { submitString(it.groups[1]!!.value) }
return
}
}
resultStrings!!.add("$stringToTranslate = ")
resultStrings.add("$stringToTranslate = ")
return
}
@ -311,7 +318,7 @@ object TranslationFileWriter {
for (field in allFields) {
field.isAccessible = true
val fieldValue = field.get(element)
if (isFieldTranslatable(field, fieldValue)) { // skip fields which must not be translated
if (isFieldTranslatable(javaClass, field, fieldValue)) { // skip fields which must not be translated
// this field can contain sub-objects, let's serialize them as well
@Suppress("RemoveRedundantQualifierName") // to clarify List does _not_ inherit from anything in java.util
when (fieldValue) {
@ -331,7 +338,7 @@ object TranslationFileWriter {
for (element in array) {
serializeElement(element!!) // let's serialize the strings recursively
// This is a small hack to insert multiple /n into the set, which can't contain identical lines
resultStrings!!.add("$specialNewLineCode ${uniqueIndexOfNewLine++}")
resultStrings.add("$specialNewLineCode ${uniqueIndexOfNewLine++}")
}
}
println("Translation writer took ${System.currentTimeMillis()-startMillis}ms for ${jsonsFolder.name()}")
@ -339,6 +346,12 @@ object TranslationFileWriter {
return generatedStrings
}
/** Exclude fields by name that contain references to items defined elsewhere
* or are otherwise Strings but not user-displayed.
*
* An exclusion applies either over _all_ json files and all classes contained in them
* or Class-specific by using a "Class.Field" notation.
*/
private val untranslatableFieldSet = setOf(
"aiFreeTechs", "aiFreeUnits", "attackSound", "building",
"cannotBeBuiltWith", "cultureBuildings", "improvement", "improvingTech",
@ -347,21 +360,24 @@ object TranslationFileWriter {
"requiredNearbyImprovedResources", "requiredResource", "requiredTech", "requires",
"resourceTerrainAllow", "revealedBy", "startBias", "techRequired",
"terrainsCanBeBuiltOn", "terrainsCanBeFoundOn", "turnsInto", "uniqueTo", "upgradesTo",
"link", "icon", "extraImage", "color" // FormattedLine
"link", "icon", "extraImage", "color", // FormattedLine
"excludedDifficulties", "RuinReward.uniques" // RuinReward
)
/** Specifies Enums where the name property _is_ translatable, by Class name */
private val translatableEnumsSet = setOf("BeliefType")
private fun isFieldTranslatable(field: Field, fieldValue: Any?): Boolean {
// Exclude fields by name that contain references to items defined elsewhere
// or are otherwise Strings but not user-displayed.
// The Modifier.STATIC exclusion removes fields from e.g. companion objects.
// Fields of enum types need that type explicitly allowed in translatableEnumsSet
// (Using an annotation failed, even with @Retention RUNTIME they were invisible to reflection)
/** Checks whether a field's value should be included in the translation templates.
* Applies explicit field exclusions from [untranslatableFieldSet].
* The Modifier.STATIC exclusion removes fields from e.g. companion objects.
* Fields of enum types need that type explicitly allowed in [translatableEnumsSet]
*/
private fun isFieldTranslatable(clazz: Class<*>, field: Field, fieldValue: Any?): Boolean {
return fieldValue != null &&
fieldValue != "" &&
(field.modifiers and Modifier.STATIC) == 0 &&
(!field.type.isEnum || field.type.simpleName in translatableEnumsSet) &&
field.name !in untranslatableFieldSet
field.name !in untranslatableFieldSet &&
(clazz.componentType?.simpleName ?: clazz.simpleName) + "." + field.name !in untranslatableFieldSet
}
private fun getJavaClassByName(name: String): Class<Any> {
@ -374,6 +390,7 @@ object TranslationFileWriter {
"Policies" -> emptyArray<PolicyBranch>().javaClass
"Quests" -> emptyArray<Quest>().javaClass
"Religions" -> emptyArray<String>().javaClass
"Ruins" -> emptyArray<RuinReward>().javaClass
"Specialists" -> emptyArray<Specialist>().javaClass
"Techs" -> emptyArray<TechColumn>().javaClass
"Terrains" -> emptyArray<Terrain>().javaClass

View File

@ -379,8 +379,8 @@ class CivilopediaScreen(
val hideReligionItems = !game.gameInfo.hasReligionEnabled()
val noCulturalVictory = VictoryType.Cultural !in game.gameInfo.gameParameters.victoryTypes
return "Will not be displayed in Civilopedia" !in uniques
&& !(hideReligionItems && "Hidden when religion is disabled" in uniques)
return Constants.hideFromCivilopediaUnique !in uniques
&& !(hideReligionItems && Constants.hiddenWithoutReligionUnique in uniques)
&& !(uniqueObjects.filter { unique -> unique.placeholderText == "Hidden when [] Victory is disabled"}.any {
unique -> !game.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0] ))
})