mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-14 20:44:05 +09:00
more pictogrammes
Former-commit-id: 670df70bbed466e56034dbdd33d7a1da31cd88d7 Former-commit-id: 2b106753155be6080d4651acca1981f10d614421
This commit is contained in:
9
src/net/torvald/terrarum/gamemap/FluidCodex.kt
Normal file
9
src/net/torvald/terrarum/gamemap/FluidCodex.kt
Normal file
@@ -0,0 +1,9 @@
|
||||
package net.torvald.terrarum.gamemap
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-08-06.
|
||||
*/
|
||||
object FluidCodex {
|
||||
const val FLUID_LAVA = 0xFE.toByte()
|
||||
const val FLUID_WATER = 0xFF.toByte()
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class MapLayer(var width: Int, var height: Int) : Iterable<Byte> {
|
||||
return if (x !in 0..width - 1 || y !in 0..height - 1)
|
||||
null
|
||||
else
|
||||
uint8ToInt32(data[y][x])
|
||||
data[y][x].toUint()
|
||||
}
|
||||
|
||||
internal fun setTile(x: Int, y: Int, tile: Byte) {
|
||||
@@ -49,11 +49,10 @@ class MapLayer(var width: Int, var height: Int) : Iterable<Byte> {
|
||||
|
||||
fun isInBound(x: Int, y: Int) = (x >= 0 && y >= 0 && x < width && y < height)
|
||||
|
||||
private fun uint8ToInt32(x: Byte): Int = java.lang.Byte.toUnsignedInt(x)
|
||||
|
||||
companion object {
|
||||
@Transient const val RANGE = 256
|
||||
@Transient const val SIZEOF: Byte = 1 // 1 for 8-bit, 2 for 16-bit, ...
|
||||
}
|
||||
}
|
||||
|
||||
fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package net.torvald.terrarum.gamemap
|
||||
|
||||
import net.torvald.random.HQRNG
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.gameactors.Player
|
||||
import net.torvald.terrarum.gameactors.roundInt
|
||||
import net.torvald.terrarum.gamemap.WorldSimulator.isSolid
|
||||
import net.torvald.terrarum.mapdrawer.MapCamera
|
||||
import net.torvald.terrarum.mapdrawer.MapDrawer
|
||||
import net.torvald.terrarum.tileproperties.TileNameCode
|
||||
import net.torvald.terrarum.tileproperties.TilePropCodex
|
||||
import org.newdawn.slick.Color
|
||||
import org.newdawn.slick.Graphics
|
||||
|
||||
/**
|
||||
@@ -16,16 +20,31 @@ object WorldSimulator {
|
||||
* In tiles;
|
||||
* square width/height = field * 2
|
||||
*/
|
||||
const val FLUID_UPDATING_SQUARE_RADIUS = 128
|
||||
const val FLUID_UPDATING_SQUARE_RADIUS = 64 // larger value will have dramatic impact on performance
|
||||
const private val DOUBLE_RADIUS = FLUID_UPDATING_SQUARE_RADIUS * 2
|
||||
|
||||
private val fluidMap = Array<IntArray>(DOUBLE_RADIUS, { IntArray(DOUBLE_RADIUS) })
|
||||
private val fluidTypeMap = Array<ByteArray>(DOUBLE_RADIUS, { ByteArray(DOUBLE_RADIUS) })
|
||||
|
||||
const val DISPLACE_CAP = 4
|
||||
const val FLUID_MAX = 16
|
||||
|
||||
var updateXFrom = 0
|
||||
var updateXTo = 0
|
||||
var updateYFrom = 0
|
||||
var updateYTo = 0
|
||||
|
||||
val colourNone = Color(0x808080)
|
||||
val colourWater = Color(0x66BBFF)
|
||||
|
||||
operator fun invoke(world: GameWorld, p: Player, delta: Int) {
|
||||
updateXFrom = p.hitbox.centeredX.div(MapDrawer.TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundInt()
|
||||
updateYFrom = p.hitbox.centeredY.div(MapDrawer.TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundInt()
|
||||
updateXTo = updateXFrom + DOUBLE_RADIUS
|
||||
updateYTo = updateYFrom + DOUBLE_RADIUS
|
||||
|
||||
moveFluids(world, p, delta)
|
||||
displaceFallables(world, p, delta)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,148 +53,169 @@ object WorldSimulator {
|
||||
* reverse-gravity.
|
||||
*/
|
||||
fun moveFluids(world: GameWorld, p: Player, delta: Int) {
|
||||
val updateXFrom = p.hitbox.centeredX.div(MapDrawer.TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundInt()
|
||||
val updateYFrom = p.hitbox.centeredY.div(MapDrawer.TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundInt()
|
||||
val updateXTo = updateXFrom + 1 * FLUID_UPDATING_SQUARE_RADIUS
|
||||
val updateYTo = updateYFrom + 1 * FLUID_UPDATING_SQUARE_RADIUS
|
||||
|
||||
/**
|
||||
* @return amount of fluid actually drained.
|
||||
* (intended drainage - this) will give you how much fluid is not yet drained.
|
||||
*/
|
||||
fun drain(x: Int, y: Int, amount: Int): Int {
|
||||
val displacement = Math.min(fluidMap[y - updateYFrom][x - updateXFrom], amount)
|
||||
|
||||
fluidMap[y - updateYFrom][x - updateXFrom] -= displacement
|
||||
|
||||
return displacement
|
||||
}
|
||||
|
||||
fun pour(x: Int, y: Int, amount: Int) {
|
||||
fun pourInternal(xpos: Int, ypos: Int, volume: Int): Int {
|
||||
var spil = 0
|
||||
|
||||
val addrX = xpos - updateXFrom
|
||||
val addrY = ypos - updateYFrom
|
||||
|
||||
if (addrX >= 0 && addrY >= 0 && addrX < DOUBLE_RADIUS && addrY < DOUBLE_RADIUS) {
|
||||
fluidMap[addrY][addrX] += volume
|
||||
if (fluidMap[addrY][addrX] > FLUID_MAX) {
|
||||
spil = fluidMap[addrY][addrX] - FLUID_MAX
|
||||
fluidMap[addrY][addrX] = FLUID_MAX
|
||||
}
|
||||
}
|
||||
|
||||
return spil
|
||||
}
|
||||
|
||||
// pour the fluid
|
||||
var spillage = pourInternal(x, y, amount)
|
||||
|
||||
if (spillage == 0) return
|
||||
|
||||
// deal with the spillage
|
||||
|
||||
val tileUp = world.getTileFromTerrain(x - updateXFrom, y - updateYFrom - 1)
|
||||
val tileDown = world.getTileFromTerrain(x - updateXFrom, y - updateYFrom + 1)
|
||||
|
||||
// try to fill downward
|
||||
if (tileDown != null && !tileDown.isSolid()) {
|
||||
spillage = pourInternal(x, y + 1, spillage)
|
||||
}
|
||||
// else, try to fill upward. if there is no space, just discard
|
||||
if (tileUp != null && !tileUp.isSolid()) {
|
||||
pourInternal(x, y - 1, spillage)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// build fluidmap //
|
||||
////////////////////
|
||||
purgeFluidMap()
|
||||
worldToFluidMap(world)
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// displace fluids. Record displacements into the fluidMap //
|
||||
/////////////////////////////////////////////////////////////
|
||||
for (y in updateYFrom..updateYTo) {
|
||||
for (x in updateXFrom..updateXTo) {
|
||||
val tile = world.getTileFromTerrain(x, y)
|
||||
val tileBottom = world.getTileFromTerrain(x, y + 1)
|
||||
val tileLeft = world.getTileFromTerrain(x - 1, y)
|
||||
val tileRight = world.getTileFromTerrain(x + 1, y)
|
||||
if (tile != null && tile.isFluid()) {
|
||||
val tile = world.getTileFromTerrain(x, y) ?: TileNameCode.STONE
|
||||
val tileBottom = world.getTileFromTerrain(x, y + 1) ?: TileNameCode.STONE
|
||||
val tileLeft = world.getTileFromTerrain(x - 1, y) ?: TileNameCode.STONE
|
||||
val tileRight = world.getTileFromTerrain(x + 1, y) ?: TileNameCode.STONE
|
||||
if (tile.isFluid()) {
|
||||
|
||||
// move down if not obstructed
|
||||
if (tileBottom != null && !tileBottom.isSolid()) {
|
||||
val drainage = drain(x, y, DISPLACE_CAP)
|
||||
pour(x, y + 1, drainage)
|
||||
if (!tileBottom.isSolid()) {
|
||||
val drainage = drain(world, x, y, DISPLACE_CAP)
|
||||
pour(world, x, y + 1, drainage)
|
||||
}
|
||||
// left and right both open (null is considered as open)
|
||||
else if ((tileLeft != null && tileRight != null && !tileLeft.isSolid() && !tileRight.isSolid()) ||
|
||||
tileLeft == null && tileRight == null) {
|
||||
// left and right both open
|
||||
else if (!tileLeft.isSolid() && !tileRight.isSolid()) {
|
||||
// half-breaker
|
||||
val moreToTheRight = HQRNG().nextBoolean()
|
||||
val displacement = drain(x, y, DISPLACE_CAP)
|
||||
val displacement = drain(world, x, y, DISPLACE_CAP)
|
||||
|
||||
if (displacement.isEven()) {
|
||||
pour(x - 1, y, displacement shr 1)
|
||||
pour(x + 1, y, displacement shr 1)
|
||||
pour(world, x - 1, y, displacement shr 1)
|
||||
pour(world, x + 1, y, displacement shr 1)
|
||||
}
|
||||
else {
|
||||
pour(x - 1, y, (displacement shr 1) + if (moreToTheRight) 0 else 1)
|
||||
pour(x + 1, y, (displacement shr 1) + if (moreToTheRight) 1 else 0)
|
||||
pour(world, x - 1, y, (displacement shr 1) + if (moreToTheRight) 0 else 1)
|
||||
pour(world, x + 1, y, (displacement shr 1) + if (moreToTheRight) 1 else 0)
|
||||
}
|
||||
}
|
||||
// left open (null is considered as open)
|
||||
else if ((tileLeft != null && !tileLeft.isSolid()) || tileLeft == null) {
|
||||
val displacement = drain(x, y, DISPLACE_CAP)
|
||||
pour(x - 1, y, displacement)
|
||||
// left open
|
||||
else if (!tileLeft.isSolid()) {
|
||||
val displacement = drain(world, x, y, DISPLACE_CAP)
|
||||
pour(world, x - 1, y, displacement)
|
||||
}
|
||||
// right open (null is considered as open)
|
||||
else if ((tileRight != null && !tileRight.isSolid()) || tileRight == null) {
|
||||
val displacement = drain(x, y, DISPLACE_CAP)
|
||||
pour(x + 1, y, displacement)
|
||||
// right open
|
||||
else if (!tileRight.isSolid()) {
|
||||
val displacement = drain(world, x, y, DISPLACE_CAP)
|
||||
pour(world, x + 1, y, displacement)
|
||||
}
|
||||
// nowhere open; do default (fill top)
|
||||
else {
|
||||
pour(x, y - 1, DISPLACE_CAP)
|
||||
pour(world, x, y - 1, DISPLACE_CAP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// replace fluids in the map according to fluidMap //
|
||||
/////////////////////////////////////////////////////
|
||||
for (y in 0..fluidMap.size - 1) {
|
||||
for (x in 0..fluidMap[0].size - 1) {
|
||||
placeFluid(world, updateXFrom + x, updateYFrom + y, WATER, fluidMap[y][x].minus(1))
|
||||
// FIXME test code: deals with water only!
|
||||
}
|
||||
}
|
||||
fluidMapToWorld(world)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* displace fallable tiles. It is scanned bottom-left first. To achieve the sens ofreal
|
||||
* falling, each tiles are displaced by ONLY ONE TILE below.
|
||||
*/
|
||||
fun displaceFallables(world: GameWorld, p: Player, delta: Int) {
|
||||
for (y in updateYFrom..updateYTo) {
|
||||
for (x in updateXFrom..updateXTo) {
|
||||
val tile = world.getTileFromTerrain(x, y) ?: TileNameCode.STONE
|
||||
val tileBelow = world.getTileFromTerrain(x, y + 1) ?: TileNameCode.STONE
|
||||
|
||||
if (tile.isFallable()) {
|
||||
// displace fluid. This statement must precede isSolid()
|
||||
if (tileBelow.isFluid()) {
|
||||
// remove tileThis to create air pocket
|
||||
world.setTileTerrain(x, y, TileNameCode.AIR)
|
||||
|
||||
pour(world, x, y, drain(world, x, y, tileBelow.fluidLevel()))
|
||||
// place our tile
|
||||
world.setTileTerrain(x, y + 1, tile)
|
||||
}
|
||||
else if (!tileBelow.isSolid()) {
|
||||
world.setTileTerrain(x, y, TileNameCode.AIR)
|
||||
world.setTileTerrain(x, y + 1, tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun drawFluidMapDebug(p: Player, g: Graphics) {
|
||||
g.font = Terrarum.fontSmallNumbers
|
||||
g.color = colourWater
|
||||
|
||||
for (y in 0..fluidMap.size - 1) {
|
||||
for (x in 0..fluidMap[0].size - 1) {
|
||||
val data = fluidMap[y][x]
|
||||
if (MapCamera.tileInCamera(x + updateXFrom, y + updateYFrom)) {
|
||||
if (data == 0)
|
||||
g.color = colourNone
|
||||
else
|
||||
g.color = colourWater
|
||||
|
||||
g.drawString(data.toString(),
|
||||
updateXFrom.plus(x).times(MapDrawer.TILE_SIZE).toFloat()
|
||||
+ if (data < 10) 4f else 0f,
|
||||
updateYFrom.plus(y).times(MapDrawer.TILE_SIZE) + 4f
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//if (data > 0) println(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun purgeFluidMap() {
|
||||
for (y in 1..DOUBLE_RADIUS)
|
||||
for (x in 1..DOUBLE_RADIUS)
|
||||
for (y in 1..DOUBLE_RADIUS) {
|
||||
for (x in 1..DOUBLE_RADIUS) {
|
||||
fluidMap[y - 1][x - 1] = 0
|
||||
fluidTypeMap[y - 1][x - 1] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun worldToFluidMap(world: GameWorld) {
|
||||
for (y in updateYFrom..updateYTo) {
|
||||
for (x in updateXFrom..updateXTo) {
|
||||
val tile = world.getTileFromTerrain(x, y) ?: TileNameCode.STONE
|
||||
if (tile.isFluid()) {
|
||||
fluidMap[y - updateYFrom][x - updateXFrom] = tile.fluidLevel()
|
||||
fluidTypeMap[y - updateYFrom][x - updateXFrom] = tile.fluidType().toByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fluidMapToWorld(world: GameWorld) {
|
||||
for (y in 0..fluidMap.size - 1) {
|
||||
for (x in 0..fluidMap[0].size - 1) {
|
||||
placeFluid(world, updateXFrom + x, updateYFrom + y
|
||||
, FluidCodex.FLUID_WATER, fluidMap[y][x] - 1
|
||||
)
|
||||
// FIXME test code: deals with water only!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.isFluid() = TilePropCodex.getProp(this).isFluid
|
||||
fun Int.isSolid() = TilePropCodex.getProp(this).isSolid
|
||||
fun Int.isSolid() = this.fluidLevel() == FLUID_MAX || TilePropCodex.getProp(this).isSolid
|
||||
//fun Int.viscosity() = TilePropCodex.getProp(this).
|
||||
fun Int.fluidLevel() = this % FLUID_MAX
|
||||
fun Int.fluidLevel() = if (!this.isFluid()) 0 else (this % FLUID_MAX) + 1
|
||||
fun Int.fluidType() = this / FLUID_MAX
|
||||
fun Int.isEven() = (this and 0x01) == 0
|
||||
fun Int.isFallable() = TilePropCodex.getProp(this).isFallable
|
||||
|
||||
private fun placeFluid(world: GameWorld, x: Int, y: Int, tileFluid: Int, amount: Int) {
|
||||
private fun placeFluid(world: GameWorld, x: Int, y: Int, fluidType: Byte, amount: Int) {
|
||||
if (world.layerTerrain.isInBound(x, y)) {
|
||||
if (amount > 0 && !world.getTileFromTerrain(x, y)!!.isSolid()) {
|
||||
world.setTileTerrain(x, y, amount.minus(1).plus(tileFluid))
|
||||
world.setTileTerrain(x, y, fluidType, amount - 1)
|
||||
}
|
||||
else if (amount == 0 && world.getTileFromTerrain(x, y)!!.isFluid()) {
|
||||
world.setTileTerrain(x, y, TileNameCode.AIR)
|
||||
@@ -183,6 +223,64 @@ object WorldSimulator {
|
||||
}
|
||||
}
|
||||
|
||||
val LAVA = TileNameCode.LAVA_1
|
||||
val WATER = TileNameCode.WATER_1
|
||||
/**
|
||||
* @param x and y: world tile coord
|
||||
* @return amount of fluid actually drained.
|
||||
* (intended drainage - this) will give you how much fluid is not yet drained.
|
||||
* TODO add fluidType support
|
||||
*/
|
||||
private fun drain(world: GameWorld, x: Int, y: Int, amount: Int): Int {
|
||||
val displacement = Math.min(fluidMap[y - updateYFrom][x - updateXFrom], amount)
|
||||
|
||||
fluidMap[y - updateYFrom][x - updateXFrom] -= displacement
|
||||
|
||||
return displacement
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x and y: world tile coord
|
||||
* TODO add fluidType support
|
||||
*/
|
||||
private fun pour(world: GameWorld, x: Int, y: Int, amount: Int) {
|
||||
/**
|
||||
* @param x and y: world tile coord
|
||||
* @return spillage
|
||||
* TODO add fluidType support
|
||||
*/
|
||||
fun pourInternal(worldXpos: Int, worldYPos: Int, volume: Int): Int {
|
||||
var spil = 0
|
||||
|
||||
val addrX = worldXpos - updateXFrom
|
||||
val addrY = worldYPos - updateYFrom
|
||||
|
||||
if (addrX >= 0 && addrY >= 0 && addrX < DOUBLE_RADIUS && addrY < DOUBLE_RADIUS) {
|
||||
fluidMap[addrY][addrX] += volume
|
||||
if (fluidMap[addrY][addrX] > FLUID_MAX) {
|
||||
spil = fluidMap[addrY][addrX] - FLUID_MAX
|
||||
fluidMap[addrY][addrX] = FLUID_MAX
|
||||
}
|
||||
}
|
||||
|
||||
return spil
|
||||
}
|
||||
|
||||
// pour the fluid
|
||||
var spillage = pourInternal(x, y, amount)
|
||||
|
||||
if (spillage <= 0) return
|
||||
|
||||
// deal with the spillage
|
||||
|
||||
val tileUp = world.getTileFromTerrain(x - updateXFrom, y - updateYFrom - 1)
|
||||
val tileDown = world.getTileFromTerrain(x - updateXFrom, y - updateYFrom + 1)
|
||||
|
||||
// try to fill downward
|
||||
if (tileDown != null && !tileDown.isSolid()) {
|
||||
spillage = pourInternal(x, y + 1, spillage)
|
||||
}
|
||||
// else, try to fill upward. if there is no space, just discard
|
||||
if (spillage >= 0 && tileUp != null && !tileUp.isSolid()) {
|
||||
pourInternal(x, y - 1, spillage)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user