In-depth serialization improvement, fixes Barbarian Camps revealed by Honor not showing immediately in multiplayer

* Fix Barbarian Camp Spawned notification not revealing the camp on the map in multiplayer

* Fix lastSeenImprovement not being cloned

* Use HashMapVector2 in BarbarianManager

* Fix value not having its class written out for proper deserializing

* Refactor: various code improvements
This commit is contained in:
Timo T
2022-05-08 12:35:41 +02:00
committed by GitHub
parent 569b51cb27
commit 86d5011da1
27 changed files with 206 additions and 94 deletions

View File

@ -6,10 +6,10 @@ import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.GuardedBy import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.unciv.json.json
import com.unciv.logic.CustomSaveLocationHelper import com.unciv.logic.CustomSaveLocationHelper
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.logic.GameSaver.json
// The Storage Access Framework is available from API 19 and up: // The Storage Access Framework is available from API 19 and up:
// https://developer.android.com/guide/topics/providers/document-provider // https://developer.android.com/guide/topics/providers/document-provider

View File

@ -1,22 +0,0 @@
package com.unciv
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Json
import com.unciv.logic.UncivShowableException
class JsonParser {
private val json = Json().apply { ignoreUnknownFields = true }
fun <T> getFromJson(tClass: Class<T>, filePath: String): T = getFromJson(tClass, Gdx.files.internal(filePath))
fun <T> getFromJson(tClass: Class<T>, file: FileHandle): T {
try {
val jsonText = file.readString(Charsets.UTF_8.name())
return json.fromJson(tClass, jsonText)
} catch (exception:Exception){
throw Exception("Could not parse json of file ${file.name()}", exception)
}
}
}

View File

@ -0,0 +1,24 @@
package com.unciv.json
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Json
import java.util.HashMap
/**
* @see NonStringKeyMapSerializer
*/
class HashMapVector2<T> : HashMap<Vector2, T>() {
companion object {
init {
@Suppress("UNCHECKED_CAST") // kotlin can't tell that HashMapVector2 is also a MutableMap within generics
val mapClass = HashMapVector2::class.java as Class<MutableMap<Vector2, Any>>
val serializer = NonStringKeyMapSerializer(
mapClass,
Vector2::class.java,
{ HashMapVector2<Any>() }
)
jsonSerializers.add(Pair(mapClass, serializer))
}
}
}

View File

@ -0,0 +1,53 @@
package com.unciv.json
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.Json.Serializer
import com.badlogic.gdx.utils.JsonValue
/**
* A [Serializer] for gdx's [Json] that serializes a map that does not have [String] as its key class.
*
* Exists solely because [Json] always serializes any map by converting its key to [String], so when you load it again,
* all your keys are [String], messing up value retrieval.
*
* To work around that, we have to use a custom serializer. A custom serializer in Json is only added for a specific class
* and only checks for direct equality, and since we can't just do `HashMap<Any, *>::class.java`, only `HashMap::class.java`,
* we have to create a completely new class and use that class as [mapClass] here.
*
* @param MT Must be a type that extends [MutableMap]
* @param KT Must be the key type of [MT]
*/
class NonStringKeyMapSerializer<MT: MutableMap<KT, Any>, KT>(
private val mapClass: Class<MT>,
private val keyClass: Class<KT>,
private val mutableMapFactory: () -> MT
) : Serializer<MT> {
override fun write(json: Json, toWrite: MT, knownType: Class<*>) {
json.writeObjectStart()
json.writeType(mapClass)
json.writeArrayStart("entries")
for ((key, value) in toWrite) {
json.writeArrayStart()
json.writeValue(key)
json.writeValue(value, null)
json.writeArrayEnd()
}
json.writeArrayEnd()
json.writeObjectEnd()
}
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): MT {
val result = mutableMapFactory()
val entries = jsonData.get("entries")
var entry = entries!!.child
while (entry != null) {
val key = json.readValue(keyClass, entry.child)
val value = json.readValue<Any>(null, entry.child.next)
result[key!!] = value!! as Any
entry = entry.next
}
return result
}
}

View File

@ -0,0 +1,31 @@
package com.unciv.json
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.Json.Serializer
internal val jsonSerializers = ArrayList<Pair<Class<*>, Serializer<*>>>()
/**
* [Json] is not thread-safe.
*/
fun json() = Json().apply {
setIgnoreDeprecated(true)
ignoreUnknownFields = true
for ((clazz, serializer) in jsonSerializers) {
@Suppress("UNCHECKED_CAST") // we used * to accept all types, so kotlin can't know if the class & serializer parameters are actually the same
setSerializer(clazz as Class<Any>, serializer as Serializer<Any>)
}
}
fun <T> Json.fromJsonFile(tClass: Class<T>, filePath: String): T = fromJsonFile(tClass, Gdx.files.internal(filePath))
fun <T> Json.fromJsonFile(tClass: Class<T>, file: FileHandle): T {
try {
val jsonText = file.readString(Charsets.UTF_8.name())
return fromJson(tClass, jsonText)
} catch (exception:Exception){
throw Exception("Could not parse json of file ${file.name()}", exception)
}
}

View File

@ -1,7 +1,9 @@
package com.unciv.logic package com.unciv.logic
import com.badlogic.gdx.math.Vector2
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.PerpetualConstruction import com.unciv.logic.city.PerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.TechManager import com.unciv.logic.civilization.TechManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager import com.unciv.logic.civilization.diplomacy.DiplomacyManager
@ -147,4 +149,14 @@ object BackwardCompatibility {
maxXPfromBarbarians = 30 maxXPfromBarbarians = 30
} }
} }
/** Removes the workaround previously used for storing a map that does not have a [String] key
* @see com.unciv.json.NonStringKeyMapSerializer
*/
@Suppress("DEPRECATION")
fun CivilizationInfo.migrateSeenImprovements() {
if (lastSeenImprovementSaved.isEmpty()) return;
lastSeenImprovement.putAll(lastSeenImprovementSaved.mapKeys { Vector2().fromString(it.key) })
lastSeenImprovementSaved.clear()
}
} }

View File

@ -2,6 +2,8 @@ package com.unciv.logic
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.Constants import com.unciv.Constants
import com.unciv.json.HashMapVector2
import com.unciv.json.json
import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
@ -15,7 +17,7 @@ import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
class BarbarianManager { class BarbarianManager {
val camps = HashMap<Vector2, Encampment>() val camps = HashMapVector2<Encampment>()
@Transient @Transient
lateinit var gameInfo: GameInfo lateinit var gameInfo: GameInfo

View File

@ -3,6 +3,7 @@ package com.unciv.logic
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions
import com.unciv.logic.BackwardCompatibility.migrateSeenImprovements
import com.unciv.logic.BackwardCompatibility.removeMissingModReferences import com.unciv.logic.BackwardCompatibility.removeMissingModReferences
import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.civilization.* import com.unciv.logic.civilization.*
@ -395,6 +396,8 @@ class GameInfo {
gameParameters.baseRuleset = baseRulesetInMods gameParameters.baseRuleset = baseRulesetInMods
gameParameters.mods = LinkedHashSet(gameParameters.mods.filter { it != baseRulesetInMods }) gameParameters.mods = LinkedHashSet(gameParameters.mods.filter { it != baseRulesetInMods })
} }
// [TEMPORARY] Convert old saves to remove json workaround
for (civInfo in civilizations) civInfo.migrateSeenImprovements()
ruleSet = RulesetCache.getComplexRuleset(gameParameters) ruleSet = RulesetCache.getComplexRuleset(gameParameters)

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.Json
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.GameSettings
import com.unciv.ui.crashhandling.crashHandlingThread import com.unciv.ui.crashhandling.crashHandlingThread
import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.crashhandling.postCrashHandlingRunnable
@ -21,8 +22,6 @@ object GameSaver {
* See https://developer.android.com/training/data-storage/app-specific#external-access-files */ * See https://developer.android.com/training/data-storage/app-specific#external-access-files */
var externalFilesDirForAndroid = "" var externalFilesDirForAndroid = ""
fun json() = Json().apply { setIgnoreDeprecated(true); ignoreUnknownFields = true } // Json() is NOT THREAD SAFE so we need to create a new one for each function
fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder
fun getSave(GameName: String, multiplayer: Boolean = false): FileHandle { fun getSave(GameName: String, multiplayer: Boolean = false): FileHandle {

View File

@ -2,13 +2,12 @@ package com.unciv.logic
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.unciv.json.json
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.ui.saves.Gzip import com.unciv.ui.saves.Gzip
object MapSaver { object MapSaver {
fun json() = GameSaver.json()
const val mapsFolder = "maps" const val mapsFolder = "maps"
var saveZipped = true var saveZipped = true

View File

@ -1,8 +1,15 @@
package com.unciv.logic.civilization package com.unciv.logic.civilization
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.Json.Serializer
import com.badlogic.gdx.utils.JsonValue
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.HashMapVector2
import com.unciv.json.json
import com.unciv.logic.BarbarianManager
import com.unciv.logic.Encampment
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.UncivShowableException import com.unciv.logic.UncivShowableException
import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.automation.NextTurnAutomation
@ -32,9 +39,6 @@ import com.unciv.ui.utils.toPercent
import com.unciv.ui.utils.withItem import com.unciv.ui.utils.withItem
import com.unciv.ui.victoryscreen.RankingType import com.unciv.ui.victoryscreen.RankingType
import java.util.* import java.util.*
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -166,13 +170,11 @@ class CivilizationInfo {
var citiesCreated = 0 var citiesCreated = 0
var exploredTiles = HashSet<Vector2>() var exploredTiles = HashSet<Vector2>()
// This double construction because for some reason the game wants to load a @Deprecated("Only for backward compatibility, will have no values after GameInfo.setTransients",
// map<Vector2, String> as a map<String, String> causing all sorts of type problems. ReplaceWith("lastSeenImprovement"))
// So we let the game have its map<String, String> and remap it in setTransients,
// everyone's happy. Sort of.
var lastSeenImprovementSaved = HashMap<String, String>() var lastSeenImprovementSaved = HashMap<String, String>()
@Transient
var lastSeenImprovement = HashMap<Vector2, String>() var lastSeenImprovement = HashMapVector2<String>()
// To correctly determine "game over" condition as clarified in #4707 // To correctly determine "game over" condition as clarified in #4707
// Nullable type meant to be deprecated and converted to non-nullable, // Nullable type meant to be deprecated and converted to non-nullable,
@ -251,7 +253,7 @@ class CivilizationInfo {
// Cloning it by-pointer is a horrific move, since the serialization would go over it ANYWAY and still lead to concurrency problems. // Cloning it by-pointer is a horrific move, since the serialization would go over it ANYWAY and still lead to concurrency problems.
// Cloning it by iterating on the tilemap values may seem ridiculous, but it's a perfectly thread-safe way to go about it, unlike the other solutions. // Cloning it by iterating on the tilemap values may seem ridiculous, but it's a perfectly thread-safe way to go about it, unlike the other solutions.
toReturn.exploredTiles.addAll(gameInfo.tileMap.values.asSequence().map { it.position }.filter { it in exploredTiles }) toReturn.exploredTiles.addAll(gameInfo.tileMap.values.asSequence().map { it.position }.filter { it in exploredTiles })
toReturn.lastSeenImprovementSaved.putAll(lastSeenImprovement.mapKeys { it.key.toString() }) toReturn.lastSeenImprovement.putAll(lastSeenImprovement)
toReturn.notifications.addAll(notifications) toReturn.notifications.addAll(notifications)
toReturn.citiesCreated = citiesCreated toReturn.citiesCreated = citiesCreated
toReturn.popupAlerts.addAll(popupAlerts) toReturn.popupAlerts.addAll(popupAlerts)
@ -805,8 +807,6 @@ class CivilizationInfo {
} }
hasLongCountDisplayUnique = hasUnique(UniqueType.MayanCalendarDisplay) hasLongCountDisplayUnique = hasUnique(UniqueType.MayanCalendarDisplay)
lastSeenImprovement.putAll(lastSeenImprovementSaved.mapKeys { Vector2().fromString(it.key) })
} }
fun updateSightAndResources() { fun updateSightAndResources() {

View File

@ -1,6 +1,6 @@
package com.unciv.logic.multiplayer package com.unciv.logic.multiplayer
import com.unciv.logic.GameSaver import com.unciv.json.json
import com.unciv.ui.utils.UncivDateFormat.parseDate import com.unciv.ui.utils.UncivDateFormat.parseDate
import java.io.* import java.io.*
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -63,12 +63,12 @@ object DropBox {
// instead of the path. // instead of the path.
val response = dropboxApi("https://api.dropboxapi.com/2/files/list_folder", val response = dropboxApi("https://api.dropboxapi.com/2/files/list_folder",
"{\"path\":\"$folder\"}", "application/json") "{\"path\":\"$folder\"}", "application/json")
var currentFolderListChunk = GameSaver.json().fromJson(FolderList::class.java, response) var currentFolderListChunk = json().fromJson(FolderList::class.java, response)
folderList.addAll(currentFolderListChunk.entries) folderList.addAll(currentFolderListChunk.entries)
while (currentFolderListChunk.has_more) { while (currentFolderListChunk.has_more) {
val continuationResponse = dropboxApi("https://api.dropboxapi.com/2/files/list_folder/continue", val continuationResponse = dropboxApi("https://api.dropboxapi.com/2/files/list_folder/continue",
"{\"cursor\":\"${currentFolderListChunk.cursor}\"}", "application/json") "{\"cursor\":\"${currentFolderListChunk.cursor}\"}", "application/json")
currentFolderListChunk = GameSaver.json().fromJson(FolderList::class.java, continuationResponse) currentFolderListChunk = json().fromJson(FolderList::class.java, continuationResponse)
folderList.addAll(currentFolderListChunk.entries) folderList.addAll(currentFolderListChunk.entries)
} }
return folderList return folderList
@ -115,7 +115,7 @@ object DropBox {
val stream = dropboxApi("https://api.dropboxapi.com/2/files/get_metadata", val stream = dropboxApi("https://api.dropboxapi.com/2/files/get_metadata",
"{\"path\":\"$fileName\"}", "application/json")!! "{\"path\":\"$fileName\"}", "application/json")!!
val reader = BufferedReader(InputStreamReader(stream)) val reader = BufferedReader(InputStreamReader(stream))
return GameSaver.json().fromJson(DropboxMetaData::class.java, reader.readText()) return json().fromJson(DropboxMetaData::class.java, reader.readText())
} }
// //

View File

@ -3,6 +3,7 @@ package com.unciv.logic.multiplayer
import com.badlogic.gdx.Net import com.badlogic.gdx.Net
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameInfoPreview import com.unciv.logic.GameInfoPreview
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
@ -86,7 +87,7 @@ class OnlineMultiplayer(var fileStorageIdentifier: String? = null) {
tryUploadGamePreview(gameInfo.asPreview()) tryUploadGamePreview(gameInfo.asPreview())
} }
val zippedGameInfo = Gzip.zip(GameSaver.json().toJson(gameInfo)) val zippedGameInfo = Gzip.zip(json().toJson(gameInfo))
fileStorage.saveFileData(gameInfo.gameId, zippedGameInfo) fileStorage.saveFileData(gameInfo.gameId, zippedGameInfo)
} }
@ -97,7 +98,7 @@ class OnlineMultiplayer(var fileStorageIdentifier: String? = null) {
* @see GameInfo.asPreview * @see GameInfo.asPreview
*/ */
fun tryUploadGamePreview(gameInfo: GameInfoPreview) { fun tryUploadGamePreview(gameInfo: GameInfoPreview) {
val zippedGameInfo = Gzip.zip(GameSaver.json().toJson(gameInfo)) val zippedGameInfo = Gzip.zip(json().toJson(gameInfo))
fileStorage.saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo) fileStorage.saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo)
} }

View File

@ -1,5 +1,6 @@
package com.unciv.logic.multiplayer package com.unciv.logic.multiplayer
import com.unciv.json.json
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameInfoPreview import com.unciv.logic.GameInfoPreview
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
@ -67,7 +68,7 @@ class ServerMutex(val gameInfo: GameInfoPreview) {
} }
try { try {
OnlineMultiplayer().fileStorage.saveFileData(fileName, Gzip.zip(GameSaver.json().toJson(LockFile()))) OnlineMultiplayer().fileStorage.saveFileData(fileName, Gzip.zip(json().toJson(LockFile())))
} catch (ex: FileStorageConflictException) { } catch (ex: FileStorageConflictException) {
return locked return locked
} }

View File

@ -3,8 +3,9 @@ package com.unciv.models.metadata
import com.badlogic.gdx.Application import com.badlogic.gdx.Application
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.unciv.JsonParser
import com.unciv.Constants import com.unciv.Constants
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.Fonts
import java.io.File import java.io.File
@ -124,7 +125,7 @@ class GameSettings {
// In fact, at this point Gdx.app or Gdx.files are null but this still works. // In fact, at this point Gdx.app or Gdx.files are null but this still works.
val file = FileHandle(base + File.separator + GameSaver.settingsFileName) val file = FileHandle(base + File.separator + GameSaver.settingsFileName)
return if (file.exists()) return if (file.exists())
JsonParser().getFromJson( json().fromJsonFile(
GameSettings::class.java, GameSettings::class.java,
file file
) )

View File

@ -4,7 +4,8 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
import com.unciv.JsonParser import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.BackwardCompatibility.updateDeprecations import com.unciv.logic.BackwardCompatibility.updateDeprecations
import com.unciv.logic.UncivShowableException import com.unciv.logic.UncivShowableException
import com.unciv.logic.map.MapParameters import com.unciv.logic.map.MapParameters
@ -72,7 +73,6 @@ class ModOptions : IHasUniques {
class Ruleset { class Ruleset {
private val jsonParser = JsonParser()
var folderLocation:FileHandle?=null var folderLocation:FileHandle?=null
var name = "" var name = ""
@ -197,7 +197,7 @@ class Ruleset {
val modOptionsFile = folderHandle.child("ModOptions.json") val modOptionsFile = folderHandle.child("ModOptions.json")
if (modOptionsFile.exists()) { if (modOptionsFile.exists()) {
try { try {
modOptions = jsonParser.getFromJson(ModOptions::class.java, modOptionsFile) modOptions = json().fromJsonFile(ModOptions::class.java, modOptionsFile)
modOptions.updateDeprecations() modOptions.updateDeprecations()
} catch (ex: Exception) {} } catch (ex: Exception) {}
modOptions.uniqueObjects = modOptions.uniques.map { Unique(it, UniqueTarget.ModOptions) } modOptions.uniqueObjects = modOptions.uniques.map { Unique(it, UniqueTarget.ModOptions) }
@ -206,7 +206,7 @@ class Ruleset {
val techFile = folderHandle.child("Techs.json") val techFile = folderHandle.child("Techs.json")
if (techFile.exists()) { if (techFile.exists()) {
val techColumns = jsonParser.getFromJson(Array<TechColumn>::class.java, techFile) val techColumns = json().fromJsonFile(Array<TechColumn>::class.java, techFile)
for (techColumn in techColumns) { for (techColumn in techColumns) {
for (tech in techColumn.techs) { for (tech in techColumn.techs) {
if (tech.cost == 0) tech.cost = techColumn.techCost if (tech.cost == 0) tech.cost = techColumn.techCost
@ -217,49 +217,49 @@ class Ruleset {
} }
val buildingsFile = folderHandle.child("Buildings.json") val buildingsFile = folderHandle.child("Buildings.json")
if (buildingsFile.exists()) buildings += createHashmap(jsonParser.getFromJson(Array<Building>::class.java, buildingsFile)) if (buildingsFile.exists()) buildings += createHashmap(json().fromJsonFile(Array<Building>::class.java, buildingsFile))
for(building in buildings.values) for(building in buildings.values)
if(building.requiredBuildingInAllCities != null) if(building.requiredBuildingInAllCities != null)
building.uniques.add(UniqueType.RequiresBuildingInAllCities.text.fillPlaceholders(building.requiredBuildingInAllCities!!)) building.uniques.add(UniqueType.RequiresBuildingInAllCities.text.fillPlaceholders(building.requiredBuildingInAllCities!!))
val terrainsFile = folderHandle.child("Terrains.json") val terrainsFile = folderHandle.child("Terrains.json")
if (terrainsFile.exists()) { if (terrainsFile.exists()) {
terrains += createHashmap(jsonParser.getFromJson(Array<Terrain>::class.java, terrainsFile)) terrains += createHashmap(json().fromJsonFile(Array<Terrain>::class.java, terrainsFile))
for (terrain in terrains.values) terrain.setTransients() for (terrain in terrains.values) terrain.setTransients()
} }
val resourcesFile = folderHandle.child("TileResources.json") val resourcesFile = folderHandle.child("TileResources.json")
if (resourcesFile.exists()) tileResources += createHashmap(jsonParser.getFromJson(Array<TileResource>::class.java, resourcesFile)) if (resourcesFile.exists()) tileResources += createHashmap(json().fromJsonFile(Array<TileResource>::class.java, resourcesFile))
val improvementsFile = folderHandle.child("TileImprovements.json") val improvementsFile = folderHandle.child("TileImprovements.json")
if (improvementsFile.exists()) tileImprovements += createHashmap(jsonParser.getFromJson(Array<TileImprovement>::class.java, improvementsFile)) if (improvementsFile.exists()) tileImprovements += createHashmap(json().fromJsonFile(Array<TileImprovement>::class.java, improvementsFile))
val erasFile = folderHandle.child("Eras.json") val erasFile = folderHandle.child("Eras.json")
if (erasFile.exists()) eras += createHashmap(jsonParser.getFromJson(Array<Era>::class.java, erasFile)) if (erasFile.exists()) eras += createHashmap(json().fromJsonFile(Array<Era>::class.java, erasFile))
// While `eras.values.toList()` might seem more logical, eras.values is a MutableCollection and // While `eras.values.toList()` might seem more logical, eras.values is a MutableCollection and
// therefore does not guarantee keeping the order of elements like a LinkedHashMap does. // therefore does not guarantee keeping the order of elements like a LinkedHashMap does.
// Using map{} sidesteps this problem // Using map{} sidesteps this problem
eras.map { it.value }.withIndex().forEach { it.value.eraNumber = it.index } eras.map { it.value }.withIndex().forEach { it.value.eraNumber = it.index }
val unitTypesFile = folderHandle.child("UnitTypes.json") val unitTypesFile = folderHandle.child("UnitTypes.json")
if (unitTypesFile.exists()) unitTypes += createHashmap(jsonParser.getFromJson(Array<UnitType>::class.java, unitTypesFile)) if (unitTypesFile.exists()) unitTypes += createHashmap(json().fromJsonFile(Array<UnitType>::class.java, unitTypesFile))
val unitsFile = folderHandle.child("Units.json") val unitsFile = folderHandle.child("Units.json")
if (unitsFile.exists()) units += createHashmap(jsonParser.getFromJson(Array<BaseUnit>::class.java, unitsFile)) if (unitsFile.exists()) units += createHashmap(json().fromJsonFile(Array<BaseUnit>::class.java, unitsFile))
val promotionsFile = folderHandle.child("UnitPromotions.json") val promotionsFile = folderHandle.child("UnitPromotions.json")
if (promotionsFile.exists()) unitPromotions += createHashmap(jsonParser.getFromJson(Array<Promotion>::class.java, promotionsFile)) if (promotionsFile.exists()) unitPromotions += createHashmap(json().fromJsonFile(Array<Promotion>::class.java, promotionsFile))
val questsFile = folderHandle.child("Quests.json") val questsFile = folderHandle.child("Quests.json")
if (questsFile.exists()) quests += createHashmap(jsonParser.getFromJson(Array<Quest>::class.java, questsFile)) if (questsFile.exists()) quests += createHashmap(json().fromJsonFile(Array<Quest>::class.java, questsFile))
val specialistsFile = folderHandle.child("Specialists.json") val specialistsFile = folderHandle.child("Specialists.json")
if (specialistsFile.exists()) specialists += createHashmap(jsonParser.getFromJson(Array<Specialist>::class.java, specialistsFile)) if (specialistsFile.exists()) specialists += createHashmap(json().fromJsonFile(Array<Specialist>::class.java, specialistsFile))
val policiesFile = folderHandle.child("Policies.json") val policiesFile = folderHandle.child("Policies.json")
if (policiesFile.exists()) { if (policiesFile.exists()) {
policyBranches += createHashmap( policyBranches += createHashmap(
jsonParser.getFromJson(Array<PolicyBranch>::class.java, policiesFile) json().fromJsonFile(Array<PolicyBranch>::class.java, policiesFile)
) )
for (branch in policyBranches.values) { for (branch in policyBranches.values) {
// Setup this branch // Setup this branch
@ -289,34 +289,34 @@ class Ruleset {
val beliefsFile = folderHandle.child("Beliefs.json") val beliefsFile = folderHandle.child("Beliefs.json")
if (beliefsFile.exists()) if (beliefsFile.exists())
beliefs += createHashmap(jsonParser.getFromJson(Array<Belief>::class.java, beliefsFile)) beliefs += createHashmap(json().fromJsonFile(Array<Belief>::class.java, beliefsFile))
val religionsFile = folderHandle.child("Religions.json") val religionsFile = folderHandle.child("Religions.json")
if (religionsFile.exists()) if (religionsFile.exists())
religions += jsonParser.getFromJson(Array<String>::class.java, religionsFile).toList() religions += json().fromJsonFile(Array<String>::class.java, religionsFile).toList()
val ruinRewardsFile = folderHandle.child("Ruins.json") val ruinRewardsFile = folderHandle.child("Ruins.json")
if (ruinRewardsFile.exists()) if (ruinRewardsFile.exists())
ruinRewards += createHashmap(jsonParser.getFromJson(Array<RuinReward>::class.java, ruinRewardsFile)) ruinRewards += createHashmap(json().fromJsonFile(Array<RuinReward>::class.java, ruinRewardsFile))
val nationsFile = folderHandle.child("Nations.json") val nationsFile = folderHandle.child("Nations.json")
if (nationsFile.exists()) { if (nationsFile.exists()) {
nations += createHashmap(jsonParser.getFromJson(Array<Nation>::class.java, nationsFile)) nations += createHashmap(json().fromJsonFile(Array<Nation>::class.java, nationsFile))
for (nation in nations.values) nation.setTransients() for (nation in nations.values) nation.setTransients()
} }
val difficultiesFile = folderHandle.child("Difficulties.json") val difficultiesFile = folderHandle.child("Difficulties.json")
if (difficultiesFile.exists()) if (difficultiesFile.exists())
difficulties += createHashmap(jsonParser.getFromJson(Array<Difficulty>::class.java, difficultiesFile)) difficulties += createHashmap(json().fromJsonFile(Array<Difficulty>::class.java, difficultiesFile))
val globalUniquesFile = folderHandle.child("GlobalUniques.json") val globalUniquesFile = folderHandle.child("GlobalUniques.json")
if (globalUniquesFile.exists()) { if (globalUniquesFile.exists()) {
globalUniques = jsonParser.getFromJson(GlobalUniques::class.java, globalUniquesFile) globalUniques = json().fromJsonFile(GlobalUniques::class.java, globalUniquesFile)
} }
val victoryTypesFiles = folderHandle.child("VictoryTypes.json") val victoryTypesFiles = folderHandle.child("VictoryTypes.json")
if (victoryTypesFiles.exists()) { if (victoryTypesFiles.exists()) {
victories += createHashmap(jsonParser.getFromJson(Array<Victory>::class.java, victoryTypesFiles)) victories += createHashmap(json().fromJsonFile(Array<Victory>::class.java, victoryTypesFiles))
} }

View File

@ -2,8 +2,9 @@ package com.unciv.models.tilesets
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.unciv.JsonParser
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
@ -49,7 +50,7 @@ object TileSetCache : HashMap<String, TileSetConfig>() {
try { try {
val key = TileSetAndMod(tileSetName, "") val key = TileSetAndMod(tileSetName, "")
assert(key !in allConfigs) assert(key !in allConfigs)
allConfigs[key] = JsonParser().getFromJson(TileSetConfig::class.java, configFile) allConfigs[key] = json().fromJsonFile(TileSetConfig::class.java, configFile)
if (printOutput) { if (printOutput) {
println("TileSetConfig loaded successfully: ${configFile.name()}") println("TileSetConfig loaded successfully: ${configFile.name()}")
println() println()
@ -78,7 +79,7 @@ object TileSetCache : HashMap<String, TileSetConfig>() {
tileSetName = configFile.nameWithoutExtension().removeSuffix("Config") tileSetName = configFile.nameWithoutExtension().removeSuffix("Config")
val key = TileSetAndMod(tileSetName, modName) val key = TileSetAndMod(tileSetName, modName)
assert(key !in allConfigs) assert(key !in allConfigs)
allConfigs[key] = JsonParser().getFromJson(TileSetConfig::class.java, configFile) allConfigs[key] = json().fromJsonFile(TileSetConfig::class.java, configFile)
if (printOutput) { if (printOutput) {
println("TileSetConfig loaded successfully: ${configFile.path()}") println("TileSetConfig loaded successfully: ${configFile.path()}")
println() println()

View File

@ -3,7 +3,8 @@ package com.unciv.models.translations
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Array
import com.unciv.JsonParser import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.models.metadata.BaseRuleset import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.metadata.LocaleCode import com.unciv.models.metadata.LocaleCode
import com.unciv.models.ruleset.* import com.unciv.models.ruleset.*
@ -219,7 +220,7 @@ object TranslationFileWriter {
private fun generateTutorialsStrings(): MutableSet<String> { private fun generateTutorialsStrings(): MutableSet<String> {
val tutorialsStrings = mutableSetOf<String>() val tutorialsStrings = mutableSetOf<String>()
val tutorials = JsonParser().getFromJson(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json") val tutorials = json().fromJsonFile(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json")
var uniqueIndexOfNewLine = 0 var uniqueIndexOfNewLine = 0
for (tutorial in tutorials) { for (tutorial in tutorials) {
@ -273,7 +274,6 @@ object TranslationFileWriter {
val startMillis = System.currentTimeMillis() val startMillis = System.currentTimeMillis()
var uniqueIndexOfNewLine = 0 var uniqueIndexOfNewLine = 0
val jsonParser = JsonParser()
val listOfJSONFiles = jsonsFolder val listOfJSONFiles = jsonsFolder
.list { file -> file.name.endsWith(".json", true) } .list { file -> file.name.endsWith(".json", true) }
.sortedBy { it.name() } // generatedStrings maintains order, so let's feed it a predictable one .sortedBy { it.name() } // generatedStrings maintains order, so let's feed it a predictable one
@ -290,7 +290,7 @@ object TranslationFileWriter {
if (javaClass == this.javaClass) if (javaClass == this.javaClass)
continue // unknown JSON, let's skip it continue // unknown JSON, let's skip it
val array = jsonParser.getFromJson(javaClass, jsonFile.path()) val array = json().fromJsonFile(javaClass, jsonFile.path())
resultStrings = mutableSetOf() resultStrings = mutableSetOf()
this[filename] = resultStrings this[filename] = resultStrings

View File

@ -9,6 +9,7 @@ import com.badlogic.gdx.utils.Align
import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.Json
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.IconTextButton
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
@ -55,7 +56,7 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
private fun tryGetSaveGame() private fun tryGetSaveGame()
= try { = try {
UncivGame.Current.gameInfo.let { gameInfo -> UncivGame.Current.gameInfo.let { gameInfo ->
Json().toJson(gameInfo).let { json().toJson(gameInfo).let {
jsonString -> Gzip.zip(jsonString) jsonString -> Gzip.zip(jsonString)
} }
} // Taken from old CrashController().buildReport(). } // Taken from old CrashController().buildReport().

View File

@ -18,6 +18,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.models.ruleset.Nation import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
@ -79,7 +80,7 @@ object ImageGetter {
fun loadModAtlases(mod: String, folder: FileHandle) { fun loadModAtlases(mod: String, folder: FileHandle) {
// See #4993 - you can't .list() on a jar file, so the ImagePacker leaves us the list of actual atlases. // See #4993 - you can't .list() on a jar file, so the ImagePacker leaves us the list of actual atlases.
val controlFile = folder.child("Atlases.json") val controlFile = folder.child("Atlases.json")
val fileNames = (if (controlFile.exists()) GameSaver.json().fromJson(Array<String>::class.java, controlFile) val fileNames = (if (controlFile.exists()) json().fromJson(Array<String>::class.java, controlFile)
else emptyArray()).toMutableList() else emptyArray()).toMutableList()
if (mod.isNotEmpty()) fileNames += "game" if (mod.isNotEmpty()) fileNames += "game"
for (fileName in fileNames) { for (fileName in fileNames) {

View File

@ -3,9 +3,9 @@ package com.unciv.ui.pickerscreens
import com.badlogic.gdx.Files import com.badlogic.gdx.Files
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.Json
import com.unciv.JsonParser import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.BackwardCompatibility.updateDeprecations import com.unciv.logic.BackwardCompatibility.updateDeprecations
import com.unciv.logic.GameSaver
import com.unciv.models.ruleset.ModOptions import com.unciv.models.ruleset.ModOptions
import java.io.* import java.io.*
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -234,7 +234,7 @@ object Github {
retries++ // An extra retry so the 403 is ignored in the retry count retries++ // An extra retry so the 403 is ignored in the retry count
} }
} ?: continue } ?: continue
return GameSaver.json().fromJson(RepoSearch::class.java, inputStream.bufferedReader().readText()) return json().fromJson(RepoSearch::class.java, inputStream.bufferedReader().readText())
} }
return null return null
} }
@ -315,13 +315,13 @@ object Github {
*/ */
fun rewriteModOptions(repo: Repo, modFolder: FileHandle) { fun rewriteModOptions(repo: Repo, modFolder: FileHandle) {
val modOptionsFile = modFolder.child("jsons/ModOptions.json") val modOptionsFile = modFolder.child("jsons/ModOptions.json")
val modOptions = if (modOptionsFile.exists()) JsonParser().getFromJson(ModOptions::class.java, modOptionsFile) else ModOptions() val modOptions = if (modOptionsFile.exists()) json().fromJsonFile(ModOptions::class.java, modOptionsFile) else ModOptions()
modOptions.modUrl = repo.html_url modOptions.modUrl = repo.html_url
modOptions.lastUpdated = repo.pushed_at modOptions.lastUpdated = repo.pushed_at
modOptions.author = repo.owner.login modOptions.author = repo.owner.login
modOptions.modSize = repo.size modOptions.modSize = repo.size
modOptions.updateDeprecations() modOptions.updateDeprecations()
Json().toJson(modOptions, modOptionsFile) json().toJson(modOptions, modOptionsFile)
} }
} }

View File

@ -6,8 +6,9 @@ import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.JsonParser
import com.unciv.MainMenuScreen import com.unciv.MainMenuScreen
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.models.ruleset.ModOptions import com.unciv.models.ruleset.ModOptions
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
@ -581,7 +582,7 @@ class ModManagementScreen(
companion object { companion object {
val modsToHideAsUrl by lazy { val modsToHideAsUrl by lazy {
val blockedModsFile = Gdx.files.internal("jsons/ManuallyBlockedMods.json") val blockedModsFile = Gdx.files.internal("jsons/ManuallyBlockedMods.json")
JsonParser().getFromJson(Array<String>::class.java, blockedModsFile) json().fromJsonFile(Array<String>::class.java, blockedModsFile)
} }
} }
} }

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.Json
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -45,7 +46,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
copyJsonButton.onClick { copyJsonButton.onClick {
thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs
try { try {
val json = Json().toJson(gameInfo) val json = json().toJson(gameInfo)
val base64Gzip = Gzip.zip(json) val base64Gzip = Gzip.zip(json)
Gdx.app.clipboard.contents = base64Gzip Gdx.app.clipboard.contents = base64Gzip
} catch (OOM: OutOfMemoryError) { } catch (OOM: OutOfMemoryError) {

View File

@ -1,8 +1,9 @@
package com.unciv.ui.tutorials package com.unciv.ui.tutorials
import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Array
import com.unciv.JsonParser
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.models.Tutorial import com.unciv.models.Tutorial
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
@ -15,7 +16,7 @@ class TutorialController(screen: BaseScreen) {
private var isTutorialShowing = false private var isTutorialShowing = false
var allTutorialsShowedCallback: (() -> Unit)? = null var allTutorialsShowedCallback: (() -> Unit)? = null
private val tutorialRender = TutorialRender(screen) private val tutorialRender = TutorialRender(screen)
private val tutorials = JsonParser().getFromJson(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json") private val tutorials = json().fromJsonFile(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json")
fun showTutorial(tutorial: Tutorial) { fun showTutorial(tutorial: Tutorial) {
tutorialQueue.add(tutorial) tutorialQueue.add(tutorial)

View File

@ -1,10 +1,10 @@
package com.unciv.app.desktop package com.unciv.app.desktop
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.unciv.json.json
import com.unciv.logic.CustomSaveLocationHelper import com.unciv.logic.CustomSaveLocationHelper
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.logic.GameSaver.json
import java.awt.event.WindowEvent import java.awt.event.WindowEvent
import java.io.File import java.io.File
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException

View File

@ -1,6 +1,7 @@
package com.unciv.testing package com.unciv.testing
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.logic.GameStarter import com.unciv.logic.GameStarter
@ -83,7 +84,7 @@ class SerializationTests {
@Test @Test
fun canSerializeGame() { fun canSerializeGame() {
val json = try { val json = try {
GameSaver.json().toJson(game) json().toJson(game)
} catch (ex: Exception) { } catch (ex: Exception) {
"" ""
} }

View File

@ -1,7 +1,8 @@
package com.unciv.testing package com.unciv.testing
import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Array
import com.unciv.JsonParser import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.models.Tutorial import com.unciv.models.Tutorial
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
@ -16,7 +17,7 @@ class TutorialTranslationTests {
@Test @Test
fun tutorialsFileIsSerializable() { fun tutorialsFileIsSerializable() {
val map = JsonParser().getFromJson(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json") val map = json().fromJsonFile(LinkedHashMap<String, Array<String>>().javaClass, "jsons/Tutorials.json")
assertTrue("The number of items from Tutorials.json must match to the enum Tutorial", assertTrue("The number of items from Tutorials.json must match to the enum Tutorial",
map.size == tutorialCount) map.size == tutorialCount)