Files
Terrarum/src/net/torvald/terrarum/serialise/PointOfInterest.kt
2023-10-24 14:21:53 +09:00

246 lines
9.6 KiB
Kotlin

package net.torvald.terrarum.serialise
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.TerrarumAppConfiguration
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.gameworld.BlockLayerI16
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.GameWorld.Companion.TERRAIN
import net.torvald.terrarum.gameworld.GameWorld.Companion.WALL
import net.torvald.terrarum.linearSearchBy
import net.torvald.terrarum.utils.HashArray
import java.io.StringReader
/**
* Created by minjaesong on 2023-10-17.
*/
class PointOfInterest(
identifier: String,
width: Int,
height: Int,
tileNumberToNameMap0: HashArray<ItemID>,
tileNameToNumberMap0: HashMap<ItemID, Int>
) : Json.Serializable {
constructor() : this("undefined", 0,0, HashArray(), HashMap())
constructor(numberToNameMap: HashArray<ItemID>, nameToNumberMap: HashMap<ItemID, Int>) : this("undefined", 0, 0, numberToNameMap, nameToNumberMap)
@Transient var w = width; private set
@Transient var h = height; private set
@Transient var layers = ArrayList<POILayer>(); private set
@Transient var id = identifier; private set
@Transient var tileNumberToNameMap: HashArray<ItemID> = tileNumberToNameMap0; private set
@Transient var tileNameToNumberMap: HashMap<ItemID, Int> = tileNameToNumberMap0; private set
@Transient lateinit var lutFromJson: HashArray<ItemID>
@Transient var wlenFromJson = 0
override fun write(json: Json) {
val tileSymbolToItemId = HashArray<ItemID>() // exported
val uniqueTiles = ArrayList(layers.flatMap { it.getUniqueTiles() }.toSet().toList().sorted()) // contains TileNumber
// build tileSymbolToItemId
uniqueTiles.forEachIndexed { index, tilenum ->
tileSymbolToItemId[index.toLong()] = if (tilenum == 65535) // largest value on BlockLayerI16
Block.NULL
else
tileNumberToNameMap[tilenum.toLong()] ?: throw NullPointerException("No tileNumber->tileName mapping for $tilenum")
}
val itemIDtoTileSym = tileSymbolToItemId.map { it.value to it.key }.toMap()
val wordSize = if (tileSymbolToItemId.size >= 255) 16 else 8
// printdbg(this, "unique tiles: ${tileSymbolToItemId.size}")
// printdbg(this, "tileSymbolToItemId=$tileSymbolToItemId")
// printdbg(this, "itemIDtoTileSym=$itemIDtoTileSym")
layers.forEach { it.getReadyForSerialisation(tileNumberToNameMap, 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) {
this.id = jsonData["id"].asString()
this.w = jsonData["w"].asInt()
this.h = jsonData["h"].asInt()
this.lutFromJson = json.readValue(HashArray<ItemID>().javaClass, jsonData["lut"])
this.wlenFromJson = jsonData["wlen"].asInt()
println("read test")
println("id: $id, w: $w, h: $h, wlen: $wlenFromJson")
println("lut: $lutFromJson")
println("==== layers (bytes) ====")
// decompress layers
jsonData["layers"].forEachIndexed { index, jsonData ->
print("#${index+1}: ")
POILayer().also {
it.w = this.w; it.h = this.h; it.id = this.id
it.read(json, jsonData)
layers.add(it)
}
}
}
fun getReadyToBeUsed(itemIDtoTileNum: Map<ItemID, Int>) {
layers.forEach {
it.getReadyToBeUsed(lutFromJson, itemIDtoTileNum, w, h, wlenFromJson / 8)
}
}
/**
* Place the specified layers onto the world. The name of the layers are case-insensitive.
*/
fun placeOnWorld(layerNames: List<String>, world: GameWorld, bottomCentreX: Int, bottomCenterY: Int) {
val layers = layerNames.map { name ->
(layers.linearSearchBy { it.name.equals(name, true) } ?: throw IllegalArgumentException("Layer with name '$name' not found"))
}
layers.forEach {
it.placeOnWorld(world, bottomCentreX, bottomCenterY)
}
}
}
/**
* @param name name of the layer, case-insensitive.
*/
class POILayer(
name: String
) : Json.Serializable {
constructor() : this("undefined")
@Transient var name = name
@Transient internal lateinit var blockLayer: ArrayList<BlockLayerI16>
@Transient internal lateinit var dat: Array<ByteArray>
@Deprecated("Used for debug print", ReplaceWith("name")) @Transient internal var id = ""
@Deprecated("Used for debug print") @Transient internal var w = 0
@Deprecated("Used for debug print") @Transient internal var h = 0
/**
* @return list of unique tiles, in the form of TileNums
*/
fun getUniqueTiles(): List<Int> {
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
*
* `tilenumToItemID[-1]` should return `Block.NULL`
*/
fun getReadyForSerialisation(tilenumToItemID: HashArray<ItemID>, itemIDtoTileSym: Map<ItemID, Long>, byteLength: Int) {
dat = blockLayer.map { layer ->
ByteArray(layer.width * layer.height * byteLength) { i ->
if (byteLength == 1) {
val tilenum = layer.unsafeGetTile(i % layer.width, i / layer.width).toLong()
val itemID = if (tilenum == 65535L) Block.NULL else tilenumToItemID[tilenum] ?: throw NullPointerException("No tileNumber->tileName mapping for $tilenum")
val tileSym = itemIDtoTileSym[itemID]!!
tileSym.toByte()
}
else if (byteLength == 2) {
val tilenum = layer.unsafeGetTile((i/2) % layer.width, (i/2) / layer.width).toLong()
val itemID = if (tilenum == 65535L) Block.NULL else tilenumToItemID[tilenum]!!
val tileSym = itemIDtoTileSym[itemID]!!
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<ItemID>, itemIDtoTileNum: Map<ItemID, Int>, width: Int, height: Int, byteLength: Int) {
if (::blockLayer.isInitialized) {
blockLayer.forEach { it.dispose() }
}
blockLayer = ArrayList<BlockLayerI16>()
dat.forEachIndexed { layerIndex, layer ->
val currentBlockLayer = BlockLayerI16(width, height).also {
blockLayer.add(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("Illegal byteLength $byteLength")
val x = w % width
val y = w / width
val itemID = tileSymbolToItemId[word.toLong()]!!
val tile = if (itemID == Block.NULL)
-1
else
itemIDtoTileNum[itemID]!!
currentBlockLayer.unsafeSetTile(x, y, tile)
}
}
}
fun placeOnWorld(world: GameWorld, bottomCentreX: Int, bottomCenterY: Int) {
val topLeftX = bottomCentreX - (blockLayer[0].width - 1) / 2
val topLeftY = bottomCenterY - (blockLayer[0].height - 1)
blockLayer.forEachIndexed { layerIndex, layer ->
for (x in 0 until layer.width) { for (y in 0 until layer.height) {
val tile = layer.unsafeGetTile(x, y)
if (tile != -1 && tile != 65535) {
val (wx, wy) = world.coerceXY(x + topLeftX, y + topLeftY)
world.setTileOnLayerUnsafe(layerIndex, wx, wy, tile)
}
} }
}
}
override fun write(json: Json) {
if (!::dat.isInitialized) throw IllegalStateException("Internal data is not prepared! please run getReadyForSerialisation() before writing!")
json.setTypeName(null)
json.writeValue("name", name)
json.writeValue("dat", dat)
}
override fun read(json: Json, jsonData: JsonValue) {
name = jsonData["name"].asString()
println(name)
dat = jsonData["dat"].mapIndexed { index, value ->
val zipdStr = value.asString()
val ba = Common.strToBytes(StringReader(zipdStr)).toByteArray()
val lname = "L${if (index == TERRAIN) "terr" else if (index == WALL) "wall" else "unk$index"}"
if (ba.size != w * h) throw IllegalStateException("Layer size mismatch: expected ${w*h} but got ${ba.size} on POI $id Layer $name $lname")
print(" $lname: ")
print("(${ba.size})")
println("[${ba.joinToString()}]")
ba
}.toTypedArray()
}
}