Fix civilopedia parsing, fix dialog does not call update to enable next turn button (#1553)

This commit is contained in:
Vladimir Tanakov 2019-12-30 19:04:12 +03:00 committed by Yair Morgenstern
parent d3311b3f24
commit 1cc8227025
12 changed files with 181 additions and 101 deletions

View File

@ -0,0 +1,14 @@
package com.unciv
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.Json
class JsonParser {
private val json = Json().apply { ignoreUnknownFields = true }
fun <T> getFromJson(tClass: Class<T>, filePath: String): T {
val jsonText = Gdx.files.internal(filePath).readString(Charsets.UTF_8.name())
return json.fromJson(tClass, jsonText)
}
}

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.JsonParser
import com.unciv.UncivGame
import com.unciv.logic.GameInfo
import com.unciv.logic.automation.NextTurnAutomation
@ -26,6 +27,9 @@ import kotlin.collections.HashMap
import kotlin.math.roundToInt
class CivilizationInfo {
private val jsonParser = JsonParser()
@Transient lateinit var gameInfo: GameInfo
@Transient lateinit var nation:Nation
/**
@ -117,7 +121,7 @@ class CivilizationInfo {
val language = UncivGame.Current.settings.language.replace(" ","_")
val filePath = "jsons/Nations/Nations_$language.json"
if(!Gdx.files.internal(filePath).exists()) return nation
val translatedNation = gameInfo.ruleSet.getFromJson(Array<Nation>::class.java, filePath)
val translatedNation = jsonParser.getFromJson(Array<Nation>::class.java, filePath)
.firstOrNull { it.name==civName}
if(translatedNation==null) // this language's trnslation doesn't contain this nation yet,
return nation // default to english

View File

@ -1,32 +1,32 @@
package com.unciv.models
enum class Tutorial(val value: String) {
enum class Tutorial(val value: String, val isCivilopedia: Boolean) {
Introduction("Introduction"),
NewGame("New_Game"),
SlowStart("_Slow_Start"),
CultureAndPolicies("Culture_and_Policies"),
Happiness("Happiness"),
Unhappiness("Unhappiness"),
GoldenAge("Golden_Age"),
RoadsAndRailroads("Roads_and_Railroads"),
VictoryTypes("Victory_Types"),
EnemyCity("Enemy_City"),
LuxuryResource("Luxury_Resource"),
StrategicResource("Strategic_Resource"),
EnemyCityNeedsConqueringWithMeleeUnit("_EnemyCityNeedsConqueringWithMeleeUnit"),
AfterConquering("After_Conquering"),
BarbarianEncountered("_BarbarianEncountered"),
OtherCivEncountered("_OtherCivEncountered"),
ApolloProgram("Apollo_Program"),
InjuredUnits("Injured_Units"),
Workers("Workers"),
SiegeUnits("Siege_Units"),
Embarking("Embarking"),
CityRange("City_Range"),
IdleUnits("Idle_Units"),
ContactMe("Contact_Me"),
Pillaging("_Pillaging");
Introduction("Introduction", true),
NewGame("New_Game", true),
SlowStart("_Slow_Start", false),
CultureAndPolicies("Culture_and_Policies", true),
Happiness("Happiness", true),
Unhappiness("Unhappiness", true),
GoldenAge("Golden_Age", true),
RoadsAndRailroads("Roads_and_Railroads", true),
VictoryTypes("Victory_Types", true),
EnemyCity("Enemy_City", true),
LuxuryResource("Luxury_Resource", true),
StrategicResource("Strategic_Resource", true),
EnemyCityNeedsConqueringWithMeleeUnit("_EnemyCityNeedsConqueringWithMeleeUnit", false),
AfterConquering("After_Conquering", true),
BarbarianEncountered("_BarbarianEncountered", false),
OtherCivEncountered("_OtherCivEncountered", false),
ApolloProgram("Apollo_Program", true),
InjuredUnits("Injured_Units", true),
Workers("Workers", true),
SiegeUnits("Siege_Units", true),
Embarking("Embarking", true),
CityRange("City_Range", true),
IdleUnits("Idle_Units", true),
ContactMe("Contact_Me", true),
Pillaging("_Pillaging", false);
companion object {
fun findByName(name: String): Tutorial? = values().find { it.value == name }

View File

@ -1,7 +1,6 @@
package com.unciv.models.ruleset
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.Json
import com.unciv.JsonParser
import com.unciv.models.ruleset.tech.TechColumn
import com.unciv.models.ruleset.tech.Technology
import com.unciv.models.ruleset.tile.Terrain
@ -12,8 +11,11 @@ import com.unciv.models.ruleset.unit.Promotion
import com.unciv.models.stats.INamed
import kotlin.collections.set
class Ruleset {
var name=""
class Ruleset(load: Boolean = true) {
private val jsonParser = JsonParser()
var name = ""
val mods = LinkedHashSet<String>()
val buildings = LinkedHashMap<String, Building>()
val terrains = LinkedHashMap<String, Terrain>()
@ -26,20 +28,16 @@ class Ruleset {
val policyBranches = LinkedHashMap<String, PolicyBranch>()
val difficulties = LinkedHashMap<String, Difficulty>()
fun clone(): Ruleset{
fun clone(): Ruleset {
val newRuleset = Ruleset(false)
newRuleset.add(this)
return newRuleset
}
constructor(load:Boolean=true){
if(load) load()
}
fun <T> getFromJson(tClass: Class<T>, filePath:String): T {
val jsonText = Gdx.files.internal(filePath).readString(Charsets.UTF_8.name())
return Json().apply { ignoreUnknownFields = true }.fromJson(tClass, jsonText)
init {
if (load) {
load()
}
}
private fun <T : INamed> createHashmap(items: Array<T>): LinkedHashMap<String, T> {
@ -49,10 +47,10 @@ class Ruleset {
return hashMap
}
fun add(ruleset: Ruleset){
fun add(ruleset: Ruleset) {
buildings.putAll(ruleset.buildings)
difficulties.putAll(ruleset.difficulties)
nations .putAll(ruleset.nations)
nations.putAll(ruleset.nations)
policyBranches.putAll(ruleset.policyBranches)
technologies.putAll(ruleset.technologies)
buildings.putAll(ruleset.buildings)
@ -63,7 +61,7 @@ class Ruleset {
units.putAll(ruleset.units)
}
fun clearExceptModNames(){
fun clearExceptModNames() {
buildings.clear()
difficulties.clear()
nations.clear()
@ -77,19 +75,18 @@ class Ruleset {
units.clear()
}
fun load(folderPath: String="jsons") {
fun load(folderPath: String = "jsons") {
val gameBasicsStartTime = System.currentTimeMillis()
val techColumns = getFromJson(Array<TechColumn>::class.java, "$folderPath/Techs.json")
val techColumns = jsonParser.getFromJson(Array<TechColumn>::class.java, "$folderPath/Techs.json")
for (techColumn in techColumns) {
for (tech in techColumn.techs) {
if (tech.cost==0) tech.cost = techColumn.techCost
if (tech.cost == 0) tech.cost = techColumn.techCost
tech.column = techColumn
technologies[tech.name] = tech
}
}
buildings += createHashmap(getFromJson(Array<Building>::class.java, "$folderPath/Buildings.json"))
buildings += createHashmap(jsonParser.getFromJson(Array<Building>::class.java, "$folderPath/Buildings.json"))
for (building in buildings.values) {
if (building.requiredTech == null) continue
val column = technologies[building.requiredTech!!]!!.column
@ -97,13 +94,13 @@ class Ruleset {
building.cost = if (building.isWonder || building.isNationalWonder) column!!.wonderCost else column!!.buildingCost
}
terrains += createHashmap(getFromJson(Array<Terrain>::class.java, "$folderPath/Terrains.json"))
tileResources += createHashmap(getFromJson(Array<TileResource>::class.java, "$folderPath/TileResources.json"))
tileImprovements += createHashmap(getFromJson(Array<TileImprovement>::class.java, "$folderPath/TileImprovements.json"))
units += createHashmap(getFromJson(Array<BaseUnit>::class.java, "$folderPath/Units.json"))
unitPromotions += createHashmap(getFromJson(Array<Promotion>::class.java, "$folderPath/UnitPromotions.json"))
terrains += createHashmap(jsonParser.getFromJson(Array<Terrain>::class.java, "$folderPath/Terrains.json"))
tileResources += createHashmap(jsonParser.getFromJson(Array<TileResource>::class.java, "$folderPath/TileResources.json"))
tileImprovements += createHashmap(jsonParser.getFromJson(Array<TileImprovement>::class.java, "$folderPath/TileImprovements.json"))
units += createHashmap(jsonParser.getFromJson(Array<BaseUnit>::class.java, "$folderPath/Units.json"))
unitPromotions += createHashmap(jsonParser.getFromJson(Array<Promotion>::class.java, "$folderPath/UnitPromotions.json"))
policyBranches += createHashmap(getFromJson(Array<PolicyBranch>::class.java, "$folderPath/Policies.json"))
policyBranches += createHashmap(jsonParser.getFromJson(Array<PolicyBranch>::class.java, "$folderPath/Policies.json"))
for (branch in policyBranches.values) {
branch.requires = ArrayList()
branch.branch = branch
@ -114,15 +111,13 @@ class Ruleset {
branch.policies.last().name = branch.name + " Complete"
}
nations += createHashmap(getFromJson(Array<Nation>::class.java, "$folderPath/Nations/Nations.json"))
for(nation in nations.values) nation.setTransients()
nations += createHashmap(jsonParser.getFromJson(Array<Nation>::class.java, "$folderPath/Nations/Nations.json"))
for (nation in nations.values) nation.setTransients()
difficulties += createHashmap(getFromJson(Array<Difficulty>::class.java, "$folderPath/Difficulties.json"))
difficulties += createHashmap(jsonParser.getFromJson(Array<Difficulty>::class.java, "$folderPath/Difficulties.json"))
val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime
println("Loading game basics - "+gameBasicsLoadTime+"ms")
println("Loading game basics - " + gameBasicsLoadTime + "ms")
}
}

View File

@ -2,6 +2,7 @@ package com.unciv.ui
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.JsonParser
import com.unciv.UncivGame
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.tr
@ -12,13 +13,13 @@ import java.util.*
class CivilopediaScreen(ruleset: Ruleset) : CameraStageBaseScreen() {
class CivilopediaEntry(var name: String, var description: String, var image: Actor? = null)
val categoryToEntries = LinkedHashMap<String, Collection<CivilopediaEntry>>()
val categoryToButtons = LinkedHashMap<String, Button>()
private val categoryToEntries = LinkedHashMap<String, Collection<CivilopediaEntry>>()
private val categoryToButtons = LinkedHashMap<String, Button>()
val entrySelectTable = Table().apply { defaults().pad(5f) }
private val entrySelectTable = Table().apply { defaults().pad(5f) }
val description = "".toLabel()
val tutorialMiner = TutorialMiner()
private val tutorialMiner = TutorialMiner(JsonParser())
fun select(category: String) {
entrySelectTable.clear()

View File

@ -10,6 +10,7 @@ class TutorialController(
private val tutorialQueue = mutableSetOf<Tutorial>()
private var isTutorialShowing = false
var allTutorialsShowedCallback: (() -> Unit)? = null
fun showTutorial(tutorial: Tutorial) {
if (!UncivGame.Current.settings.showTutorials) return
@ -23,7 +24,9 @@ class TutorialController(
private fun showTutorialIfNeeded() {
val tutorial = tutorialQueue.firstOrNull()
if (tutorial != null && !isTutorialShowing) {
if (tutorial == null) {
allTutorialsShowedCallback?.invoke()
} else if (!isTutorialShowing) {
isTutorialShowing = true
val texts = tutorialMiner.getTutorial(tutorial, UncivGame.Current.settings.language)
tutorialRender.showTutorial(TutorialForRender(tutorial, texts)) {

View File

@ -2,22 +2,34 @@ package com.unciv.ui.tutorials
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.Array
import com.unciv.UncivGame
import com.unciv.JsonParser
import com.unciv.models.Tutorial
class TutorialMiner {
class TutorialMiner(private val jsonParser: JsonParser) {
companion object {
private const val TUTORIALS_PATH = "jsons/Tutorials/Tutorials_%s.json"
}
fun getAllTutorials(language: String): Map<Tutorial, List<String>> {
fun getCivilopediaTutorials(language: String): Map<Tutorial, List<String>> =
getAllTutorials(language).filter { it.key.isCivilopedia }
fun getTutorial(tutorial: Tutorial, language: String): List<String> {
val tutors = getAllTutorials(language)[tutorial]
if (tutors != null) {
return tutors
} else {
return emptyList()
}
}
private fun getAllTutorials(language: String): Map<Tutorial, List<String>> {
val path = TUTORIALS_PATH.format(language)
if (!Gdx.files.internal(path).exists()) return emptyMap()
// ...Yes. Disgusting. I wish I didn't have to do this.
val x = LinkedHashMap<String, Array<Array<String>>>()
val tutorials: LinkedHashMap<String, Array<Array<String>>> = UncivGame.Current.ruleset.getFromJson(x.javaClass, path)
val tutorials: LinkedHashMap<String, Array<Array<String>>> = jsonParser.getFromJson(x.javaClass, path)
val tutorialMap = mutableMapOf<Tutorial, List<String>>()
for (tutorial in tutorials) {
@ -25,10 +37,4 @@ class TutorialMiner {
}
return tutorialMap
}
fun getCivilopediaTutorials(language: String): Map<Tutorial, List<String>> =
getAllTutorials(language).filter { !it.key.name.startsWith("_") }
fun getTutorial(tutorial: Tutorial, language: String): List<String> =
getAllTutorials(language)[tutorial] ?: emptyList()
}

View File

@ -7,10 +7,24 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.graphics.g2d.Batch
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.scenes.scene2d.*
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.InputListener
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
import com.badlogic.gdx.utils.viewport.ExtendViewport
import com.unciv.JsonParser
import com.unciv.UncivGame
import com.unciv.models.Tutorial
import com.unciv.models.translations.tr
@ -26,7 +40,9 @@ open class CameraStageBaseScreen : Screen {
var stage: Stage
var hasPopupOpen = false
private var tutorialController: TutorialController? = null
val tutorialController by lazy {
TutorialController(TutorialMiner(JsonParser()), TutorialRender(this))
}
init {
val width:Float
@ -40,10 +56,8 @@ open class CameraStageBaseScreen : Screen {
height = resolutions[1]
stage = Stage(ExtendViewport(width, height), batch)
}
override fun show() {}
override fun render(delta: Float) {
@ -67,14 +81,11 @@ open class CameraStageBaseScreen : Screen {
override fun dispose() {}
fun displayTutorial(tutorial: Tutorial) {
if (tutorialController == null) {
tutorialController = TutorialController(TutorialMiner(), TutorialRender(this))
}
tutorialController?.showTutorial(tutorial)
tutorialController.showTutorial(tutorial)
}
fun hasVisibleDialogs(): Boolean =
tutorialController?.isTutorialShowing() == true || stage.actors.any { it is TradePopup } || hasPopupOpen
tutorialController.isTutorialShowing() || stage.actors.any { it is TradePopup } || hasPopupOpen
companion object {
var skin = Skin(Gdx.files.internal("skin/flat-earth-ui.json"))

View File

@ -25,7 +25,15 @@ import com.unciv.ui.pickerscreens.PolicyPickerScreen
import com.unciv.ui.pickerscreens.TechButton
import com.unciv.ui.pickerscreens.TechPickerScreen
import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.*
import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.centerX
import com.unciv.ui.utils.colorFromRGB
import com.unciv.ui.utils.disable
import com.unciv.ui.utils.enable
import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.setFontSize
import com.unciv.ui.utils.toLabel
import com.unciv.ui.worldscreen.bottombar.BattleTable
import com.unciv.ui.worldscreen.bottombar.TileInfoTable
import com.unciv.ui.worldscreen.optionstable.OnlineMultiplayer
@ -121,12 +129,16 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
}, Actions.delay(10f)))) // delay is in seconds
}
tutorialController.allTutorialsShowedCallback = {
shouldUpdate = true
}
// don't run update() directly, because the UncivGame.worldScreen should be set so that the city buttons and tile groups
// know what the viewing civ is.
shouldUpdate=true
shouldUpdate = true
}
fun loadLatestMultiplayerState(){
private fun loadLatestMultiplayerState(){
val loadingGamePopup = PopupTable(this)
loadingGamePopup.add("Loading latest game state...")
loadingGamePopup.open()
@ -430,7 +442,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
}
}
fun updateNextTurnButton(isSomethingOpen: Boolean) {
private fun updateNextTurnButton(isSomethingOpen: Boolean) {
val text = when {
!isPlayersTurn -> "Waiting for other players..."
viewingCiv.shouldGoToDueUnit() -> "Next unit"

View File

@ -3,6 +3,7 @@ package com.unciv.testing
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.Array
import com.unciv.JsonParser
import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.Translations
@ -10,12 +11,13 @@ import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
import java.util.HashSet
@RunWith(GdxTestRunner::class)
class TranslationTests {
var translations = Translations()
var ruleSet = Ruleset(true)
private var translations = Translations()
private var ruleSet = Ruleset(true)
private val jsonParser = JsonParser()
@Before
fun loadTranslations() {
@ -39,8 +41,8 @@ class TranslationTests {
fun allUnitUniquesHaveTranslation() {
val strings: MutableSet<String> = HashSet()
for (unit in ruleSet.units.values) for (unique in unit.uniques) if (!unique.startsWith("Bonus")
&& !unique.startsWith("Penalty")
&& !unique.contains("[")) // templates
&& !unique.startsWith("Penalty")
&& !unique.contains("[")) // templates
strings.add(unique)
val allStringsHaveTranslation = allStringAreTranslated(strings)
Assert.assertTrue(allStringsHaveTranslation)
@ -151,7 +153,7 @@ class TranslationTests {
@Test
fun allTranslatedNationsFilesAreSerializable() {
for (file in Gdx.files.internal("jsons/Nations").list()) {
ruleSet.getFromJson(Array<Nation>().javaClass, file.path())
jsonParser.getFromJson(Array<Nation>().javaClass, file.path())
}
Assert.assertTrue("This test will only pass when there is a translation for all promotions",
true)
@ -167,18 +169,16 @@ class TranslationTests {
val placeholders = placeholderPattern.findAll(key).map { it.value }.toList()
for (language in languages) {
placeholders.forEach { placeholder ->
if(!translations.get(key, language).contains(placeholder)) {
if (!translations.get(key, language).contains(placeholder)) {
allTranslationsHaveCorrectPlaceholders = false
println("Placeholder `$placeholder` not found in `$language` for key `$key`")
}
}
}
}
Assert.assertTrue(
"This test will only pass when all translations' placeholders match those of the key",
allTranslationsHaveCorrectPlaceholders
"This test will only pass when all translations' placeholders match those of the key",
allTranslationsHaveCorrectPlaceholders
)
}
}

View File

@ -0,0 +1,34 @@
package com.unciv.testing
import com.unciv.JsonParser
import com.unciv.models.Tutorial
import com.unciv.ui.tutorials.TutorialMiner
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(GdxTestRunner::class)
class TutorialMinerTests {
private val languages = listOf(
"English", "Czech", "French", "Italian",
"Korean", "Polish", "Ukrainian", "Russian",
"Simplified_Chinese", "Traditional_Chinese"
)
private val jsonParser = JsonParser()
private val tutorialMiner = TutorialMiner(jsonParser)
@Test
fun getCivilopediaTutorials() {
// GIVEN
val expectedTutorKeys = Tutorial.values().filter { it.isCivilopedia }.map { it.value }
// WHEN
val result = languages.map { it to tutorialMiner.getCivilopediaTutorials(it).map { it.key.value } }
// THEN
result.forEach { (language, keys) ->
assertTrue("$language civilopedia does not match", keys.containsAll(expectedTutorKeys))
}
}
}

View File

@ -24,7 +24,7 @@ class TutorialTranslationTests {
@Test
fun testValidNumberOfTranslationTutorials() {
languages.forEach { language ->
for (language in languages) {
val keys = getKeysForLanguage(language)
assertTrue("$language tutorial does not match", keys.size == tutorialCount)
}