mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-09 18:14:06 +09:00
new map data format and its read/writer
!! UNTESTED !! UNTESTED !! UNTESTED !!
This commit is contained in:
@@ -20,8 +20,8 @@ open class GameWorld(val width: Int, val height: Int) {
|
|||||||
val layerWallLowBits: PairedMapLayer
|
val layerWallLowBits: PairedMapLayer
|
||||||
val layerTerrainLowBits: PairedMapLayer
|
val layerTerrainLowBits: PairedMapLayer
|
||||||
|
|
||||||
val layerThermal: MapLayerHalfFloat // in Kelvins
|
//val layerThermal: MapLayerHalfFloat // in Kelvins
|
||||||
val layerAirPressure: MapLayerHalfFloat // (milibar - 1000)
|
//val layerAirPressure: MapLayerHalfFloat // (milibar - 1000)
|
||||||
|
|
||||||
/** Tilewise spawn point */
|
/** Tilewise spawn point */
|
||||||
var spawnX: Int
|
var spawnX: Int
|
||||||
@@ -57,10 +57,10 @@ open class GameWorld(val width: Int, val height: Int) {
|
|||||||
layerWallLowBits = PairedMapLayer(width, height)
|
layerWallLowBits = PairedMapLayer(width, height)
|
||||||
|
|
||||||
// temperature layer: 2x2 is one cell
|
// temperature layer: 2x2 is one cell
|
||||||
layerThermal = MapLayerHalfFloat(width / 2, height / 2, averageTemperature)
|
//layerThermal = MapLayerHalfFloat(width / 2, height / 2, averageTemperature)
|
||||||
|
|
||||||
// air pressure layer: 4 * 8 is one cell
|
// air pressure layer: 4 * 8 is one cell
|
||||||
layerAirPressure = MapLayerHalfFloat(width / 4, height / 8, 13f) // 1013 mBar
|
//layerAirPressure = MapLayerHalfFloat(width / 4, height / 8, 13f) // 1013 mBar
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,11 +282,13 @@ open class GameWorld(val width: Int, val height: Int) {
|
|||||||
|
|
||||||
|
|
||||||
fun getTemperature(worldTileX: Int, worldTileY: Int): Float? {
|
fun getTemperature(worldTileX: Int, worldTileY: Int): Float? {
|
||||||
return layerThermal.getValue((worldTileX fmod width) / 2, worldTileY / 2)
|
return null
|
||||||
|
//return layerThermal.getValue((worldTileX fmod width) / 2, worldTileY / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAirPressure(worldTileX: Int, worldTileY: Int): Float? {
|
fun getAirPressure(worldTileX: Int, worldTileY: Int): Float? {
|
||||||
return layerAirPressure.getValue((worldTileX fmod width) / 4, worldTileY / 8)
|
return null
|
||||||
|
//return layerAirPressure.getValue((worldTileX fmod width) / 4, worldTileY / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,23 @@ package net.torvald.terrarum.gameworld
|
|||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2016-01-17.
|
* Created by minjaesong on 2016-01-17.
|
||||||
*/
|
*/
|
||||||
open class MapLayer(val width: Int, val height: Int) : Iterable<Byte> {
|
open class MapLayer : Iterable<Byte> {
|
||||||
|
|
||||||
|
val width: Int; val height: Int
|
||||||
internal @Volatile var data: ByteArray // in parallel programming: do not trust your register; always read freshly from RAM!
|
internal @Volatile var data: ByteArray // in parallel programming: do not trust your register; always read freshly from RAM!
|
||||||
|
|
||||||
init {
|
constructor(width: Int, height: Int) {
|
||||||
|
this.width = width
|
||||||
|
this.height = height
|
||||||
data = ByteArray(width * height)
|
data = ByteArray(width * height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(width: Int, height: Int, data: ByteArray) {
|
||||||
|
this.data = data
|
||||||
|
this.width = width
|
||||||
|
this.height = height
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an iterator over elements of type `T`.
|
* Returns an iterator over elements of type `T`.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package net.torvald.terrarum.gameworld
|
|||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2016-02-15.
|
* Created by minjaesong on 2016-02-15.
|
||||||
*/
|
*/
|
||||||
open class PairedMapLayer(width: Int, val height: Int) : Iterable<Byte> {
|
open class PairedMapLayer : Iterable<Byte> {
|
||||||
|
|
||||||
|
val width: Int; val height: Int
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 0b_xxxx_yyyy, x for lower index, y for higher index
|
* 0b_xxxx_yyyy, x for lower index, y for higher index
|
||||||
@@ -15,14 +18,19 @@ open class PairedMapLayer(width: Int, val height: Int) : Iterable<Byte> {
|
|||||||
*/
|
*/
|
||||||
internal @Volatile var data: ByteArray
|
internal @Volatile var data: ByteArray
|
||||||
|
|
||||||
val width: Int
|
constructor(width: Int, height: Int) {
|
||||||
|
|
||||||
init {
|
|
||||||
this.width = width / 2
|
this.width = width / 2
|
||||||
|
this.height = height
|
||||||
data = ByteArray(width * height / 2)
|
data = ByteArray(width * height / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(width: Int, height: Int, data: ByteArray) {
|
||||||
|
this.data = data
|
||||||
|
this.width = width / 2
|
||||||
|
this.height = height
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an iterator over elements of type `T`.
|
* Returns an iterator over elements of type `T`.
|
||||||
* Note: this iterator will return combined damage, that is 0bxxxx_yyyy as whole.
|
* Note: this iterator will return combined damage, that is 0bxxxx_yyyy as whole.
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ object LandUtil {
|
|||||||
fun resolveBlockAddr(world: GameWorld, t: BlockAddress): Pair<Int, Int> =
|
fun resolveBlockAddr(world: GameWorld, t: BlockAddress): Pair<Int, Int> =
|
||||||
Pair((t % world.width).toInt(), (t / world.width).toInt())
|
Pair((t % world.width).toInt(), (t / world.width).toInt())
|
||||||
|
|
||||||
|
fun resolveBlockAddr(width: Int, t: BlockAddress): Pair<Int, Int> =
|
||||||
|
Pair((t % width).toInt(), (t / width).toInt())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get owner ID as an Actor/Faction
|
* Get owner ID as an Actor/Faction
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package net.torvald.terrarum.serialise;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/1094703/java-file-input-with-rewind-reset-capability#1094758
|
||||||
|
*
|
||||||
|
* Created by minjaesong on 2018-10-03.
|
||||||
|
*/
|
||||||
|
public class MarkableFileInputStream extends FilterInputStream {
|
||||||
|
private FileChannel myFileChannel;
|
||||||
|
private long mark = -1;
|
||||||
|
|
||||||
|
public MarkableFileInputStream(FileInputStream fis) {
|
||||||
|
super(fis);
|
||||||
|
myFileChannel = fis.getChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(int readlimit) {
|
||||||
|
try {
|
||||||
|
mark = myFileChannel.position();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
mark = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
if (mark == -1) {
|
||||||
|
throw new IOException("not marked");
|
||||||
|
}
|
||||||
|
myFileChannel.position(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
254
src/net/torvald/terrarum/serialise/ReadLayerDataZip.kt
Normal file
254
src/net/torvald/terrarum/serialise/ReadLayerDataZip.kt
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
package net.torvald.terrarum.serialise
|
||||||
|
|
||||||
|
import net.torvald.terrarum.gameworld.BlockAddress
|
||||||
|
import net.torvald.terrarum.gameworld.BlockDamage
|
||||||
|
import net.torvald.terrarum.gameworld.MapLayer
|
||||||
|
import net.torvald.terrarum.gameworld.PairedMapLayer
|
||||||
|
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||||
|
import net.torvald.terrarum.realestate.LandUtil
|
||||||
|
import java.io.*
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.util.*
|
||||||
|
import java.util.zip.Inflater
|
||||||
|
import java.util.zip.InflaterOutputStream
|
||||||
|
import kotlin.IllegalArgumentException
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2016-08-24.
|
||||||
|
*/
|
||||||
|
// internal for everything: prevent malicious module from messing up the savedata
|
||||||
|
internal object ReadLayerDataZip {
|
||||||
|
|
||||||
|
// FIXME UNTESTED !!
|
||||||
|
|
||||||
|
internal operator fun invoke(file: File): LayerData {
|
||||||
|
val inputStream = MarkableFileInputStream(FileInputStream(file))
|
||||||
|
|
||||||
|
|
||||||
|
val magicBytes = ByteArray(4)
|
||||||
|
val versionNumber = ByteArray(1)
|
||||||
|
val layerCount = ByteArray(1)
|
||||||
|
val payloadCountByte = ByteArray(1)
|
||||||
|
val compression = ByteArray(1)
|
||||||
|
val worldWidth = ByteArray(4)
|
||||||
|
val worldHeight = ByteArray(4)
|
||||||
|
val spawnAddress = ByteArray(6)
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// FILE READING //
|
||||||
|
//////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// read header first
|
||||||
|
inputStream.read(magicBytes)
|
||||||
|
if (!Arrays.equals(magicBytes, WriteLayerDataZip.MAGIC)) {
|
||||||
|
throw IllegalArgumentException("File not a Layer Data")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inputStream.read(versionNumber)
|
||||||
|
inputStream.read(layerCount)
|
||||||
|
inputStream.read(payloadCountByte)
|
||||||
|
inputStream.read(compression)
|
||||||
|
inputStream.read(worldWidth)
|
||||||
|
inputStream.read(worldHeight)
|
||||||
|
inputStream.read(spawnAddress)
|
||||||
|
|
||||||
|
// read payloads
|
||||||
|
|
||||||
|
val payloadCount = payloadCountByte[0].toUint()
|
||||||
|
val pldBuffer4 = ByteArray(4)
|
||||||
|
val pldBuffer6 = ByteArray(6)
|
||||||
|
val pldBuffer8 = ByteArray(8)
|
||||||
|
|
||||||
|
val payloads = HashMap<String, TEMzPayload>()
|
||||||
|
|
||||||
|
for (pldCnt in 0 until payloadCount) {
|
||||||
|
inputStream.read(pldBuffer4)
|
||||||
|
|
||||||
|
// check payload header
|
||||||
|
if (!pldBuffer4.contentEquals(WriteLayerDataZip.PAYLOAD_HEADER))
|
||||||
|
throw InternalError("Payload not found")
|
||||||
|
|
||||||
|
// get payload's name
|
||||||
|
inputStream.read(pldBuffer4)
|
||||||
|
val payloadName = pldBuffer4.toString(Charset.forName("US-ASCII"))
|
||||||
|
|
||||||
|
// get uncompressed size
|
||||||
|
inputStream.read(pldBuffer6)
|
||||||
|
val uncompressedSize = pldBuffer6.toLittleInt48()
|
||||||
|
|
||||||
|
// get deflated size
|
||||||
|
inputStream.mark(2147483647) // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
// creep forward until we hit the PAYLOAD_FOOTER
|
||||||
|
var deflatedSize: Int = 0 // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
// loop init
|
||||||
|
inputStream.read(pldBuffer8)
|
||||||
|
// loop main
|
||||||
|
while (!pldBuffer8.contentEquals(WriteLayerDataZip.PAYLOAD_FOOTER)) {
|
||||||
|
val aByte = inputStream.read(); deflatedSize += 1
|
||||||
|
if (aByte == -1) throw InternalError("Unexpected end-of-file")
|
||||||
|
pldBuffer8.shiftLeftBy(1, aByte.toByte())
|
||||||
|
}
|
||||||
|
// at this point, we should have correct size of deflated bytestream
|
||||||
|
val deflatedBytes = ByteArray(deflatedSize) // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
inputStream.reset() // go back to marked spot
|
||||||
|
inputStream.read(deflatedBytes)
|
||||||
|
// put constructed payload into a container
|
||||||
|
payloads.put(payloadName, TEMzPayload(uncompressedSize, deflatedBytes))
|
||||||
|
|
||||||
|
// skip over to be aligned with the next payload
|
||||||
|
inputStream.skip(18L + deflatedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// test for EOF
|
||||||
|
inputStream.read(pldBuffer8)
|
||||||
|
if (!pldBuffer8.contentEquals(WriteLayerDataZip.FILE_FOOTER))
|
||||||
|
throw InternalError("Expected end-of-file, got not-so-end-of-file")
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////
|
||||||
|
// END OF FILE READ //
|
||||||
|
//////////////////////
|
||||||
|
|
||||||
|
val width = worldWidth.toLittleInt()
|
||||||
|
val height = worldHeight.toLittleInt()
|
||||||
|
val worldSize = width.toLong() * height
|
||||||
|
|
||||||
|
val payloadBytes = HashMap<String, ByteArray>()
|
||||||
|
|
||||||
|
payloads.forEach { t, u ->
|
||||||
|
val inflatedFile = ByteArray(u.uncompressedSize.toInt()) // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
val inflater = Inflater()
|
||||||
|
inflater.setInput(u.bytes, 0, u.bytes.size)
|
||||||
|
val uncompLen = inflater.inflate(inflatedFile)
|
||||||
|
|
||||||
|
// just in case
|
||||||
|
if (uncompLen.toLong() != u.uncompressedSize)
|
||||||
|
throw InternalError("DEFLATE size mismatch -- expected ${u.uncompressedSize}, got $uncompLen")
|
||||||
|
|
||||||
|
// deal with (MSB ++ LSB)
|
||||||
|
if (t == "TERR" || t == "WALL") {
|
||||||
|
payloadBytes["${t}_MSB"] = inflatedFile.sliceArray(0 until worldSize.toInt()) // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
payloadBytes["${t}_LSb"] = inflatedFile.sliceArray(worldSize.toInt() until inflatedFile.size) // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
payloadBytes[t] = inflatedFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val spawnPoint = LandUtil.resolveBlockAddr(width, spawnAddress.toLittleInt48())
|
||||||
|
|
||||||
|
val terrainDamages = HashMap<BlockAddress, BlockDamage>()
|
||||||
|
val wallDamages = HashMap<BlockAddress, BlockDamage>()
|
||||||
|
|
||||||
|
// parse terrain damages
|
||||||
|
for (c in 0 until payloadBytes["TdMG"]!!.size step 10) {
|
||||||
|
val bytes = payloadBytes["TdMG"]!!
|
||||||
|
|
||||||
|
val tileAddr = bytes.sliceArray(c..c+5)
|
||||||
|
val value = bytes.sliceArray(c+6..c+9)
|
||||||
|
|
||||||
|
terrainDamages[tileAddr.toLittleInt48()] = value.toLittleFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// parse wall damages
|
||||||
|
for (c in 0 until payloadBytes["WdMG"]!!.size step 10) {
|
||||||
|
val bytes = payloadBytes["WdMG"]!!
|
||||||
|
|
||||||
|
val tileAddr = bytes.sliceArray(c..c+5)
|
||||||
|
val value = bytes.sliceArray(c+6..c+9)
|
||||||
|
|
||||||
|
wallDamages[tileAddr.toLittleInt48()] = value.toLittleFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return LayerData(
|
||||||
|
MapLayer(width, height, payloadBytes["WALL_MSB"]!!),
|
||||||
|
MapLayer(width, height, payloadBytes["TERR_MSB"]!!),
|
||||||
|
MapLayer(width, height, payloadBytes["WIRE"]!!),
|
||||||
|
PairedMapLayer(width, height, payloadBytes["WALL_LSB"]!!),
|
||||||
|
PairedMapLayer(width, height, payloadBytes["TERR_LSB"]!!),
|
||||||
|
|
||||||
|
spawnPoint.first, spawnPoint.second,
|
||||||
|
|
||||||
|
wallDamages, terrainDamages
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TEMzPayload(val uncompressedSize: Long, val bytes: ByteArray) // FIXME deflated stream cannot be larger than 2 GB
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately deployable, a part of the gameworld
|
||||||
|
*/
|
||||||
|
internal data class LayerData(
|
||||||
|
val layerWall: MapLayer,
|
||||||
|
val layerTerrain: MapLayer,
|
||||||
|
val layerWire: MapLayer,
|
||||||
|
val layerWallLowBits: PairedMapLayer,
|
||||||
|
val layerTerrainLowBits: PairedMapLayer,
|
||||||
|
//val layerThermal: MapLayerHalfFloat, // in Kelvins
|
||||||
|
//val layerAirPressure: MapLayerHalfFloat, // (milibar - 1000)
|
||||||
|
|
||||||
|
val spawnX: Int,
|
||||||
|
val spawnY: Int,
|
||||||
|
val wallDamages: HashMap<BlockAddress, BlockDamage>,
|
||||||
|
val terrainDamages: HashMap<BlockAddress, BlockDamage>
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun ByteArray.shiftLeftBy(size: Int, fill: Byte = 0.toByte()) {
|
||||||
|
if (size == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (size < 0) {
|
||||||
|
throw IllegalArgumentException("This won't shift to right (size = $size)")
|
||||||
|
}
|
||||||
|
else if (size >= this.size) {
|
||||||
|
Arrays.fill(this, 0.toByte())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (c in size..this.lastIndex) {
|
||||||
|
this[c - size] = this[c]
|
||||||
|
}
|
||||||
|
for (c in (this.size - size)..this.lastIndex) {
|
||||||
|
this[c] = fill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun InputStream.readRelative(b: ByteArray, off: Int, len: Int): Int {
|
||||||
|
if (b == null) {
|
||||||
|
throw NullPointerException()
|
||||||
|
} else if (off < 0 || len < 0 || len > b.size) {
|
||||||
|
throw IndexOutOfBoundsException()
|
||||||
|
} else if (len == 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = read()
|
||||||
|
if (c == -1) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
b[0] = c.toByte()
|
||||||
|
|
||||||
|
var i = 1
|
||||||
|
try {
|
||||||
|
while (i < len) {
|
||||||
|
c = read()
|
||||||
|
if (c == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b[i] = c.toByte()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
} catch (ee: IOException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ internal object WriteLayerData {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Int.toLittle() = byteArrayOf(
|
/*fun Int.toLittle() = byteArrayOf(
|
||||||
this.and(0xFF).toByte(),
|
this.and(0xFF).toByte(),
|
||||||
this.ushr(8).and(0xFF).toByte(),
|
this.ushr(8).and(0xFF).toByte(),
|
||||||
this.ushr(16).and(0xFF).toByte(),
|
this.ushr(16).and(0xFF).toByte(),
|
||||||
@@ -125,4 +125,4 @@ fun ByteArray.toLittleLong() =
|
|||||||
fun ByteArray.toLittleDouble() = java.lang.Double.longBitsToDouble(this.toLittleLong())
|
fun ByteArray.toLittleDouble() = java.lang.Double.longBitsToDouble(this.toLittleLong())
|
||||||
|
|
||||||
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
||||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)*/
|
||||||
229
src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt
Normal file
229
src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package net.torvald.terrarum.serialise
|
||||||
|
|
||||||
|
import net.torvald.terrarum.gameworld.GameWorld
|
||||||
|
import net.torvald.terrarum.Terrarum
|
||||||
|
import net.torvald.terrarum.console.EchoError
|
||||||
|
import net.torvald.terrarum.realestate.LandUtil
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
import java.util.zip.DeflaterOutputStream
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO this one does not use TerranVirtualDisk
|
||||||
|
*
|
||||||
|
* Created by minjaesong on 2016-03-18.
|
||||||
|
*/
|
||||||
|
// internal for everything: prevent malicious module from messing up the savedata
|
||||||
|
internal object WriteLayerDataZip {
|
||||||
|
|
||||||
|
// FIXME UNTESTED !!
|
||||||
|
|
||||||
|
val LAYERS_FILENAME = "worldinfo1"
|
||||||
|
|
||||||
|
val MAGIC = byteArrayOf(0x54, 0x45, 0x4D, 0x7A)
|
||||||
|
val VERSION_NUMBER = 3.toByte()
|
||||||
|
val NUMBER_OF_LAYERS = 3.toByte()
|
||||||
|
val NUMBER_OF_PAYLOADS = 5.toByte()
|
||||||
|
val COMPRESSION_ALGORITHM = 1.toByte()
|
||||||
|
val PAYLOAD_HEADER = byteArrayOf(0, 0x70, 0x4C, 0x64)
|
||||||
|
val PAYLOAD_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x50, 0x59, 0x4C, 0x64, -1)
|
||||||
|
val FILE_FOOTER = byteArrayOf(0x45, 0x6E, 0x64, 0x54, 0x45, 0x4D, -1, -2)
|
||||||
|
|
||||||
|
val NULL: Byte = 0
|
||||||
|
|
||||||
|
|
||||||
|
internal operator fun invoke(saveDirectoryName: String): Boolean {
|
||||||
|
val path = "${Terrarum.defaultSaveDir}/$saveDirectoryName/${LAYERS_FILENAME}"
|
||||||
|
val tempPath = "${path}_bak"
|
||||||
|
val world = (Terrarum.ingame!!.world)
|
||||||
|
|
||||||
|
val parentDir = File("${Terrarum.defaultSaveDir}/$saveDirectoryName")
|
||||||
|
if (!parentDir.exists()) {
|
||||||
|
parentDir.mkdir()
|
||||||
|
}
|
||||||
|
else if (!parentDir.isDirectory) {
|
||||||
|
EchoError("Savegame directory is not actually a directory, aborting...")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val tempFile = File(tempPath)
|
||||||
|
val outFile = File(path)
|
||||||
|
tempFile.createNewFile()
|
||||||
|
|
||||||
|
val outputStream = BufferedOutputStream(FileOutputStream(tempFile), 8192)
|
||||||
|
val deflater = DeflaterOutputStream(outputStream, true)
|
||||||
|
|
||||||
|
fun wb(byteArray: ByteArray) { outputStream.write(byteArray) }
|
||||||
|
fun wb(byte: Byte) { outputStream.write(byte.toInt()) }
|
||||||
|
fun wb(byte: Int) { outputStream.write(byte) }
|
||||||
|
fun wi32(int: Int) { wb(int.toLittle()) }
|
||||||
|
fun wi48(long: Long) { wb(long.toLittle48()) }
|
||||||
|
fun wi64(long: Long) { wb(long.toLittle()) }
|
||||||
|
fun wf32(float: Float) { wi32(float.toRawBits()) }
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// WRITE BINARIES //
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// all the necessary headers
|
||||||
|
wb(MAGIC); wb(VERSION_NUMBER); wb(NUMBER_OF_LAYERS); wb(NUMBER_OF_PAYLOADS); wb(COMPRESSION_ALGORITHM)
|
||||||
|
|
||||||
|
// world width, height, and spawn point
|
||||||
|
wb(world.width); wb(world.height)
|
||||||
|
wi48(LandUtil.getBlockAddr(world, world.spawnX, world.spawnY))
|
||||||
|
|
||||||
|
// write payloads //
|
||||||
|
outputStream.flush()
|
||||||
|
|
||||||
|
// TERR payload
|
||||||
|
wb(PAYLOAD_HEADER); wb("TERR".toByteArray())
|
||||||
|
wi48(world.width * world.height * 3L / 2)
|
||||||
|
deflater.write(world.terrainArray)
|
||||||
|
deflater.write(world.layerTerrainLowBits.data)
|
||||||
|
deflater.flush()
|
||||||
|
deflater.finish()
|
||||||
|
wb(PAYLOAD_FOOTER)
|
||||||
|
|
||||||
|
// WALL payload
|
||||||
|
wb(PAYLOAD_HEADER); wb("WALL".toByteArray())
|
||||||
|
wi48(world.width * world.height * 3L / 2)
|
||||||
|
deflater.write(world.wallArray)
|
||||||
|
deflater.write(world.layerWall.data)
|
||||||
|
deflater.flush()
|
||||||
|
deflater.finish()
|
||||||
|
wb(PAYLOAD_FOOTER)
|
||||||
|
|
||||||
|
// WIRE payload
|
||||||
|
wb(PAYLOAD_HEADER); wb("WIRE".toByteArray())
|
||||||
|
wi48(world.width * world.height.toLong())
|
||||||
|
deflater.write(world.wireArray)
|
||||||
|
deflater.flush()
|
||||||
|
deflater.finish()
|
||||||
|
wb(PAYLOAD_FOOTER)
|
||||||
|
|
||||||
|
// TdMG payload
|
||||||
|
wb(PAYLOAD_HEADER); wb("TdMG".toByteArray())
|
||||||
|
wi48(world.terrainDamages.size.toLong())
|
||||||
|
|
||||||
|
world.terrainDamages.forEach { t, u ->
|
||||||
|
deflater.write(t.toLittle48())
|
||||||
|
deflater.write(u.toRawBits().toLittle())
|
||||||
|
}
|
||||||
|
|
||||||
|
deflater.flush()
|
||||||
|
deflater.finish()
|
||||||
|
wb(PAYLOAD_FOOTER)
|
||||||
|
|
||||||
|
// WdMG payload
|
||||||
|
wb(PAYLOAD_HEADER); wb("WdMG".toByteArray())
|
||||||
|
wi48(world.wallDamages.size.toLong())
|
||||||
|
|
||||||
|
world.wallDamages.forEach { t, u ->
|
||||||
|
deflater.write(t.toLittle48())
|
||||||
|
deflater.write(u.toRawBits().toLittle())
|
||||||
|
}
|
||||||
|
|
||||||
|
deflater.flush()
|
||||||
|
deflater.finish()
|
||||||
|
wb(PAYLOAD_FOOTER)
|
||||||
|
|
||||||
|
// write footer
|
||||||
|
wb(FILE_FOOTER)
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// END OF WRITE //
|
||||||
|
//////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// replace savemeta with tempfile
|
||||||
|
try {
|
||||||
|
deflater.close()
|
||||||
|
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
outFile.delete()
|
||||||
|
tempFile.copyTo(outFile, overwrite = true)
|
||||||
|
tempFile.delete()
|
||||||
|
println("Saved map data '$LAYERS_FILENAME' to $saveDirectoryName.")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
outputStream.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.toLittle() = byteArrayOf(
|
||||||
|
this.and(0xFF).toByte(),
|
||||||
|
this.ushr(8).and(0xFF).toByte(),
|
||||||
|
this.ushr(16).and(0xFF).toByte(),
|
||||||
|
this.ushr(24).and(0xFF).toByte()
|
||||||
|
)
|
||||||
|
fun Long.toLittle() = byteArrayOf(
|
||||||
|
this.and(0xFF).toByte(),
|
||||||
|
this.ushr(8).and(0xFF).toByte(),
|
||||||
|
this.ushr(16).and(0xFF).toByte(),
|
||||||
|
this.ushr(24).and(0xFF).toByte(),
|
||||||
|
this.ushr(32).and(0xFF).toByte(),
|
||||||
|
this.ushr(40).and(0xFF).toByte(),
|
||||||
|
this.ushr(48).and(0xFF).toByte(),
|
||||||
|
this.ushr(56).and(0xFF).toByte()
|
||||||
|
)
|
||||||
|
fun Long.toLittle48() = byteArrayOf(
|
||||||
|
this.and(0xFF).toByte(),
|
||||||
|
this.ushr(8).and(0xFF).toByte(),
|
||||||
|
this.ushr(16).and(0xFF).toByte(),
|
||||||
|
this.ushr(24).and(0xFF).toByte(),
|
||||||
|
this.ushr(32).and(0xFF).toByte(),
|
||||||
|
this.ushr(40).and(0xFF).toByte()
|
||||||
|
)
|
||||||
|
fun Double.toLittle() = java.lang.Double.doubleToRawLongBits(this).toLittle()
|
||||||
|
fun Boolean.toLittle() = byteArrayOf(if (this) 0xFF.toByte() else 0.toByte())
|
||||||
|
|
||||||
|
fun ByteArray.toLittleInt() =
|
||||||
|
if (this.size != 4) throw Error("Array not in size of 4")
|
||||||
|
else this[0].toUint() or
|
||||||
|
this[1].toUint().shl(8) or
|
||||||
|
this[2].toUint().shl(16) or
|
||||||
|
this[3].toUint().shl(24)
|
||||||
|
fun ByteArray.toLittleLong() =
|
||||||
|
if (this.size != 8) throw Error("Array not in size of 8")
|
||||||
|
else this[0].toUlong() or
|
||||||
|
this[1].toUlong().shl(8) or
|
||||||
|
this[2].toUlong().shl(16) or
|
||||||
|
this[3].toUlong().shl(24) or
|
||||||
|
this[4].toUlong().shl(32) or
|
||||||
|
this[5].toUlong().shl(40) or
|
||||||
|
this[6].toUlong().shl(48) or
|
||||||
|
this[7].toUlong().shl(56)
|
||||||
|
fun ByteArray.toLittleInt48() =
|
||||||
|
if (this.size != 6) throw Error("Array not in size of 6")
|
||||||
|
else this[0].toUlong() or
|
||||||
|
this[1].toUlong().shl(8) or
|
||||||
|
this[2].toUlong().shl(16) or
|
||||||
|
this[3].toUlong().shl(24) or
|
||||||
|
this[4].toUlong().shl(32) or
|
||||||
|
this[5].toUlong().shl(40)
|
||||||
|
fun ByteArray.toLittleFloat() = java.lang.Float.intBitsToFloat(this.toLittleInt())
|
||||||
|
|
||||||
|
fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this)
|
||||||
|
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||||
@@ -79,6 +79,10 @@ abstract class UIItem(var parentUI: UICanvas) { // do not replace parentUI to UI
|
|||||||
open var clickOnceListenerFired = false
|
open var clickOnceListenerFired = false
|
||||||
|
|
||||||
|
|
||||||
|
/** Since gamepads can't just choose which UIItem to control, this variable is used to allow processing of
|
||||||
|
* gamepad button events for one or more UIItems in one or more UICanvases. */
|
||||||
|
open var controllerInFocus = false
|
||||||
|
|
||||||
|
|
||||||
open fun update(delta: Float) {
|
open fun update(delta: Float) {
|
||||||
if (parentUI.isVisible) {
|
if (parentUI.isVisible) {
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ Payload "WIRE" -- world wires data
|
|||||||
Uncompressed size will be as same as (width * height)
|
Uncompressed size will be as same as (width * height)
|
||||||
|
|
||||||
Payload "TdMG" -- world terrain damage data, array of: (Int48 tileAddress, Float32 damage)
|
Payload "TdMG" -- world terrain damage data, array of: (Int48 tileAddress, Float32 damage)
|
||||||
Uncompressed size will be arbitrary
|
Uncompressed size will be arbitrary (multiple of tens)
|
||||||
|
|
||||||
Payload "WdMG" -- world walls damage data, array of: (Int48 tileAddress, Float32 damage)
|
Payload "WdMG" -- world walls damage data, array of: (Int48 tileAddress, Float32 damage)
|
||||||
Uncompressed size will be arbitrary
|
Uncompressed size will be arbitrary (multiple of tens)
|
||||||
|
|
||||||
EOF 45 E
|
EOF 45 E
|
||||||
EOF 6E n
|
EOF 6E n
|
||||||
|
|||||||
Reference in New Issue
Block a user