KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/world/Sky.kt
2024-04-11 15:06:32 +07:00

283 lines
8.9 KiB
Kotlin

package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kommons.io.VarIntValueCodec
import ru.dbotthepony.kommons.io.map
import ru.dbotthepony.kommons.math.linearInterpolation
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kommons.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.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())
private set
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
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 (startInWarp)
flyingType = FlyingType.WARP
else if (flyingType == FlyingType.NONE)
flyingType = FlyingType.DISEMBARKING
// flyingTimer = 0.0
this.enterHyperspace = enterHyperspace
this.startInWarp = startInWarp
}
fun stopFlyingAt(destination: SkyParameters) {
this.destination = destination
if (skyType != SkyType.WARP)
skyType = SkyType.ORBITAL
}
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 = linearInterpolation(percentage, previous.offset, entry.offset)
pathRotation = linearInterpolation(percentage, previous.rotation, entry.rotation)
return false
}
previous = entry
}
return true
}
}