Fix old barbarian camps not being properly loaded after 86d5011 (#6724)

* Fix old barbarian camps not being properly loaded after 86d5011

* Fix first load resulting in error
This commit is contained in:
Timo T 2022-05-08 20:21:25 +02:00 committed by GitHub
parent d00b72806b
commit 7c24b0b6af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 17 deletions

View File

@ -9,15 +9,19 @@ import java.util.HashMap
*/
class HashMapVector2<T> : HashMap<Vector2, T>() {
companion object {
init {
fun createSerializer(): NonStringKeyMapSerializer<MutableMap<Vector2, Any>, Vector2> {
@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(
return NonStringKeyMapSerializer(
mapClass,
Vector2::class.java,
{ HashMapVector2<Any>() }
{ HashMapVector2() }
)
jsonSerializers.add(Pair(mapClass, serializer))
}
fun getSerializerClass(): Class<MutableMap<Vector2, Any>> {
@Suppress("UNCHECKED_CAST") // kotlin can't tell that HashMapVector2 is also a MutableMap within generics
return HashMapVector2::class.java as Class<MutableMap<Vector2, Any>>
}
}
}

View File

@ -40,14 +40,36 @@ class NonStringKeyMapSerializer<MT: MutableMap<KT, Any>, KT>(
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
if (entries == null) {
readOldFormat(jsonData, json, result)
} else {
readNewFormat(entries!!, json, result)
}
return result
}
@Deprecated("This is only here temporarily until all users migrate the old properties to the new ones")
private fun readOldFormat(jsonData: JsonValue, json: Json, result: MT) {
val map = result as MutableMap<String, Any>
var child: JsonValue? = jsonData.child
while (child != null) {
if (child.name == "class") {
child = child.next
continue
}
map[child.name] = json.readValue(null, child)
child = child.next
}
}
private fun readNewFormat(entries: JsonValue, json: Json, result: MT) {
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!!
entry = entry.next
}
}
}

View File

@ -2,21 +2,19 @@ package com.unciv.json
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.math.Vector2
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.
* [Json] is not thread-safe. Use a new one for each parse.
*/
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>)
}
setSerializer(HashMapVector2.getSerializerClass(), HashMapVector2.createSerializer())
}
fun <T> Json.fromJsonFile(tClass: Class<T>, filePath: String): T = fromJsonFile(tClass, Gdx.files.internal(filePath))

View File

@ -1,6 +1,9 @@
package com.unciv.logic
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.JsonValue
import com.unciv.json.HashMapVector2
import com.unciv.json.json
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.PerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo
@ -159,4 +162,38 @@ object BackwardCompatibility {
lastSeenImprovement.putAll(lastSeenImprovementSaved.mapKeys { Vector2().fromString(it.key) })
lastSeenImprovementSaved.clear()
}
/**
* Fixes barbarian manager camps not being correctly serialized. Previously we had a [HashMap<Vector2, Encampment] but it was being
* serialized as [HashMap<String, Encampment>]. We need to fix that each time an old save is loaded.
*
* When removing this, also remove [com.unciv.json.NonStringKeyMapSerializer.readOldFormat]
*/
@Suppress("DEPRECATION")
fun BarbarianManager.migrateBarbarianCamps() {
if (isOldFormat(this)) {
val newFormat = HashMapVector2<Encampment>()
@Suppress("UNCHECKED_CAST") // The old format is deserialized to a <String, JsonValue> map
for ((key, value) in camps as MutableMap<String, JsonValue>) {
val newKey = Vector2().fromString(key)
val newValue = json().readValue(Encampment::class.java, value)
newFormat[newKey] = newValue
}
camps.clear()
camps.putAll(newFormat)
}
}
private fun isOldFormat(manager: BarbarianManager): Boolean {
val keys = manager.camps.keys as Set<Any>
val iterator = keys.iterator()
while (iterator.hasNext()) {
val key = iterator.next()
if (key is String) {
return true
}
}
return false
}
}

View File

@ -3,6 +3,7 @@ package com.unciv.logic
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions
import com.unciv.logic.BackwardCompatibility.migrateBarbarianCamps
import com.unciv.logic.BackwardCompatibility.migrateSeenImprovements
import com.unciv.logic.BackwardCompatibility.removeMissingModReferences
import com.unciv.logic.automation.NextTurnAutomation
@ -398,6 +399,7 @@ class GameInfo {
}
// [TEMPORARY] Convert old saves to remove json workaround
for (civInfo in civilizations) civInfo.migrateSeenImprovements()
barbarians.migrateBarbarianCamps()
ruleSet = RulesetCache.getComplexRuleset(gameParameters)