From b380fa7ce77575ce416e9f7820245f5e13ee7d76 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 3 Oct 2018 19:20:11 +0900 Subject: [PATCH] new map data format and its read/writer !! UNTESTED !! UNTESTED !! UNTESTED !! --- .../torvald/terrarum/gameworld/GameWorld.kt | 14 +- .../torvald/terrarum/gameworld/MapLayer.kt | 13 +- .../terrarum/gameworld/PairedMapLayer.kt | 18 +- .../torvald/terrarum/realestate/LandUtil.kt | 3 + .../serialise/MarkableFileInputStream.java | 43 +++ .../terrarum/serialise/ReadLayerDataZip.kt | 254 ++++++++++++++++++ .../terrarum/serialise/WriteLayerData.kt | 4 +- .../terrarum/serialise/WriteLayerDataZip.kt | 229 ++++++++++++++++ src/net/torvald/terrarum/ui/UIItem.kt | 4 + work_files/DataFormats/Map data format.txt | 4 +- 10 files changed, 569 insertions(+), 17 deletions(-) create mode 100644 src/net/torvald/terrarum/serialise/MarkableFileInputStream.java create mode 100644 src/net/torvald/terrarum/serialise/ReadLayerDataZip.kt create mode 100644 src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index 6f8ad7833..b67035cce 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -20,8 +20,8 @@ open class GameWorld(val width: Int, val height: Int) { val layerWallLowBits: PairedMapLayer val layerTerrainLowBits: PairedMapLayer - val layerThermal: MapLayerHalfFloat // in Kelvins - val layerAirPressure: MapLayerHalfFloat // (milibar - 1000) + //val layerThermal: MapLayerHalfFloat // in Kelvins + //val layerAirPressure: MapLayerHalfFloat // (milibar - 1000) /** Tilewise spawn point */ var spawnX: Int @@ -57,10 +57,10 @@ open class GameWorld(val width: Int, val height: Int) { layerWallLowBits = PairedMapLayer(width, height) // 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 - 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? { - 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? { - return layerAirPressure.getValue((worldTileX fmod width) / 4, worldTileY / 8) + return null + //return layerAirPressure.getValue((worldTileX fmod width) / 4, worldTileY / 8) } diff --git a/src/net/torvald/terrarum/gameworld/MapLayer.kt b/src/net/torvald/terrarum/gameworld/MapLayer.kt index 0124600a2..aeb662a65 100644 --- a/src/net/torvald/terrarum/gameworld/MapLayer.kt +++ b/src/net/torvald/terrarum/gameworld/MapLayer.kt @@ -3,14 +3,23 @@ package net.torvald.terrarum.gameworld /** * Created by minjaesong on 2016-01-17. */ -open class MapLayer(val width: Int, val height: Int) : Iterable { +open class MapLayer : Iterable { + val width: Int; val height: Int 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) } + constructor(width: Int, height: Int, data: ByteArray) { + this.data = data + this.width = width + this.height = height + } + /** * Returns an iterator over elements of type `T`. diff --git a/src/net/torvald/terrarum/gameworld/PairedMapLayer.kt b/src/net/torvald/terrarum/gameworld/PairedMapLayer.kt index 8c88fb8e3..58574d5af 100644 --- a/src/net/torvald/terrarum/gameworld/PairedMapLayer.kt +++ b/src/net/torvald/terrarum/gameworld/PairedMapLayer.kt @@ -3,7 +3,10 @@ package net.torvald.terrarum.gameworld /** * Created by minjaesong on 2016-02-15. */ -open class PairedMapLayer(width: Int, val height: Int) : Iterable { +open class PairedMapLayer : Iterable { + + val width: Int; val height: Int + /** * 0b_xxxx_yyyy, x for lower index, y for higher index @@ -15,14 +18,19 @@ open class PairedMapLayer(width: Int, val height: Int) : Iterable { */ internal @Volatile var data: ByteArray - val width: Int - - init { + constructor(width: Int, height: Int) { this.width = width / 2 - + this.height = height 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`. * Note: this iterator will return combined damage, that is 0bxxxx_yyyy as whole. diff --git a/src/net/torvald/terrarum/realestate/LandUtil.kt b/src/net/torvald/terrarum/realestate/LandUtil.kt index 9b56ebd95..06994fc34 100644 --- a/src/net/torvald/terrarum/realestate/LandUtil.kt +++ b/src/net/torvald/terrarum/realestate/LandUtil.kt @@ -15,6 +15,9 @@ object LandUtil { fun resolveBlockAddr(world: GameWorld, t: BlockAddress): Pair = Pair((t % world.width).toInt(), (t / world.width).toInt()) + fun resolveBlockAddr(width: Int, t: BlockAddress): Pair = + Pair((t % width).toInt(), (t / width).toInt()) + /** * Get owner ID as an Actor/Faction */ diff --git a/src/net/torvald/terrarum/serialise/MarkableFileInputStream.java b/src/net/torvald/terrarum/serialise/MarkableFileInputStream.java new file mode 100644 index 000000000..55b3733e4 --- /dev/null +++ b/src/net/torvald/terrarum/serialise/MarkableFileInputStream.java @@ -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); + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/ReadLayerDataZip.kt b/src/net/torvald/terrarum/serialise/ReadLayerDataZip.kt new file mode 100644 index 000000000..b4ba41c0c --- /dev/null +++ b/src/net/torvald/terrarum/serialise/ReadLayerDataZip.kt @@ -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() + + 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() + + 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() + val wallDamages = HashMap() + + // 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, + val terrainDamages: HashMap + ) + + 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 + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteLayerData.kt b/src/net/torvald/terrarum/serialise/WriteLayerData.kt index 0b07730fc..2a11ea584 100644 --- a/src/net/torvald/terrarum/serialise/WriteLayerData.kt +++ b/src/net/torvald/terrarum/serialise/WriteLayerData.kt @@ -87,7 +87,7 @@ internal object WriteLayerData { } -fun Int.toLittle() = byteArrayOf( +/*fun Int.toLittle() = byteArrayOf( this.and(0xFF).toByte(), this.ushr(8).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 Byte.toUlong() = java.lang.Byte.toUnsignedLong(this) -fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this) \ No newline at end of file +fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)*/ \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt b/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt new file mode 100644 index 000000000..0c71e4dc6 --- /dev/null +++ b/src/net/torvald/terrarum/serialise/WriteLayerDataZip.kt @@ -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) \ No newline at end of file diff --git a/src/net/torvald/terrarum/ui/UIItem.kt b/src/net/torvald/terrarum/ui/UIItem.kt index e16dc25c4..c6fc52d39 100644 --- a/src/net/torvald/terrarum/ui/UIItem.kt +++ b/src/net/torvald/terrarum/ui/UIItem.kt @@ -79,6 +79,10 @@ abstract class UIItem(var parentUI: UICanvas) { // do not replace parentUI to UI 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) { if (parentUI.isVisible) { diff --git a/work_files/DataFormats/Map data format.txt b/work_files/DataFormats/Map data format.txt index e0a8fb896..429c60bb0 100644 --- a/work_files/DataFormats/Map data format.txt +++ b/work_files/DataFormats/Map data format.txt @@ -56,10 +56,10 @@ Payload "WIRE" -- world wires data Uncompressed size will be as same as (width * height) 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) - Uncompressed size will be arbitrary + Uncompressed size will be arbitrary (multiple of tens) EOF 45 E EOF 6E n