mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
implementing water sim but not actually working
This commit is contained in:
@@ -33,6 +33,7 @@ import java.io.File
|
||||
import java.io.IOException
|
||||
import net.torvald.getcpuname.GetCpuName
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
|
||||
@@ -908,7 +909,7 @@ inline fun Double.abs() = Math.abs(this)
|
||||
inline fun Double.sqr() = this * this
|
||||
inline fun Double.sqrt() = Math.sqrt(this)
|
||||
inline fun Float.sqrt() = FastMath.sqrt(this)
|
||||
inline fun Int.abs() = if (this < 0) -this else this
|
||||
inline fun Int.abs() = this.absoluteValue
|
||||
fun Double.bipolarClamp(limit: Double) =
|
||||
this.coerceIn(-limit, limit)
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package net.torvald.terrarum.blockproperties
|
||||
|
||||
import net.torvald.terrarum.gameworld.FluidType
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-08-06.
|
||||
*/
|
||||
object Fluid {
|
||||
|
||||
val NULL = 0
|
||||
val NULL = FluidType(0)
|
||||
|
||||
val WATER = 1
|
||||
val STATIC_WATER = -1
|
||||
val WATER = FluidType(1)
|
||||
val STATIC_WATER = FluidType(-1)
|
||||
|
||||
}
|
||||
@@ -5,9 +5,11 @@ import com.badlogic.gdx.graphics.Color
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.blockproperties.Fluid
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataLzma
|
||||
import org.dyn4j.geometry.Vector2
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
typealias BlockAddress = Long
|
||||
|
||||
@@ -45,7 +47,7 @@ open class GameWorld {
|
||||
|
||||
val wallDamages: HashMap<BlockAddress, Float>
|
||||
val terrainDamages: HashMap<BlockAddress, Float>
|
||||
val fluidTypes: HashMap<BlockAddress, Int>
|
||||
val fluidTypes: HashMap<BlockAddress, FluidType>
|
||||
val fluidFills: HashMap<BlockAddress, Float>
|
||||
|
||||
//public World physWorld = new World( new Vec2(0, -Terrarum.game.gravitationalAccel) );
|
||||
@@ -79,7 +81,7 @@ open class GameWorld {
|
||||
|
||||
wallDamages = HashMap<BlockAddress, Float>()
|
||||
terrainDamages = HashMap<BlockAddress, Float>()
|
||||
fluidTypes = HashMap<BlockAddress, Int>()
|
||||
fluidTypes = HashMap<BlockAddress, FluidType>()
|
||||
fluidFills = HashMap<BlockAddress, Float>()
|
||||
|
||||
// temperature layer: 2x2 is one cell
|
||||
@@ -154,8 +156,8 @@ open class GameWorld {
|
||||
get() = layerTerrainLowBits.data
|
||||
|
||||
fun getTileFromWall(x: Int, y: Int): Int? {
|
||||
val wall: Int? = layerWall.getTile(x fmod width, y)
|
||||
val wallDamage: Int? = getWallLowBits(x fmod width, y)
|
||||
val wall: Int? = layerWall.getTile(x fmod width, y.coerceWorld().coerceWorld())
|
||||
val wallDamage: Int? = getWallLowBits(x fmod width, y.coerceWorld())
|
||||
return if (wall == null || wallDamage == null)
|
||||
null
|
||||
else
|
||||
@@ -163,8 +165,8 @@ open class GameWorld {
|
||||
}
|
||||
|
||||
fun getTileFromTerrain(x: Int, y: Int): Int? {
|
||||
val terrain: Int? = layerTerrain.getTile(x fmod width, y)
|
||||
val terrainDamage: Int? = getTerrainLowBits(x fmod width, y)
|
||||
val terrain: Int? = layerTerrain.getTile(x fmod width, y.coerceWorld())
|
||||
val terrainDamage: Int? = getTerrainLowBits(x fmod width, y.coerceWorld())
|
||||
return if (terrain == null || terrainDamage == null)
|
||||
null
|
||||
else
|
||||
@@ -172,15 +174,15 @@ open class GameWorld {
|
||||
}
|
||||
|
||||
fun getTileFromWire(x: Int, y: Int): Int? {
|
||||
return layerWire.getTile(x fmod width, y)
|
||||
return layerWire.getTile(x fmod width, y.coerceWorld())
|
||||
}
|
||||
|
||||
fun getWallLowBits(x: Int, y: Int): Int? {
|
||||
return layerWallLowBits.getData(x fmod width, y)
|
||||
return layerWallLowBits.getData(x fmod width, y.coerceWorld())
|
||||
}
|
||||
|
||||
fun getTerrainLowBits(x: Int, y: Int): Int? {
|
||||
return layerTerrainLowBits.getData(x fmod width, y)
|
||||
return layerTerrainLowBits.getData(x fmod width, y.coerceWorld())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,7 +194,7 @@ open class GameWorld {
|
||||
* @param combinedTilenum (tilenum * 16) + damage
|
||||
*/
|
||||
fun setTileWall(x: Int, y: Int, combinedTilenum: Int) {
|
||||
setTileWall(x fmod width, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
|
||||
setTileWall(x fmod width, y.coerceWorld(), (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,23 +206,23 @@ open class GameWorld {
|
||||
* @param combinedTilenum (tilenum * 16) + damage
|
||||
*/
|
||||
fun setTileTerrain(x: Int, y: Int, combinedTilenum: Int) {
|
||||
setTileTerrain(x fmod width, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
|
||||
setTileTerrain(x fmod width, y.coerceWorld(), (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
|
||||
}
|
||||
|
||||
fun setTileWall(x: Int, y: Int, tile: Byte, damage: Int) {
|
||||
layerWall.setTile(x fmod width, y, tile)
|
||||
layerWallLowBits.setData(x fmod width, y, damage)
|
||||
layerWall.setTile(x fmod width, y.coerceWorld(), tile)
|
||||
layerWallLowBits.setData(x fmod width, y.coerceWorld(), damage)
|
||||
wallDamages.remove(LandUtil.getBlockAddr(this, x, y))
|
||||
}
|
||||
|
||||
fun setTileTerrain(x: Int, y: Int, tile: Byte, damage: Int) {
|
||||
layerTerrain.setTile(x fmod width, y, tile)
|
||||
layerTerrainLowBits.setData(x fmod width, y, damage)
|
||||
layerTerrain.setTile(x fmod width, y.coerceWorld(), tile)
|
||||
layerTerrainLowBits.setData(x fmod width, y.coerceWorld(), damage)
|
||||
terrainDamages.remove(LandUtil.getBlockAddr(this, x, y))
|
||||
}
|
||||
|
||||
fun setTileWire(x: Int, y: Int, tile: Byte) {
|
||||
layerWire.setTile(x fmod width, y, tile)
|
||||
layerWire.setTile(x fmod width, y.coerceWorld(), tile)
|
||||
}
|
||||
|
||||
fun getTileFrom(mode: Int, x: Int, y: Int): Int? {
|
||||
@@ -340,13 +342,20 @@ open class GameWorld {
|
||||
fun getWallDamage(x: Int, y: Int): Float =
|
||||
wallDamages[LandUtil.getBlockAddr(this, x, y)] ?: 0f
|
||||
|
||||
fun setFluid(x: Int, y: Int, fluidType: Int, fill: Float) {
|
||||
fun setFluid(x: Int, y: Int, fluidType: FluidType, fill: Float) {
|
||||
val addr = LandUtil.getBlockAddr(this, x, y)
|
||||
// fluid completely drained
|
||||
if (fill <= WorldSimulator.FLUID_MIN_MASS) {
|
||||
fluidTypes.remove(addr)
|
||||
fluidFills.remove(addr)
|
||||
setTileTerrain(x, y, 0)
|
||||
/**********/ fluidTypes.remove(addr)
|
||||
val oldMap = fluidFills.remove(addr)
|
||||
|
||||
// oldMap not being null means there actually was a fluid there, so we can put AIR onto it
|
||||
// otherwise, it means it was some solid and therefore we DON'T want to put AIR onto it
|
||||
if (oldMap != null) {
|
||||
setTileTerrain(x, y, 0)
|
||||
}
|
||||
}
|
||||
// update the fluid amount
|
||||
else {
|
||||
fluidTypes[addr] = fluidType
|
||||
fluidFills[addr] = fill
|
||||
@@ -354,27 +363,28 @@ open class GameWorld {
|
||||
}
|
||||
}
|
||||
|
||||
fun getFluid(x: Int, y: Int): Pair<Int, Float>? {
|
||||
fun getFluid(x: Int, y: Int): FluidInfo {
|
||||
val addr = LandUtil.getBlockAddr(this, x, y)
|
||||
val fill = fluidFills[addr]
|
||||
val type = fluidTypes[addr]
|
||||
|
||||
return if (type == null) null else Pair(type!!, fill!!)
|
||||
return if (type == null) FluidInfo(Fluid.NULL, 0f) else FluidInfo(type, fill!!)
|
||||
}
|
||||
|
||||
data class FluidInfo(val type: FluidType, val amount: Float)
|
||||
|
||||
|
||||
fun getTemperature(worldTileX: Int, worldTileY: Int): Float? {
|
||||
return null
|
||||
//return layerThermal.getValue((worldTileX fmod width) / 2, worldTileY / 2)
|
||||
}
|
||||
|
||||
fun getAirPressure(worldTileX: Int, worldTileY: Int): Float? {
|
||||
return null
|
||||
//return layerFluidPressure.getValue((worldTileX fmod width) / 4, worldTileY / 8)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun Int.coerceWorld() = this.coerceIn(0, height - 1)
|
||||
|
||||
companion object {
|
||||
@Transient val WALL = 0
|
||||
@Transient val TERRAIN = 1
|
||||
@@ -390,3 +400,7 @@ open class GameWorld {
|
||||
|
||||
infix fun Int.fmod(other: Int) = Math.floorMod(this, other)
|
||||
infix fun Float.fmod(other: Float) = if (this >= 0f) this % other else (this % other) + other
|
||||
|
||||
inline class FluidType(val value: Int) {
|
||||
infix fun sameAs(other: FluidType) = this.value.absoluteValue == other.value.absoluteValue
|
||||
}
|
||||
@@ -598,7 +598,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
|
||||
}
|
||||
|
||||
actorNowPlaying = newActor
|
||||
WorldSimulator(actorNowPlaying, Terrarum.deltaTime)
|
||||
//WorldSimulator(actorNowPlaying, Terrarum.deltaTime)
|
||||
}
|
||||
|
||||
private fun changePossession(refid: Int) {
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameworld
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.roundInt
|
||||
import net.torvald.terrarum.worlddrawer.BlocksDrawer
|
||||
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.roundInt
|
||||
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.blockproperties.Fluid
|
||||
import net.torvald.terrarum.gameworld.FluidCodex
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.Ingame
|
||||
import net.torvald.terrarum.gameworld.FluidType
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
|
||||
|
||||
/**
|
||||
@@ -25,15 +21,20 @@ 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 val FLUID_UPDATING_SQUARE_RADIUS = 80 // larger value will have dramatic impact on performance
|
||||
const private val DOUBLE_RADIUS = FLUID_UPDATING_SQUARE_RADIUS * 2
|
||||
|
||||
// maps are separated as old-new for obvious reason, also it'll allow concurrent modification
|
||||
private val fluidMap = Array(DOUBLE_RADIUS, { FloatArray(DOUBLE_RADIUS) })
|
||||
private val fluidTypeMap = Array(DOUBLE_RADIUS, { IntArray(DOUBLE_RADIUS) })
|
||||
private val fluidTypeMap = Array(DOUBLE_RADIUS, { Array<FluidType>(DOUBLE_RADIUS) { Fluid.NULL } })
|
||||
private val fluidNewMap = Array(DOUBLE_RADIUS, { FloatArray(DOUBLE_RADIUS) })
|
||||
private val fluidNewTypeMap = Array(DOUBLE_RADIUS, { Array<FluidType>(DOUBLE_RADIUS) { Fluid.NULL } })
|
||||
|
||||
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
|
||||
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 minFlow = 0.01f
|
||||
const val maxSpeed = 1f // max units of water moved out of one block to another, per timestamp
|
||||
|
||||
// END OF FLUID-RELATED STUFFS
|
||||
|
||||
@@ -68,11 +69,160 @@ object WorldSimulator {
|
||||
* TODO multithread
|
||||
*/
|
||||
fun moveFluids(delta: Float) {
|
||||
////////////////////
|
||||
// build fluidmap //
|
||||
////////////////////
|
||||
purgeFluidMap()
|
||||
makeFluidMapFromWorld()
|
||||
|
||||
|
||||
// before data: fluidMap/fluidTypeMap
|
||||
// after data: fluidNewMap/fluidNewTypeMap
|
||||
var flow = 0f
|
||||
var remainingMass = 0f
|
||||
|
||||
for (y in 1 until fluidMap.size - 1) {
|
||||
for (x in 1 until fluidMap[0].size - 1) {
|
||||
val worldX = x + updateXFrom
|
||||
val worldY = y + updateYFrom
|
||||
|
||||
// check solidity
|
||||
if (isSolid(worldX, worldY)) continue
|
||||
// check if the fluid is a same kind
|
||||
//if (!isFlowable(type, worldX, worldY))) continue
|
||||
|
||||
|
||||
// Custom push-only flow
|
||||
flow = 0f
|
||||
remainingMass = fluidMap[y][x]
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// The block below this one
|
||||
if (!isSolid(worldX, worldY + 1)) { // TODO use isFlowable
|
||||
flow = getStableStateB(remainingMass + fluidMap[y + 1][x]) - fluidMap[y + 1][x]
|
||||
if (flow > minFlow) {
|
||||
flow *= 0.5f // leads to smoother flow
|
||||
}
|
||||
flow.coerceIn(0f, minOf(maxSpeed, remainingMass))
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y + 1][x] += flow
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// Left
|
||||
if (!isSolid(worldX - 1, worldY)) { // TODO use isFlowable
|
||||
// Equalise the amount fo water in this block and its neighbour
|
||||
flow = (fluidMap[y][x] - fluidMap[y][x - 1]) / 4f
|
||||
if (flow > minFlow) {
|
||||
flow *= 0.5f
|
||||
}
|
||||
flow.coerceIn(0f, remainingMass)
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y][x - 1] += flow
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// Right
|
||||
if (!isSolid(worldX + 1, worldY)) { // TODO use isFlowable
|
||||
// Equalise the amount fo water in this block and its neighbour
|
||||
flow = (fluidMap[y][x] - fluidMap[y][x + 1]) / 4f
|
||||
if (flow > minFlow) {
|
||||
flow *= 0.5f
|
||||
}
|
||||
flow.coerceIn(0f, remainingMass)
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y][x + 1] += flow
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// Up; only compressed water flows upwards
|
||||
if (!isSolid(worldX, worldY - 1)) { // TODO use isFlowable
|
||||
flow = remainingMass - getStableStateB(remainingMass + fluidMap[y - 1][x])
|
||||
if (flow > minFlow) {
|
||||
flow *= 0.5f
|
||||
}
|
||||
flow.coerceIn(0f, minOf(maxSpeed, remainingMass))
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y - 1][x] += flow
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fluidmapToWorld()
|
||||
}
|
||||
|
||||
fun isFlowable(type: FluidType, worldX: Int, worldY: Int): Boolean {
|
||||
val targetFluid = world.getFluid(worldX, worldY)
|
||||
|
||||
// true if target's type is the same as mine, or it's NULL (air)
|
||||
return (targetFluid.type sameAs type || targetFluid.type sameAs Fluid.NULL)
|
||||
}
|
||||
|
||||
fun isSolid(worldX: Int, worldY: Int): Boolean {
|
||||
val tile = world.getTileFromTerrain(worldX, worldY)
|
||||
if (tile != Block.FLUID_MARKER) {
|
||||
// check for block properties isSolid
|
||||
return BlockCodex[tile].isSolid
|
||||
}
|
||||
else {
|
||||
// check for fluid
|
||||
|
||||
// no STATIC is implement yet, just return true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Explanation of get_stable_state_b (well, kind-of) :
|
||||
|
||||
if x <= 1, all water goes to the lower cell
|
||||
* a = 0
|
||||
* b = 1
|
||||
|
||||
if x > 1 & x < 2*MaxMass + MaxCompress, the lower cell should have MaxMass + (upper_cell/MaxMass) * MaxCompress
|
||||
b = MaxMass + (a/MaxMass)*MaxCompress
|
||||
a = x - b
|
||||
|
||||
->
|
||||
|
||||
b = MaxMass + ((x - b)/MaxMass)*MaxCompress ->
|
||||
b = MaxMass + (x*MaxCompress - b*MaxCompress)/MaxMass
|
||||
b*MaxMass = MaxMass^2 + (x*MaxCompress - b*MaxCompress)
|
||||
b*(MaxMass + MaxCompress) = MaxMass*MaxMass + x*MaxCompress
|
||||
|
||||
* b = (MaxMass*MaxMass + x*MaxCompress)/(MaxMass + MaxCompress)
|
||||
* a = x - b;
|
||||
|
||||
if x >= 2 * MaxMass + MaxCompress, the lower cell should have upper+MaxCompress
|
||||
|
||||
b = a + MaxCompress
|
||||
a = x - b
|
||||
|
||||
->
|
||||
|
||||
b = x - b + MaxCompress ->
|
||||
2b = x + MaxCompress ->
|
||||
|
||||
* b = (x + MaxCompress)/2
|
||||
* a = x - b
|
||||
*/
|
||||
private fun getStableStateB(totalMass: Float): Float {
|
||||
if (totalMass <= 1)
|
||||
return 1f
|
||||
else if (totalMass < 2f * FLUID_MAX_MASS + FLUID_MAX_COMP)
|
||||
return (FLUID_MAX_MASS * FLUID_MAX_MASS + totalMass * FLUID_MAX_COMP) / (FLUID_MAX_MASS + FLUID_MAX_COMP)
|
||||
else
|
||||
return (totalMass + FLUID_MAX_COMP) / 2f
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,11 +259,22 @@ object WorldSimulator {
|
||||
|
||||
}
|
||||
|
||||
private fun purgeFluidMap() {
|
||||
for (y in 1..DOUBLE_RADIUS) {
|
||||
for (x in 1..DOUBLE_RADIUS) {
|
||||
fluidMap[y - 1][x - 1] = 0f
|
||||
fluidTypeMap[y - 1][x - 1] = Fluid.NULL
|
||||
private fun makeFluidMapFromWorld() {
|
||||
for (y in 0 until fluidMap.size) {
|
||||
for (x in 0 until fluidMap[0].size) {
|
||||
val fluidData = world.getFluid(x + updateXFrom, y + updateYFrom)
|
||||
fluidMap[y][x] = fluidData.amount
|
||||
fluidTypeMap[y][x] = fluidData.type
|
||||
fluidNewMap[y][x] = fluidData.amount
|
||||
fluidNewTypeMap[y][x] = fluidData.type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fluidmapToWorld() {
|
||||
for (y in 0 until fluidMap.size) {
|
||||
for (x in 0 until fluidMap[0].size) {
|
||||
world.setFluid(x + updateXFrom, y + updateYFrom, fluidNewTypeMap[y][x], fluidNewMap[y][x])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.torvald.terrarum.serialise
|
||||
import com.badlogic.gdx.utils.compression.Lzma
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.gameworld.BlockAddress
|
||||
import net.torvald.terrarum.gameworld.FluidType
|
||||
import net.torvald.terrarum.gameworld.MapLayer
|
||||
import net.torvald.terrarum.gameworld.PairedMapLayer
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
@@ -156,7 +157,7 @@ internal object ReadLayerDataLzma {
|
||||
|
||||
val terrainDamages = HashMap<BlockAddress, Float>()
|
||||
val wallDamages = HashMap<BlockAddress, Float>()
|
||||
val fluidTypes = HashMap<BlockAddress, Int>()
|
||||
val fluidTypes = HashMap<BlockAddress, FluidType>()
|
||||
val fluidFills = HashMap<BlockAddress, Float>()
|
||||
|
||||
// parse terrain damages
|
||||
@@ -215,7 +216,7 @@ internal object ReadLayerDataLzma {
|
||||
val spawnY: Int,
|
||||
val wallDamages: HashMap<BlockAddress, Float>,
|
||||
val terrainDamages: HashMap<BlockAddress, Float>,
|
||||
val fluidTypes: HashMap<BlockAddress, Int>,
|
||||
val fluidTypes: HashMap<BlockAddress, FluidType>,
|
||||
val fluidFills: HashMap<BlockAddress, Float>
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user