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,
/* !! */"exportmeta" to ExportMeta,
/* !! */"exportworld" to ExportWorld,
/* !! */"importlayer" to ImportLayerData,
/* !! */"exportfborgb" to ExportRendererFboRGB
)

View File

@@ -2,6 +2,9 @@
package net.torvald.terrarum.gameworld
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.terrarum.*
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.gameactors.WireActor
import net.torvald.terrarum.gameitem.ItemID
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.serialise.Ascii85
import net.torvald.util.SortedArrayList
import org.apache.commons.codec.digest.DigestUtils
import org.dyn4j.geometry.Vector2
import java.util.zip.GZIPOutputStream
import kotlin.experimental.and
import kotlin.experimental.or
import kotlin.math.absoluteValue
@@ -47,9 +54,7 @@ open class GameWorld : Disposable {
open val loadTime: Long = System.currentTimeMillis() / 1000L
//layers
@TEMzPayload("WALL", TEMzPayload.INT16_LITTLE)
val layerWall: BlockLayer
@TEMzPayload("TERR", TEMzPayload.INT16_LITTLE)
val layerTerrain: BlockLayer
//val layerWire: MapLayer
@@ -61,19 +66,14 @@ open class GameWorld : Disposable {
/** Tilewise spawn point */
open var spawnY: Int
@TEMzPayload("WdMG", TEMzPayload.INT48_FLOAT_PAIR)
val wallDamages: HashMap<BlockAddress, Float>
@TEMzPayload("TdMG", TEMzPayload.INT48_FLOAT_PAIR)
val terrainDamages: HashMap<BlockAddress, Float>
@TEMzPayload("FlTP", TEMzPayload.INT48_SHORT_PAIR)
val fluidTypes: HashMap<BlockAddress, FluidType>
@TEMzPayload("FlFL", TEMzPayload.INT48_FLOAT_PAIR)
val fluidFills: HashMap<BlockAddress, Float>
/**
* 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 wiringGraph = HashMap<BlockAddress, HashMap<ItemID, WiringSimCell>>()
@@ -106,7 +106,6 @@ open class GameWorld : Disposable {
)
@TEMzPayload("TMaP", TEMzPayload.EXTERNAL_JSON)
val tileNumberToNameMap: HashMap<Int, ItemID>
// does not go to the savefile
val tileNameToNumberMap: HashMap<ItemID, Int>
@@ -672,6 +671,67 @@ open class GameWorld : Disposable {
open fun updateWorldTime(delta: Float) {
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)
@@ -683,21 +743,34 @@ inline class FluidType(val value: Int) {
fun abs() = this.value.absoluteValue
}
/**
* @param payloadName Payload name defined in Map Data Format.txt
* * 4 Letters: regular payload
* * 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
* @param b a BlockLayer
* @return Bytes in [b] which are GZip'd then Ascii85-encoded
*/
annotation class TEMzPayload(val payloadName: String, val arg: Int) {
companion object {
const val EXTERNAL_JAVAPROPERTIES = -3
const val EXTERNAL_CSV = -2
const val EXTERNAL_JSON = -1
const val INT16_LITTLE = 1
const val INT48_FLOAT_PAIR = 2
const val INT48_SHORT_PAIR = 3
const val INT48_INT_PAIR = 4
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

@@ -7,6 +7,7 @@ import net.torvald.terrarum.console.ConsoleCommand
import net.torvald.terrarum.console.Echo
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.serialise.WriteMeta
import net.torvald.terrarum.serialise.WriteWorld
import net.torvald.terrarum.utils.JsonWriter
import java.io.IOException
@@ -31,4 +32,25 @@ object ExportMeta : ConsoleCommand {
override fun printUsage() {
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
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonWriter
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldTime
@@ -37,4 +39,10 @@ class GameWorldExtension : GameWorld {
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
}
}
/**
* @param b a ByteArray
* @return Bytes in [b] which are GZip'd then Ascii85-encoded
*/
private fun bytesToZipdStr(b: ByteArray): String {
val sb = StringBuilder()
val bo = ByteArray64GrowableOutputStream()
val zo = GZIPOutputStream(bo)
/**
* @param b a ByteArray
* @return Bytes in [b] which are GZip'd then Ascii85-encoded
*/
fun bytesToZipdStr(b: ByteArray): String {
val sb = StringBuilder()
val bo = ByteArray64GrowableOutputStream()
val zo = GZIPOutputStream(bo)
b.forEach {
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()
b.forEach {
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

@@ -1,49 +1,21 @@
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.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
import java.util.zip.GZIPOutputStream
/**
* Created by minjaesong on 2021-08-23.
*/
class WriteWorld {
class WriteWorld(val ingame: TerrarumIngame) {
open fun invoke(): String {
return ""
val world = ingame.world
return "{${world.getJsonFields().joinToString(",\n")}}"
}
/**
* @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 */
time_t: <in-game TIME_T of this world>,
terr: {
s: 33554432,
h: "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11",
b: <Ascii85-encoded gzipped terrain layerdata>
},
wall: {
s: 33554432,
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wall layerdata>
},
tdmg: {
s: 8795,
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped terrain damage in JSON>
},
wdmg: {
s: 2,
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wall damage in JSON>
},
flui: {
s: 15734
flut: {
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: {
s: 2,
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: {
s: 4316,
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped tilenumber-to-tilename map in JSON>
}