From c2fdb4b26a9bd1940ff989ebce2aa088b90e09fa Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 26 Aug 2021 23:11:03 +0900 Subject: [PATCH] tile damage and wire layers are now save/loaded --- src/net/torvald/terrarum/IngameInstance.kt | 5 +- src/net/torvald/terrarum/Terrarum.kt | 22 +- .../torvald/terrarum/console/CommandDict.kt | 3 +- .../gamecontroller/IngameController.kt | 6 - .../torvald/terrarum/gameworld/GameWorld.kt | 70 ++--- .../terrarum/modulebasegame/IngameRenderer.kt | 10 +- .../terrarum/modulebasegame/TerrarumIngame.kt | 8 +- .../modulebasegame/console/PrintWorld.kt | 37 +++ .../gameitems/WireGraphDebugger.kt | 6 +- src/net/torvald/terrarum/serialise/Common.kt | 267 ++++++++++++++++++ .../torvald/terrarum/serialise/ReadWorld.kt | 4 +- .../torvald/terrarum/serialise/WriteActor.kt | 17 +- .../torvald/terrarum/serialise/WriteWorld.kt | 183 +----------- src/net/torvald/terrarum/utils/HashArray.kt | 18 ++ .../terrarum/worlddrawer/LightmapRenderer.kt | 4 +- 15 files changed, 390 insertions(+), 270 deletions(-) create mode 100644 src/net/torvald/terrarum/modulebasegame/console/PrintWorld.kt create mode 100644 src/net/torvald/terrarum/serialise/Common.kt create mode 100644 src/net/torvald/terrarum/utils/HashArray.kt diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt index 73060bd0b..83b08742d 100644 --- a/src/net/torvald/terrarum/IngameInstance.kt +++ b/src/net/torvald/terrarum/IngameInstance.kt @@ -2,7 +2,6 @@ package net.torvald.terrarum import com.badlogic.gdx.Screen import com.badlogic.gdx.graphics.g2d.SpriteBatch -import com.badlogic.gdx.utils.Queue import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.BlockMarkerActor @@ -10,7 +9,6 @@ import net.torvald.terrarum.gameitem.ItemID import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid -import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.ui.ConsoleWindow import net.torvald.util.SortedArrayList import java.util.concurrent.locks.Lock @@ -31,6 +29,8 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { val consoleOpened: Boolean get() = consoleHandler.isOpened || consoleHandler.isOpening + var newWorldLoadedLatch = false + init { consoleHandler.setPosition(0, 0) @@ -40,6 +40,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { open var world: GameWorld = GameWorld.makeNullWorld() set(value) { + newWorldLoadedLatch = true printdbg(this, "Ingame instance ${this.hashCode()}, accepting new world ${value.layerTerrain}; called from") printStackTrace(this) field = value diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index abf11c10f..d6544fcc8 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -483,17 +483,17 @@ fun MutableList.shuffle() { val ccW = GameFontBase.toColorCode(0xFFFF) -val ccY = GameFontBase.toColorCode(0xFE8F) -val ccO = GameFontBase.toColorCode(0xFB2F) -val ccR = GameFontBase.toColorCode(0xF88F) -val ccF = GameFontBase.toColorCode(0xFAEF) -val ccM = GameFontBase.toColorCode(0xEAFF) -val ccB = GameFontBase.toColorCode(0x88FF) -val ccC = GameFontBase.toColorCode(0x8FFF) -val ccG = GameFontBase.toColorCode(0x8F8F) -val ccV = GameFontBase.toColorCode(0x080F) -val ccX = GameFontBase.toColorCode(0x853F) -val ccK = GameFontBase.toColorCode(0x888F) +val ccY = GameFontBase.toColorCode(0xFFE8) +val ccO = GameFontBase.toColorCode(0xFFB2) +val ccR = GameFontBase.toColorCode(0xFF88) +val ccF = GameFontBase.toColorCode(0xFFAE) +val ccM = GameFontBase.toColorCode(0xFEAF) +val ccB = GameFontBase.toColorCode(0xF88F) +val ccC = GameFontBase.toColorCode(0xF8FF) +val ccG = GameFontBase.toColorCode(0xF8F8) +val ccV = GameFontBase.toColorCode(0xF080) +val ccX = GameFontBase.toColorCode(0xF853) +val ccK = GameFontBase.toColorCode(0xF888) typealias Second = Float diff --git a/src/net/torvald/terrarum/console/CommandDict.kt b/src/net/torvald/terrarum/console/CommandDict.kt index 525cb26e4..1db00da02 100644 --- a/src/net/torvald/terrarum/console/CommandDict.kt +++ b/src/net/torvald/terrarum/console/CommandDict.kt @@ -63,7 +63,8 @@ object CommandDict { /* !! */"exportworld" to ExportWorld, /* !! */"exportactor" to ExportActor, /* !! */"importworld" to ImportWorld, - /* !! */"exportfborgb" to ExportRendererFboRGB + /* !! */"exportfborgb" to ExportRendererFboRGB, + /* !! */"printworld" to PrintWorld ) operator fun get(commandName: String): ConsoleCommand { diff --git a/src/net/torvald/terrarum/gamecontroller/IngameController.kt b/src/net/torvald/terrarum/gamecontroller/IngameController.kt index 3ad3f72d1..6af623b06 100644 --- a/src/net/torvald/terrarum/gamecontroller/IngameController.kt +++ b/src/net/torvald/terrarum/gamecontroller/IngameController.kt @@ -92,12 +92,6 @@ class IngameController(val terrarumIngame: TerrarumIngame) : InputAdapter() { tKeyUp(key) keyStatus[key] = keyDown - - - - if (key == Input.Keys.ENTER && keyDown) { - printdbg(this, "ENTER down") - } } // control mouse/touch events val newmx = Gdx.input.x diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index 3c243cfb8..c0dc3f4ef 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -3,10 +3,8 @@ 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 com.badlogic.gdx.utils.compression.Lzma -import net.torvald.UnsafePtr import net.torvald.UnsafePtrInputStream import net.torvald.gdx.graphics.Cvec import net.torvald.terrarum.* @@ -19,16 +17,13 @@ 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.terrarum.serialise.bytesToLzmadStr import net.torvald.terrarum.serialise.bytesToZipdStr +import net.torvald.terrarum.utils.* 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 -import kotlin.math.sign typealias BlockAddress = Long @@ -71,24 +66,24 @@ class GameWorld : Disposable { /** Tilewise spawn point */ var spawnY: Int - val wallDamages = HashMap() - val terrainDamages = HashMap() - val fluidTypes = HashMap() - val fluidFills = HashMap() + val wallDamages = HashArray() + val terrainDamages = HashArray() + val fluidTypes = HashedFluidType() + val fluidFills = HashArray() /** * Single block can have multiple conduits, different types of conduits are stored separately. */ - private val wirings = HashMap() + public val wirings = HashedWirings() - private val wiringGraph = HashMap>() + private val wiringGraph = HashedWiringGraph() @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. */ - //private val wiringBlocks: HashMap + //private val wiringBlocks: HashArray //public World physWorld = new World( new Vec2(0, -Terrarum.game.gravitationalAccel) ); //physics @@ -111,7 +106,7 @@ class GameWorld : Disposable { ) - val tileNumberToNameMap = HashMap() + val tileNumberToNameMap = HashArray() // does not go to the savefile @Transient val tileNameToNumberMap = HashMap() @@ -163,7 +158,7 @@ class GameWorld : Disposable { AppLoader.tileMaker.tags.forEach { printdbg(this, "tileNumber ${it.value.tileNumber} <-> tileName ${it.key}") - tileNumberToNameMap[it.value.tileNumber] = it.key + tileNumberToNameMap[it.value.tileNumber.toLong()] = it.key tileNameToNumberMap[it.key] = it.value.tileNumber } @@ -219,7 +214,7 @@ class GameWorld : Disposable { val (x, y) = coerceXY(rawX, rawY) try { - return tileNumberToNameMap[layerWall.unsafeGetTile(x, y)]!! + return tileNumberToNameMap[layerWall.unsafeGetTile(x, y).toLong()]!! } catch (e: NullPointerException) { val msg = "No tile name mapping for wall ${layerWall.unsafeGetTile(x, y)} in ($x, $y)" @@ -234,7 +229,7 @@ class GameWorld : Disposable { val (x, y) = coerceXY(rawX, rawY) try { - return tileNumberToNameMap[layerTerrain.unsafeGetTile(x, y)]!! + return tileNumberToNameMap[layerTerrain.unsafeGetTile(x, y).toLong()]!! } catch (e: NullPointerException) { val msg = "No tile name mapping for terrain ${layerTerrain.unsafeGetTile(x, y)} in ($x, $y)" @@ -317,10 +312,10 @@ class GameWorld : Disposable { val wireNode = wirings[blockAddr] if (wireNode == null) { - wirings[blockAddr] = WiringNode(blockAddr, SortedArrayList()) + wirings[blockAddr] = WiringNode(SortedArrayList()) } - wirings[blockAddr]!!.wires.add(tile) + wirings[blockAddr]!!.ws.add(tile) if (!bypassEvent) Terrarum.ingame?.queueWireChangedEvent(tile, false, x, y) @@ -349,7 +344,7 @@ class GameWorld : Disposable { } fun getWireGraphUnsafe(blockAddr: BlockAddress, itemID: ItemID): Int? { - return wiringGraph[blockAddr]?.get(itemID)?.connections + return wiringGraph[blockAddr]?.get(itemID)?.cnx } fun getWireEmitStateOf(x: Int, y: Int, itemID: ItemID): Vector2? { @@ -359,7 +354,7 @@ class GameWorld : Disposable { } fun getWireEmitStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): Vector2? { - return wiringGraph[blockAddr]?.get(itemID)?.emitState + return wiringGraph[blockAddr]?.get(itemID)?.emt } fun getWireRecvStateOf(x: Int, y: Int, itemID: ItemID): ArrayList? { @@ -369,7 +364,7 @@ class GameWorld : Disposable { } fun getWireRecvStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): ArrayList? { - return wiringGraph[blockAddr]?.get(itemID)?.recvStates + return wiringGraph[blockAddr]?.get(itemID)?.rcv } fun setWireGraphOf(x: Int, y: Int, itemID: ItemID, cnx: Int) { @@ -380,11 +375,11 @@ class GameWorld : Disposable { fun setWireGraphOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, cnx: Int) { if (wiringGraph[blockAddr] == null) - wiringGraph[blockAddr] = HashMap() + wiringGraph[blockAddr] = WiringGraphMap() if (wiringGraph[blockAddr]!![itemID] == null) wiringGraph[blockAddr]!![itemID] = WiringSimCell(cnx) - wiringGraph[blockAddr]!![itemID]!!.connections = cnx + wiringGraph[blockAddr]!![itemID]!!.cnx = cnx } fun setWireEmitStateOf(x: Int, y: Int, itemID: ItemID, vector: Vector2) { @@ -395,11 +390,11 @@ class GameWorld : Disposable { fun setWireEmitStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, vector: Vector2) { if (wiringGraph[blockAddr] == null) - wiringGraph[blockAddr] = HashMap() + wiringGraph[blockAddr] = WiringGraphMap() if (wiringGraph[blockAddr]!![itemID] == null) wiringGraph[blockAddr]!![itemID] = WiringSimCell(0, vector) - wiringGraph[blockAddr]!![itemID]!!.emitState = vector + wiringGraph[blockAddr]!![itemID]!!.emt = vector } fun addWireRecvStateOf(x: Int, y: Int, itemID: ItemID, state: WireRecvState) { @@ -416,11 +411,11 @@ class GameWorld : Disposable { fun addWireRecvStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, state: WireRecvState) { if (wiringGraph[blockAddr] == null) - wiringGraph[blockAddr] = HashMap() + wiringGraph[blockAddr] = WiringGraphMap() if (wiringGraph[blockAddr]!![itemID] == null) wiringGraph[blockAddr]!![itemID] = WiringSimCell(0) - wiringGraph[blockAddr]!![itemID]!!.recvStates.add(state) + wiringGraph[blockAddr]!![itemID]!!.rcv.add(state) } fun getAllWiringGraph(x: Int, y: Int): HashMap? { @@ -435,7 +430,7 @@ class GameWorld : Disposable { fun clearAllWireRecvStateUnsafe(blockAddr: BlockAddress) { wiringGraph[blockAddr]?.forEach { - it.value.recvStates.clear() + it.value.rcv.clear() } } @@ -446,7 +441,7 @@ class GameWorld : Disposable { } fun getAllWiresFrom(blockAddr: BlockAddress): SortedArrayList? { - return wirings[blockAddr]?.wires + return wirings[blockAddr]?.ws } fun getTileFrom(mode: Int, x: Int, y: Int): ItemID { @@ -624,13 +619,8 @@ class GameWorld : Disposable { * If the wire does not allow them (e.g. wire bridge, thicknet), connect top-bottom and left-right nodes. */ data class WiringNode( - val position: BlockAddress = -1, // may seem redundant and it kinda is, but don't remove! - val wires: SortedArrayList = SortedArrayList() // what could possibly go wrong bloating up the RAM footprint when it's practically infinite these days? - ) : Comparable { - override fun compareTo(other: WiringNode): Int { - return (this.position - other.position).sign - } - } + val ws: SortedArrayList = SortedArrayList() // what could possibly go wrong bloating up the RAM footprint when it's practically infinite these days? + ) data class WireRecvState( var dist: Int = -1, // how many tiles it took to traverse @@ -642,9 +632,9 @@ class GameWorld : Disposable { * These values must be updated by none other than [WorldSimulator]() */ data class WiringSimCell( - var connections: Int = 0, // connections - var emitState: Vector2 = Vector2(0.0, 0.0), // i'm emitting this much power - var recvStates: ArrayList = ArrayList() // how far away are the power sources + var cnx: Int = 0, // connections + var emt: Vector2 = Vector2(0.0, 0.0), // i'm emitting this much power + var rcv: ArrayList = ArrayList() // how far away are the power sources ) fun getTemperature(worldTileX: Int, worldTileY: Int): Float? { diff --git a/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt b/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt index bf1a68340..d1193d35f 100644 --- a/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt +++ b/src/net/torvald/terrarum/modulebasegame/IngameRenderer.kt @@ -84,6 +84,7 @@ object IngameRenderer : Disposable { var world: GameWorld = GameWorld.makeNullWorld() private set // the grammar "IngameRenderer.world = gameWorld" seemes mundane and this function needs special care! + private var newWorldLoadedLatch = false // these codes will run regardless of the invocation of the "initialise()" function // the "initialise()" function will also be called @@ -162,6 +163,8 @@ object IngameRenderer : Disposable { LightmapRenderer.internalSetWorld(world) BlocksDrawer.world = world FeaturesDrawer.world = world + + newWorldLoadedLatch = true } } catch (e: UninitializedPropertyAccessException) { @@ -201,10 +204,10 @@ object IngameRenderer : Disposable { this.player = player - if (!gamePaused) { + if (!gamePaused || newWorldLoadedLatch) { measureDebugTime("Renderer.ApparentLightRun") { // recalculate for even frames, or if the sign of the cam-x changed - if (AppLoader.GLOBAL_RENDER_TIMER % 3 == 0 || WorldCamera.x * oldCamX < 0) + if (AppLoader.GLOBAL_RENDER_TIMER % 3 == 0 || WorldCamera.x * oldCamX < 0 || newWorldLoadedLatch) LightmapRenderer.fireRecalculateEvent(actorsRenderBehind, actorsRenderFront, actorsRenderMidTop, actorsRenderMiddle, actorsRenderOverlay) oldCamX = WorldCamera.x @@ -345,6 +348,9 @@ object IngameRenderer : Disposable { // works but some UI elements have wrong transparency -> should be fixed with Terrarum.gdxCleanAndSetBlend -- Torvald 2019-01-12 blendNormal(batch) batch.color = Color.WHITE + + + if (newWorldLoadedLatch) newWorldLoadedLatch = false } diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt index 85f1cdecc..3dc60c95a 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt @@ -560,7 +560,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { // will also queue up the block/wall/wire placed events ingameController.update() - if (!paused) { + if (!paused || newWorldLoadedLatch) { //hypothetical_input_capturing_function_if_you_finally_decided_to_forgo_gdx_input_processor_and_implement_your_own_to_synchronise_everything() @@ -583,7 +583,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { // fill up visibleActorsRenderFront for wires, if: // 1. something is cued on the wire change queue // 2. wire renderclass changed - if (wireChangeQueue.isNotEmpty() || selectedWireRenderClass != oldSelectedWireRenderClass) { + if (newWorldLoadedLatch || wireChangeQueue.isNotEmpty() || selectedWireRenderClass != oldSelectedWireRenderClass) { measureDebugTime("Ingame.FillUpWiresBuffer") { fillUpWiresBuffer() } @@ -614,7 +614,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { } - if (!paused) { + if (!paused || newWorldLoadedLatch) { // completely consume block change queues because why not terrainChangeQueue.clear() wallChangeQueue.clear() @@ -638,6 +638,8 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { } //println("paused = $paused") + + if (!paused && newWorldLoadedLatch) newWorldLoadedLatch = false } diff --git a/src/net/torvald/terrarum/modulebasegame/console/PrintWorld.kt b/src/net/torvald/terrarum/modulebasegame/console/PrintWorld.kt new file mode 100644 index 000000000..b5817cd82 --- /dev/null +++ b/src/net/torvald/terrarum/modulebasegame/console/PrintWorld.kt @@ -0,0 +1,37 @@ +package net.torvald.terrarum.modulebasegame.console + +import net.torvald.terrarum.Terrarum +import net.torvald.terrarum.console.ConsoleCommand +import net.torvald.terrarum.console.Echo +import net.torvald.terrarum.gameworld.BlockAddress +import kotlin.reflect.full.memberProperties + +/** + * Created by minjaesong on 2021-08-26. + */ +object PrintWorld : ConsoleCommand { + override fun execute(args: Array) { + if (args.size == 2) { + val w = Terrarum.ingame!!.world + + val field = w::class.java.getDeclaredField(args[1]) + val fieldAccessibility = field.isAccessible + + field.isAccessible = true + Echo(field.get(w).toString()) + Echo(field.get(w).javaClass.simpleName) + w.wirings.forEach { i, node -> + Echo(i.toString()) + } + + field.isAccessible = fieldAccessibility + } + else { + printUsage() + } + } + + override fun printUsage() { + Echo("Usage: Exportworld ") + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/gameitems/WireGraphDebugger.kt b/src/net/torvald/terrarum/modulebasegame/gameitems/WireGraphDebugger.kt index 6caa48486..94f6d68f1 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameitems/WireGraphDebugger.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameitems/WireGraphDebugger.kt @@ -48,11 +48,11 @@ class WireGraphDebugger(originalID: ItemID) : GameItem(originalID) { Terrarum.ingame!!.world.getAllWiringGraph(mx, my)?.forEach { (itemID, simCell) -> if (sb.isNotEmpty()) sb.append('\n') - val connexionIcon = (simCell.connections + 0xE0A0).toChar() + val connexionIcon = (simCell.cnx + 0xE0A0).toChar() val wireName = WireCodex[itemID].nameKey - val emit = simCell.emitState - val recv = simCell.recvStates + val emit = simCell.emt + val recv = simCell.rcv sb.append("$connexionIcon $wireName") sb.append("\nE: $emit") diff --git a/src/net/torvald/terrarum/serialise/Common.kt b/src/net/torvald/terrarum/serialise/Common.kt new file mode 100644 index 000000000..803335db8 --- /dev/null +++ b/src/net/torvald/terrarum/serialise/Common.kt @@ -0,0 +1,267 @@ +package net.torvald.terrarum.serialise + +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonValue +import com.badlogic.gdx.utils.JsonWriter +import net.torvald.terrarum.AppLoader +import net.torvald.terrarum.AppLoader.printdbg +import net.torvald.terrarum.console.EchoError +import net.torvald.terrarum.gameworld.BlockLayer +import net.torvald.terrarum.gameworld.GameWorld +import net.torvald.terrarum.gameworld.WorldTime +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream +import net.torvald.terrarum.utils.* +import org.apache.commons.codec.digest.DigestUtils +import java.math.BigInteger +import java.util.zip.GZIPOutputStream + +/** + * Created by minjaesong on 2021-08-26. + */ +object Common { + /** dispose of the `offendingObject` after rejection! */ + class BlockLayerHashMismatchError(val oldHash: String, val newHash: String, val offendingObject: BlockLayer) : Error("Old Hash $oldHash != New Hash $newHash") + + 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) + + AppLoader.printdbg(this, "pre: ${(0L..1023L).map { obj.ptr[it].tostr() }.joinToString(" ")}") + + + 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 + try { + return strToBlockLayer(LayerInfo( + jsonData.getString("h"), + jsonData.getString("b"), + jsonData.getInt("x"), + jsonData.getInt("y") + )) + } + catch (e: BlockLayerHashMismatchError) { + EchoError(e.message ?: "") + return e.offendingObject + } + } + }) + // 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()) + } + }) + // HashArray + jsoner.setSerializer(HashArray::class.java, object : Json.Serializer> { + override fun write(json: Json, obj: HashArray<*>, knownType: Class<*>?) { + json.writeObjectStart() + obj.forEach { (k, v) -> + json.writeValue(k.toString(), v) + } + json.writeObjectEnd() + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashArray<*> { + val hashMap = HashArray() + printdbg(type?.canonicalName, "deserialising '$jsonData'") + JsonFetcher.forEach(jsonData) { key, obj -> + hashMap[key.toLong()] = json.readValue(null, obj) + printdbg(this, "key: $key, value: ${hashMap[key.toLong()]}") + } + return hashMap + } + }) + // HashedWirings + jsoner.setSerializer(HashedWirings::class.java, object : Json.Serializer { + override fun write(json: Json, obj: HashedWirings, knownType: Class<*>?) { + json.writeObjectStart() + obj.forEach { (k, v) -> + json.writeValue(k.toString(), v) + } + json.writeObjectEnd() + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWirings { + val hashMap = HashedWirings() + printdbg(type?.canonicalName, "deserialising '$jsonData'") + JsonFetcher.forEach(jsonData) { key, obj -> + hashMap[key.toLong()] = json.readValue(GameWorld.WiringNode::class.java, obj) + printdbg(this, "key: $key, value: ${hashMap[key.toLong()]}") + } + return hashMap + } + }) + // HashedWiringGraph + jsoner.setSerializer(HashedWiringGraph::class.java, object : Json.Serializer { + override fun write(json: Json, obj: HashedWiringGraph, knownType: Class<*>?) { + json.writeObjectStart() + obj.forEach { (k, v) -> + json.writeValue(k.toString(), v, WiringGraphMap::class.java) + } + json.writeObjectEnd() + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWiringGraph { + val hashMap = HashedWiringGraph() + printdbg(type?.canonicalName, "deserialising '$jsonData'") + JsonFetcher.forEach(jsonData) { key, obj -> + hashMap[key.toLong()] = json.readValue(WiringGraphMap::class.java, obj) + printdbg(this, "key: $key, value: ${hashMap[key.toLong()]}") + } + return hashMap + } + }) + // WiringGraphMap; this serialiser is here just to reduce the JSON filesize + jsoner.setSerializer(WiringGraphMap::class.java, object : Json.Serializer { + override fun write(json: Json, obj: WiringGraphMap, knownType: Class<*>?) { + json.writeObjectStart() + obj.forEach { (k, v) -> + json.writeValue(k, v, GameWorld.WiringSimCell::class.java) + } + json.writeObjectEnd() + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WiringGraphMap { + val hashMap = WiringGraphMap() + printdbg(type?.canonicalName, "deserialising '$jsonData'") + JsonFetcher.forEach(jsonData) { key, obj -> + hashMap[key] = json.readValue(GameWorld.WiringSimCell::class.java, obj) + printdbg(this, "key: $key, value: ${hashMap[key]}") + } + return hashMap + } + }) + } + + 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 { + b.bytesIterator().forEachRemaining { + 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 + val sb = StringBuilder() +// unzipdBytes.forEach { + unasciidBytes.forEach { + if (writeCursor < layer.ptr.size) { + + if (writeCursor < 1024) { + sb.append("${it.tostr()} ") + } + + + layer.ptr[writeCursor] = it + digester.update(it) + writeCursor += 1 + } + } + + + AppLoader.printdbg(this, "post: $sb") + + + // check hash + val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() } + + if (hash != layerInfo.h) { + throw BlockLayerHashMismatchError(layerInfo.h, hash, layer) + } + + return layer + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/ReadWorld.kt b/src/net/torvald/terrarum/serialise/ReadWorld.kt index a49a5b03c..70b4b9789 100644 --- a/src/net/torvald/terrarum/serialise/ReadWorld.kt +++ b/src/net/torvald/terrarum/serialise/ReadWorld.kt @@ -13,11 +13,11 @@ import java.io.Reader open class ReadWorld(val ingame: TerrarumIngame) { open fun invoke(worldDataStream: InputStream) { - postRead(WriteWorld.jsoner.fromJson(GameWorld::class.java, worldDataStream)) + postRead(Common.jsoner.fromJson(GameWorld::class.java, worldDataStream)) } open fun invoke(worldDataStream: Reader) { - postRead(WriteWorld.jsoner.fromJson(GameWorld::class.java, worldDataStream)) + postRead(Common.jsoner.fromJson(GameWorld::class.java, worldDataStream)) } private fun postRead(world: GameWorld) { diff --git a/src/net/torvald/terrarum/serialise/WriteActor.kt b/src/net/torvald/terrarum/serialise/WriteActor.kt index 6b57eca51..90ada814a 100644 --- a/src/net/torvald/terrarum/serialise/WriteActor.kt +++ b/src/net/torvald/terrarum/serialise/WriteActor.kt @@ -12,23 +12,8 @@ import java.math.BigInteger */ object WriteActor { - private val jsoner = Json(JsonWriter.OutputType.json) - - // install custom (de)serialiser - init { - 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()) - } - }) - } - operator fun invoke(actor: Actor): String { - return jsoner.toJson(actor) + return Common.jsoner.toJson(actor) } fun encodeToByteArray64(actor: Actor): ByteArray64 { diff --git a/src/net/torvald/terrarum/serialise/WriteWorld.kt b/src/net/torvald/terrarum/serialise/WriteWorld.kt index b16126f59..8d0a1fc3d 100644 --- a/src/net/torvald/terrarum/serialise/WriteWorld.kt +++ b/src/net/torvald/terrarum/serialise/WriteWorld.kt @@ -1,22 +1,7 @@ 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.AppLoader.printdbg -import net.torvald.terrarum.console.EchoError -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 net.torvald.terrarum.serialise.WriteWorld.Companion.tostr -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. @@ -26,7 +11,7 @@ open class WriteWorld(val ingame: TerrarumIngame) { open fun invoke(): String { val world = ingame.world //return "{${world.getJsonFields().joinToString(",\n")}}" - return jsoner.toJson(world) + return Common.jsoner.toJson(world) } fun encodeToByteArray64(): ByteArray64 { @@ -44,170 +29,4 @@ open class WriteWorld(val ingame: TerrarumIngame) { return ba } - companion object { - /** dispose of the `offendingObject` after rejection! */ - class BlockLayerHashMismatchError(val oldHash: String, val newHash: String, val offendingObject: BlockLayer) : Error("Old Hash $oldHash != New Hash $newHash") - - 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) - - printdbg(this, "pre: ${(0L..1023L).map { obj.ptr[it].tostr() }.joinToString(" ")}") - - - 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 - try { - return strToBlockLayer(LayerInfo( - jsonData.getString("h"), - jsonData.getString("b"), - jsonData.getInt("x"), - jsonData.getInt("y") - )) - } - catch (e: BlockLayerHashMismatchError) { - EchoError(e.message ?: "") - return e.offendingObject - } - } - }) - // 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 { - b.bytesIterator().forEachRemaining { - 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 - val sb = StringBuilder() -// unzipdBytes.forEach { - unasciidBytes.forEach { - if (writeCursor < layer.ptr.size) { - - if (writeCursor < 1024) { - sb.append("${it.tostr()} ") - } - - - layer.ptr[writeCursor] = it - digester.update(it) - writeCursor += 1 - } - } - - - printdbg(this, "post: $sb") - - - // check hash - val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() } - - if (hash != layerInfo.h) { - throw BlockLayerHashMismatchError(layerInfo.h, hash, layer) - } - - return layer - } - } } diff --git a/src/net/torvald/terrarum/utils/HashArray.kt b/src/net/torvald/terrarum/utils/HashArray.kt new file mode 100644 index 000000000..a6e0c37db --- /dev/null +++ b/src/net/torvald/terrarum/utils/HashArray.kt @@ -0,0 +1,18 @@ +package net.torvald.terrarum.utils + + +import net.torvald.terrarum.gameitem.ItemID +import net.torvald.terrarum.gameworld.BlockAddress +import net.torvald.terrarum.gameworld.FluidType +import net.torvald.terrarum.gameworld.GameWorld + +/** + * Created by minjaesong on 2021-08-26. + */ +class HashArray: HashMap() // primitives are working just fine tho + +// Oh for the fucks sake fuck you everyone; json shit won't work with generics +class WiringGraphMap: HashMap() +class HashedFluidType: HashMap() +class HashedWirings: HashMap() +class HashedWiringGraph: HashMap() diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt index 98c30fa6a..e9d2b4a7d 100644 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt +++ b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt @@ -413,9 +413,9 @@ object LightmapRenderer { _thisTerrain = world.getTileFromTerrainRaw(worldX, worldY) - _thisTerrainProp = BlockCodex[world.tileNumberToNameMap[_thisTerrain]] + _thisTerrainProp = BlockCodex[world.tileNumberToNameMap[_thisTerrain.toLong()]] _thisWall = world.getTileFromWallRaw(worldX, worldY) - _thisWallProp = BlockCodex[world.tileNumberToNameMap[_thisWall]] + _thisWallProp = BlockCodex[world.tileNumberToNameMap[_thisWall.toLong()]] _thisFluid = world.getFluid(worldX, worldY) _thisFluidProp = BlockCodex[_thisFluid.type]