diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index 6433fae67..422aa9503 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -56,7 +56,7 @@ open class GameWorld : Disposable { internal set /** Used to calculate play time */ - open val loadTime: Long = System.currentTimeMillis() / 1000L + @Transient open val loadTime: Long = System.currentTimeMillis() / 1000L //layers val layerWall: BlockLayer @@ -82,8 +82,8 @@ open class GameWorld : Disposable { private val wirings: HashMap private val wiringGraph = HashMap>() - private val WIRE_POS_MAP = intArrayOf(1,2,4,8) - private val WIRE_ANTIPOS_MAP = intArrayOf(4,8,1,2) + @Transient private val WIRE_POS_MAP = intArrayOf(1,2,4,8) + @Transient private val WIRE_ANTIPOS_MAP = intArrayOf(4,8,1,2) /** * Used by the renderer. When wirings are updated, `wirings` and this properties must be synchronised. @@ -102,7 +102,7 @@ open class GameWorld : Disposable { open var generatorSeed: Long = 0 internal set - var disposed = false + @Transient var disposed = false private set val worldTime: WorldTime = WorldTime( // Year EPOCH (125), Month 1, Day 1 is implied @@ -113,7 +113,7 @@ open class GameWorld : Disposable { val tileNumberToNameMap: HashMap // does not go to the savefile - val tileNameToNumberMap: HashMap + @Transient val tileNameToNumberMap: HashMap /** * Create new world diff --git a/src/net/torvald/terrarum/modulebasegame/gameworld/GameWorldExtension.kt b/src/net/torvald/terrarum/modulebasegame/gameworld/GameWorldExtension.kt index e61277bec..4f0e83221 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameworld/GameWorldExtension.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameworld/GameWorldExtension.kt @@ -14,8 +14,8 @@ class GameWorldExtension : GameWorld { //internal constructor(worldIndex: Int, layerData: ReadLayerDataZip.LayerData, creationTIME_T: Long, lastPlayTIME_T: Long, totalPlayTime: Int) : super(worldIndex, layerData, creationTIME_T, lastPlayTIME_T, totalPlayTime) - val economy = GameEconomy() - + var economy = GameEconomy() + internal set // delegated properties // /*val layerWall: MapLayer; get() = baseworld.layerWall diff --git a/src/net/torvald/terrarum/serialise/ReadWorld.kt b/src/net/torvald/terrarum/serialise/ReadWorld.kt new file mode 100644 index 000000000..340134097 --- /dev/null +++ b/src/net/torvald/terrarum/serialise/ReadWorld.kt @@ -0,0 +1,28 @@ +package net.torvald.terrarum.serialise + +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonValue +import net.torvald.terrarum.modulebasegame.TerrarumIngame +import net.torvald.terrarum.modulebasegame.gameworld.GameEconomy +import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension + +/** + * Created by minjaesong on 2021-08-25. + */ +open class ReadWorld(val ingame: TerrarumIngame) { + + open fun invoke(worldIndex: Int, metadata: JsonValue, worlddata: JsonValue) { + val json = Json() + val world = GameWorldExtension( + worldIndex, + worlddata.getInt("width"), + worlddata.getInt("height"), + metadata.getLong("creation_t"), + metadata.getLong("lastplay_t"), + metadata.getInt("playtime_t") + ) + + //world.economy = json.fromJson(GameEconomy::class.java, worlddata.get("basegame.economy").) + } + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteWorld.kt b/src/net/torvald/terrarum/serialise/WriteWorld.kt index 65b574116..60776d4a0 100644 --- a/src/net/torvald/terrarum/serialise/WriteWorld.kt +++ b/src/net/torvald/terrarum/serialise/WriteWorld.kt @@ -1,16 +1,29 @@ package net.torvald.terrarum.serialise +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonReader +import com.badlogic.gdx.utils.JsonValue +import com.badlogic.gdx.utils.JsonWriter +import net.torvald.terrarum.gameworld.BlockLayer +import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream +import org.apache.commons.codec.digest.DigestUtils +import java.math.BigInteger +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream /** * Created by minjaesong on 2021-08-23. */ -class WriteWorld(val ingame: TerrarumIngame) { +open class WriteWorld(val ingame: TerrarumIngame) { open fun invoke(): String { val world = ingame.world - return "{${world.getJsonFields().joinToString(",\n")}}" + //return "{${world.getJsonFields().joinToString(",\n")}}" + return jsoner.toJson(world) } fun encodeToByteArray64(): ByteArray64 { @@ -27,4 +40,148 @@ class WriteWorld(val ingame: TerrarumIngame) { ba.add('}'.code.toByte()) return ba } + + companion object { + /** dispose of the `offendingObject` after rejection! */ + class BlockLayerHashMismatchError(val offendingObject: BlockLayer) : Error() + + private fun Byte.tostr() = this.toInt().and(255).toString(16).padStart(2,'0') + private val digester = DigestUtils.getSha256Digest() + + val jsoner = Json(JsonWriter.OutputType.json) + + // install custom (de)serialiser + init { + // BigInteger + jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer { + + override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) { + json.writeValue(obj?.toString()) + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): BigInteger { + return BigInteger(jsonData.asString()) + } + }) + // BlockLayer + jsoner.setSerializer(BlockLayer::class.java, object : Json.Serializer { + + override fun write(json: Json, obj: BlockLayer, knownType: Class<*>?) { + digester.reset() + obj.bytesIterator().forEachRemaining { digester.update(it) } + val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() } + + val layer = LayerInfo(hash, blockLayerToStr(obj), obj.width, obj.height) + json.writeValue(layer) + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>): BlockLayer { + // full auto + //return strToBlockLayer(json.fromJson(type, jsonData.toJson(JsonWriter.OutputType.minimal)) as LayerInfo) + + // full manual + return strToBlockLayer(LayerInfo( + jsonData.getString("h"), + jsonData.getString("b"), + jsonData.getInt("x"), + jsonData.getInt("y") + )) + } + }) + // WorldTime + jsoner.setSerializer(WorldTime::class.java, object : Json.Serializer { + override fun write(json: Json, obj: WorldTime, knownType: Class<*>?) { + json.writeValue(obj.TIME_T) + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WorldTime { + return WorldTime(jsonData.asLong()) + } + }) + } + + private data class LayerInfo(val h: String, val b: String, val x: Int, val y: Int) + + /** + * @param b a BlockLayer + * @return Bytes in [b] which are GZip'd then Ascii85-encoded + */ + private fun blockLayerToStr(b: BlockLayer): String { + val sb = StringBuilder() + val bo = ByteArray64GrowableOutputStream() + val zo = GZIPOutputStream(bo) + + // zip + b.bytesIterator().forEachRemaining { + zo.write(it.toInt()) + } + zo.flush(); zo.close() + + // enascii + val ba = bo.toByteArray64() + var bai = 0 + val buf = IntArray(4) { Ascii85.PAD_BYTE } + ba.forEach { + if (bai > 0 && bai % 4 == 0) { + sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3])) + buf.fill(Ascii85.PAD_BYTE) + } + + buf[bai % 4] = it.toInt() and 255 + + bai += 1 + }; sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3])) + + return sb.toString() + } + + private fun strToBlockLayer(layerInfo: LayerInfo): BlockLayer { + val layer = BlockLayer(layerInfo.x, layerInfo.y) + val unasciidBytes = ByteArray64() + val unzipdBytes = ByteArray64() + + // unascii + var bai = 0 + val buf = CharArray(5) { Ascii85.PAD_CHAR } + layerInfo.b.forEach { + if (bai > 0 && bai % 5 == 0) { + Ascii85.decode(buf[0], buf[1], buf[2], buf[3], buf[4]).forEach { unasciidBytes.add(it) } + buf.fill(Ascii85.PAD_CHAR) + } + + buf[bai % 5] = it + + bai += 1 + }; Ascii85.decode(buf[0], buf[1], buf[2], buf[3], buf[4]).forEach { unasciidBytes.add(it) } + + // unzip + val zi = GZIPInputStream(ByteArray64InputStream(unasciidBytes)) + while (true) { + val byte = zi.read() + if (byte == -1) break + unzipdBytes.add(byte.toByte()) + } + zi.close() + + // write to blocklayer and the digester + digester.reset() + var writeCursor = 0L + unzipdBytes.forEach { + if (writeCursor < layer.ptr.size) { + layer.ptr[writeCursor] = it + digester.update(it) + writeCursor += 1 + } + } + + // check hash + val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() } + + if (hash != layerInfo.h) { + throw BlockLayerHashMismatchError(layer) + } + + return layer + } + } } diff --git a/work_files/DataFormats/just-json-it-saveformat.md b/work_files/DataFormats/just-json-it-saveformat.md index 621897e4c..d5fcfd3d2 100644 --- a/work_files/DataFormats/just-json-it-saveformat.md +++ b/work_files/DataFormats/just-json-it-saveformat.md @@ -27,59 +27,56 @@ Following code is an example savegame JSON files. #### world1.json -File is named as `"world"+world_index+".json"` +File is named as `"world"+world_index+".json"`. +The fields are auto-generated by GDX's JSON serialiser. ``` { - worldname: "New World", - comp: , + worldName: "New World", + worldIndex: 1, width: 8192, height: 2048, - spawnx: 4096, - spawny: 248, - genver: 4, /* generator version in integer */ - time_t: , - terr: { + spawnX: 4096, + spawnY: 248, + creationTime: 1629857065, + lastPlayTime: 1629857065, + totalPlayTime: 0, + layerTerrain: { h: "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11", - b: + b: , + x: 8192, + y: 2048 }, - wall: { + layerWall: { h: , - b: + b: , + x: 8192, + y: 2048 }, - tdmg: { - h: , - b: + wallDamages:{}, + terrainDamages: {}, + fluidTypes: {} + fluidFills: {}, + wirings: {}, + wiringGraph: {}, + gravitation: {y:9.8} + globalLight: { + r:0.8826928, + g:0.8901961, + b:0.9055425, + a:0.93691504 }, - wdmg: { - h: , - b: - }, - flut: { - h: , - b: - }, - fluf: { - h: , - b: - }, - wire: { - h: , - b: - }, - wirg: { - h: , - b: - }, - tmap: { - h: , - b: - } + averageTemperature: 288, + generatorSeed: 0, + worldTime: 27874, + tileNumberToNameMap: {} } ``` #### actors.json +The fields are auto-generated by GDX's JSON serialiser. + ``` { : { actor serialised in JSON },