tile damage and wire layers are now save/loaded

This commit is contained in:
minjaesong
2021-08-26 23:11:03 +09:00
parent e5c25c5a10
commit c2fdb4b26a
15 changed files with 390 additions and 270 deletions

View File

@@ -2,7 +2,6 @@ package net.torvald.terrarum
import com.badlogic.gdx.Screen import com.badlogic.gdx.Screen
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Queue
import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameactors.BlockMarkerActor 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.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.ui.ConsoleWindow import net.torvald.terrarum.ui.ConsoleWindow
import net.torvald.util.SortedArrayList import net.torvald.util.SortedArrayList
import java.util.concurrent.locks.Lock import java.util.concurrent.locks.Lock
@@ -31,6 +29,8 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
val consoleOpened: Boolean val consoleOpened: Boolean
get() = consoleHandler.isOpened || consoleHandler.isOpening get() = consoleHandler.isOpened || consoleHandler.isOpening
var newWorldLoadedLatch = false
init { init {
consoleHandler.setPosition(0, 0) consoleHandler.setPosition(0, 0)
@@ -40,6 +40,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
open var world: GameWorld = GameWorld.makeNullWorld() open var world: GameWorld = GameWorld.makeNullWorld()
set(value) { set(value) {
newWorldLoadedLatch = true
printdbg(this, "Ingame instance ${this.hashCode()}, accepting new world ${value.layerTerrain}; called from") printdbg(this, "Ingame instance ${this.hashCode()}, accepting new world ${value.layerTerrain}; called from")
printStackTrace(this) printStackTrace(this)
field = value field = value

View File

@@ -483,17 +483,17 @@ fun MutableList<Any>.shuffle() {
val ccW = GameFontBase.toColorCode(0xFFFF) val ccW = GameFontBase.toColorCode(0xFFFF)
val ccY = GameFontBase.toColorCode(0xFE8F) val ccY = GameFontBase.toColorCode(0xFFE8)
val ccO = GameFontBase.toColorCode(0xFB2F) val ccO = GameFontBase.toColorCode(0xFFB2)
val ccR = GameFontBase.toColorCode(0xF88F) val ccR = GameFontBase.toColorCode(0xFF88)
val ccF = GameFontBase.toColorCode(0xFAEF) val ccF = GameFontBase.toColorCode(0xFFAE)
val ccM = GameFontBase.toColorCode(0xEAFF) val ccM = GameFontBase.toColorCode(0xFEAF)
val ccB = GameFontBase.toColorCode(0x88FF) val ccB = GameFontBase.toColorCode(0xF88F)
val ccC = GameFontBase.toColorCode(0x8FFF) val ccC = GameFontBase.toColorCode(0xF8FF)
val ccG = GameFontBase.toColorCode(0x8F8F) val ccG = GameFontBase.toColorCode(0xF8F8)
val ccV = GameFontBase.toColorCode(0x080F) val ccV = GameFontBase.toColorCode(0xF080)
val ccX = GameFontBase.toColorCode(0x853F) val ccX = GameFontBase.toColorCode(0xF853)
val ccK = GameFontBase.toColorCode(0x888F) val ccK = GameFontBase.toColorCode(0xF888)
typealias Second = Float typealias Second = Float

View File

@@ -63,7 +63,8 @@ object CommandDict {
/* !! */"exportworld" to ExportWorld, /* !! */"exportworld" to ExportWorld,
/* !! */"exportactor" to ExportActor, /* !! */"exportactor" to ExportActor,
/* !! */"importworld" to ImportWorld, /* !! */"importworld" to ImportWorld,
/* !! */"exportfborgb" to ExportRendererFboRGB /* !! */"exportfborgb" to ExportRendererFboRGB,
/* !! */"printworld" to PrintWorld
) )
operator fun get(commandName: String): ConsoleCommand { operator fun get(commandName: String): ConsoleCommand {

View File

@@ -92,12 +92,6 @@ class IngameController(val terrarumIngame: TerrarumIngame) : InputAdapter() {
tKeyUp(key) tKeyUp(key)
keyStatus[key] = keyDown keyStatus[key] = keyDown
if (key == Input.Keys.ENTER && keyDown) {
printdbg(this, "ENTER down")
}
} }
// control mouse/touch events // control mouse/touch events
val newmx = Gdx.input.x val newmx = Gdx.input.x

View File

@@ -3,10 +3,8 @@ 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.Json
import com.badlogic.gdx.utils.JsonValue
import com.badlogic.gdx.utils.JsonWriter import com.badlogic.gdx.utils.JsonWriter
import com.badlogic.gdx.utils.compression.Lzma import com.badlogic.gdx.utils.compression.Lzma
import net.torvald.UnsafePtr
import net.torvald.UnsafePtrInputStream import net.torvald.UnsafePtrInputStream
import net.torvald.gdx.graphics.Cvec import net.torvald.gdx.graphics.Cvec
import net.torvald.terrarum.* 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.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.terrarum.serialise.Ascii85
import net.torvald.terrarum.serialise.bytesToLzmadStr
import net.torvald.terrarum.serialise.bytesToZipdStr import net.torvald.terrarum.serialise.bytesToZipdStr
import net.torvald.terrarum.utils.*
import net.torvald.util.SortedArrayList import net.torvald.util.SortedArrayList
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import org.dyn4j.geometry.Vector2 import org.dyn4j.geometry.Vector2
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import kotlin.experimental.and
import kotlin.experimental.or
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.sign
typealias BlockAddress = Long typealias BlockAddress = Long
@@ -71,24 +66,24 @@ class GameWorld : Disposable {
/** Tilewise spawn point */ /** Tilewise spawn point */
var spawnY: Int var spawnY: Int
val wallDamages = HashMap<BlockAddress, Float>() val wallDamages = HashArray<Float>()
val terrainDamages = HashMap<BlockAddress, Float>() val terrainDamages = HashArray<Float>()
val fluidTypes = HashMap<BlockAddress, FluidType>() val fluidTypes = HashedFluidType()
val fluidFills = HashMap<BlockAddress, Float>() val fluidFills = HashArray<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.
*/ */
private val wirings = HashMap<BlockAddress, WiringNode>() public val wirings = HashedWirings()
private val wiringGraph = HashMap<BlockAddress, HashMap<ItemID, WiringSimCell>>() private val wiringGraph = HashedWiringGraph()
@Transient private val WIRE_POS_MAP = intArrayOf(1,2,4,8) @Transient private val WIRE_POS_MAP = intArrayOf(1,2,4,8)
@Transient private val WIRE_ANTIPOS_MAP = intArrayOf(4,8,1,2) @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. * Used by the renderer. When wirings are updated, `wirings` and this properties must be synchronised.
*/ */
//private val wiringBlocks: HashMap<BlockAddress, ItemID> //private val wiringBlocks: HashArray<ItemID>
//public World physWorld = new World( new Vec2(0, -Terrarum.game.gravitationalAccel) ); //public World physWorld = new World( new Vec2(0, -Terrarum.game.gravitationalAccel) );
//physics //physics
@@ -111,7 +106,7 @@ class GameWorld : Disposable {
) )
val tileNumberToNameMap = HashMap<Int, ItemID>() val tileNumberToNameMap = HashArray<ItemID>()
// does not go to the savefile // does not go to the savefile
@Transient val tileNameToNumberMap = HashMap<ItemID, Int>() @Transient val tileNameToNumberMap = HashMap<ItemID, Int>()
@@ -163,7 +158,7 @@ class GameWorld : Disposable {
AppLoader.tileMaker.tags.forEach { AppLoader.tileMaker.tags.forEach {
printdbg(this, "tileNumber ${it.value.tileNumber} <-> tileName ${it.key}") 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 tileNameToNumberMap[it.key] = it.value.tileNumber
} }
@@ -219,7 +214,7 @@ class GameWorld : Disposable {
val (x, y) = coerceXY(rawX, rawY) val (x, y) = coerceXY(rawX, rawY)
try { try {
return tileNumberToNameMap[layerWall.unsafeGetTile(x, y)]!! return tileNumberToNameMap[layerWall.unsafeGetTile(x, y).toLong()]!!
} }
catch (e: NullPointerException) { catch (e: NullPointerException) {
val msg = "No tile name mapping for wall ${layerWall.unsafeGetTile(x, y)} in ($x, $y)" 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) val (x, y) = coerceXY(rawX, rawY)
try { try {
return tileNumberToNameMap[layerTerrain.unsafeGetTile(x, y)]!! return tileNumberToNameMap[layerTerrain.unsafeGetTile(x, y).toLong()]!!
} }
catch (e: NullPointerException) { catch (e: NullPointerException) {
val msg = "No tile name mapping for terrain ${layerTerrain.unsafeGetTile(x, y)} in ($x, $y)" 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] val wireNode = wirings[blockAddr]
if (wireNode == null) { if (wireNode == null) {
wirings[blockAddr] = WiringNode(blockAddr, SortedArrayList()) wirings[blockAddr] = WiringNode(SortedArrayList())
} }
wirings[blockAddr]!!.wires.add(tile) wirings[blockAddr]!!.ws.add(tile)
if (!bypassEvent) if (!bypassEvent)
Terrarum.ingame?.queueWireChangedEvent(tile, false, x, y) Terrarum.ingame?.queueWireChangedEvent(tile, false, x, y)
@@ -349,7 +344,7 @@ class GameWorld : Disposable {
} }
fun getWireGraphUnsafe(blockAddr: BlockAddress, itemID: ItemID): Int? { 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? { fun getWireEmitStateOf(x: Int, y: Int, itemID: ItemID): Vector2? {
@@ -359,7 +354,7 @@ class GameWorld : Disposable {
} }
fun getWireEmitStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): Vector2? { 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<WireRecvState>? { fun getWireRecvStateOf(x: Int, y: Int, itemID: ItemID): ArrayList<WireRecvState>? {
@@ -369,7 +364,7 @@ class GameWorld : Disposable {
} }
fun getWireRecvStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): ArrayList<WireRecvState>? { fun getWireRecvStateUnsafe(blockAddr: BlockAddress, itemID: ItemID): ArrayList<WireRecvState>? {
return wiringGraph[blockAddr]?.get(itemID)?.recvStates return wiringGraph[blockAddr]?.get(itemID)?.rcv
} }
fun setWireGraphOf(x: Int, y: Int, itemID: ItemID, cnx: Int) { 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) { fun setWireGraphOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, cnx: Int) {
if (wiringGraph[blockAddr] == null) if (wiringGraph[blockAddr] == null)
wiringGraph[blockAddr] = HashMap() wiringGraph[blockAddr] = WiringGraphMap()
if (wiringGraph[blockAddr]!![itemID] == null) if (wiringGraph[blockAddr]!![itemID] == null)
wiringGraph[blockAddr]!![itemID] = WiringSimCell(cnx) 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) { 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) { fun setWireEmitStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, vector: Vector2) {
if (wiringGraph[blockAddr] == null) if (wiringGraph[blockAddr] == null)
wiringGraph[blockAddr] = HashMap() wiringGraph[blockAddr] = WiringGraphMap()
if (wiringGraph[blockAddr]!![itemID] == null) if (wiringGraph[blockAddr]!![itemID] == null)
wiringGraph[blockAddr]!![itemID] = WiringSimCell(0, vector) 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) { 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) { fun addWireRecvStateOfUnsafe(blockAddr: BlockAddress, itemID: ItemID, state: WireRecvState) {
if (wiringGraph[blockAddr] == null) if (wiringGraph[blockAddr] == null)
wiringGraph[blockAddr] = HashMap() wiringGraph[blockAddr] = WiringGraphMap()
if (wiringGraph[blockAddr]!![itemID] == null) if (wiringGraph[blockAddr]!![itemID] == null)
wiringGraph[blockAddr]!![itemID] = WiringSimCell(0) wiringGraph[blockAddr]!![itemID] = WiringSimCell(0)
wiringGraph[blockAddr]!![itemID]!!.recvStates.add(state) wiringGraph[blockAddr]!![itemID]!!.rcv.add(state)
} }
fun getAllWiringGraph(x: Int, y: Int): HashMap<ItemID, WiringSimCell>? { fun getAllWiringGraph(x: Int, y: Int): HashMap<ItemID, WiringSimCell>? {
@@ -435,7 +430,7 @@ class GameWorld : Disposable {
fun clearAllWireRecvStateUnsafe(blockAddr: BlockAddress) { fun clearAllWireRecvStateUnsafe(blockAddr: BlockAddress) {
wiringGraph[blockAddr]?.forEach { wiringGraph[blockAddr]?.forEach {
it.value.recvStates.clear() it.value.rcv.clear()
} }
} }
@@ -446,7 +441,7 @@ class GameWorld : Disposable {
} }
fun getAllWiresFrom(blockAddr: BlockAddress): SortedArrayList<ItemID>? { fun getAllWiresFrom(blockAddr: BlockAddress): SortedArrayList<ItemID>? {
return wirings[blockAddr]?.wires return wirings[blockAddr]?.ws
} }
fun getTileFrom(mode: Int, x: Int, y: Int): ItemID { 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. * If the wire does not allow them (e.g. wire bridge, thicknet), connect top-bottom and left-right nodes.
*/ */
data class WiringNode( data class WiringNode(
val position: BlockAddress = -1, // may seem redundant and it kinda is, but don't remove! val ws: SortedArrayList<ItemID> = SortedArrayList<ItemID>() // what could possibly go wrong bloating up the RAM footprint when it's practically infinite these days?
val wires: SortedArrayList<ItemID> = SortedArrayList<ItemID>() // what could possibly go wrong bloating up the RAM footprint when it's practically infinite these days? )
) : Comparable<WiringNode> {
override fun compareTo(other: WiringNode): Int {
return (this.position - other.position).sign
}
}
data class WireRecvState( data class WireRecvState(
var dist: Int = -1, // how many tiles it took to traverse 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]() * These values must be updated by none other than [WorldSimulator]()
*/ */
data class WiringSimCell( data class WiringSimCell(
var connections: Int = 0, // connections var cnx: Int = 0, // connections
var emitState: Vector2 = Vector2(0.0, 0.0), // i'm emitting this much power var emt: Vector2 = Vector2(0.0, 0.0), // i'm emitting this much power
var recvStates: ArrayList<WireRecvState> = ArrayList() // how far away are the power sources var rcv: ArrayList<WireRecvState> = ArrayList() // how far away are the power sources
) )
fun getTemperature(worldTileX: Int, worldTileY: Int): Float? { fun getTemperature(worldTileX: Int, worldTileY: Int): Float? {

View File

@@ -84,6 +84,7 @@ object IngameRenderer : Disposable {
var world: GameWorld = GameWorld.makeNullWorld() var world: GameWorld = GameWorld.makeNullWorld()
private set // the grammar "IngameRenderer.world = gameWorld" seemes mundane and this function needs special care! 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 // these codes will run regardless of the invocation of the "initialise()" function
// the "initialise()" function will also be called // the "initialise()" function will also be called
@@ -162,6 +163,8 @@ object IngameRenderer : Disposable {
LightmapRenderer.internalSetWorld(world) LightmapRenderer.internalSetWorld(world)
BlocksDrawer.world = world BlocksDrawer.world = world
FeaturesDrawer.world = world FeaturesDrawer.world = world
newWorldLoadedLatch = true
} }
} }
catch (e: UninitializedPropertyAccessException) { catch (e: UninitializedPropertyAccessException) {
@@ -201,10 +204,10 @@ object IngameRenderer : Disposable {
this.player = player this.player = player
if (!gamePaused) { if (!gamePaused || newWorldLoadedLatch) {
measureDebugTime("Renderer.ApparentLightRun") { measureDebugTime("Renderer.ApparentLightRun") {
// recalculate for even frames, or if the sign of the cam-x changed // 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) LightmapRenderer.fireRecalculateEvent(actorsRenderBehind, actorsRenderFront, actorsRenderMidTop, actorsRenderMiddle, actorsRenderOverlay)
oldCamX = WorldCamera.x 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 // works but some UI elements have wrong transparency -> should be fixed with Terrarum.gdxCleanAndSetBlend -- Torvald 2019-01-12
blendNormal(batch) blendNormal(batch)
batch.color = Color.WHITE batch.color = Color.WHITE
if (newWorldLoadedLatch) newWorldLoadedLatch = false
} }

View File

@@ -560,7 +560,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
// will also queue up the block/wall/wire placed events // will also queue up the block/wall/wire placed events
ingameController.update() 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() //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: // fill up visibleActorsRenderFront for wires, if:
// 1. something is cued on the wire change queue // 1. something is cued on the wire change queue
// 2. wire renderclass changed // 2. wire renderclass changed
if (wireChangeQueue.isNotEmpty() || selectedWireRenderClass != oldSelectedWireRenderClass) { if (newWorldLoadedLatch || wireChangeQueue.isNotEmpty() || selectedWireRenderClass != oldSelectedWireRenderClass) {
measureDebugTime("Ingame.FillUpWiresBuffer") { measureDebugTime("Ingame.FillUpWiresBuffer") {
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 // completely consume block change queues because why not
terrainChangeQueue.clear() terrainChangeQueue.clear()
wallChangeQueue.clear() wallChangeQueue.clear()
@@ -638,6 +638,8 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
} }
//println("paused = $paused") //println("paused = $paused")
if (!paused && newWorldLoadedLatch) newWorldLoadedLatch = false
} }

View File

@@ -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<String>) {
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 <field>")
}
}

View File

@@ -48,11 +48,11 @@ class WireGraphDebugger(originalID: ItemID) : GameItem(originalID) {
Terrarum.ingame!!.world.getAllWiringGraph(mx, my)?.forEach { (itemID, simCell) -> Terrarum.ingame!!.world.getAllWiringGraph(mx, my)?.forEach { (itemID, simCell) ->
if (sb.isNotEmpty()) sb.append('\n') if (sb.isNotEmpty()) sb.append('\n')
val connexionIcon = (simCell.connections + 0xE0A0).toChar() val connexionIcon = (simCell.cnx + 0xE0A0).toChar()
val wireName = WireCodex[itemID].nameKey val wireName = WireCodex[itemID].nameKey
val emit = simCell.emitState val emit = simCell.emt
val recv = simCell.recvStates val recv = simCell.rcv
sb.append("$connexionIcon $wireName") sb.append("$connexionIcon $wireName")
sb.append("\nE: $emit") sb.append("\nE: $emit")

View File

@@ -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<BigInteger> {
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<BlockLayer> {
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<WorldTime> {
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<HashArray<*>> {
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<Any>()
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<HashedWirings> {
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<HashedWiringGraph> {
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<WiringGraphMap> {
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
}
}

View File

@@ -13,11 +13,11 @@ import java.io.Reader
open class ReadWorld(val ingame: TerrarumIngame) { open class ReadWorld(val ingame: TerrarumIngame) {
open fun invoke(worldDataStream: InputStream) { 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) { 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) { private fun postRead(world: GameWorld) {

View File

@@ -12,23 +12,8 @@ import java.math.BigInteger
*/ */
object WriteActor { object WriteActor {
private val jsoner = Json(JsonWriter.OutputType.json)
// install custom (de)serialiser
init {
jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer<BigInteger> {
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 { operator fun invoke(actor: Actor): String {
return jsoner.toJson(actor) return Common.jsoner.toJson(actor)
} }
fun encodeToByteArray64(actor: Actor): ByteArray64 { fun encodeToByteArray64(actor: Actor): ByteArray64 {

View File

@@ -1,22 +1,7 @@
package net.torvald.terrarum.serialise 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.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 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. * Created by minjaesong on 2021-08-23.
@@ -26,7 +11,7 @@ open class WriteWorld(val ingame: TerrarumIngame) {
open fun invoke(): String { open fun invoke(): String {
val world = ingame.world val world = ingame.world
//return "{${world.getJsonFields().joinToString(",\n")}}" //return "{${world.getJsonFields().joinToString(",\n")}}"
return jsoner.toJson(world) return Common.jsoner.toJson(world)
} }
fun encodeToByteArray64(): ByteArray64 { fun encodeToByteArray64(): ByteArray64 {
@@ -44,170 +29,4 @@ open class WriteWorld(val ingame: TerrarumIngame) {
return ba 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<BigInteger> {
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<BlockLayer> {
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<WorldTime> {
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
}
}
} }

View File

@@ -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<R>: HashMap<Long, R>() // primitives are working just fine tho
// Oh for the fucks sake fuck you everyone; json shit won't work with generics
class WiringGraphMap: HashMap<ItemID, GameWorld.WiringSimCell>()
class HashedFluidType: HashMap<BlockAddress, FluidType>()
class HashedWirings: HashMap<BlockAddress, GameWorld.WiringNode>()
class HashedWiringGraph: HashMap<BlockAddress, WiringGraphMap>()

View File

@@ -413,9 +413,9 @@ object LightmapRenderer {
_thisTerrain = world.getTileFromTerrainRaw(worldX, worldY) _thisTerrain = world.getTileFromTerrainRaw(worldX, worldY)
_thisTerrainProp = BlockCodex[world.tileNumberToNameMap[_thisTerrain]] _thisTerrainProp = BlockCodex[world.tileNumberToNameMap[_thisTerrain.toLong()]]
_thisWall = world.getTileFromWallRaw(worldX, worldY) _thisWall = world.getTileFromWallRaw(worldX, worldY)
_thisWallProp = BlockCodex[world.tileNumberToNameMap[_thisWall]] _thisWallProp = BlockCodex[world.tileNumberToNameMap[_thisWall.toLong()]]
_thisFluid = world.getFluid(worldX, worldY) _thisFluid = world.getFluid(worldX, worldY)
_thisFluidProp = BlockCodex[_thisFluid.type] _thisFluidProp = BlockCodex[_thisFluid.type]