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.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.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, 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 } }