mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-10 18:44:05 +09:00
refactoring around
This commit is contained in:
@@ -14,7 +14,7 @@ import net.torvald.terrarum.gameactors.*
|
||||
import net.torvald.terrarum.gameitem.ItemID
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
|
||||
import net.torvald.terrarum.gameworld.WorldTime
|
||||
import net.torvald.terrarum.modulebasegame.ui.Notification
|
||||
import net.torvald.terrarum.modulebasegame.ui.UIBuildingMakerBlockChooser
|
||||
import net.torvald.terrarum.modulebasegame.ui.UIBuildingMakerPenMenu
|
||||
|
||||
@@ -28,7 +28,7 @@ import net.torvald.terrarum.gameactors.WireActor
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.*
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
|
||||
import net.torvald.terrarum.gameworld.WorldSimulator
|
||||
import net.torvald.terrarum.modulebasegame.ui.*
|
||||
import net.torvald.terrarum.weather.WeatherMixer
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.torvald.terrarum.modulebasegame.console
|
||||
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
|
||||
import net.torvald.terrarum.gameworld.WorldTime
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.console.ConsoleCommand
|
||||
import net.torvald.terrarum.console.Echo
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameworld
|
||||
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.gameworld.WorldTime
|
||||
import net.torvald.terrarum.serialise.ReadLayerDataZip
|
||||
|
||||
/**
|
||||
@@ -12,17 +13,8 @@ class GameWorldExtension : GameWorld {
|
||||
internal constructor(worldIndex: Int, layerData: ReadLayerDataZip.LayerData, creationTIME_T: Long, lastPlayTIME_T: Long, totalPlayTime: Int) : super(worldIndex, layerData, creationTIME_T, lastPlayTIME_T, totalPlayTime)
|
||||
|
||||
|
||||
/** Extended world time */
|
||||
val worldTime: WorldTime
|
||||
val economy = GameEconomy()
|
||||
|
||||
override var TIME_T: Long
|
||||
get() = worldTime.TIME_T
|
||||
set(value) { worldTime.TIME_T = value }
|
||||
override var dayLength: Int
|
||||
get() = WorldTime.DAY_LENGTH
|
||||
set(value) { throw UnsupportedOperationException() }
|
||||
|
||||
|
||||
// delegated properties //
|
||||
/*val layerWall: MapLayer; get() = baseworld.layerWall
|
||||
@@ -44,14 +36,6 @@ class GameWorldExtension : GameWorld {
|
||||
val damageDataArray: ByteArray; get() = baseworld.damageDataArray*/
|
||||
|
||||
init {
|
||||
worldTime = WorldTime( // Year EPOCH (125), Month 1, Day 1 is implied
|
||||
7 * WorldTime.HOUR_SEC +
|
||||
30L * WorldTime.MINUTE_SEC
|
||||
)
|
||||
}
|
||||
|
||||
fun updateWorldTime(delta: Float) {
|
||||
worldTime.update(delta)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,480 +0,0 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameworld
|
||||
|
||||
import com.badlogic.gdx.Input
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||
import net.torvald.terrarum.blockproperties.Block
|
||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||
import net.torvald.terrarum.blockproperties.Fluid
|
||||
import net.torvald.terrarum.gameactors.ActorWithBody
|
||||
import net.torvald.terrarum.gamecontroller.KeyToggler
|
||||
import net.torvald.terrarum.gameitem.ItemID
|
||||
import net.torvald.terrarum.gameworld.FluidType
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
|
||||
import org.khelekore.prtree.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2016-08-03.
|
||||
*/
|
||||
object WorldSimulator {
|
||||
|
||||
private val DEBUG_STEPPING_MODE = false // use period key
|
||||
|
||||
// FLUID-RELATED STUFFS //
|
||||
|
||||
/**
|
||||
* In tiles;
|
||||
* square width/height = field * 2
|
||||
*/
|
||||
// TODO: increase the radius and then MULTITHREAD
|
||||
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, { 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. 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
|
||||
|
||||
/** Top-left point */
|
||||
var updateXFrom = 0
|
||||
/** Bottom-right point */
|
||||
var updateXTo = 0
|
||||
/** Top-left point */
|
||||
var updateYFrom = 0
|
||||
/** Bottom-right point */
|
||||
var updateYTo = 0
|
||||
|
||||
private val ingame: IngameInstance
|
||||
get() = Terrarum.ingame!!
|
||||
private val world: GameWorld
|
||||
get() = ingame.world
|
||||
|
||||
|
||||
private lateinit var actorsRTree: PRTree<ActorWithBody>
|
||||
|
||||
fun resetForThisFrame() {
|
||||
|
||||
}
|
||||
|
||||
/** Must be called BEFORE the actors update -- actors depend on the R-Tree for various things */
|
||||
operator fun invoke(player: ActorHumanoid?, delta: Float) {
|
||||
// build the r-tree that will be used during a single frame of updating
|
||||
actorsRTree = PRTree(actorMBRConverter, 24)
|
||||
actorsRTree.load(ingame.actorContainerActive.filterIsInstance<ActorWithBody>())
|
||||
|
||||
|
||||
|
||||
//printdbg(this, "============================")
|
||||
|
||||
if (player != null) {
|
||||
updateXFrom = player.hitbox.centeredX.div(TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundToInt()
|
||||
updateYFrom = player.hitbox.centeredY.div(TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundToInt()
|
||||
updateXTo = updateXFrom + DOUBLE_RADIUS
|
||||
updateYTo = updateYFrom + DOUBLE_RADIUS
|
||||
}
|
||||
//moveFluids(delta)
|
||||
displaceFallables(delta)
|
||||
simulateWires(delta)
|
||||
|
||||
//printdbg(this, "============================")
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of actors under the bounding box given, list may be empty if no actor is under the point.
|
||||
*/
|
||||
fun getActorsAt(startPoint: Point2d, endPoint: Point2d): List<ActorWithBody> {
|
||||
val outList = ArrayList<ActorWithBody>()
|
||||
actorsRTree.find(startPoint.x, startPoint.y, endPoint.x, endPoint.y, outList)
|
||||
return outList
|
||||
}
|
||||
|
||||
fun getActorsAt(worldX: Double, worldY: Double): List<ActorWithBody> {
|
||||
val outList = ArrayList<ActorWithBody>()
|
||||
actorsRTree.find(worldX, worldY, worldX + 1.0, worldY + 1.0, outList)
|
||||
return outList
|
||||
}
|
||||
|
||||
/** Will use centre point of the actors
|
||||
* @return List of DistanceResult, list may be empty */
|
||||
fun findKNearestActors(from: ActorWithBody, maxHits: Int): List<DistanceResult<ActorWithBody>> {
|
||||
return actorsRTree.nearestNeighbour(actorDistanceCalculator, null, maxHits, object : PointND {
|
||||
override fun getDimensions(): Int = 2
|
||||
override fun getOrd(axis: Int): Double = when(axis) {
|
||||
0 -> from.hitbox.centeredX
|
||||
1 -> from.hitbox.centeredY
|
||||
else -> throw IllegalArgumentException("nonexistent axis $axis for ${dimensions}-dimensional object")
|
||||
}
|
||||
})
|
||||
}
|
||||
/** Will use centre point of the actors
|
||||
* @return Pair of: the actor, distance from the actor; null if none found */
|
||||
fun findNearestActors(from: ActorWithBody): DistanceResult<ActorWithBody>? {
|
||||
val t = findKNearestActors(from, 1)
|
||||
return if (t.isNotEmpty())
|
||||
t[0]
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Procedure: CP world fluidmap -> sim on fluidmap -> CP fluidmap world
|
||||
* TODO multithread
|
||||
*/
|
||||
fun moveFluids(delta: Float) {
|
||||
makeFluidMapFromWorld()
|
||||
|
||||
simCompression()
|
||||
|
||||
if (AppLoader.IS_DEVELOPMENT_BUILD) {
|
||||
monitorIllegalFluidSetup() // non-air non-zero fluid is kinda inevitable
|
||||
}
|
||||
|
||||
fluidmapToWorld()
|
||||
}
|
||||
|
||||
fun isFlowable(type: FluidType, worldX: Int, worldY: Int): Boolean {
|
||||
val fluid = world.getFluid(worldX, worldY)
|
||||
val tile = world.getTileFromTerrain(worldX, worldY)
|
||||
|
||||
// true if target's type is the same as mine, or it's NULL (air)
|
||||
return ((fluid.type sameAs type || fluid.type sameAs Fluid.NULL) && !BlockCodex[tile].isSolid)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(delta: Float) {
|
||||
/*for (y in updateYFrom..updateYTo) {
|
||||
for (x in updateXFrom..updateXTo) {
|
||||
val tile = world.getTileFromTerrain(x, y) ?: Block.STONE
|
||||
val tileBelow = world.getTileFromTerrain(x, y + 1) ?: Block.STONE
|
||||
|
||||
if (tile.maxSupport()) {
|
||||
// displace fluid. This statement must precede isSolid()
|
||||
if (tileBelow.isFluid()) {
|
||||
// remove tileThis to create air pocket
|
||||
world.setTileTerrain(x, y, Block.AIR)
|
||||
|
||||
pour(x, y, drain(x, y, tileBelow.fluidLevel().toInt()))
|
||||
// place our tile
|
||||
world.setTileTerrain(x, y + 1, tile)
|
||||
}
|
||||
else if (!tileBelow.isSolid()) {
|
||||
world.setTileTerrain(x, y, Block.AIR)
|
||||
world.setTileTerrain(x, y + 1, tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// displace fallables (TODO implement blocks with fallable supports e.g. scaffolding)
|
||||
// only displace SINGLE BOTTOMMOST block on single X-coord (this doesn't mean they must fall only one block)
|
||||
// so that the "falling" should be visible to the end user
|
||||
if (!DEBUG_STEPPING_MODE || DEBUG_STEPPING_MODE && KeyToggler.isOn (Input.Keys.PERIOD)) {
|
||||
for (x in updateXFrom..updateXTo) {
|
||||
var fallDownCounter = 0
|
||||
var fallableStackProcessed = false
|
||||
// one "stack" is a contiguous fallable blocks, regardless of the actual block number
|
||||
// when you are simulating the gradual falling, it is natural to process all the "stacks" at the same run,
|
||||
// otherwise you'll get an artefact.
|
||||
for (y in updateYTo downTo updateYFrom) {
|
||||
val currentTile = world.getTileFromTerrain(x, y)
|
||||
val prop = BlockCodex[currentTile]
|
||||
val isAir = currentTile == Block.AIR
|
||||
val support = prop.maxSupport
|
||||
val isFallable = support != -1
|
||||
|
||||
// mark the beginnig of the new "stack"
|
||||
if (fallableStackProcessed && !isFallable) {
|
||||
fallableStackProcessed = false
|
||||
} // do not chain with "else if"
|
||||
|
||||
// process the gradual falling of the selected "stack"
|
||||
if (!fallableStackProcessed && fallDownCounter != 0 && isFallable) {
|
||||
// replace blocks
|
||||
world.setTileTerrain(x, y, Block.AIR, true)
|
||||
world.setTileTerrain(x, y + fallDownCounter, currentTile, true)
|
||||
|
||||
fallableStackProcessed = true
|
||||
}
|
||||
else if (!isAir) {
|
||||
fallDownCounter = 0
|
||||
}
|
||||
else if (!isFallable && fallDownCounter < FALLABLE_MAX_FALL_SPEED) {
|
||||
fallDownCounter += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_STEPPING_MODE) {
|
||||
KeyToggler.forceSet(Input.Keys.PERIOD, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun disperseHeat(delta: Float) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
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
|
||||
}
|
||||
|
||||
private fun simCompression() {
|
||||
// before data: fluidMap/fluidTypeMap
|
||||
// after data: fluidNewMap/fluidNewTypeMap
|
||||
|
||||
// FIXME water doesn't disappear when they should
|
||||
// FIXME >as it turns out, fluid FUCKING MULTIPLIES themselves (wut D:)
|
||||
|
||||
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
|
||||
val remainingType = fluidTypeMap[y][x]
|
||||
|
||||
// check solidity
|
||||
if (!isFlowable(remainingType, worldX, worldY)) continue
|
||||
// check if the fluid is a same kind
|
||||
//if (!isFlowable(type, worldX, worldY))) continue
|
||||
|
||||
|
||||
// Custom push-only flow
|
||||
var flow = 0f
|
||||
var remainingMass = fluidMap[y][x]
|
||||
//val remainingType = fluidTypeMap[y][x]
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// The block below this one
|
||||
if (isFlowable(remainingType, worldX, worldY + 1)) {
|
||||
flow = getStableStateB(remainingMass + fluidMap[y + 1][x]) - fluidMap[y + 1][x]
|
||||
if (flow > minFlow) {
|
||||
flow *= 0.5f // leads to smoother flow
|
||||
}
|
||||
flow = flow.coerceIn(0f, minOf(maxSpeed, remainingMass))
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y + 1][x] += flow
|
||||
fluidNewTypeMap[y + 1][x] = remainingType
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// Left
|
||||
if (isFlowable(remainingType, worldX - 1, worldY)) {
|
||||
// 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 = flow.coerceIn(0f, remainingMass)
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y][x - 1] += flow
|
||||
fluidNewTypeMap[y][x - 1] = remainingType
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// Right
|
||||
if (isFlowable(remainingType, worldX + 1, worldY)) {
|
||||
// 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 = flow.coerceIn(0f, remainingMass)
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y][x + 1] += flow
|
||||
fluidNewTypeMap[y][x + 1] = remainingType
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
if (remainingMass <= 0) continue
|
||||
|
||||
// Up; only compressed water flows upwards
|
||||
if (isFlowable(remainingType, worldX, worldY - 1)) {
|
||||
flow = remainingMass - getStableStateB(remainingMass + fluidMap[y - 1][x])
|
||||
if (flow > minFlow) {
|
||||
flow *= 0.5f
|
||||
}
|
||||
flow = flow.coerceIn(0f, minOf(maxSpeed, remainingMass))
|
||||
|
||||
fluidNewMap[y][x] -= flow
|
||||
fluidNewMap[y - 1][x] += flow
|
||||
fluidNewTypeMap[y - 1][x] = remainingType
|
||||
remainingMass -= flow
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val FALLABLE_MAX_FALL_SPEED = 2
|
||||
|
||||
private fun monitorIllegalFluidSetup() {
|
||||
for (y in fluidMap.indices) {
|
||||
for (x in fluidMap[0].indices) {
|
||||
val fluidData = world.getFluid(x + updateXFrom, y + updateYFrom)
|
||||
if (fluidData.amount < 0f) {
|
||||
throw InternalError("Negative amount of fluid at (${x + updateXFrom},${y + updateYFrom}): $fluidData")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeFluidMapFromWorld() {
|
||||
//printdbg(this, "Scan area: ($updateXFrom,$updateYFrom)..(${updateXFrom + fluidMap[0].size},${updateYFrom + fluidMap.size})")
|
||||
|
||||
for (y in fluidMap.indices) {
|
||||
for (x in fluidMap[0].indices) {
|
||||
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
|
||||
|
||||
/*if (x + updateXFrom == 60 && y + updateYFrom == 256) {
|
||||
printdbg(this, "making array amount ${fluidData.amount} for (60,256)")
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fluidmapToWorld() {
|
||||
for (y in fluidMap.indices) {
|
||||
for (x in fluidMap[0].indices) {
|
||||
world.setFluid(x + updateXFrom, y + updateYFrom, fluidNewTypeMap[y][x], fluidNewMap[y][x])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun ItemID.isFallable() = BlockCodex[this].maxSupport
|
||||
|
||||
|
||||
private val actorMBRConverter = object : MBRConverter<ActorWithBody> {
|
||||
override fun getDimensions(): Int = 2
|
||||
override fun getMin(axis: Int, t: ActorWithBody): Double =
|
||||
when (axis) {
|
||||
0 -> t.hitbox.startX
|
||||
1 -> t.hitbox.startY
|
||||
else -> throw IllegalArgumentException("nonexistent axis $axis for ${dimensions}-dimensional object")
|
||||
}
|
||||
|
||||
override fun getMax(axis: Int, t: ActorWithBody): Double =
|
||||
when (axis) {
|
||||
0 -> t.hitbox.endX
|
||||
1 -> t.hitbox.endY
|
||||
else -> throw IllegalArgumentException("nonexistent axis $axis for ${dimensions}-dimensional object")
|
||||
}
|
||||
}
|
||||
|
||||
// simple euclidean norm, squared
|
||||
private val actorDistanceCalculator = DistanceCalculator<ActorWithBody> { t: ActorWithBody, p: PointND ->
|
||||
val dist1 = (p.getOrd(0) - t.hitbox.centeredX).sqr() + (p.getOrd(1) - t.hitbox.centeredY).sqr()
|
||||
// ROUNDWORLD implementation
|
||||
val dist2 = (p.getOrd(0) - (t.hitbox.centeredX - world.width * TILE_SIZE)).sqr() + (p.getOrd(1) - t.hitbox.centeredY).sqr()
|
||||
val dist3 = (p.getOrd(0) - (t.hitbox.centeredX + world.width * TILE_SIZE)).sqr() + (p.getOrd(1) - t.hitbox.centeredY).sqr()
|
||||
|
||||
minOf(dist1, minOf(dist2, dist3))
|
||||
}
|
||||
|
||||
|
||||
private fun simulateWires(delta: Float) {
|
||||
|
||||
}
|
||||
|
||||
private enum class WireConStatus { THRU, END, BRANCH }
|
||||
private val wireConToStatus = arrayOf(
|
||||
WireConStatus.END, // 0000
|
||||
WireConStatus.END, // 0001
|
||||
WireConStatus.END, // 0010
|
||||
WireConStatus.THRU,// 0011
|
||||
WireConStatus.END, // 0100
|
||||
WireConStatus.THRU,// 0101
|
||||
WireConStatus.THRU,// 0110
|
||||
WireConStatus.BRANCH,// 0111
|
||||
WireConStatus.END, // 1000
|
||||
WireConStatus.THRU,// 1001
|
||||
WireConStatus.THRU,// 1010
|
||||
WireConStatus.BRANCH,// 1011
|
||||
WireConStatus.THRU,// 1100
|
||||
WireConStatus.BRANCH,// 1101
|
||||
WireConStatus.BRANCH,// 1110
|
||||
WireConStatus.BRANCH // 1111
|
||||
)
|
||||
|
||||
data class wireGraphBranch(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val con: Byte
|
||||
)
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameworld
|
||||
|
||||
import net.torvald.terrarum.gameworld.fmod
|
||||
|
||||
|
||||
/**
|
||||
* Please also see:
|
||||
* https://en.wikipedia.org/wiki/World_Calendar
|
||||
*
|
||||
* There is no AM/PM concept, 24-hour clock is forced; no leap years.
|
||||
* An ingame day should last 22 real-life minutes.
|
||||
*
|
||||
* ## The Yearly Calendar
|
||||
*
|
||||
* A calendar tailored to this very game. A year is consisted of 4 seasons (month),
|
||||
* and each season last fixed length of 30 days, leap years does not occur.
|
||||
*
|
||||
* =========================
|
||||
* |Mo|Ty|Mi|To|Fr|La|Su|Ve|
|
||||
* |--|--|--|--|--|--|--|--|
|
||||
* | 1| 2| 3| 4| 5| 6| 7| | <- Spring
|
||||
* | 8| 9|10|11|12|13|14| |
|
||||
* |15|16|17|18|19|20|21| |
|
||||
* |22|23|24|25|26|27|28| |
|
||||
* |29|30| 1| 2| 3| 4| 5| | <- Summer
|
||||
* | 6| 7| 8| 9|10|11|12| |
|
||||
* |13|14|15|16|17|18|19| |
|
||||
* |20|21|22|23|24|25|26| |
|
||||
* |27|28|29|30| 1| 2| 3| | <- Autumn
|
||||
* | 4| 5| 6| 7| 8| 9|10| |
|
||||
* |11|12|13|14|15|16|17| |
|
||||
* |18|19|20|21|22|23|24| |
|
||||
* |25|26|27|28|29|30| 1| | <- Winter
|
||||
* | 2| 3| 4| 5| 6| 7| 8| |
|
||||
* | 9|10|11|12|13|14|15| |
|
||||
* |16|17|18|19|20|21|22| |
|
||||
* |23|24|25|26|27|28|29|30|
|
||||
* =========================
|
||||
*
|
||||
* - A year is 120 days, 8th day of the week (Verddag, Winter 30th) does occur as in The World calendar.
|
||||
* - Starting day of the week is monday (Mondag).
|
||||
* - Spring 1st is the New Year holiday, Winter 30th is the New Year's Eve holiday.
|
||||
* - Human-readable date format is always Year-MonthName-Date, no matter where you (the real-life you) come from.
|
||||
* For number-only format, months are enumerated from 1.
|
||||
* (Spring-1, Summer-2, Autumn-3, Winter-4) E.g. 0125-Wint-07, or 0125-04-07. For more details, please refer to the
|
||||
* internal functions `getFormattedTime()`, `getShortTime()`, and `getFilenameTime()`
|
||||
* - Preferred computerised date format is YearMonthDate. E.g. 01250407
|
||||
* - Rest of the format (e.g. time intervals) follows ISO 8601 standard.
|
||||
*
|
||||
* (Check please:)
|
||||
* - Equinox/Solstice always occur on 21st day of the month
|
||||
*
|
||||
*
|
||||
* Created by minjaesong on 2016-01-24.
|
||||
*/
|
||||
class WorldTime(initTime: Long = 0L) {
|
||||
|
||||
/** It is not recommended to directly modify the TIME_T. Use provided methods instead. */
|
||||
var TIME_T = 0L // Epoch: Year 125 Spring 1st, 0h00:00 (Mondag) // 125-01-01
|
||||
|
||||
init {
|
||||
TIME_T = initTime
|
||||
}
|
||||
|
||||
inline val seconds: Int // 0 - 59
|
||||
get() = TIME_T.toPositiveInt() % MINUTE_SEC
|
||||
inline val minutes: Int // 0 - 59
|
||||
get() = TIME_T.div(MINUTE_SEC).abs().toInt() % HOUR_MIN
|
||||
inline val hours: Int // 0 - 21
|
||||
get() = TIME_T.div(HOUR_SEC).abs().toInt() % HOURS_PER_DAY
|
||||
|
||||
// The World Calendar implementation
|
||||
/*inline val yearlyDays: Int // 0 - 364
|
||||
get() = (TIME_T.toPositiveInt().div(DAY_LENGTH) % YEAR_DAYS)
|
||||
|
||||
inline val days: Int // 1 - 31
|
||||
get() = quarterlyDays + 1 -
|
||||
if (quarterlyMonthOffset == 0) 0
|
||||
else if (quarterlyMonthOffset == 1) 31
|
||||
else 61
|
||||
inline val months: Int // 1 - 12
|
||||
get() = if (yearlyDays == YEAR_DAYS - 1) 12 else
|
||||
quarter * 3 + 1 +
|
||||
if (quarterlyDays < 31) 0
|
||||
else if (quarterlyDays < 61) 1
|
||||
else 2
|
||||
inline val years: Int
|
||||
get() = TIME_T.div(YEAR_DAYS * DAY_LENGTH).abs().toInt() + EPOCH_YEAR
|
||||
|
||||
inline val quarter: Int // 0 - 3
|
||||
get() = if (yearlyDays == YEAR_DAYS - 1) 3 else yearlyDays / QUARTER_LENGTH
|
||||
inline val quarterlyDays: Int // 0 - 90(91)
|
||||
get() = if (yearlyDays == YEAR_DAYS - 1) 91 else (yearlyDays % QUARTER_LENGTH)
|
||||
inline val quarterlyMonthOffset: Int // 0 - 2
|
||||
get() = months.minus(1) % 3*/
|
||||
|
||||
|
||||
// these functions won't need inlining for performance
|
||||
val ordinalDay: Int // 0 - 119
|
||||
get() = (TIME_T.div(DAY_LENGTH) fmod YEAR_DAYS.toLong()).toInt()
|
||||
val calendarDay: Int // 1 - 30 fixed
|
||||
get() = (ordinalDay % MONTH_LENGTH) + 1
|
||||
val calendarMonth: Int // 1 - 4
|
||||
get() = (ordinalDay / MONTH_LENGTH) + 1
|
||||
val years: Int
|
||||
get() = TIME_T.div(YEAR_DAYS * DAY_LENGTH).abs().toInt() + EPOCH_YEAR
|
||||
|
||||
val quarter = calendarMonth - 1 // 0 - 3
|
||||
|
||||
|
||||
val dayOfWeek: Int //0: Mondag-The first day of weekday (0 - 7)
|
||||
get() = if (ordinalDay == YEAR_DAYS - 1) 7 else ordinalDay % 7
|
||||
|
||||
var timeDelta: Int = 1
|
||||
set(value) {
|
||||
field = if (value < 0) 0 else value
|
||||
}
|
||||
|
||||
inline val moonPhase: Double
|
||||
get() = (TIME_T.plus(1700000L) % LUNAR_CYCLE).toDouble() / LUNAR_CYCLE
|
||||
|
||||
@Transient private var realSecAcc: Double = 0.0
|
||||
@Transient private val REAL_SEC_TO_GAME_SECS = 1.0 / GAME_MIN_TO_REAL_SEC // how slow is real-life clock (second-wise) relative to the ingame one
|
||||
|
||||
val DAY_NAMES = arrayOf(//daynames are taken from Nynorsk (å -> o)
|
||||
"Mondag", "Tysdag", "Midtveke" //middle-week
|
||||
, "Torsdag", "Fredag", "Laurdag", "Sundag", "Verddag" //From Norsk word 'verd'
|
||||
)
|
||||
val DAY_NAMES_SHORT = arrayOf("Mon", "Tys", "Mid", "Tor", "Fre", "Lau", "Sun", "Ver")
|
||||
|
||||
// dwarven calendar of 12 monthes
|
||||
/*val MONTH_NAMES = arrayOf(
|
||||
"Opal", "Obsidian", "Granite", "Slate", "Felsite", "Hematite",
|
||||
"Malachite", "Galena", "Limestone", "Sandstone", "Timber", "Moonstone"
|
||||
)
|
||||
val MONTH_NAMES_SHORT = arrayOf("Opal", "Obsi", "Gran", "Slat", "Fels", "Hema",
|
||||
"Mala", "Gale", "Lime", "Sand", "Timb", "Moon")*/
|
||||
val MONTH_NAMES = arrayOf("Spring", "Summer", "Autumn", "Winter")
|
||||
val MONTH_NAMES_SHORT = arrayOf("Spri", "Summ", "Autm", "Wint")
|
||||
|
||||
companion object {
|
||||
/** Each day is displayed as 24 hours, but in real-life clock it's 22 mins long */
|
||||
val DAY_LENGTH = 86400 //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: Double = 720.0 / 11.0
|
||||
val HOURS_PER_DAY = DAY_LENGTH / HOUR_SEC
|
||||
|
||||
val YEAR_DAYS: Int = 120
|
||||
|
||||
val MONTH_LENGTH = 30 // ingame calendar specific
|
||||
|
||||
val EPOCH_YEAR = 125
|
||||
|
||||
/**
|
||||
* Parse a time in the format of "8h30" (hour and minute) or "39882" (second) and return a time of day, in seconds
|
||||
*/
|
||||
fun parseTime(s: String): Int =
|
||||
if (s.length >= 4 && s.contains('h')) {
|
||||
s.toLowerCase().substringBefore('h').toInt() * HOUR_SEC +
|
||||
s.toLowerCase().substringAfter('h').toInt() * MINUTE_SEC
|
||||
}
|
||||
else if (s.endsWith("h", true)) {
|
||||
s.toLowerCase().substring(0, s.length - 1).toInt() * HOUR_SEC
|
||||
}
|
||||
else {
|
||||
s.toInt()
|
||||
}
|
||||
|
||||
|
||||
val LUNAR_CYCLE: Int = 29 * DAY_LENGTH + 12 * HOUR_SEC + 44 * MINUTE_SEC + 3 // 29 days, 12 hours, 44 minutes, and 3 seconds in-game calendar
|
||||
}
|
||||
|
||||
fun update(delta: Float) {
|
||||
//time
|
||||
realSecAcc += delta
|
||||
if (realSecAcc >= REAL_SEC_TO_GAME_SECS) {
|
||||
while (realSecAcc >= REAL_SEC_TO_GAME_SECS) {
|
||||
realSecAcc -= REAL_SEC_TO_GAME_SECS
|
||||
TIME_T += timeDelta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val todaySeconds: Int
|
||||
get() = TIME_T.toPositiveInt() % DAY_LENGTH
|
||||
|
||||
fun setTimeOfToday(t: Int) {
|
||||
TIME_T = TIME_T - todaySeconds + t
|
||||
}
|
||||
|
||||
fun addTime(t: Int) {
|
||||
TIME_T += t
|
||||
}
|
||||
|
||||
val dayName: String
|
||||
get() = DAY_NAMES[dayOfWeek]
|
||||
|
||||
fun Long.toPositiveInt() = this.and(0x7FFFFFFF).toInt()
|
||||
fun Long.abs() = Math.abs(this)
|
||||
|
||||
/** Format: "%A, %Y %B %d %X" */
|
||||
fun getFormattedTime() = "${getDayNameShort()}, " +
|
||||
"$years " +
|
||||
"${getMonthNameFull()} " +
|
||||
"$calendarDay " +
|
||||
"${String.format("%02d", hours)}:" +
|
||||
"${String.format("%02d", minutes)}:" +
|
||||
"${String.format("%02d", seconds)}"
|
||||
fun getShortTime() = "${years.toString().padStart(4, '0')}-${getMonthNameShort()}-${calendarDay.toString().padStart(2, '0')}"
|
||||
fun getFilenameTime() = "${years.toString().padStart(4, '0')}${calendarMonth.toString().padStart(2, '0')}${calendarDay.toString().padStart(2, '0')}"
|
||||
|
||||
fun getDayNameFull() = DAY_NAMES[dayOfWeek]
|
||||
fun getDayNameShort() = DAY_NAMES_SHORT[dayOfWeek]
|
||||
fun getMonthNameFull() = MONTH_NAMES[calendarMonth - 1]
|
||||
fun getMonthNameShort() = MONTH_NAMES_SHORT[calendarMonth - 1]
|
||||
|
||||
override fun toString() = getFormattedTime()
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package net.torvald.terrarum.modulebasegame.gameworld
|
||||
|
||||
/**
|
||||
* The World Calendar implementation of Dwarven Calendar (we're talking about DF!)
|
||||
*
|
||||
* Please see:
|
||||
* https://en.wikipedia.org/wiki/World_Calendar
|
||||
* http://dwarffortresswiki.org/index.php/DF2014:Calendar
|
||||
*
|
||||
* Normal format for day is
|
||||
* Tysdag 12th Granite
|
||||
*
|
||||
* And there is no AM/PM concept, 22-hour clock is forced.
|
||||
*
|
||||
* Created by minjaesong on 2016-01-24.
|
||||
*/
|
||||
@Deprecated("Are you even reading the name?")
|
||||
class YeOldeWorldTime {
|
||||
|
||||
internal var seconds: Int // 0 - 59
|
||||
internal var minutes: Int // 0 - 59
|
||||
internal var hours: Int // 0 - 21
|
||||
|
||||
// days on the year
|
||||
internal var yearlyDays: Int //NOT a calendar day
|
||||
|
||||
internal var days: Int // 1 - 31
|
||||
internal var months: Int // 1 - 12
|
||||
internal var years: Int // 1+
|
||||
|
||||
internal var dayOfWeek: Int //0: Mondag-The first day of weekday (0 - 7)
|
||||
|
||||
internal var timeDelta = 1
|
||||
|
||||
@Transient private var realMillisec: Int
|
||||
|
||||
val DAY_NAMES = arrayOf(//daynames are taken from Nynorsk (å -> o)
|
||||
"Mondag", "Tysdag", "Midvikdag" //From Islenska Miðvikudagur
|
||||
, "Torsdag", "Fredag", "Laurdag", "Sundag", "Verddag" //From Norsk word 'verd'
|
||||
)
|
||||
val DAY_NAMES_SHORT = arrayOf("Mon", "Tys", "Mid", "Tor", "Fre", "Lau", "Sun", "Ver")
|
||||
|
||||
val MONTH_NAMES = arrayOf(
|
||||
"Opal", "Obsidian", "Granite", "Slate", "Felsite", "Hematite",
|
||||
"Malachite", "Galena", "Limestone", "Sandstone", "Timber", "Moonstone"
|
||||
)
|
||||
val MONTH_NAMES_SHORT = arrayOf("Opal", "Obsi", "Gran", "Slat", "Fels", "Hema",
|
||||
"Mala", "Gale", "Lime", "Sand", "Timb", "Moon")
|
||||
|
||||
|
||||
@Transient val REAL_SEC_IN_MILLI = 1000
|
||||
|
||||
companion object {
|
||||
/** Each day is 22-hour long */
|
||||
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
|
||||
|
||||
val YEAR_DAYS: Int = 365
|
||||
|
||||
fun parseTime(s: String): Int =
|
||||
if (s.length >= 4 && s.contains('h')) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// The day when the new world ever is being made.
|
||||
// If we use Multiverse system (which replaces Terraria's "hack"
|
||||
// as a reward rather than a cheat), time of current world's time is
|
||||
// copied to the new world's. (it's Multi-nation rather than Multiverse)
|
||||
seconds = 0
|
||||
minutes = 30
|
||||
hours = 8
|
||||
yearlyDays = 73
|
||||
days = 12
|
||||
months = 3
|
||||
years = 125
|
||||
dayOfWeek = 1 // Tysdag
|
||||
realMillisec = 0
|
||||
}
|
||||
|
||||
fun update(delta: Int) {
|
||||
val oldsec = seconds
|
||||
|
||||
//time
|
||||
realMillisec += delta * timeDelta
|
||||
val newsec = Math.round(GAME_MIN_TO_REAL_SEC / REAL_SEC_IN_MILLI.toFloat() * realMillisec.toFloat())
|
||||
seconds = newsec
|
||||
|
||||
if (realMillisec >= REAL_SEC_IN_MILLI)
|
||||
realMillisec -= REAL_SEC_IN_MILLI
|
||||
|
||||
kickVariables()
|
||||
}
|
||||
|
||||
/**
|
||||
* How much time has passed today, in seconds.
|
||||
* 0 == 6 AM
|
||||
* @return
|
||||
*/
|
||||
val elapsedSeconds: Int
|
||||
get() = (HOUR_SEC * hours + MINUTE_SEC * minutes + seconds) % DAY_LENGTH
|
||||
|
||||
/** Sets time of this day. */
|
||||
fun setTime(t: Int) {
|
||||
days += t / DAY_LENGTH
|
||||
hours = t / HOUR_SEC
|
||||
minutes = (t - HOUR_SEC * hours) / MINUTE_SEC
|
||||
seconds = t - minutes * MINUTE_SEC
|
||||
yearlyDays += t / DAY_LENGTH
|
||||
}
|
||||
|
||||
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 && days == 31) {
|
||||
dayOfWeek = 7
|
||||
}
|
||||
|
||||
if (months == 12 && 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++
|
||||
yearlyDays = 1
|
||||
}
|
||||
}
|
||||
|
||||
/** Format: "%A %d %B %Y %X" */
|
||||
fun getFormattedTime() = "${getDayNameFull()} " +
|
||||
"$days " +
|
||||
"${getMonthNameFull()} " +
|
||||
"$years " +
|
||||
"${String.format("%02d", hours)}:" +
|
||||
"${String.format("%02d", minutes)}:" +
|
||||
"${String.format("%02d", seconds)}"
|
||||
|
||||
fun getDayNameFull() = DAY_NAMES[dayOfWeek]
|
||||
fun getDayNameShort() = DAY_NAMES_SHORT[dayOfWeek]
|
||||
fun getMonthNameFull() = MONTH_NAMES[months - 1]
|
||||
fun getMonthNameShort() = MONTH_NAMES_SHORT[months - 1]
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.GameWorldExtension
|
||||
import net.torvald.terrarum.modulebasegame.gameworld.WorldTime
|
||||
import net.torvald.terrarum.gameworld.WorldTime
|
||||
import net.torvald.terrarum.modulebasegame.imagefont.WatchFont
|
||||
import net.torvald.terrarum.ui.UICanvas
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
|
||||
Reference in New Issue
Block a user