Some sky implementation work

This commit is contained in:
DBotThePony 2024-03-30 20:12:40 +07:00
parent ff746f43ae
commit cb63b47b12
Signed by: DBot
GPG Key ID: DCC23B5715498507
14 changed files with 292 additions and 55 deletions

View File

@ -1,7 +1,6 @@
package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableList
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.io.StreamCodec
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either
@ -41,10 +40,10 @@ enum class FlyingType {
}
}
enum class WarpPhase(val stupidassbitch: Int) {
SLOWING_DOWN(-1),
MAINTAIN(0),
SPEEDING_UP(1)
enum class WarpPhase {
SLOWING_DOWN,
MAINTAIN,
SPEEDING_UP;
}
enum class SkyOrbiterType(override val jsonName: String) : IStringSerializable {
@ -168,12 +167,40 @@ data class SkyParameters(
}
}
@JsonFactory
data class SkyGlobalConfig(
val stars: Stars,
val disembarkOrigin: PathPiece,
val disembarkPath: ImmutableList<PathPiece>,
val spaceDisembarkOrigin: PathPiece,
val spaceDisembarkPath: ImmutableList<PathPiece>,
val arrivalOrigin: PathPiece,
val arrivalPath: ImmutableList<PathPiece>,
val spaceArrivalOrigin: PathPiece,
val spaceArrivalPath: ImmutableList<PathPiece>,
val correctionPower: Double = 0.0,
val speedupTime: Double = 1.0,
val hyperspaceSpeedupTime: Double = 2.4,
val slowdownTime: Double = 1.0,
val hyperspaceSlowdownTime: Double = 2.0,
val flyMaxVelocity: Double = 0.0,
val starVelocityFactor: Double = 0.0,
val flyingTimer: Double = 0.0,
val flashTimer: Double = 1.0,
) {
@JsonFactory
data class Stars(
val frames: Int,
val list: ImmutableList<AssetPath>,
val hyperlist: ImmutableList<AssetPath>,
)
@JsonFactory
data class PathPiece(
val offset: Vector2d,
val rotation: Double = 0.0,
val time: Double = 0.0
) {
val rotationRad = Math.toDegrees(rotation)
}
}

View File

@ -34,6 +34,7 @@ import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.CentralStructureUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ChatReceivePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.EnvironmentUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.FindUniqueEntityResponsePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileUpdatePacket
@ -419,7 +420,7 @@ class PacketRegistry(val isLegacy: Boolean) {
LEGACY.add(::TileDamageUpdatePacket)
LEGACY.skip("TileModificationFailure")
LEGACY.skip("GiveItem")
LEGACY.skip("EnvironmentUpdate")
LEGACY.add(::EnvironmentUpdatePacket)
LEGACY.skip("UpdateTileProtection")
LEGACY.skip("SetDungeonGravity")
LEGACY.skip("SetDungeonBreathable")

View File

@ -0,0 +1,22 @@
package ru.dbotthepony.kstarbound.network.packets.clientbound
import it.unimi.dsi.fastutil.bytes.ByteArrayList
import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.network.IClientPacket
import java.io.DataInputStream
import java.io.DataOutputStream
class EnvironmentUpdatePacket(val sky: ByteArrayList, val weather: ByteArrayList) : IClientPacket {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(ByteArrayList.wrap(stream.readByteArray()), ByteArrayList.wrap(stream.readByteArray()))
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByteArray(sky.elements(), 0, sky.size)
stream.writeByteArray(weather.elements(), 0, weather.size)
}
override fun play(connection: ClientConnection) {
TODO("Not yet implemented")
}
}

View File

@ -9,6 +9,7 @@ import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.defs.WarpAction
import ru.dbotthepony.kstarbound.defs.WarpAlias
import ru.dbotthepony.kstarbound.defs.WorldID
import ru.dbotthepony.kstarbound.defs.world.SkyType
import ru.dbotthepony.kstarbound.network.Connection
import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.ConnectionType
@ -23,7 +24,7 @@ import ru.dbotthepony.kstarbound.server.world.ServerWorld
import java.util.HashMap
import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.TimeUnit
import kotlin.properties.Delegates
// serverside part of connection

View File

@ -83,7 +83,7 @@ class ServerWorld private constructor(
if (action != null)
client.send(PlayerWarpResultPacket(true, action, false))
client.tracker?.remove()
client.tracker?.remove("Transiting to new world")
clients.add(ServerWorldTracker(this, client, start))
}

View File

@ -20,6 +20,7 @@ import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.CentralStructureUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.EnvironmentUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
@ -141,6 +142,12 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
return
}
run {
val (data, version) = world.sky.networkedGroup.write(skyVersion, isLegacy = client.isLegacy)
skyVersion = version
send(EnvironmentUpdatePacket(data, ByteArrayList()))
}
run {
var next = tasks.poll()
@ -269,7 +276,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
}
}
fun remove() {
fun remove(reason: String = "ServerWorldTracker got removed") {
if (isRemoved.compareAndSet(false, true)) {
// erase all tasks just to be sure
tasks.clear()
@ -283,7 +290,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
client.tracker = null
client.playerEntity = null
client.worldID = WorldID.Limbo
client.send(WorldStopPacket("Removed"))
client.send(WorldStopPacket(reason))
}
}

View File

@ -15,6 +15,9 @@ fun String.sbIntern2(): String {
return Starbound.STRINGS.intern(this.intern())
}
val JsonElement.asStringOrNull: String?
get() = if (isJsonNull) null else asString
fun traverseJsonPath(path: String?, element: JsonElement?): JsonElement? {
element ?: return null
path ?: return element

View File

@ -1,65 +1,89 @@
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.util.value
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.GlobalDefaults
import ru.dbotthepony.kstarbound.Starbound
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.NetworkedGroup
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
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.networkedUnsignedInt
import ru.dbotthepony.kstarbound.network.syncher.networkedVec2f
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
class Sky() {
private val skyParametersNetState = networkedJson(SkyParameters())
val networkedGroup = MasterElement(NetworkedGroup())
private val skyTypeNetState = networkedEnumStupid(SkyType.ORBITAL)
private val timeNetState = networkedDouble()
private val flyingTypeNetState = networkedUnsignedInt()
private val enterHyperspaceNetState = networkedBoolean()
private val startInWarpNetState = networkedBoolean()
private val worldMoveNetState = networkedEnumStupid(WarpPhase.MAINTAIN)
private val starMoveNetState = networkedVec2f()
private val warpPhaseNetState = networkedVec2f()
private val flyingTimerNetState = networkedFloat()
private val skyParametersNetState = networkedGroup.upstream.add(networkedJson(SkyParameters()))
var skyType by skyTypeNetState
var skyType by networkedGroup.upstream.add(networkedEnumStupid(SkyType.ORBITAL))
var time by networkedGroup.upstream.add(networkedDouble())
private set
var time by timeNetState
var flyingType by networkedGroup.upstream.add(networkedEnum(FlyingType.NONE))
private set
var flyingType by flyingTypeNetState
var enterHyperspace by networkedGroup.upstream.add(networkedBoolean())
private set
var enterHyperspace by enterHyperspaceNetState
var startInWarp by networkedGroup.upstream.add(networkedBoolean())
private set
var startInWarp by startInWarpNetState
var warpPhase by networkedGroup.upstream.add(networkedData(WarpPhase.MAINTAIN, VarIntValueCodec.map({ WarpPhase.entries[this - 1] }, { ordinal - 1 })))
private set
var worldMove by worldMoveNetState
var worldMoveOffset by networkedGroup.upstream.add(networkedVec2f())
private set
var starMove by starMoveNetState
var starMoveOffset by networkedGroup.upstream.add(networkedVec2f())
private set
var warpPhase by warpPhaseNetState
private set
var flyingTimer by flyingTimerNetState
var flyingTimer by networkedGroup.upstream.add(networkedFloat())
private set
val networkedGroup = MasterElement(NetworkedGroup(
skyParametersNetState,
skyTypeNetState,
timeNetState,
flyingTypeNetState,
enterHyperspaceNetState,
startInWarpNetState,
worldMoveNetState,
starMoveNetState,
warpPhaseNetState,
flyingTimerNetState,
))
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 starRotation: Double = 0.0
private set
var pathRotation: Double = 0.0
private set
var destination: SkyParameters? = null
private set
val speedupTime: Double get() {
if (enterHyperspace) {
return GlobalDefaults.sky.hyperspaceSpeedupTime.coerceAtLeast(0.01)
} else {
return GlobalDefaults.sky.speedupTime.coerceAtLeast(0.01)
}
}
val slowdownTime: Double get() {
if (enterHyperspace) {
return GlobalDefaults.sky.hyperspaceSlowdownTime.coerceAtLeast(0.01)
} else {
return GlobalDefaults.sky.slowdownTime.coerceAtLeast(0.01)
}
}
constructor(parameters: SkyParameters, inOrbit: Boolean) : this() {
skyParametersNetState.value = parameters.copy()
@ -70,4 +94,158 @@ class Sky() {
skyType = parameters.skyType
}
}
fun startFlying(enterHyperspace: Boolean, startInWarp: Boolean = false) {
if (startInWarp)
flyingType = FlyingType.WARP
else
flyingType = FlyingType.DISEMBARKING
flyingTimer = 0.0
this.enterHyperspace = enterHyperspace
this.startInWarp = startInWarp
}
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)) * GlobalDefaults.sky.flyMaxVelocity / 2.0 * speedupTime
starMoveOffset = Vector2d(x = GlobalDefaults.sky.flyMaxVelocity * GlobalDefaults.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 -> {
}
WarpPhase.MAINTAIN -> {
flashTimer = GlobalDefaults.sky.flashTimer
skyType = SkyType.WARP
sentSFX = false
}
WarpPhase.SPEEDING_UP -> {
flyingTimer = 0.0
}
}
}
lastFlyingType = flyingType
}
fun tick(delta: Double = Starbound.TIMESTEP) {
time += delta
flashTimer = (flashTimer - delta).coerceAtLeast(0.0)
if (flyingType != FlyingType.NONE) {
flyingTimer += delta
if (flyingType == FlyingType.DISEMBARKING) {
val finished = if (skyParametersNetState.value.skyType == SkyType.SPACE)
controlledMovement(GlobalDefaults.sky.spaceDisembarkPath, GlobalDefaults.sky.spaceDisembarkOrigin, flyingTimer)
else
controlledMovement(GlobalDefaults.sky.disembarkPath, GlobalDefaults.sky.disembarkOrigin, flyingTimer)
if (finished) {
flyingType = FlyingType.WARP
}
} else if (flyingType == FlyingType.ARRIVING) {
val finished = if (skyParametersNetState.value.skyType == SkyType.SPACE)
controlledMovement(GlobalDefaults.sky.spaceArrivalPath, GlobalDefaults.sky.spaceArrivalOrigin, flyingTimer)
else
controlledMovement(GlobalDefaults.sky.arrivalPath, GlobalDefaults.sky.arrivalOrigin, flyingTimer)
if (finished) {
flyingType = FlyingType.NONE
}
starOffset -= starOffset * GlobalDefaults.sky.correctionPower
worldOffset -= worldOffset * GlobalDefaults.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 * GlobalDefaults.sky.flyMaxVelocity * delta * GlobalDefaults.sky.starVelocityFactor
worldOffset = worldMoveOffset
}
if (warpPhase == WarpPhase.SPEEDING_UP && flyingTimer >= speedupTime && !enterHyperspace && destination != null) {
skyParametersNetState.value = 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 >= GlobalDefaults.sky.flyingTimer && destination != null) {
skyParametersNetState.value = 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
}
}

View File

@ -266,11 +266,11 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
entities.values.forEach { it.tick() }
mailbox.executeQueuedTasks()
for (chunk in chunkMap) {
for (chunk in chunkMap)
chunk.tick()
}
mailbox.executeQueuedTasks()
sky.tick()
}
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType

View File

@ -78,7 +78,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
get() = true
protected open fun onJoinWorld(world: World<*, *>) { }
protected open fun onRemove(world: World<*, *>) { }
protected open fun onRemove(world: World<*, *>, isDeath: Boolean) { }
val networkGroup = MasterElement(NetworkedGroup())
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
@ -116,7 +116,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
mailbox.shutdownNow()
check(world.entities.remove(entityID) == this) { "Tried to remove $this from $world, but removed something else!" }
onRemove(world)
onRemove(world, isDeath)
spatialEntry?.remove()
spatialEntry = null
innerWorld = null

View File

@ -41,7 +41,7 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) {
forceChunkRepos = true
}
override fun onRemove(world: World<*, *>) {
override fun onRemove(world: World<*, *>, isDeath: Boolean) {
world.dynamicEntities.remove(this)
movement.remove()
}

View File

@ -28,6 +28,6 @@ abstract class TileEntity(path: String) : AbstractEntity(path) {
tilePosition = tilePosition
}
override fun onRemove(world: World<*, *>) {
override fun onRemove(world: World<*, *>, isDeath: Boolean) {
}
}

View File

@ -20,6 +20,7 @@ import ru.dbotthepony.kstarbound.server.world.ServerWorld
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.util.asStringOrNull
import ru.dbotthepony.kstarbound.world.Side
import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
@ -35,10 +36,7 @@ open class WorldObject(
orientationIndex = data.get("orientationIndex", -1)
interactive = data.get("interactive", false)
data["uniqueId"]?.let {
if (!it.isJsonNull)
uniqueId = it.asString
}
uniqueId = data["uniqueId"]?.asStringOrNull
for ((k, v) in data.get("parameters") { JsonObject() }.entrySet()) {
properties[k] = v.deepCopy()

View File

@ -107,8 +107,8 @@ class PlayerEntity() : HumanoidActorEntity("/") {
metaFixture = spatialEntry!!.Fixture()
}
override fun onRemove(world: World<*, *>) {
super.onRemove(world)
override fun onRemove(world: World<*, *>, isDeath: Boolean) {
super.onRemove(world, isDeath)
metaFixture?.remove()
metaFixture = null
}