diff --git a/assets/mods/basegame/ores/1.tga b/assets/mods/basegame/ores/1.tga new file mode 100644 index 000000000..58cf4f382 --- /dev/null +++ b/assets/mods/basegame/ores/1.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09330d417565a116f9fd63582dd8e8382d50d1638ca97f06ab84478591ec7446 +size 16402 diff --git a/src/net/torvald/terrarum/blockproperties/Fluid.kt b/src/net/torvald/terrarum/blockproperties/Fluid.kt index eed140bc0..bdb54f96d 100644 --- a/src/net/torvald/terrarum/blockproperties/Fluid.kt +++ b/src/net/torvald/terrarum/blockproperties/Fluid.kt @@ -1,5 +1,7 @@ package net.torvald.terrarum.blockproperties +import net.torvald.terrarum.gameitems.ItemID + /** * Created by minjaesong on 2016-08-06. diff --git a/src/net/torvald/terrarum/gameworld/BlockLayerI16.kt b/src/net/torvald/terrarum/gameworld/BlockLayerI16.kt index 670b49838..e5fb97bd9 100644 --- a/src/net/torvald/terrarum/gameworld/BlockLayerI16.kt +++ b/src/net/torvald/terrarum/gameworld/BlockLayerI16.kt @@ -69,10 +69,7 @@ open class BlockLayerI16(val width: Int, val height: Int) : BlockLayer { override fun unsafeToBytes(x: Int, y: Int): ByteArray { val offset = BYTES_PER_BLOCK * (y * width + x) - val lsb = ptr[offset] - val msb = ptr[offset + 1] - - return byteArrayOf(msb, lsb) + return byteArrayOf(ptr[offset + 1], ptr[offset + 0]) } internal fun unsafeSetTile(x: Int, y: Int, tile: Int) { diff --git a/src/net/torvald/terrarum/gameworld/BlockLayerI16F16.kt b/src/net/torvald/terrarum/gameworld/BlockLayerI16F16.kt new file mode 100644 index 000000000..07f2dc120 --- /dev/null +++ b/src/net/torvald/terrarum/gameworld/BlockLayerI16F16.kt @@ -0,0 +1,128 @@ +package net.torvald.terrarum.gameworld + +import net.torvald.terrarum.App +import net.torvald.terrarum.blockproperties.Fluid +import net.torvald.terrarum.gameworld.WorldSimulator.FLUID_MIN_MASS +import net.torvald.terrarum.serialise.toUint +import net.torvald.unsafe.UnsafeHelper +import net.torvald.unsafe.UnsafePtr +import net.torvald.util.Float16 + +/** + * * Memory layout: + * * ``` + * * a7 a6 a5 a4 a3 a2 a1 a0 | aF aE aD aC aB aA a9 a8 | f7 f6 f5 f4 f3 f2 f1 f0 | fF fE fD fC fB fA f9 f8 || + * * ``` + * * where a_n is a fluid number, f_n is a fluid fill + * + * Created by minjaesong on 2023-10-10. + */ +class BlockLayerI16F16(val width: Int, val height: Int) : BlockLayer { + override val bytesPerBlock = BYTES_PER_BLOCK + + // for some reason, all the efforts of saving the memory space were futile. + + // using unsafe pointer gets you 100 fps, whereas using directbytebuffer gets you 90 + internal val ptr: UnsafePtr = UnsafeHelper.allocate(width * height * BYTES_PER_BLOCK) + + val ptrDestroyed: Boolean + get() = ptr.destroyed + + init { + ptr.fillWith(0) + } + + /** + * @param data Byte array representation of the layer + */ + constructor(width: Int, height: Int, data: ByteArray) : this(width, height) { + TODO() + data.forEachIndexed { index, byte -> UnsafeHelper.unsafe.putByte(ptr.ptr + index, byte) } + } + + /** + * Returns an iterator over stored bytes. + * + * @return an Iterator. + */ + fun bytesIterator(): Iterator { + return object : Iterator { + private var iteratorCount = 0L + override fun hasNext(): Boolean { + return iteratorCount < width * height * BYTES_PER_BLOCK + } + override fun next(): Byte { + iteratorCount += 1 + return ptr[iteratorCount - 1] + } + } + } + + internal fun unsafeGetTile(x: Int, y: Int): Pair { + val offset = BYTES_PER_BLOCK * (y * width + x) + val lsb = ptr[offset] + val msb = ptr[offset + 1] + val hbits = (ptr[offset + 2].toUint() or ptr[offset + 3].toUint().shl(8)).toShort() + val fill = Float16.toFloat(hbits) + + return lsb.toUint() + msb.toUint().shl(8) to fill + } + + override fun unsafeToBytes(x: Int, y: Int): ByteArray { + val offset = BYTES_PER_BLOCK * (y * width + x) + return byteArrayOf(ptr[offset + 1], ptr[offset + 0], ptr[offset + 3], ptr[offset + 2]) + } + + internal fun unsafeSetTile(x: Int, y: Int, tile0: Int, fill: Float) { + val offset = BYTES_PER_BLOCK * (y * width + x) + val hbits = Float16.fromFloat(fill).toInt().and(0xFFFF) + + val tile = if (fill < FLUID_MIN_MASS) 0 else tile0 + + val lsb = tile.and(0xff).toByte() + val msb = tile.ushr(8).and(0xff).toByte() + + val hlsb = hbits.and(0xff).toByte() + val hmsb = hbits.ushr(8).and(0xff).toByte() + + ptr[offset] = lsb + ptr[offset + 1] = msb + ptr[offset + 2] = hlsb + ptr[offset + 3] = hmsb + + } + + override fun unsafeSetTile(x: Int, y: Int, bytes: ByteArray) { + val offset = BYTES_PER_BLOCK * (y * width + x) + ptr[offset] = bytes[1] + ptr[offset + 1] = bytes[0] + ptr[offset + 2] = bytes[3] + ptr[offset + 3] = bytes[2] + } + + /** + * @param blockOffset Offset in blocks. BlockOffset of 0x100 is equal to ```layerPtr + 0x200``` + */ + /*internal fun unsafeSetTile(blockOffset: Long, tile: Int) { + val offset = BYTES_PER_BLOCK * blockOffset + + val lsb = tile.and(0xff).toByte() + val msb = tile.ushr(8).and(0xff).toByte() + + unsafe.putByte(layerPtr + offset, lsb) + unsafe.putByte(layerPtr + offset + 1, msb) + }*/ + + fun isInBound(x: Int, y: Int) = (x >= 0 && y >= 0 && x < width && y < height) + + override fun dispose() { + ptr.destroy() + App.printdbg(this, "BlockLayerI16F16 with ptr ($ptr) successfully freed") + } + + override fun toString(): String = ptr.toString("BlockLayerI16F16") + + companion object { + @Transient val BYTES_PER_BLOCK = 4L + } +} diff --git a/src/net/torvald/terrarum/gameworld/BlockLayerI16I8.kt b/src/net/torvald/terrarum/gameworld/BlockLayerI16I8.kt index a0775ba23..5362d4173 100644 --- a/src/net/torvald/terrarum/gameworld/BlockLayerI16I8.kt +++ b/src/net/torvald/terrarum/gameworld/BlockLayerI16I8.kt @@ -66,11 +66,7 @@ class BlockLayerI16I8 (val width: Int, val height: Int) : BlockLayer { override fun unsafeToBytes(x: Int, y: Int): ByteArray { val offset = BYTES_PER_BLOCK * (y * width + x) - val lsb = ptr[offset] - val msb = ptr[offset + 1] - val placement = ptr[offset + 2] - - return byteArrayOf(msb, lsb, placement) + return byteArrayOf(ptr[offset + 1], ptr[offset + 0], ptr[offset + 2]) } internal fun unsafeSetTile(x: Int, y: Int, tile: Int, placement: Int) { diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index 26559e2f0..83a96657d 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -84,9 +84,9 @@ open class GameWorld( @Transient lateinit open var layerWall: BlockLayerI16 @Transient lateinit open var layerTerrain: BlockLayerI16 @Transient lateinit open var layerOres: BlockLayerI16I8 // damage to the block follows `terrainDamages` + @Transient lateinit open var layerFluids: BlockLayerI16F16 val wallDamages = HashArray() val terrainDamages = HashArray() - val layerFluids = HashedFluidTypeAndFills() // TODO: chunk them using BlockLayerI32 @@ -142,10 +142,27 @@ open class GameWorld( 30L * WorldTime.MINUTE_SEC ) - - val tileNumberToNameMap = HashArray() + @Transient private val forcedTileNumberToNames = hashSetOf( + Block.AIR, Block.UPDATE + ) + @Transient private val forcedFluidNumberToTiles = hashSetOf( + Fluid.NULL + ) + val tileNumberToNameMap = HashArray().also { + it[0] = Block.AIR + it[2] = Block.UPDATE + } + val fluidNumberToNameMap = HashArray().also { + it[0] = Fluid.NULL + } // does not go to the savefile - @Transient val tileNameToNumberMap = HashMap() + @Transient val tileNameToNumberMap = HashMap().also { + it[Block.AIR] = 0 + it[Block.UPDATE] = 2 + } + @Transient val fluidNameToNumberMap = HashMap().also { + it[Fluid.NULL] = 0 + } val extraFields = HashMap() @@ -202,6 +219,7 @@ open class GameWorld( layerTerrain = BlockLayerI16(width, height) layerWall = BlockLayerI16(width, height) layerOres = BlockLayerI16I8(width, height) + layerFluids = BlockLayerI16F16(width, height) // temperature layer: 2x2 is one cell //layerThermal = MapLayerHalfFloat(width, height, averageTemperature) @@ -223,20 +241,9 @@ open class GameWorld( tileNameToNumberMap[it.key] = it.value.tileNumber } } - - // AN EXCEPTIONAL TERM: tilenum 0 is always redirected to Air tile, even if the tilenum for actual Air tile is not zero - tileNumberToNameMap[0] = Block.AIR - tileNameToNumberMap[Block.AIR] = 0 - - tileNumberToNameMap[2] = Block.UPDATE - tileNameToNumberMap[Block.UPDATE] = 2 } } - @Transient private val forcedTileNumberToNames = hashSetOf( - Block.AIR, Block.UPDATE - ) - fun coordInWorld(x: Int, y: Int) = y in 0 until height // ROUNDWORLD implementation fun coordInWorldStrict(x: Int, y: Int) = x in 0 until width && y in 0 until height // ROUNDWORLD implementation @@ -258,12 +265,13 @@ open class GameWorld( } } - // AN EXCEPTIONAL TERM: tilenum 0 is always redirected to Air tile, even if the tilenum for actual Air tile is not zero + // force this rule to the old saves tileNumberToNameMap[0] = Block.AIR - tileNameToNumberMap[Block.AIR] = 0 - tileNumberToNameMap[2] = Block.UPDATE + tileNameToNumberMap[Block.AIR] = 0 tileNameToNumberMap[Block.UPDATE] = 2 + fluidNumberToNameMap[0] = Fluid.NULL + fluidNameToNumberMap[Fluid.NULL] = 0 } /** @@ -274,9 +282,9 @@ open class GameWorld( // get() = layerWire.data fun getLayer(index: Int) = when(index) { - 0 -> layerTerrain - 1 -> layerWall - 2 -> layerOres + TERRAIN -> layerTerrain + WALL -> layerWall + ORES -> layerOres else -> null//throw IllegalArgumentException("Unknown layer index: $index") } @@ -361,7 +369,7 @@ open class GameWorld( terrainDamages.remove(blockAddr) if (BlockCodex[itemID].isSolid) { - layerFluids.remove(blockAddr) + layerFluids.unsafeSetTile(x, y, fluidNameToNumberMap[Fluid.NULL]!!, 0f) } // fluid tiles-item should be modified so that they will also place fluid onto their respective map @@ -546,17 +554,14 @@ open class GameWorld( return getTileFromWall(x, y) } else - throw IllegalArgumentException("illegal mode input: " + mode.toString()) + throw IllegalArgumentException("illegal mode input: $mode") } - fun getTileFromOre(rawX: Int, rawY: Int): OrePlacement? { + fun getTileFromOre(rawX: Int, rawY: Int): OrePlacement { val (x, y) = coerceXY(rawX, rawY) val (tileNum, placement) = layerOres.unsafeGetTile(x, y) val tileName = tileNumberToNameMap[tileNum.toLong()] - if (tileName == Block.AIR) - return null - else - return OrePlacement(tileName ?: Block.UPDATE, placement) + return OrePlacement(tileName ?: Block.UPDATE, placement) } fun setTileOre(rawX: Int, rawY: Int, ore: ItemID, placement: Int) { @@ -684,12 +689,14 @@ open class GameWorld( val addr = LandUtil.getBlockAddr(this, x, y) + val fluidNumber = fluidNameToNumberMap[fluidType]!! + if (fill > WorldSimulator.FLUID_MIN_MASS) { //setTileTerrain(x, y, fluidTypeToBlock(fluidType)) - layerFluids[addr] = Fill(fluidType, fill) + layerFluids.unsafeSetTile(x, y, fluidNumber, fill) } else { - layerFluids.remove(addr) + layerFluids.unsafeSetTile(x, y, fluidNumber, 0f) } @@ -701,10 +708,11 @@ open class GameWorld( } fun getFluid(x: Int, y: Int): FluidInfo { - val addr = LandUtil.getBlockAddr(this, x, y) - val type = layerFluids[addr]?.item - val fill = layerFluids[addr]?.amount - return if (type == null) FluidInfo(Fluid.NULL, 0f) else FluidInfo(type, fill!!) + val (x, y) = coerceXY(x, y) + val (type, fill) = layerFluids.unsafeGetTile(x, y) + val fluidID = fluidNumberToNameMap[type.toLong()] ?: throw NullPointerException("No such fluid: $type") + + return FluidInfo(fluidID, fill) } /*private fun fluidTypeToBlock(type: FluidType) = when (type.abs()) { @@ -764,9 +772,9 @@ open class GameWorld( override fun equals(other: Any?) = layerTerrain.ptr == (other as GameWorld).layerTerrain.ptr companion object { - @Transient const val WALL = 0 - @Transient const val TERRAIN = 1 - @Transient const val WIRE = 2 + @Transient const val WALL = 1 + @Transient const val TERRAIN = 0 + @Transient const val ORES = 2 @Transient val TILES_SUPPORTED = ReferencingRanges.TILES.last + 1 //@Transient val SIZEOF: Byte = 2 diff --git a/src/net/torvald/terrarum/gameworld/WorldSimulator.kt b/src/net/torvald/terrarum/gameworld/WorldSimulator.kt index 9544db779..27145ddb6 100644 --- a/src/net/torvald/terrarum/gameworld/WorldSimulator.kt +++ b/src/net/torvald/terrarum/gameworld/WorldSimulator.kt @@ -42,7 +42,7 @@ object WorldSimulator { const val FLUID_MAX_MASS = 1f // The normal, un-pressurized mass of a full water cell const val FLUID_MAX_COMP = 0.02f // How much excess water a cell can store, compared to the cell above it. A tile of fluid can contain more than MaxMass water. - const val FLUID_MIN_MASS = 0.0001f //Ignore cells that are almost dry + const val FLUID_MIN_MASS = 1f / 1024f //Ignore cells that are almost dry (smaller than epsilon of float16) const val WIRE_MIN_FLOW = 0.0001f const val minFlow = 0.01f const val maxSpeed = 1f // max units of water moved out of one block to another, per timestamp diff --git a/src/net/torvald/terrarum/modulebasegame/serialise/WriteSavegame.kt b/src/net/torvald/terrarum/modulebasegame/serialise/WriteSavegame.kt index 836d09923..d67cbba0a 100644 --- a/src/net/torvald/terrarum/modulebasegame/serialise/WriteSavegame.kt +++ b/src/net/torvald/terrarum/modulebasegame/serialise/WriteSavegame.kt @@ -5,6 +5,7 @@ import net.torvald.terrarum.* import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.console.Echo import net.torvald.terrarum.gameworld.BlockLayerI16 +import net.torvald.terrarum.gameworld.BlockLayerI16F16 import net.torvald.terrarum.gameworld.BlockLayerI16I8 import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.langpack.Lang @@ -147,6 +148,7 @@ object LoadSavegame { world.layerTerrain = BlockLayerI16(world.width, world.height) world.layerWall = BlockLayerI16(world.width, world.height) world.layerOres = BlockLayerI16I8(world.width, world.height) + world.layerFluids = BlockLayerI16F16(world.width, world.height) newIngame.world = world // must be set before the loadscreen, otherwise the loadscreen will try to read from the NullWorld which is already destroyed newIngame.worldDisk = VDUtil.readDiskArchive(worldDisk.diskFile, Level.INFO) diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt index 43363e438..959d0964d 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt @@ -84,6 +84,12 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par world.setTileTerrain(x, y, terrBlock, true) world.setTileWall(x, y, wallBlock, true) + + + // TODO TEST CODE + if (terrBlock == Block.DIRT) { + world.setTileOre(x, y, "ores@basegame:1", 0) + } } // dither shits @@ -111,12 +117,6 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par world.setTileTerrain(x, y, newTile, true) world.setTileWall(x, y, newTile, true) - - - // TODO TEST CODE - if (newTile == Block.DIRT) { - world.setTileOre(x, y, "ores@basegame:1", 0) - } } /* diff --git a/src/net/torvald/terrarum/serialise/ReadSimpleWorld.kt b/src/net/torvald/terrarum/serialise/ReadSimpleWorld.kt index 2a6ee3e86..0536549aa 100644 --- a/src/net/torvald/terrarum/serialise/ReadSimpleWorld.kt +++ b/src/net/torvald/terrarum/serialise/ReadSimpleWorld.kt @@ -4,6 +4,7 @@ import net.torvald.terrarum.App import net.torvald.terrarum.IngameInstance import net.torvald.terrarum.ItemCodex import net.torvald.terrarum.gameactors.Actor +import net.torvald.terrarum.gameworld.BlockLayerI16F16 import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.SimpleGameWorld import java.io.File @@ -23,6 +24,7 @@ object ReadSimpleWorld { world.tileNumberToNameMap.forEach { l, s -> world.tileNameToNumberMap[s] = l.toInt() } + world.layerFluids = BlockLayerI16F16(world.width, world.height) ItemCodex.loadFromSave(origin, world.dynamicToStaticTable, world.dynamicItemInventory) } diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index 063c66c1e..8d4a6f289 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -247,13 +247,15 @@ class BasicDebugInfoWindow : UICanvas() { val wallNum = it.getTileFromWall(mouseTileX, mouseTileY) val tileNum = it.getTileFromTerrain(mouseTileX, mouseTileY) + val oreNum = it.getTileFromOre(mouseTileX, mouseTileY).item val wires = it.getAllWiresFrom(mouseTileX, mouseTileY) val fluid = it.getFluid(mouseTileX, mouseTileY) val wireCount = wires.first?.size?.toString() ?: "no" App.fontSmallNumbers.draw(batch, "$ccO$TERRAIN$ccG$tileNum", gap + 7f*(tileCursX + 3), line(tileCursY)) App.fontSmallNumbers.draw(batch, "$ccO$WALL$ccG$wallNum", gap + 7f*(tileCursX + 3), line(tileCursY + 1)) - App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG${fluid.type.padEnd(3)}$ccO$BEAKER$ccG${fluid.amount.toIntAndFrac(2)}", gap + 7f*(tileCursX + 3), line(tileCursY + 2)) +// App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG${fluid.type.padEnd(3)}$ccO$BEAKER$ccG${fluid.amount.toIntAndFrac(2)}", gap + 7f*(tileCursX + 3), line(tileCursY + 2)) + App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG$oreNum", gap + 7f*(tileCursX + 3), line(tileCursY + 2)) App.fontSmallNumbers.draw(batch, "$ccO$WIRE$ccG$wireCount ${ccY}X$ccO$mouseTileX ${ccY}Y$ccO$mouseTileY", gap + 7f*(tileCursX + 3), line(tileCursY + 3)) App.fontSmallNumbers.draw(batch, "$ccR$rawR $ccG$rawG $ccB$rawB $ccW$rawA", gap + 7f*(tileCursX + 3), line(tileCursY + 4)) diff --git a/src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt b/src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt index 0633c041b..a6bbbdf57 100644 --- a/src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt +++ b/src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt @@ -62,6 +62,7 @@ internal object BlocksDrawer { val WALL = GameWorld.WALL val TERRAIN = GameWorld.TERRAIN + val ORES = GameWorld.ORES //val WIRE = GameWorld.WIRE val FLUID = -2 val OCCLUSION = 31337 @@ -295,6 +296,7 @@ internal object BlocksDrawer { val thisTile: ItemID = when (mode) { WALL -> world.getTileFromWall(x, y) TERRAIN -> world.getTileFromTerrain(x, y) + ORES -> world.getTileFromOre(x, y).item FLUID -> "basegame:-1" // TODO need new wire storing format //world.getFluid(x, y).type.abs() OCCLUSION -> "placeholder_occlusion" else -> throw IllegalArgumentException() @@ -338,6 +340,9 @@ internal object BlocksDrawer { // special case: fluids else if (mode == FLUID) tileNumberBase + connectLut47[nearbyTilesInfo] + // special case: ores + else if (mode == ORES) + tileNumberBase + world.getTileFromOre(x, y).tilePlacement // rest of the cases: terrain and walls else tileNumberBase + when (renderTag.maskType) { CreateTileAtlas.RenderTag.MASK_NA -> 0