serialiser for world

This commit is contained in:
minjaesong
2021-08-24 14:33:04 +09:00
parent 67091d0c84
commit cb73a9fea2
7 changed files with 173 additions and 96 deletions

View File

@@ -60,6 +60,7 @@ object CommandDict {
"savetest" to SavegameWriterTest, "savetest" to SavegameWriterTest,
/* !! */"exportmeta" to ExportMeta, /* !! */"exportmeta" to ExportMeta,
/* !! */"exportworld" to ExportWorld,
/* !! */"importlayer" to ImportLayerData, /* !! */"importlayer" to ImportLayerData,
/* !! */"exportfborgb" to ExportRendererFboRGB /* !! */"exportfborgb" to ExportRendererFboRGB
) )

View File

@@ -2,6 +2,9 @@
package net.torvald.terrarum.gameworld package net.torvald.terrarum.gameworld
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import com.badlogic.gdx.utils.JsonWriter
import net.torvald.gdx.graphics.Cvec import net.torvald.gdx.graphics.Cvec
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.AppLoader.printdbg
@@ -10,9 +13,13 @@ import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.Fluid import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameactors.WireActor import net.torvald.terrarum.gameactors.WireActor
import net.torvald.terrarum.gameitem.ItemID import net.torvald.terrarum.gameitem.ItemID
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.serialise.Ascii85
import net.torvald.util.SortedArrayList import net.torvald.util.SortedArrayList
import org.apache.commons.codec.digest.DigestUtils
import org.dyn4j.geometry.Vector2 import org.dyn4j.geometry.Vector2
import java.util.zip.GZIPOutputStream
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.experimental.or import kotlin.experimental.or
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@@ -47,9 +54,7 @@ open class GameWorld : Disposable {
open val loadTime: Long = System.currentTimeMillis() / 1000L open val loadTime: Long = System.currentTimeMillis() / 1000L
//layers //layers
@TEMzPayload("WALL", TEMzPayload.INT16_LITTLE)
val layerWall: BlockLayer val layerWall: BlockLayer
@TEMzPayload("TERR", TEMzPayload.INT16_LITTLE)
val layerTerrain: BlockLayer val layerTerrain: BlockLayer
//val layerWire: MapLayer //val layerWire: MapLayer
@@ -61,19 +66,14 @@ open class GameWorld : Disposable {
/** Tilewise spawn point */ /** Tilewise spawn point */
open var spawnY: Int open var spawnY: Int
@TEMzPayload("WdMG", TEMzPayload.INT48_FLOAT_PAIR)
val wallDamages: HashMap<BlockAddress, Float> val wallDamages: HashMap<BlockAddress, Float>
@TEMzPayload("TdMG", TEMzPayload.INT48_FLOAT_PAIR)
val terrainDamages: HashMap<BlockAddress, Float> val terrainDamages: HashMap<BlockAddress, Float>
@TEMzPayload("FlTP", TEMzPayload.INT48_SHORT_PAIR)
val fluidTypes: HashMap<BlockAddress, FluidType> val fluidTypes: HashMap<BlockAddress, FluidType>
@TEMzPayload("FlFL", TEMzPayload.INT48_FLOAT_PAIR)
val fluidFills: HashMap<BlockAddress, Float> val fluidFills: HashMap<BlockAddress, Float>
/** /**
* Single block can have multiple conduits, different types of conduits are stored separately. * Single block can have multiple conduits, different types of conduits are stored separately.
*/ */
@TEMzPayload("WiNt", TEMzPayload.EXTERNAL_JSON)
private val wirings: HashMap<BlockAddress, WiringNode> private val wirings: HashMap<BlockAddress, WiringNode>
private val wiringGraph = HashMap<BlockAddress, HashMap<ItemID, WiringSimCell>>() private val wiringGraph = HashMap<BlockAddress, HashMap<ItemID, WiringSimCell>>()
@@ -106,7 +106,6 @@ open class GameWorld : Disposable {
) )
@TEMzPayload("TMaP", TEMzPayload.EXTERNAL_JSON)
val tileNumberToNameMap: HashMap<Int, ItemID> val tileNumberToNameMap: HashMap<Int, ItemID>
// does not go to the savefile // does not go to the savefile
val tileNameToNumberMap: HashMap<ItemID, Int> val tileNameToNumberMap: HashMap<ItemID, Int>
@@ -672,6 +671,67 @@ open class GameWorld : Disposable {
open fun updateWorldTime(delta: Float) { open fun updateWorldTime(delta: Float) {
worldTime.update(delta) worldTime.update(delta)
} }
/**
* Returns lines that are part of the entire JSON
*
* To extend this function, you can code something like this:
* ```
* return super.getJsonFields() + arrayListOf(
* """"<myModuleName>.<myNewObject>": ${Json(JsonWriter.OutputType.json).toJson(<myNewObject>)}"""
* )
* ```
*/
open fun getJsonFields(): List<String> {
fun Byte.tostr() = this.toInt().and(255).toString(16).padStart(2,'0')
val tdmgstr = Json(JsonWriter.OutputType.json).toJson(terrainDamages)
val wdmgstr = Json(JsonWriter.OutputType.json).toJson(wallDamages)
val flutstr = Json(JsonWriter.OutputType.json).toJson(fluidTypes)
val flufstr = Json(JsonWriter.OutputType.json).toJson(fluidFills)
val wirestr = Json(JsonWriter.OutputType.json).toJson(wirings)
val wirgstr = Json(JsonWriter.OutputType.json).toJson(wiringGraph)
val digester = DigestUtils.getSha256Digest()
layerTerrain.bytesIterator().forEachRemaining { digester.update(it) }
val terrhash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() }
layerWall.bytesIterator().forEachRemaining { digester.update(it) }
val wallhash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() }
return arrayListOf(
""""worldname": "$worldName"""",
""""comp": 1""",
""""width": $width""",
""""height": $height""",
""""genver": 4""",
""""time_t": ${worldTime.TIME_T}""",
""""terr": {
|"h": "$terrhash",
|"b": "${blockLayerToStr(layerTerrain)}"}""".trimMargin(),
""""wall": {
|"h": "$wallhash",
|"b": "${blockLayerToStr(layerWall)}"}""".trimMargin(),
""""tdmg": {
|"h": "${StringBuilder().let { sb -> digester.digest(tdmgstr.toByteArray()).forEach { sb.append(it.tostr()) }; sb.toString() }}",
|"b": $tdmgstr}""".trimMargin(),
""""wdmg": {
|"h": "${StringBuilder().let { sb -> digester.digest(wdmgstr.toByteArray()).forEach { sb.append(it.tostr()) }; sb.toString() }}",
|"b": $wdmgstr}""".trimMargin(),
""""flut": {
|"h": "${StringBuilder().let { sb -> digester.digest(flutstr.toByteArray()).forEach { sb.append(it.tostr()) }; sb.toString() }}",
|"b": $flutstr}""".trimMargin(),
""""fluf": {
|"h": "${StringBuilder().let { sb -> digester.digest(flufstr.toByteArray()).forEach { sb.append(it.tostr()) }; sb.toString() }}",
|"b": $flufstr}""".trimMargin(),
""""wire": {
|"h": "${StringBuilder().let { sb -> digester.digest(wirestr.toByteArray()).forEach { sb.append(it.tostr()) }; sb.toString() }}",
|"b": $wirestr}""".trimMargin(),
""""wirg": {
|"h": "${StringBuilder().let { sb -> digester.digest(wirgstr.toByteArray()).forEach { sb.append(it.tostr()) }; sb.toString() }}",
|"b": $wirgstr}""".trimMargin()
)
}
} }
infix fun Int.fmod(other: Int) = Math.floorMod(this, other) infix fun Int.fmod(other: Int) = Math.floorMod(this, other)
@@ -683,21 +743,34 @@ inline class FluidType(val value: Int) {
fun abs() = this.value.absoluteValue fun abs() = this.value.absoluteValue
} }
/** /**
* @param payloadName Payload name defined in Map Data Format.txt * @param b a BlockLayer
* * 4 Letters: regular payload * @return Bytes in [b] which are GZip'd then Ascii85-encoded
* * 3 Letters: only valid for arrays with 16 elements, names are auto-generated by appending '0'..'9'+'a'..'f'. E.g.: 'CfL' turns into 'CfL0', 'CfL1' ... 'CfLe', 'CfLf'
*
* @param arg 0 for 8 MSBs of Terrain/Wall layer, 1 for 4 LSBs of Terrain/Wall layer, 2 for Int48-Float pair, 3 for Int48-Short pair, 4 for Int48-Int pair
*/ */
annotation class TEMzPayload(val payloadName: String, val arg: Int) { fun blockLayerToStr(b: BlockLayer): String {
companion object { val sb = StringBuilder()
const val EXTERNAL_JAVAPROPERTIES = -3 val bo = ByteArray64GrowableOutputStream()
const val EXTERNAL_CSV = -2 val zo = GZIPOutputStream(bo)
const val EXTERNAL_JSON = -1
const val INT16_LITTLE = 1 b.bytesIterator().forEachRemaining {
const val INT48_FLOAT_PAIR = 2 zo.write(it.toInt())
const val INT48_SHORT_PAIR = 3
const val INT48_INT_PAIR = 4
} }
zo.flush(); zo.close()
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()
} }

View File

@@ -7,6 +7,7 @@ import net.torvald.terrarum.console.ConsoleCommand
import net.torvald.terrarum.console.Echo import net.torvald.terrarum.console.Echo
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.serialise.WriteMeta import net.torvald.terrarum.serialise.WriteMeta
import net.torvald.terrarum.serialise.WriteWorld
import net.torvald.terrarum.utils.JsonWriter import net.torvald.terrarum.utils.JsonWriter
import java.io.IOException import java.io.IOException
@@ -32,3 +33,24 @@ object ExportMeta : ConsoleCommand {
Echo("Usage: Exportmeta") Echo("Usage: Exportmeta")
} }
} }
object ExportWorld : ConsoleCommand {
override fun execute(args: Array<String>) {
try {
val world = Terrarum.ingame!!.world
val str = WriteWorld(Terrarum.ingame!! as TerrarumIngame).invoke()
val writer = java.io.FileWriter(AppLoader.defaultDir + "/Exports/world${world.worldIndex}.json", false)
writer.write(str)
writer.close()
Echo("Exportworld: exported to world${world.worldIndex}.json")
}
catch (e: IOException) {
Echo("Exportworld: IOException raised.")
e.printStackTrace()
}
}
override fun printUsage() {
Echo("Usage: Exportworld")
}
}

View File

@@ -1,5 +1,7 @@
package net.torvald.terrarum.modulebasegame.gameworld package net.torvald.terrarum.modulebasegame.gameworld
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonWriter
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.gameworld.WorldTime
@@ -37,4 +39,10 @@ class GameWorldExtension : GameWorld {
init { init {
} }
override fun getJsonFields(): List<String> {
return super.getJsonFields() + arrayListOf(
""""basegame.economy": ${Json(JsonWriter.OutputType.json).toJson(economy)}"""
)
}
} }

View File

@@ -65,35 +65,35 @@ open class WriteMeta(val ingame: TerrarumIngame) {
return json return json
} }
}
/**
* @param b a ByteArray /**
* @return Bytes in [b] which are GZip'd then Ascii85-encoded * @param b a ByteArray
*/ * @return Bytes in [b] which are GZip'd then Ascii85-encoded
private fun bytesToZipdStr(b: ByteArray): String { */
val sb = StringBuilder() fun bytesToZipdStr(b: ByteArray): String {
val bo = ByteArray64GrowableOutputStream() val sb = StringBuilder()
val zo = GZIPOutputStream(bo) val bo = ByteArray64GrowableOutputStream()
val zo = GZIPOutputStream(bo)
b.forEach {
zo.write(it.toInt()) b.forEach {
} zo.write(it.toInt())
zo.flush(); zo.close() }
zo.flush(); zo.close()
val ba = bo.toByteArray64()
var bai = 0 val ba = bo.toByteArray64()
val buf = IntArray(4) { Ascii85.PAD_BYTE } var bai = 0
ba.forEach { val buf = IntArray(4) { Ascii85.PAD_BYTE }
if (bai > 0 && bai % 4 == 0) { ba.forEach {
sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3])) if (bai > 0 && bai % 4 == 0) {
buf.fill(Ascii85.PAD_BYTE) sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3]))
} buf.fill(Ascii85.PAD_BYTE)
}
buf[bai % 4] = it.toInt() and 255
buf[bai % 4] = it.toInt() and 255
bai += 1
}; sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3])) bai += 1
}; sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3]))
return sb.toString()
} return sb.toString()
} }

View File

@@ -1,49 +1,21 @@
package net.torvald.terrarum.serialise package net.torvald.terrarum.serialise
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonWriter
import net.torvald.terrarum.gameworld.BlockLayer import net.torvald.terrarum.gameworld.BlockLayer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
/** /**
* Created by minjaesong on 2021-08-23. * Created by minjaesong on 2021-08-23.
*/ */
class WriteWorld { class WriteWorld(val ingame: TerrarumIngame) {
open fun invoke(): String { open fun invoke(): String {
val world = ingame.world
return "{${world.getJsonFields().joinToString(",\n")}}"
return ""
} }
/**
* @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)
b.bytesIterator().forEachRemaining {
zo.write(it.toInt())
}
zo.flush(); zo.close()
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()
}
} }

View File

@@ -40,37 +40,38 @@ File is named as `"world"+world_index+".json"`
genver: 4, /* generator version in integer */ genver: 4, /* generator version in integer */
time_t: <in-game TIME_T of this world>, time_t: <in-game TIME_T of this world>,
terr: { terr: {
s: 33554432,
h: "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11", h: "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11",
b: <Ascii85-encoded gzipped terrain layerdata> b: <Ascii85-encoded gzipped terrain layerdata>
}, },
wall: { wall: {
s: 33554432,
h: <SHA-256 hash of 'b'>, h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wall layerdata> b: <Ascii85-encoded gzipped wall layerdata>
}, },
tdmg: { tdmg: {
s: 8795,
h: <SHA-256 hash of 'b'>, h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped terrain damage in JSON> b: <Ascii85-encoded gzipped terrain damage in JSON>
}, },
wdmg: { wdmg: {
s: 2,
h: <SHA-256 hash of 'b'>, h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wall damage in JSON> b: <Ascii85-encoded gzipped wall damage in JSON>
}, },
flui: { flut: {
s: 15734
h: <SHA-256 hash of 'b'>, h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped fluids in JSON> b: <Ascii85-encoded gzipped fluidTypes in JSON>
},
fluf: {
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped fluidFills in JSON>
}, },
wire: { wire: {
s: 2,
h: <SHA-256 hash of 'b'>, h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wiring nodes in JSON> b: <Ascii85-encoded gzipped wirings in JSON>
},
wirg: {
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wiringGraph in JSON>
}, },
tmap: { tmap: {
s: 4316,
h: <SHA-256 hash of 'b'>, h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped tilenumber-to-tilename map in JSON> b: <Ascii85-encoded gzipped tilenumber-to-tilename map in JSON>
} }