346 lines
11 KiB
Kotlin
346 lines
11 KiB
Kotlin
package ru.dbotthepony.kstarbound.world
|
|
|
|
import ru.dbotthepony.kstarbound.io.map
|
|
import ru.dbotthepony.kommons.math.linearInterpolation
|
|
import ru.dbotthepony.kommons.util.getValue
|
|
import ru.dbotthepony.kommons.util.setValue
|
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|
import ru.dbotthepony.kstarbound.Globals
|
|
import ru.dbotthepony.kstarbound.defs.world.FlyingType
|
|
import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig
|
|
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
|
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
|
import ru.dbotthepony.kstarbound.defs.world.WarpPhase
|
|
import ru.dbotthepony.kstarbound.io.VarIntValueCodec
|
|
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedEnum
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedEnumStupid
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedJson
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedVec2f
|
|
import ru.dbotthepony.kstarbound.util.IClock
|
|
import kotlin.math.cos
|
|
import kotlin.math.pow
|
|
import kotlin.math.sin
|
|
|
|
class Sky() {
|
|
val networkedGroup = MasterElement(NetworkedGroup())
|
|
|
|
var skyParameters by networkedGroup.upstream.add(networkedJson(SkyParameters()))
|
|
var skyType by networkedGroup.upstream.add(networkedEnumStupid(SkyType.ORBITAL))
|
|
var time by networkedGroup.upstream.add(networkedDouble())
|
|
|
|
var flyingType by networkedGroup.upstream.add(networkedEnum(FlyingType.NONE))
|
|
private set
|
|
var enterHyperspace by networkedGroup.upstream.add(networkedBoolean())
|
|
private set
|
|
var startInWarp by networkedGroup.upstream.add(networkedBoolean())
|
|
private set
|
|
var worldMoveOffset by networkedGroup.upstream.add(networkedVec2f())
|
|
private set
|
|
var starMoveOffset by networkedGroup.upstream.add(networkedVec2f())
|
|
private set
|
|
var warpPhase by networkedGroup.upstream.add(networkedData(WarpPhase.MAINTAIN, VarIntValueCodec.map({ WarpPhase.entries[this - 1] }, { ordinal - 1 })))
|
|
private set
|
|
var flyingTimer by networkedGroup.upstream.add(networkedFloat())
|
|
private set
|
|
|
|
var flashTimer = 0.0
|
|
private set
|
|
|
|
var starOffset = Vector2d.ZERO
|
|
private set
|
|
var worldOffset = Vector2d.ZERO
|
|
private set
|
|
var pathOffset = Vector2d.ZERO
|
|
private set
|
|
|
|
var worldRotation: Double = 0.0
|
|
private set
|
|
var starRotation: Double = 0.0
|
|
private set
|
|
var pathRotation: Double = 0.0
|
|
private set
|
|
|
|
val dayLength: Double
|
|
get() = skyParameters.dayLength ?: 1000.0
|
|
|
|
val day: Int
|
|
get() = if (dayLength <= 1.0) 0 else (time / dayLength).toInt()
|
|
|
|
val timeOfDay: Double
|
|
get() = if (dayLength <= 1.0) 0.0 else time % dayLength
|
|
|
|
var destination: SkyParameters? = null
|
|
private set
|
|
|
|
var referenceClock: IClock? = null
|
|
set(value) {
|
|
field = value
|
|
time = value?.time ?: time
|
|
}
|
|
|
|
val speedupTime: Double get() {
|
|
if (enterHyperspace) {
|
|
return Globals.sky.hyperspaceSpeedupTime.coerceAtLeast(0.01)
|
|
} else {
|
|
return Globals.sky.speedupTime.coerceAtLeast(0.01)
|
|
}
|
|
}
|
|
|
|
val slowdownTime: Double get() {
|
|
if (enterHyperspace) {
|
|
return Globals.sky.hyperspaceSlowdownTime.coerceAtLeast(0.01)
|
|
} else {
|
|
return Globals.sky.slowdownTime.coerceAtLeast(0.01)
|
|
}
|
|
}
|
|
|
|
constructor(parameters: SkyParameters) : this() {
|
|
skyParameters = parameters.copy()
|
|
skyType = parameters.skyType
|
|
}
|
|
|
|
fun startFlying(enterHyperspace: Boolean, startInWarp: Boolean = false) {
|
|
if (flyingType == FlyingType.NONE)
|
|
flyingTimer = 0.0
|
|
else if (flyingType == FlyingType.WARP && warpPhase == WarpPhase.SLOWING_DOWN) {
|
|
warpPhase = WarpPhase.SPEEDING_UP
|
|
flyingTimer = speedupTime
|
|
}
|
|
|
|
if (startInWarp)
|
|
flyingType = FlyingType.WARP
|
|
else if (flyingType == FlyingType.NONE)
|
|
flyingType = FlyingType.DISEMBARKING
|
|
|
|
this.enterHyperspace = enterHyperspace
|
|
this.startInWarp = startInWarp
|
|
}
|
|
|
|
fun stopFlyingAt(destination: SkyParameters) {
|
|
this.destination = destination
|
|
|
|
if (skyType != SkyType.WARP)
|
|
skyType = SkyType.ORBITAL
|
|
|
|
if (flyingType == FlyingType.NONE || flyingType == FlyingType.ARRIVING) {
|
|
this.skyParameters = destination
|
|
this.destination = null
|
|
}
|
|
}
|
|
|
|
private var lastFlyingType = FlyingType.NONE
|
|
private var lastWarpPhase = WarpPhase.MAINTAIN
|
|
private var sentSFX = false
|
|
|
|
private fun stateUpdate() {
|
|
if (flyingType != lastFlyingType) {
|
|
flyingTimer = 0.0
|
|
|
|
if (flyingType == FlyingType.WARP) {
|
|
warpPhase = WarpPhase.SPEEDING_UP
|
|
|
|
if (startInWarp) {
|
|
if (enterHyperspace) {
|
|
warpPhase = WarpPhase.MAINTAIN
|
|
} else {
|
|
flyingTimer = speedupTime
|
|
}
|
|
|
|
lastWarpPhase = warpPhase
|
|
}
|
|
|
|
worldMoveOffset = Vector2d(cos(pathRotation), sin(pathRotation)) * Globals.sky.flyMaxVelocity / 2.0 * speedupTime
|
|
starMoveOffset = Vector2d(x = Globals.sky.flyMaxVelocity * Globals.sky.starVelocityFactor / 2.0 * speedupTime)
|
|
} else if (flyingType == FlyingType.ARRIVING) {
|
|
sentSFX = false
|
|
worldOffset = Vector2d.ZERO
|
|
starOffset = Vector2d.ZERO
|
|
}
|
|
}
|
|
|
|
if (warpPhase != lastWarpPhase) {
|
|
flyingTimer = 0.0
|
|
|
|
when (warpPhase) {
|
|
WarpPhase.SLOWING_DOWN -> {
|
|
skyType = SkyType.ORBITAL
|
|
//flashTimer = GlobalDefaults.sky.flashTimer
|
|
sentSFX = false
|
|
|
|
val origin = if (skyType == SkyType.SPACE) Globals.sky.spaceArrivalOrigin else Globals.sky.arrivalOrigin
|
|
val path = if (skyType == SkyType.SPACE) Globals.sky.spaceArrivalPath else Globals.sky.arrivalPath
|
|
|
|
pathOffset = origin.offset
|
|
pathRotation = origin.rotationRad
|
|
|
|
var exitDistance = Globals.sky.flyMaxVelocity / 2.0 * slowdownTime
|
|
worldMoveOffset = Vector2d(x = exitDistance)
|
|
worldOffset = worldMoveOffset
|
|
|
|
exitDistance *= Globals.sky.starVelocityFactor
|
|
starMoveOffset = Vector2d(x = exitDistance)
|
|
starOffset = starMoveOffset
|
|
|
|
worldRotation = 0.0
|
|
starRotation = 0.0
|
|
flyingTimer = 0.0
|
|
}
|
|
|
|
WarpPhase.MAINTAIN -> {
|
|
//flashTimer = GlobalDefaults.sky.flashTimer
|
|
|
|
skyType = SkyType.WARP
|
|
sentSFX = false
|
|
}
|
|
|
|
WarpPhase.SPEEDING_UP -> {
|
|
flyingTimer = 0.0
|
|
}
|
|
}
|
|
}
|
|
|
|
lastFlyingType = flyingType
|
|
lastWarpPhase = warpPhase
|
|
}
|
|
|
|
|
|
fun tick(delta: Double) {
|
|
time = referenceClock?.time ?: (time + delta)
|
|
flashTimer = (flashTimer - delta).coerceAtLeast(0.0)
|
|
|
|
if (flyingType != FlyingType.NONE) {
|
|
flyingTimer += delta
|
|
|
|
if (flyingType == FlyingType.DISEMBARKING) {
|
|
val finished = if (skyParameters.skyType == SkyType.SPACE)
|
|
controlledMovement(Globals.sky.spaceDisembarkPath, Globals.sky.spaceDisembarkOrigin, flyingTimer)
|
|
else
|
|
controlledMovement(Globals.sky.disembarkPath, Globals.sky.disembarkOrigin, flyingTimer)
|
|
|
|
if (finished) {
|
|
flyingType = FlyingType.WARP
|
|
}
|
|
} else if (flyingType == FlyingType.ARRIVING) {
|
|
val finished = if (skyParameters.skyType == SkyType.SPACE)
|
|
controlledMovement(Globals.sky.spaceArrivalPath, Globals.sky.spaceArrivalOrigin, flyingTimer)
|
|
else
|
|
controlledMovement(Globals.sky.arrivalPath, Globals.sky.arrivalOrigin, flyingTimer)
|
|
|
|
if (finished) {
|
|
flyingType = FlyingType.NONE
|
|
}
|
|
|
|
starOffset -= starOffset * Globals.sky.correctionPower
|
|
worldOffset -= worldOffset * Globals.sky.correctionPower
|
|
} else if (flyingType == FlyingType.WARP) {
|
|
val percentage = when (warpPhase) {
|
|
WarpPhase.SLOWING_DOWN -> (flyingTimer / speedupTime).pow(2.0)
|
|
WarpPhase.MAINTAIN -> 1.0
|
|
WarpPhase.SPEEDING_UP -> (1.0 - flyingTimer / speedupTime).pow(2.0)
|
|
}
|
|
|
|
if (percentage < 1.0) {
|
|
val dir = warpPhase.ordinal - 1.0
|
|
starOffset = (starMoveOffset * percentage * dir).rotate(-(starRotation + pathRotation))
|
|
worldOffset = (worldMoveOffset * percentage * dir).rotate(-(starRotation + pathRotation))
|
|
} else {
|
|
val angle = -(starRotation + pathRotation)
|
|
val angleVec = Vector2d(cos(angle), sin(angle))
|
|
starOffset += angleVec * Globals.sky.flyMaxVelocity * delta * Globals.sky.starVelocityFactor
|
|
worldOffset = worldMoveOffset
|
|
}
|
|
|
|
if (warpPhase == WarpPhase.SPEEDING_UP && flyingTimer >= speedupTime && !enterHyperspace && destination != null) {
|
|
skyParameters = destination!!
|
|
destination = null
|
|
warpPhase = WarpPhase.SLOWING_DOWN
|
|
} else if (warpPhase == WarpPhase.SPEEDING_UP && flyingTimer >= speedupTime && enterHyperspace) {
|
|
warpPhase = WarpPhase.MAINTAIN
|
|
} else if (warpPhase == WarpPhase.MAINTAIN && flyingTimer >= Globals.sky.flyingTimer && destination != null) {
|
|
skyParameters = destination!!
|
|
destination = null
|
|
warpPhase = WarpPhase.SLOWING_DOWN
|
|
} else if (warpPhase == WarpPhase.SLOWING_DOWN && flyingTimer >= slowdownTime) {
|
|
flyingType = FlyingType.ARRIVING
|
|
}
|
|
}
|
|
} else {
|
|
starOffset = Vector2d.ZERO
|
|
worldOffset = Vector2d.ZERO
|
|
pathOffset = Vector2d.ZERO
|
|
}
|
|
|
|
stateUpdate()
|
|
}
|
|
|
|
private fun controlledMovement(path: List<SkyGlobalConfig.PathPiece>, origin: SkyGlobalConfig.PathPiece, timeOffset: Double): Boolean {
|
|
var previous = origin
|
|
var stepTime = 0.0
|
|
|
|
for (entry in path) {
|
|
stepTime += entry.time
|
|
|
|
if (timeOffset <= stepTime) {
|
|
val percentage = (timeOffset - previous.time) / (stepTime - previous.time)
|
|
pathOffset = ru.dbotthepony.kstarbound.math.vector.linearInterpolation(percentage, previous.offset, entry.offset)
|
|
pathRotation = linearInterpolation(percentage, previous.rotation, entry.rotation)
|
|
return false
|
|
}
|
|
|
|
previous = entry
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
val dayLevel: Double get() {
|
|
// Turn the dayCycle value into a value that blends evenly between 0.0 at
|
|
// mid-night and 1.0 at mid-day and then back again.
|
|
|
|
val dayCycle = dayCycle
|
|
|
|
if (dayCycle < 1.0)
|
|
return dayCycle / 2.0 + 0.5
|
|
else if (dayCycle > 3.0)
|
|
return (dayCycle - 3.0) / 2.0
|
|
else
|
|
return 1.0 - (dayCycle - 1.0) / 2.0
|
|
}
|
|
|
|
val dayCycle: Double get() {
|
|
// Always middle of the night in orbit or warp space.
|
|
if (skyType == SkyType.ORBITAL || skyType == SkyType.WARP)
|
|
return 3.0
|
|
|
|
var transitionTime = Globals.sky.dayTransitionTime
|
|
val dayLength = dayLength
|
|
val timeOfDay = timeOfDay
|
|
|
|
// Original sources put this situation as follows:
|
|
// This will misbehave badly if dayTransitionTime is greater than dayLength / 2
|
|
// So, let's fix it then.
|
|
// If sunset/sunrise duration is greater than third of day length, then
|
|
// clamp sunset/sunrise to fourth of day length
|
|
if (transitionTime > dayLength / 3.0) {
|
|
transitionTime = dayLength / 4.0
|
|
}
|
|
|
|
// timeOfDay() is defined such that 0.0 is mid-dawn. For convenience, shift
|
|
// the time of day forwards such that 0.0 is the beginning of the morning.
|
|
val shiftedTime = (timeOfDay + transitionTime / 2.0) % dayLength
|
|
|
|
// There are 5 times here, beginning of the morning, end of the morning,
|
|
// beginning of the evening, end of the evening, and then the beginning of
|
|
// the morning again (wrapping around).
|
|
|
|
// TODO()
|
|
return 1.0
|
|
}
|
|
}
|