mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-27 08:09:21 +07:00
Notifications remove backward compatibility (#10228)
* Remove backwards compatibility code from Notification serializer * Notification constructor simplified * One measly typo
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
package com.unciv.logic.civilization
|
package com.unciv.logic.civilization
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Json
|
import com.badlogic.gdx.utils.Json
|
||||||
@ -32,15 +31,13 @@ open class Notification() : IsPartOfGameInfoSerialization {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
text: String,
|
text: String,
|
||||||
notificationIcons: Array<out String>,
|
notificationIcons: Array<out String>, // `out` needed so we can pass a vararg directly
|
||||||
actions: Iterable<NotificationAction>?,
|
actions: Iterable<NotificationAction>?,
|
||||||
category: NotificationCategory = NotificationCategory.General
|
category: NotificationCategory = NotificationCategory.General
|
||||||
) : this() {
|
) : this() {
|
||||||
this.category = category
|
this.category = category
|
||||||
this.text = text
|
this.text = text
|
||||||
if (notificationIcons.isNotEmpty()) {
|
notificationIcons.toCollection(this.icons)
|
||||||
this.icons = notificationIcons.toCollection(ArrayList())
|
|
||||||
}
|
|
||||||
actions?.toCollection(this.actions)
|
actions?.toCollection(this.actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,25 +95,23 @@ open class Notification() : IsPartOfGameInfoSerialization {
|
|||||||
/**
|
/**
|
||||||
* Custom [Gdx.Json][Json] serializer/deserializer for one [Notification].
|
* Custom [Gdx.Json][Json] serializer/deserializer for one [Notification].
|
||||||
*
|
*
|
||||||
* Migration roadmap:
|
* Example of the serialized format:
|
||||||
*
|
* ```json
|
||||||
* 1.) Change internal structures but write old json format
|
* "notifications":[
|
||||||
* 2.) Wait for good distribution in multiplayer user base
|
* {
|
||||||
* 3.) Switch to writing new format
|
* "category":"Production",
|
||||||
* 4.) Wait for Versions prior to Step 3 to fade out, keep switch for quick revert
|
* "text":"[Nobel Foundation] has been built in [Stockholm]",
|
||||||
* 5.) Remove Switch, old format routines and this comment
|
* "icons":["BuildingIcons/Nobel Foundation"],
|
||||||
*
|
* "actions":[
|
||||||
* Caveats:
|
* {"LocationAction":{"location":{"x":9,"y":3}}},
|
||||||
*
|
* {"CivilopediaAction":{"link":"Wonder/Nobel Foundation"}},
|
||||||
* * New format can express Notifications the old can't.
|
* {"CityAction":{"city":{"x":9,"y":3}}}
|
||||||
* In that case, in Phase 1, reduce to first action and throw away the rest.
|
* ]
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
class Serializer : Json.Serializer<Notification> {
|
class Serializer : Json.Serializer<Notification> {
|
||||||
companion object {
|
|
||||||
/** The switch that starts Phase III and dies with Phase V
|
|
||||||
* @see Serializer */
|
|
||||||
private const val compatibilityMode = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(json: Json, notification: Notification, knownType: Class<*>?) {
|
override fun write(json: Json, notification: Notification, knownType: Class<*>?) {
|
||||||
json.writeObjectStart()
|
json.writeObjectStart()
|
||||||
@ -127,13 +122,12 @@ open class Notification() : IsPartOfGameInfoSerialization {
|
|||||||
if (notification.icons.isNotEmpty())
|
if (notification.icons.isNotEmpty())
|
||||||
json.writeValue("icons", notification.icons, null, String::class.java)
|
json.writeValue("icons", notification.icons, null, String::class.java)
|
||||||
|
|
||||||
if (compatibilityMode) writeOldFormatAction(json, notification)
|
writeActions(json, notification)
|
||||||
else writeNewFormatActions(json, notification)
|
|
||||||
|
|
||||||
json.writeObjectEnd()
|
json.writeObjectEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeNewFormatActions(json: Json, notification: Notification) {
|
private fun writeActions(json: Json, notification: Notification) {
|
||||||
if (notification.actions.isEmpty()) return
|
if (notification.actions.isEmpty()) return
|
||||||
json.writeArrayStart("actions")
|
json.writeArrayStart("actions")
|
||||||
for (action in notification.actions) {
|
for (action in notification.actions) {
|
||||||
@ -146,35 +140,14 @@ open class Notification() : IsPartOfGameInfoSerialization {
|
|||||||
json.writeArrayEnd()
|
json.writeArrayEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeOldFormatAction(json: Json, notification: Notification) {
|
|
||||||
if (notification.actions.isEmpty()) return
|
|
||||||
val firstAction = notification.actions.first()
|
|
||||||
if (firstAction !is LocationAction) {
|
|
||||||
json.writeValue("action", firstAction, null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val locations = notification.actions.filterIsInstance<LocationAction>()
|
|
||||||
.map { it.location }.toTypedArray()
|
|
||||||
json.writeObjectStart("action")
|
|
||||||
json.writeValue("class", "com.unciv.logic.civilization.LocationAction")
|
|
||||||
json.writeValue("locations", locations, Array<Vector2>::class.java, Vector2::class.java)
|
|
||||||
json.writeObjectEnd()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?) = Notification().apply {
|
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?) = Notification().apply {
|
||||||
// Cannot be distinguished 100% certain by field names but if neither action / actions exist then both formats are compatible
|
|
||||||
json.readField(this, "category", jsonData)
|
json.readField(this, "category", jsonData)
|
||||||
json.readField(this, "text", jsonData)
|
json.readField(this, "text", jsonData)
|
||||||
readOldFormatAction(json, jsonData)
|
readActions(json, jsonData)
|
||||||
readNewFormatActions(json, jsonData)
|
|
||||||
json.readField(this, "icons", jsonData)
|
json.readField(this, "icons", jsonData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Notification.readNewFormatActions(json: Json, jsonData: JsonValue) {
|
private fun Notification.readActions(json: Json, jsonData: JsonValue) {
|
||||||
// New format looks like this: "notifications":[
|
|
||||||
// {"category":"Cities","text":"[Stockholm] has expanded its borders!","icons":["StatIcons/Culture"],"actions":[{"LocationAction":{"location":{"x":7,"y":1}}},{"LocationAction":{"location":{"x":9,"y":3}}}]},
|
|
||||||
// {"category":"Production","text":"[Nobel Foundation] has been built in [Stockholm]","icons":["BuildingIcons/Nobel Foundation"],"actions":[{"LocationAction":{"location":{"x":9,"y":3}}}]}
|
|
||||||
// ]
|
|
||||||
if (!jsonData.hasChild("actions")) return
|
if (!jsonData.hasChild("actions")) return
|
||||||
var entry = jsonData.get("actions").child
|
var entry = jsonData.get("actions").child
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
@ -182,26 +155,5 @@ open class Notification() : IsPartOfGameInfoSerialization {
|
|||||||
entry = entry.next
|
entry = entry.next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Notification.readOldFormatAction(json: Json, jsonData: JsonValue) {
|
|
||||||
// Old format looks like: "notifications":[
|
|
||||||
// {"text":"[Stockholm] has expanded its borders!","icons":["StatIcons/Culture"],"action":{"class":"com.unciv.logic.civilization.LocationAction","locations":[{"x":7,"y":1},{"x":9,"y":3}]},"category":"Cities"},
|
|
||||||
// {"text":"[Nobel Foundation] has been built in [Stockholm]","icons":["BuildingIcons/Nobel Foundation"],"action":{"class":"com.unciv.logic.civilization.LocationAction","locations":[{"x":9,"y":3}]},"category":"Production"}
|
|
||||||
// ]
|
|
||||||
val actionData = jsonData.get("action") ?: return
|
|
||||||
val actionClass = actionData.getString("class")
|
|
||||||
when (actionClass.substring(actionClass.lastIndexOf('.') + 1)) {
|
|
||||||
"LocationAction" -> actions += getOldFormatLocations(json, actionData)
|
|
||||||
"TechAction" -> actions += json.readValue(TechAction::class.java, actionData)
|
|
||||||
"CityAction" -> actions += json.readValue(CityAction::class.java, actionData)
|
|
||||||
"DiplomacyAction" -> actions += json.readValue(DiplomacyAction::class.java, actionData)
|
|
||||||
"MayaLongCountAction" -> actions += MayaLongCountAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getOldFormatLocations(json: Json, actionData: JsonValue): Sequence<LocationAction> {
|
|
||||||
val locations = json.readValue("locations", Array<Vector2>::class.java, actionData)
|
|
||||||
return locations.asSequence().map { LocationAction(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ class OverviewAction(
|
|||||||
internal class NotificationActionsDeserializer {
|
internal class NotificationActionsDeserializer {
|
||||||
/* This exists as trick to leverage readFields for Json deserialization.
|
/* This exists as trick to leverage readFields for Json deserialization.
|
||||||
// The serializer writes each NotificationAction as json object (within the actions array),
|
// The serializer writes each NotificationAction as json object (within the actions array),
|
||||||
// containing the class simpleName as subfield name, which carries any (on none) subclass-
|
// containing the class simpleName as subfield name, which carries any (or none) subclass-
|
||||||
// specific data as its object value. So, reading this from json data will fill just one of the
|
// specific data as its object value. So, reading this from json data will fill just one of the
|
||||||
// fields, and the listOfNotNull will output that field only.
|
// fields, and the listOfNotNull will output that field only.
|
||||||
// Even though we know there's only one result, no need to first() since it's no advantage to the caller.
|
// Even though we know there's only one result, no need to first() since it's no advantage to the caller.
|
||||||
|
Reference in New Issue
Block a user