mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-15 21:14:04 +09:00
new item type, "Dynamic Item"; working text terminal
Former-commit-id: 81e6d836f5f1e6490027d38146a32d404cf9ce3e Former-commit-id: af6557340f9cd0ea0b67eb7a8825aeffe75f9d82
This commit is contained in:
9
src/net/torvald/terrarum/gameworld/FluidCodex.kt
Normal file
9
src/net/torvald/terrarum/gameworld/FluidCodex.kt
Normal file
@@ -0,0 +1,9 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-08-06.
|
||||
*/
|
||||
object FluidCodex {
|
||||
const val FLUID_LAVA = 0xFE.toByte()
|
||||
const val FLUID_WATER = 0xFF.toByte()
|
||||
}
|
||||
228
src/net/torvald/terrarum/gameworld/GameWorld.kt
Normal file
228
src/net/torvald/terrarum/gameworld/GameWorld.kt
Normal file
@@ -0,0 +1,228 @@
|
||||
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
import net.torvald.terrarum.gameactors.Player
|
||||
import net.torvald.terrarum.gameactors.roundInt
|
||||
import net.torvald.terrarum.mapdrawer.MapDrawer
|
||||
import org.dyn4j.geometry.Vector2
|
||||
import org.newdawn.slick.SlickException
|
||||
|
||||
class GameWorld
|
||||
/**
|
||||
* @param width
|
||||
* *
|
||||
* @param height
|
||||
* *
|
||||
* @throws SlickException
|
||||
*/
|
||||
@Throws(SlickException::class)
|
||||
constructor(//properties
|
||||
val width: Int, val height: Int) {
|
||||
|
||||
//layers
|
||||
val layerWall: MapLayer
|
||||
/**
|
||||
* Get MapLayer object of terrain
|
||||
|
||||
* @return MapLayer terrain layer
|
||||
*/
|
||||
val layerTerrain: MapLayer
|
||||
val layerWire: MapLayer
|
||||
val wallDamage: PairedMapLayer
|
||||
val terrainDamage: PairedMapLayer
|
||||
val spawnX: Int
|
||||
val spawnY: Int
|
||||
|
||||
//public World physWorld = new World( new Vec2(0, -TerrarumMain.game.gravitationalAccel) );
|
||||
//physics
|
||||
/** Meter per second squared. Currently only the downward gravity is supported. No reverse gravity :p */
|
||||
var gravitation: Vector2 = Vector2(0.0, 9.8)
|
||||
/** RGB in Integer */
|
||||
var globalLight: Int = 0
|
||||
val time: WorldTime
|
||||
|
||||
var generatorSeed: Long = 0
|
||||
|
||||
init {
|
||||
this.spawnX = width / 2
|
||||
this.spawnY = 200
|
||||
|
||||
layerTerrain = MapLayer(width, height)
|
||||
layerWall = MapLayer(width, height)
|
||||
layerWire = MapLayer(width, height)
|
||||
terrainDamage = PairedMapLayer(width, height)
|
||||
wallDamage = PairedMapLayer(width, height)
|
||||
|
||||
time = WorldTime()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 2d array data of terrain
|
||||
|
||||
* @return byte[][] terrain layer
|
||||
*/
|
||||
val terrainArray: Array<ByteArray>
|
||||
get() = layerTerrain.data
|
||||
|
||||
/**
|
||||
* Get 2d array data of wall
|
||||
|
||||
* @return byte[][] wall layer
|
||||
*/
|
||||
val wallArray: Array<ByteArray>
|
||||
get() = layerWall.data
|
||||
|
||||
/**
|
||||
* Get 2d array data of wire
|
||||
|
||||
* @return byte[][] wire layer
|
||||
*/
|
||||
val wireArray: Array<ByteArray>
|
||||
get() = layerWire.data
|
||||
|
||||
/**
|
||||
* Get paired array data of damage codes.
|
||||
* Format: 0baaaabbbb, aaaa for x = 0, 2, 4, ..., bbbb for x = 1, 3, 5, ...
|
||||
* @return byte[][] damage code pair
|
||||
*/
|
||||
val damageDataArray: Array<ByteArray>
|
||||
get() = terrainDamage.dataPair
|
||||
|
||||
fun getTileFromWall(x: Int, y: Int): Int? {
|
||||
val wall: Int? = layerWall.getTile(x, y)
|
||||
val wallDamage: Int? = getWallDamage(x, y)
|
||||
return if (wall == null || wallDamage == null)
|
||||
null
|
||||
else
|
||||
wall * PairedMapLayer.RANGE + wallDamage
|
||||
}
|
||||
|
||||
fun getTileFromTerrain(x: Int, y: Int): Int? {
|
||||
val terrain: Int? = layerTerrain.getTile(x, y)
|
||||
val terrainDamage: Int? = getTerrainDamage(x, y)
|
||||
return if (terrain == null || terrainDamage == null)
|
||||
null
|
||||
else
|
||||
terrain * PairedMapLayer.RANGE + terrainDamage
|
||||
}
|
||||
|
||||
fun getTileFromWire(x: Int, y: Int): Int? {
|
||||
return layerWire.getTile(x, y)
|
||||
}
|
||||
|
||||
fun getWallDamage(x: Int, y: Int): Int? {
|
||||
return wallDamage.getData(x, y)
|
||||
}
|
||||
|
||||
fun getTerrainDamage(x: Int, y: Int): Int? {
|
||||
return terrainDamage.getData(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tile of wall as specified, with damage value of zero.
|
||||
* @param x
|
||||
* *
|
||||
* @param y
|
||||
* *
|
||||
* @param combinedTilenum (tilenum * 16) + damage
|
||||
*/
|
||||
fun setTileWall(x: Int, y: Int, combinedTilenum: Int) {
|
||||
setTileWall(x, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tile of wall as specified, with damage value of zero.
|
||||
* @param x
|
||||
* *
|
||||
* @param y
|
||||
* *
|
||||
* @param combinedTilenum (tilenum * 16) + damage
|
||||
*/
|
||||
fun setTileTerrain(x: Int, y: Int, combinedTilenum: Int) {
|
||||
setTileTerrain(x, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
|
||||
}
|
||||
|
||||
fun setTileWall(x: Int, y: Int, tile: Byte, damage: Int) {
|
||||
layerWall.setTile(x, y, tile)
|
||||
wallDamage.setData(x, y, damage)
|
||||
}
|
||||
|
||||
fun setTileTerrain(x: Int, y: Int, tile: Byte, damage: Int) {
|
||||
layerTerrain.setTile(x, y, tile)
|
||||
terrainDamage.setData(x, y, damage)
|
||||
}
|
||||
|
||||
fun setTileWire(x: Int, y: Int, tile: Byte) {
|
||||
layerWire.data[y][x] = tile
|
||||
}
|
||||
|
||||
fun getTileFrom(mode: Int, x: Int, y: Int): Int? {
|
||||
if (mode == TERRAIN) {
|
||||
return getTileFromTerrain(x, y)
|
||||
}
|
||||
else if (mode == WALL) {
|
||||
return getTileFromWall(x, y)
|
||||
}
|
||||
else if (mode == WIRE) {
|
||||
return getTileFromWire(x, y)
|
||||
}
|
||||
else
|
||||
throw IllegalArgumentException("illegal mode input: " + mode.toString())
|
||||
}
|
||||
|
||||
fun updateWorldTime(delta: Int) {
|
||||
time.update(delta)
|
||||
}
|
||||
|
||||
fun terrainIterator(): Iterator<Int> {
|
||||
return object : Iterator<Int> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return iteratorCount < width * height
|
||||
}
|
||||
|
||||
override fun next(): Int {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 1
|
||||
|
||||
return getTileFromTerrain(x, y)!!
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun wallIterator(): Iterator<Int> {
|
||||
return object : Iterator<Int> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean =
|
||||
iteratorCount < width * height
|
||||
|
||||
override fun next(): Int {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 1
|
||||
|
||||
return getTileFromWall(x, y)!!
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@Transient val WALL = 0
|
||||
@Transient val TERRAIN = 1
|
||||
@Transient val WIRE = 2
|
||||
|
||||
@Transient val TILES_SUPPORTED = MapLayer.RANGE * PairedMapLayer.RANGE
|
||||
@Transient val SIZEOF: Byte = MapLayer.SIZEOF
|
||||
@Transient val LAYERS: Byte = 4 // terrain, wall (terrainDamage + wallDamage), wire
|
||||
}
|
||||
}
|
||||
58
src/net/torvald/terrarum/gameworld/MapLayer.kt
Normal file
58
src/net/torvald/terrarum/gameworld/MapLayer.kt
Normal file
@@ -0,0 +1,58 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-01-17.
|
||||
*/
|
||||
class MapLayer(var width: Int, var height: Int) : Iterable<Byte> {
|
||||
|
||||
internal var data: Array<ByteArray>
|
||||
|
||||
init {
|
||||
data = Array(height) { ByteArray(width) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over elements of type `T`.
|
||||
|
||||
* @return an Iterator.
|
||||
*/
|
||||
override fun iterator(): Iterator<Byte> {
|
||||
return object : Iterator<Byte> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return iteratorCount < width * height
|
||||
}
|
||||
|
||||
override fun next(): Byte {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 1
|
||||
|
||||
return data[y][x]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getTile(x: Int, y: Int): Int? {
|
||||
return if (x !in 0..width - 1 || y !in 0..height - 1)
|
||||
null
|
||||
else
|
||||
data[y][x].toUint()
|
||||
}
|
||||
|
||||
internal fun setTile(x: Int, y: Int, tile: Byte) {
|
||||
data[y][x] = tile
|
||||
}
|
||||
|
||||
fun isInBound(x: Int, y: Int) = (x >= 0 && y >= 0 && x < width && y < height)
|
||||
|
||||
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)
|
||||
35
src/net/torvald/terrarum/gameworld/MapPoint.kt
Normal file
35
src/net/torvald/terrarum/gameworld/MapPoint.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
import net.torvald.point.Point2d
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
|
||||
class MapPoint {
|
||||
var startPoint: Point2d? = null
|
||||
private set
|
||||
var endPoint: Point2d? = null
|
||||
private set
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
constructor(p1: Point2d, p2: Point2d) {
|
||||
setPoint(p1, p2)
|
||||
}
|
||||
|
||||
constructor(x1: Int, y1: Int, x2: Int, y2: Int) {
|
||||
setPoint(x1, y1, x2, y2)
|
||||
}
|
||||
|
||||
fun setPoint(p1: Point2d, p2: Point2d) {
|
||||
startPoint = p1
|
||||
endPoint = p2
|
||||
}
|
||||
|
||||
fun setPoint(x1: Int, y1: Int, x2: Int, y2: Int) {
|
||||
startPoint = Point2d(x1.toDouble(), y1.toDouble())
|
||||
endPoint = Point2d(x2.toDouble(), y2.toDouble())
|
||||
}
|
||||
}
|
||||
86
src/net/torvald/terrarum/gameworld/PairedMapLayer.kt
Normal file
86
src/net/torvald/terrarum/gameworld/PairedMapLayer.kt
Normal file
@@ -0,0 +1,86 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.Spliterator
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-02-15.
|
||||
*/
|
||||
class PairedMapLayer(width: Int, var height: Int) : Iterable<Byte> {
|
||||
|
||||
/**
|
||||
* 0b_xxxx_yyyy, x for lower index, y for higher index
|
||||
|
||||
* e.g.
|
||||
|
||||
* 0110 1101 is interpreted as
|
||||
* 6 for tile 0, 13 for tile 1.
|
||||
*/
|
||||
internal var dataPair: Array<ByteArray>
|
||||
|
||||
var width: Int = 0
|
||||
|
||||
init {
|
||||
this.width = width / 2
|
||||
|
||||
dataPair = Array(height) { ByteArray(width / 2) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over elements of type `T`.
|
||||
* Note: this iterator will return combined damage, that is 0bxxxx_yyyy as whole.
|
||||
|
||||
* @return an Iterator.
|
||||
*/
|
||||
override fun iterator(): Iterator<Byte> {
|
||||
return object : Iterator<Byte> {
|
||||
|
||||
private var iteratorCount = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return iteratorCount < width * height
|
||||
}
|
||||
|
||||
override fun next(): Byte {
|
||||
val y = iteratorCount / width
|
||||
val x = iteratorCount % width
|
||||
// advance counter
|
||||
iteratorCount += 1
|
||||
|
||||
return dataPair[y][x]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getData(x: Int, y: Int): Int? {
|
||||
return if (x !in 0..width * 2 - 1 || y !in 0..height - 1)
|
||||
null
|
||||
else {
|
||||
if (x and 0x1 == 0)
|
||||
// higher four bits for i = 0, 2, 4, ...
|
||||
(java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0xF0) ushr 4
|
||||
else
|
||||
// lower four bits for i = 1, 3, 5, ...
|
||||
java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0x0F
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setData(x: Int, y: Int, data: Int) {
|
||||
if (data < 0 || data >= 16) throw IllegalArgumentException("[PairedMapLayer] $data: invalid data value.")
|
||||
if (x and 0x1 == 0)
|
||||
// higher four bits for i = 0, 2, 4, ...
|
||||
dataPair[y][x / 2] =
|
||||
(java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0x0F
|
||||
or (data and 0xF shl 4)).toByte()
|
||||
else
|
||||
// lower four bits for i = 1, 3, 5, ...
|
||||
dataPair[y][x / 2] = (java.lang.Byte.toUnsignedInt(dataPair[y][x / 2]) and 0xF0
|
||||
or (data and 0xF)).toByte()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Transient const val RANGE = 16
|
||||
@Transient const val SIZEOF: Byte = 1 // 1 for 8-bit, 2 for 16-bit, ...
|
||||
}
|
||||
}
|
||||
286
src/net/torvald/terrarum/gameworld/WorldSimulator.kt
Normal file
286
src/net/torvald/terrarum/gameworld/WorldSimulator.kt
Normal file
@@ -0,0 +1,286 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
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.gameworld.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
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-08-03.
|
||||
*/
|
||||
object WorldSimulator {
|
||||
/**
|
||||
* In tiles;
|
||||
* square width/height = field * 2
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* displace fluids. Note that the code assumes the gravity pulls things downward ONLY,
|
||||
* which means you'll need to modify the code A LOT if you're going to implement zero- or
|
||||
* reverse-gravity.
|
||||
*/
|
||||
fun moveFluids(world: GameWorld, p: Player, delta: Int) {
|
||||
////////////////////
|
||||
// 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) ?: 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.isSolid()) {
|
||||
val drainage = drain(world, x, y, DISPLACE_CAP)
|
||||
pour(world, x, y + 1, drainage)
|
||||
}
|
||||
// left and right both open
|
||||
else if (!tileLeft.isSolid() && !tileRight.isSolid()) {
|
||||
// half-breaker
|
||||
val moreToTheRight = HQRNG().nextBoolean()
|
||||
val displacement = drain(world, x, y, DISPLACE_CAP)
|
||||
|
||||
if (displacement.isEven()) {
|
||||
pour(world, x - 1, y, displacement shr 1)
|
||||
pour(world, x + 1, y, displacement shr 1)
|
||||
}
|
||||
else {
|
||||
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
|
||||
else if (!tileLeft.isSolid()) {
|
||||
val displacement = drain(world, x, y, DISPLACE_CAP)
|
||||
pour(world, 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(world, x, y - 1, DISPLACE_CAP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// replace fluids in the map according to fluidMap //
|
||||
/////////////////////////////////////////////////////
|
||||
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) {
|
||||
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() = this.fluidLevel() == FLUID_MAX || TilePropCodex.getProp(this).isSolid
|
||||
//fun Int.viscosity() = TilePropCodex.getProp(this).
|
||||
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, fluidType: Byte, amount: Int) {
|
||||
if (world.layerTerrain.isInBound(x, y)) {
|
||||
if (amount > 0 && !world.getTileFromTerrain(x, y)!!.isSolid()) {
|
||||
world.setTileTerrain(x, y, fluidType, amount - 1)
|
||||
}
|
||||
else if (amount == 0 && world.getTileFromTerrain(x, y)!!.isFluid()) {
|
||||
world.setTileTerrain(x, y, TileNameCode.AIR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
}
|
||||
}
|
||||
}
|
||||
165
src/net/torvald/terrarum/gameworld/WorldTime.kt
Normal file
165
src/net/torvald/terrarum/gameworld/WorldTime.kt
Normal file
@@ -0,0 +1,165 @@
|
||||
package net.torvald.terrarum.gameworld
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 16-01-24.
|
||||
*/
|
||||
class WorldTime {
|
||||
internal var seconds: Int
|
||||
internal var minutes: Int
|
||||
internal var hours: Int
|
||||
|
||||
internal var yearlyDays: Int //NOT a calendar day
|
||||
|
||||
internal var days: Int
|
||||
internal var months: Int
|
||||
internal var years: Int
|
||||
|
||||
internal var dayOfWeek: Int //0: Mondag-The first day of weekday
|
||||
|
||||
internal var timeDelta = 1
|
||||
|
||||
@Transient private var realMillisec: Int
|
||||
|
||||
val DAY_NAMES = arrayOf(//daynames are taken from Nynorsk (å -> o)
|
||||
"Mondag", "Tysdag", "Midtedag" //From Islenska Miðvikudagur
|
||||
, "Torsdag", "Fredag", "Laurdag", "Sundag", "Verdag" //From Norsk word 'verd'
|
||||
)
|
||||
val DAY_NAMES_SHORT = arrayOf("Mon", "Tys", "Mid", "Tor", "Fre", "Lau", "Sun", "Ver")
|
||||
|
||||
|
||||
@Transient val REAL_SEC_IN_MILLI = 1000
|
||||
|
||||
init {
|
||||
seconds = 0
|
||||
minutes = 30
|
||||
hours = 8
|
||||
yearlyDays = 1
|
||||
days = 12
|
||||
months = 3
|
||||
years = 125
|
||||
dayOfWeek = 0
|
||||
realMillisec = 0
|
||||
}
|
||||
|
||||
fun update(delta: Int) {
|
||||
//time
|
||||
realMillisec += delta * timeDelta
|
||||
seconds = Math.round(GAME_MIN_TO_REAL_SEC.toFloat() / REAL_SEC_IN_MILLI.toFloat() * realMillisec.toFloat())
|
||||
|
||||
if (realMillisec >= REAL_SEC_IN_MILLI)
|
||||
realMillisec -= REAL_SEC_IN_MILLI
|
||||
|
||||
kickVariables()
|
||||
}
|
||||
|
||||
/**
|
||||
* How much time has passed today, in seconds.
|
||||
* 0 == 6 AM
|
||||
* @return
|
||||
*/
|
||||
fun elapsedSeconds(): Int {
|
||||
return (HOUR_SEC * hours + MINUTE_SEC * minutes + seconds) % DAY_LENGTH
|
||||
}
|
||||
|
||||
val isLeapYear: Boolean
|
||||
get() = years % 4 == 0 && years % 100 != 0 || years % 400 == 0
|
||||
|
||||
fun setTime(t: Int) {
|
||||
days += t / DAY_LENGTH
|
||||
hours = t / HOUR_SEC
|
||||
minutes = (t - HOUR_SEC * hours) / MINUTE_SEC
|
||||
seconds = t - minutes * MINUTE_SEC
|
||||
}
|
||||
|
||||
fun addTime(t: Int) {
|
||||
setTime(elapsedSeconds() + t)
|
||||
}
|
||||
|
||||
fun setTimeDelta(d: Int) {
|
||||
timeDelta = if (d < 0) 0 else d
|
||||
}
|
||||
|
||||
val dayName: String
|
||||
get() = DAY_NAMES[dayOfWeek]
|
||||
|
||||
private fun kickVariables() {
|
||||
if (seconds >= MINUTE_SEC) {
|
||||
seconds = 0
|
||||
minutes += 1
|
||||
}
|
||||
|
||||
if (minutes >= HOUR_MIN) {
|
||||
minutes = 0
|
||||
hours += 1
|
||||
}
|
||||
|
||||
if (hours >= DAY_LENGTH / HOUR_SEC) {
|
||||
hours = 0
|
||||
days += 1
|
||||
yearlyDays += 1
|
||||
dayOfWeek += 1
|
||||
}
|
||||
|
||||
//calendar (the world calendar)
|
||||
if (dayOfWeek == 7) {
|
||||
dayOfWeek = 0
|
||||
}
|
||||
if ((months == 12 || months == 7 && isLeapYear) && days == 31) {
|
||||
dayOfWeek = 7
|
||||
}
|
||||
|
||||
if ((months == 12 || months == 7 && isLeapYear) && days == 32) {
|
||||
days = 1
|
||||
months = 1
|
||||
years++
|
||||
}
|
||||
else if ((months == 1 || months == 4 || months == 7 || months == 10) && days > 31) {
|
||||
days = 1
|
||||
months++
|
||||
}
|
||||
else if (days > 30) {
|
||||
days = 1
|
||||
months++
|
||||
}
|
||||
|
||||
if (months > 12) {
|
||||
months = 1
|
||||
years++
|
||||
}
|
||||
}
|
||||
|
||||
fun getFormattedTime(): String {
|
||||
fun formatMin(min: Int): String {
|
||||
return if (min < 10) "0${min.toString()}" else min.toString()
|
||||
}
|
||||
|
||||
return "${hours}h${formatMin(minutes)}"
|
||||
}
|
||||
|
||||
fun getDayNameFull(): String = DAY_NAMES[dayOfWeek]
|
||||
fun getDayNameShort(): String = DAY_NAMES_SHORT[dayOfWeek]
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* 22h
|
||||
*/
|
||||
val DAY_LENGTH = 79200 //must be the multiple of 3600
|
||||
|
||||
val HOUR_SEC: Int = 3600
|
||||
val MINUTE_SEC: Int = 60
|
||||
val HOUR_MIN: Int = 60
|
||||
val GAME_MIN_TO_REAL_SEC: Float = 60f
|
||||
|
||||
fun parseTime(s: String): Int =
|
||||
if (s.length >= 4) {
|
||||
s.toLowerCase().substringBefore('h').toInt() * WorldTime.HOUR_SEC +
|
||||
s.toLowerCase().substringAfter('h').toInt() * WorldTime.MINUTE_SEC
|
||||
}
|
||||
else if (s.endsWith("h", true)) {
|
||||
s.toLowerCase().substring(0, s.length - 1).toInt() * WorldTime.HOUR_SEC
|
||||
}
|
||||
else {
|
||||
s.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user