diff --git a/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt b/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt index ba49f28f6..10967c96d 100644 --- a/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt +++ b/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt @@ -14,12 +14,16 @@ import net.torvald.terrarum.gameactors.* import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameparticles.ParticleBase +import net.torvald.terrarum.gameworld.BlockLayerI16 import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.modulebasegame.ui.UIBuildingMakerBlockChooser import net.torvald.terrarum.modulebasegame.ui.UIBuildingMakerPenMenu import net.torvald.terrarum.modulebasegame.ui.UIPaletteSelector +import net.torvald.terrarum.serialise.Common +import net.torvald.terrarum.serialise.PointOfInterest +import net.torvald.terrarum.serialise.POILayer import net.torvald.terrarum.weather.WeatherMixer import net.torvald.terrarum.ui.UINSMenu import net.torvald.terrarum.worlddrawer.WorldCamera @@ -34,7 +38,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) { - File - New Flat ter. - New Rand. ter. - - Export… + - Export… : net.torvald.terrarum.modulebasegame.YamlCommandToolExportTest - Import… - Save World… - Load World… @@ -698,3 +702,13 @@ class YamlCommandToolToggleMarqueeOverlay : YamlInvokable { (args[0] as BuildingMaker).showSelection = !(args[0] as BuildingMaker).showSelection } } + +class YamlCommandToolExportTest : YamlInvokable { + override fun invoke(args: Array) { + val a = PointOfInterest("test", 10, 10) + val dat = BlockLayerI16(10, 10) + a.layers.add(POILayer("layerr1").also { it.blockLayer.add(BlockLayerI16(10, 10)) }) + a.layers.add(POILayer("layerr2").also { it.blockLayer.add(BlockLayerI16(10, 10)) }) + println(Common.jsoner.toJson(a)) + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/PointOfInterest.kt b/src/net/torvald/terrarum/serialise/PointOfInterest.kt new file mode 100644 index 000000000..2ac883d5e --- /dev/null +++ b/src/net/torvald/terrarum/serialise/PointOfInterest.kt @@ -0,0 +1,119 @@ +package net.torvald.terrarum.serialise + +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonValue +import net.torvald.terrarum.TerrarumAppConfiguration +import net.torvald.terrarum.gameitems.ItemID +import net.torvald.terrarum.gameworld.BlockLayerI16 +import net.torvald.terrarum.utils.HashArray + +/** + * Created by minjaesong on 2023-10-17. + */ +class PointOfInterest( + identifier: String, + width: Int, + height: Int +) : Json.Serializable { + + constructor() : this("undefined", 0,0) + + @Transient val w = width + @Transient val h = height + @Transient val layers = ArrayList() + @Transient val id = identifier + + override fun write(json: Json) { + val tileSymbolToItemId = HashArray() // exported + val tilenumToItemID = HashMap() // not exported + + // TODO populate above vars. Block.NULL is always integer -1 + + val itemIDtoTileSym = tileSymbolToItemId.map { it.value to it.key }.toMap() + + val wordSize = if (tileSymbolToItemId.size >= 255) 16 else 8 + layers.forEach { it.getReadyForSerialisation(tilenumToItemID, itemIDtoTileSym, wordSize / 8) } + + json.setTypeName(null) + json.writeValue("genver", TerrarumAppConfiguration.VERSION_RAW) + json.writeValue("id", id) + json.writeValue("wlen", wordSize) + json.writeValue("w", w) + json.writeValue("h", h) + json.writeValue("lut", tileSymbolToItemId) + json.writeValue("layers", layers) + } + + override fun read(json: Json, jsonData: JsonValue) { + TODO("Not yet implemented") + } +} + +class POILayer( + name: String +) : Json.Serializable { + constructor() : this("undefined") + + @Transient val name = name + @Transient val blockLayer = ArrayList() + @Transient private lateinit var dat: Array + + fun getUniqueTiles(): List { + return blockLayer.flatMap { layer -> + (0 until layer.height * layer.width).map { layer.unsafeGetTile(it % layer.width, it / layer.width) } + }.toSet().toList().sorted() + } + + /** + * Converts `blockLayer` into `dat` internally, the hidden property used for the serialisation + * + * Tilenum: tile number in the block layer, identical to the any other block layers + * TileSymbol: condensed version of the Tilenum, of which the higheset number is equal to the number of unique tiles used in the layer + */ + fun getReadyForSerialisation(tilenumToItemID: Map, itemIDtoTileSym: Map, byteLength: Int) { + dat = blockLayer.map { layer -> + ByteArray(layer.width * layer.height * byteLength) { i -> + if (byteLength == 1) { + itemIDtoTileSym[tilenumToItemID[layer.unsafeGetTile(i % layer.width, i / layer.width)]!!]!!.toByte() + } + else if (byteLength == 2) { + val tileSym = itemIDtoTileSym[tilenumToItemID[layer.unsafeGetTile((i/2) % layer.width, (i/2) / layer.width)]!!]!! + if (i % 2 == 0) tileSym.and(255).toByte() else tileSym.ushr(8).and(255).toByte() + } + else throw IllegalArgumentException() + } + }.toTypedArray() + } + + /** + * Converts `dat` into `blockLayer` so the Layer can be actually utilised. + */ + fun getReadyToBeUsed(tileSymbolToItemId: HashArray, itemIDtoTileNum: Map, width: Int, height: Int, byteLength: Int) { + blockLayer.forEach { it.dispose() } + blockLayer.clear() + + dat.forEachIndexed { layerIndex, layer -> + val currentBlockLayer = BlockLayerI16(width, height).also { + blockLayer[layerIndex] = it + } + for (w in 0 until layer.size / byteLength) { + val word = if (byteLength == 1) layer[w].toUint() else if (byteLength == 2) layer.toULittleShort(2*w) else throw IllegalArgumentException() + val x = w % width + val y = w / width + val tile = itemIDtoTileNum[tileSymbolToItemId[word.toLong()]!!]!! + currentBlockLayer.unsafeSetTile(x, y, tile) + } + } + } + + override fun write(json: Json) { + json.setTypeName(null) + json.writeValue("name", name) + json.writeValue("dat", dat) + } + + override fun read(json: Json?, jsonData: JsonValue?) { + TODO("Not yet implemented") + } + +} diff --git a/work_files/DataFormats/poi.md b/work_files/DataFormats/poi.md new file mode 100644 index 000000000..fb960724d --- /dev/null +++ b/work_files/DataFormats/poi.md @@ -0,0 +1,35 @@ +POI (Point of Interest) is a placement of blocks that can be used by the world generator. + +POIs are serialised as following: + +```json +{ + "genver": 1234567890, /* game version in Int64 */ + "id": "test_struct_with_enumerable_variations", + "wlen": 8, /* word length of the tile number. Can be 8 or 16 */ + "lut": {"0": "basegame:0", "1": "basegame:48"}, + "w": 7, "h": 4, + "layers": [ /* order matters! */ + {"name": "base", "dat": [ + "...layer_0.gz.b85", /* each byte matches what's on the LUT, except 0xFF (0xFFFF if wlen=16) always refers to the null tile. Endianness: little */ + "...layer_1.gz.b85" + ]}, + {"name": "varianceA", "dat": [ + "...layer_0.gz.b85", + "...layer_1.gz.b85" + ]}, + {"name": "varianceB", "dat": [ + "...layer_0.gz.b85", + "...layer_1.gz.b85" + ]}, + {"name": "varianceC", "dat": [ + "...layer_0.gz.b85", + "...layer_1.gz.b85" + ]}, + {"name": "overlay", "dat": [ + "...layer_0.gz.b85", + "...layer_1.gz.b85" + ]} + ] +} +``` \ No newline at end of file