diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index fedc064f..1272db60 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -703,7 +703,7 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo private fun renderWorld(world: ClientWorld) { updateViewportParams() - world.tick() + world.tick(Starbound.TIMESTEP) stack.clear(Matrix3f.identity()) @@ -772,7 +772,7 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo GLFW.glfwPollEvents() if (world != null && Starbound.initialized) - world.tick() + world.tick(Starbound.TIMESTEP) activeConnection?.flush() return diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/math/PeriodicFunction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/math/PeriodicFunction.kt index 4970a000..57e4a8ba 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/math/PeriodicFunction.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/math/PeriodicFunction.kt @@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound.math import ru.dbotthepony.kommons.math.linearInterpolation import java.util.random.RandomGenerator -import kotlin.math.PI -import kotlin.math.sin data class PeriodicFunction( val period: Double = 1.0, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt index b96b7032..cac34a4e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt @@ -72,7 +72,7 @@ data class ClientConnectPacket( connection.receiveShipChunks(shipChunks) connection.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation)) - connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.seconds)) + connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.time)) connection.channel.flush() connection.inGame() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/BasicNetworkedElement.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/BasicNetworkedElement.kt index 7fb130a5..df78c034 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/BasicNetworkedElement.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/BasicNetworkedElement.kt @@ -10,7 +10,7 @@ import java.util.function.Consumer open class BasicNetworkedElement(private var value: TYPE, protected val codec: StreamCodec, protected val legacyCodec: StreamCodec, protected val toLegacy: (TYPE) -> LEGACY, protected val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate { protected val valueListeners = Listenable.Impl() - protected val queue = LinkedList>() + protected val queue = InterpolationQueue { this.value = it; valueListeners.accept(value) } protected var isInterpolating = false protected var currentTime = 0.0 @@ -48,17 +48,9 @@ open class BasicNetworkedElement(private var value: TYPE, protecte override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) { if (isLegacy) { - if (queue.isNotEmpty()) { - legacyCodec.write(data, toLegacy(queue.last.second)) - } else { - legacyCodec.write(data, toLegacy(value)) - } + legacyCodec.write(data, toLegacy(queue.last { value })) } else { - if (queue.isNotEmpty()) { - codec.write(data, queue.last.second) - } else { - codec.write(data, value) - } + codec.write(data, queue.last { value }) } } @@ -67,19 +59,7 @@ open class BasicNetworkedElement(private var value: TYPE, protecte bumpVersion() if (isInterpolating) { - // Only append an incoming delta to our pending value list if the incoming - // step is forward in time of every other pending value. In any other - // case, this is an error or the step tracking is wildly off, so just clear - // any other incoming values. - val actualDelay = interpolationDelay + currentTime - - if (interpolationDelay > 0.0 && (queue.isEmpty() || queue.last.first <= actualDelay)) { - queue.add(actualDelay to read) - } else { - value = read - queue.clear() - valueListeners.accept(read) - } + queue.push(read, interpolationDelay) } else { value = read valueListeners.accept(read) @@ -90,38 +70,22 @@ open class BasicNetworkedElement(private var value: TYPE, protecte writeInitial(data, isLegacy) } - override fun readBlankDelta(interpolationDelay: Double) { - // TODO: original engine doesn't override this, is this intentional? - // tickInterpolation(interpolationDelay) - } + override fun readBlankDelta(interpolationDelay: Double) {} override fun enableInterpolation(extrapolation: Double) { if (!isInterpolating) { isInterpolating = true - queue.clear() } } override fun disableInterpolation() { if (isInterpolating) { isInterpolating = false - - if (queue.isNotEmpty()) { - value = queue.last.second - valueListeners.accept(value) - } - queue.clear() } } override fun tickInterpolation(delta: Double) { - require(delta >= 0.0) { "Negative interpolation delta: $delta" } - currentTime += delta - - while (queue.isNotEmpty() && queue.first.first <= currentTime) { - value = queue.removeFirst().second - valueListeners.accept(value) - } + queue.tick(delta) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt index cc010e10..070efe59 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt @@ -70,7 +70,8 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va } } - private val queue = ArrayDeque>() + private data class QueueEntry(val time: Double, val value: Double) + private val queue = ArrayDeque() var currentTime = 0.0 private set @@ -95,7 +96,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va if (isInterpolating) { queue.clear() - queue.add(currentTime to t) + queue.add(QueueEntry(currentTime, t)) } valueListeners.accept(t) @@ -130,13 +131,13 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va queue.clear() if (isInterpolating) { - queue.add(currentTime to value) + queue.add(QueueEntry(currentTime, value)) } } override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) { if (queue.isNotEmpty()) - (if (isLegacy) legacyOps else ops).write(data, queue.last().second) + (if (isLegacy) legacyOps else ops).write(data, queue.last().value) else (if (isLegacy) legacyOps else ops).write(data, value) } @@ -146,10 +147,10 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va if (isInterpolating) { val realDelay = interpolationDelay + currentTime - if (queue.last().first > realDelay) + if (queue.last().time > realDelay) queue.clear() - queue.add(realDelay to read) + queue.add(QueueEntry(realDelay, read)) value = interpolated() valueListeners.accept(value) } else { @@ -172,7 +173,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va if (actual < last) queue.clear() - queue.add(actual to lastPoint) + queue.add(QueueEntry(actual, lastPoint)) val old = value value = interpolated() @@ -187,7 +188,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va if (!isInterpolating) { isInterpolating = true queue.clear() - queue.add(currentTime to value) + queue.add(QueueEntry(currentTime, value)) } this.extrapolation = extrapolation @@ -198,7 +199,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va isInterpolating = false if (queue.isNotEmpty()) { - value = queue.last().second + value = queue.last().value } queue.clear() @@ -210,7 +211,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va currentTime += delta if (isInterpolating && queue.size >= 2) { - while (queue.size > 2 && queue[1].first <= currentTime) { + while (queue.size > 2 && queue[1].time <= currentTime) { queue.removeFirst() } @@ -229,7 +230,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va val actualTime = time + currentTime - if (actualTime < queue.first().first) { + if (actualTime < queue.first().time) { // extrapolate into past? val (time0, value0) = queue[0] val (time1, value1) = queue[1] @@ -240,7 +241,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va diff = 0.0 return interpolator.interpolate(diff, value0, value1) - } else if (actualTime > queue.last().first) { + } else if (actualTime > queue.last().time) { // extrapolate into future val (time0, value0) = queue[queue.size - 2] val (time1, value1) = queue[queue.size - 1] diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/InterpolationQueue.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/InterpolationQueue.kt new file mode 100644 index 00000000..1f6027a4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/InterpolationQueue.kt @@ -0,0 +1,95 @@ +package ru.dbotthepony.kstarbound.network.syncher + +import java.util.LinkedList + +class InterpolationQueue(private val visitor: (T) -> Unit) : Iterable { + private data class Entry(val time: Double, val value: T) : Comparable> { + override fun compareTo(other: Entry<*>): Int { + return time.compareTo(other.time) + } + } + + private var currentTime = 0.0 + private val queue = LinkedList>() + + /** + * Clears all interpolated values + */ + fun drop() { + queue.clear() + } + + /** + * Applies interpolation queue, and clears it + */ + fun clear() { + try { + queue.forEach { visitor(it.value) } + } finally { + queue.clear() + } + } + + override fun iterator(): Iterator { + return object : Iterator { + private val parent = queue.iterator() + + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): T { + return parent.next().value + } + } + } + + val isEmpty: Boolean + get() = queue.isEmpty() + + val isNotEmpty: Boolean + get() = queue.isNotEmpty() + + val size: Int + get() = queue.size + + fun last(): T { + return queue.last.value + } + + inline fun last(orElse: () -> T): T { + if (isEmpty) + return orElse.invoke() + else + return last() + } + + fun tick(delta: Double) { + require(delta >= 0.0) { "Negative interpolation delta: $delta" } + currentTime += delta + + while (queue.isNotEmpty() && queue[0].time <= currentTime) { + visitor(queue.removeAt(0).value) + } + } + + fun push(value: T, delay: Double, visitAllIfDeviated: Boolean = true) { + val actualTime = delay + currentTime + + // Only append an incoming delta to our pending value list if the incoming + // step is forward in time of every other pending value. In any other + // case, this is an error or the step tracking is wildly off, so just clear + // any other incoming values. + if (queue.isNotEmpty() && queue.last.time > actualTime) { + if (visitAllIfDeviated) + queue.forEach { visitor(it.value) } + + queue.clear() + } + + if (delay > 0L) + queue.add(Entry(actualTime, value)) + else + visitor(value) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedList.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedList.kt index ca76b96f..ab1d6ae6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedList.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedList.kt @@ -19,8 +19,8 @@ class NetworkedList( private val elementsFactory: (Int) -> MutableList = ::ArrayList ) : NetworkedElement(), MutableList { private val backlog = ArrayDeque>>() - private val queue = ArrayDeque>>() private val elements = elementsFactory(10) + private val queue = InterpolationQueue> { it.apply(elements) } private enum class Type { ADD, REMOVE, CLEAR; @@ -41,7 +41,6 @@ class NetworkedList( private val clearEntry = Entry(Type.CLEAR, 0, KOptional()) private var isInterpolating = false - private var currentTime = 0.0 private var isRemote = false private val listeners = CopyOnWriteArrayList() @@ -56,7 +55,7 @@ class NetworkedList( } private fun latestState(): List { - if (queue.isEmpty()) { + if (queue.isEmpty) { return elements } else { val copy = elementsFactory(elements.size) @@ -64,9 +63,7 @@ class NetworkedList( for (v in elements) copy.add(v) - for ((_, e) in queue) - e.apply(copy) - + queue.forEach { it.apply(copy) } return copy } } @@ -126,17 +123,7 @@ class NetworkedList( } if (isInterpolating) { - val actualTime = interpolationDelay + currentTime - - if (queue.isNotEmpty() && queue.last().first >= actualTime) { - queue.forEach { it.second.apply(elements) } - queue.clear() - } - - if (interpolationDelay > 0.0) - queue.add(actualTime to entry) - else - entry.apply(elements) + queue.push(entry, interpolationDelay) } else { entry.apply(elements) } @@ -186,16 +173,11 @@ class NetworkedList( override fun disableInterpolation() { isInterpolating = false - queue.forEach { it.second.apply(elements) } queue.clear() } override fun tickInterpolation(delta: Double) { - currentTime += delta - - while (queue.isNotEmpty() && queue.first().first <= currentTime) { - queue.removeFirst().second.apply(elements) - } + queue.tick(delta) } override fun hasChangedSince(version: Long): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedMap.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedMap.kt index d970136d..283119af 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedMap.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedMap.kt @@ -50,12 +50,12 @@ class NetworkedMap( init { map.addListener(object : ListenableMap.MapListener { override fun onClear() { - if (isReading) return + if (isReading > 0) return check(!isRemote) { "This map is not owned by this side" } // this is fragile (due to interpolation fuckery, we remove everything before applying delayed changes), // but let's hope it doesn't break - delayed.clear() + queue.clear() backlog.add(currentVersion() to clearEntry) purgeBacklog() @@ -63,7 +63,7 @@ class NetworkedMap( } override fun onValueAdded(key: K, value: V) { - if (isReading) return + if (isReading > 0) return check(!isRemote) { "This map is not owned by this side" } backlog.add(currentVersion() to Entry(Action.ADD, KOptional(nativeKey.copy(key)), KOptional(nativeValue.copy(value)))) purgeBacklog() @@ -71,7 +71,7 @@ class NetworkedMap( } override fun onValueRemoved(key: K, value: V) { - if (isReading) return + if (isReading > 0) return check(!isRemote) { "This map is not owned by this side" } backlog.add(currentVersion() to Entry(Action.REMOVE, KOptional(nativeKey.copy(key)), KOptional())) purgeBacklog() @@ -168,9 +168,14 @@ class NetworkedMap( private val clearEntry = Entry(Action.CLEAR, KOptional(), KOptional()) private val backlog = ArrayDeque>>() - private val delayed = ArrayDeque>>() - private var currentTime = 0.0 - private var isReading = false + + private val queue = InterpolationQueue> { + isReading++ + it.apply(this) + isReading-- + } + + private var isReading = 0 private var isRemote = false private var isInterpolating = false @@ -192,10 +197,10 @@ class NetworkedMap( override fun readInitial(data: DataInputStream, isLegacy: Boolean) { try { isRemote = true - isReading = true + isReading++ backlog.clear() - delayed.clear() + queue.clear() clear() backlog.add(currentVersion() to clearEntry) @@ -228,31 +233,31 @@ class NetworkedMap( purgeBacklog() } finally { - isReading = false + isReading-- } } override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) { if (isDumb && isLegacy) { - val construct = HashMap(size + delayed.size) + val construct = HashMap(size + queue.size) for ((k, v) in entries) { construct[k] = v } - for ((_, v) in delayed) { + for (v in queue) { v.apply(construct) } dumbCodec.write(data, construct) } else { - data.writeVarInt(size + delayed.size) + data.writeVarInt(size + queue.size) for ((k, v) in entries) { Entry(Action.ADD, KOptional(k), KOptional(v)).write(data, isLegacy, this) } - for ((_, v) in delayed) { + for (v in queue) { v.write(data, isLegacy, this) } } @@ -265,31 +270,18 @@ class NetworkedMap( } try { - isReading = true + isReading++ while (true) { when (val action = data.readUnsignedByte()) { 0 -> break - 1 -> { - readInitial(data, isLegacy) - isReading = true - } + 1 -> readInitial(data, isLegacy) 2 -> { val change = if (isLegacy) readLegacyEntry(data) else readNativeEntry(data) backlog.add(currentVersion() to change) if (isInterpolating) { - val actualDelay = interpolationDelay + currentTime - - if (delayed.isNotEmpty() && delayed.last().first > actualDelay) { - delayed.forEach { it.second.apply(this) } - delayed.clear() - } - - if (interpolationDelay > 0.0) - delayed.add(actualDelay to change) - else - change.apply(this) + queue.push(change, interpolationDelay) } else { change.apply(this) } @@ -301,7 +293,7 @@ class NetworkedMap( purgeBacklog() } finally { - isReading = false + isReading-- } } @@ -341,30 +333,12 @@ class NetworkedMap( override fun disableInterpolation() { if (isInterpolating) { isInterpolating = false - - try { - isReading = true - delayed.forEach { it.second.apply(this) } - delayed.clear() - } finally { - isReading = false - } - + queue.clear() purgeBacklog() } } override fun tickInterpolation(delta: Double) { - currentTime += delta - - try { - isReading = true - - while (delayed.isNotEmpty() && delayed.first().first <= currentTime) { - delayed.removeFirst().second.apply(this) - } - } finally { - isReading = false - } + queue.tick(delta) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedSignal.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedSignal.kt index 80d52e80..904dbff0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedSignal.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/NetworkedSignal.kt @@ -17,10 +17,9 @@ import java.io.DataOutputStream class NetworkedSignal(private val codec: StreamCodec, private val maxSize: Int = 100) : NetworkedElement(), Iterable, Iterator { private data class Signal(val version: Long, val signal: S) - private var currentTime = 0.0 private val visibleSignals = ArrayDeque>() private val internalSignals = ArrayDeque>() - private val delayedSignals = ArrayDeque>() + private val delayedSignals = InterpolationQueue { push(it) } private var isInterpolating = false override fun readInitial(data: DataInputStream, isLegacy: Boolean) {} @@ -34,14 +33,7 @@ class NetworkedSignal(private val codec: StreamCodec, private val maxSize: val signal = codec.read(data) if (isInterpolating && interpolationDelay > 0.0) { - val actualDelay = interpolationDelay + currentTime - - if (delayedSignals.isNotEmpty() && delayedSignals.last().first > actualDelay) { - delayedSignals.forEach { push(it.second) } - delayedSignals.clear() - } - - delayedSignals.add(actualDelay to signal) + delayedSignals.push(signal, interpolationDelay) } else { push(signal) } @@ -68,20 +60,11 @@ class NetworkedSignal(private val codec: StreamCodec, private val maxSize: override fun disableInterpolation() { isInterpolating = false - - for ((_, v) in delayedSignals) { - push(v) - } - delayedSignals.clear() } override fun tickInterpolation(delta: Double) { - currentTime += delta - - while (delayedSignals.isNotEmpty() && delayedSignals.first().first <= currentTime) { - push(delayedSignals.removeFirst().second) - } + delayedSignals.tick(delta) } val isEmpty: Boolean diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/IntegratedStarboundServer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/IntegratedStarboundServer.kt index 6ebd3623..dbdc855e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/IntegratedStarboundServer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/IntegratedStarboundServer.kt @@ -9,7 +9,7 @@ class IntegratedStarboundServer(val client: StarboundClient, root: File) : Starb channels.createLocalChannel() } - override fun tick0() { + override fun tick0(delta: Double) { if (client.isShutdown) { shutdown() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt index 1f3af7f3..80ec7a64 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt @@ -423,7 +423,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn warpQueue.trySend(destination to deploy) } - fun tick() { + fun tick(delta: Double) { if (!isConnected || !channel.isOpen) return diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt index bafb23a0..b14efce4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt @@ -187,11 +187,11 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread init { scheduleAtFixedRate(Runnable { - channels.broadcast(UniverseTimeUpdatePacket(universeClock.seconds)) + channels.broadcast(UniverseTimeUpdatePacket(universeClock.time)) }, Globals.universeServer.clockUpdatePacketInterval, Globals.universeServer.clockUpdatePacketInterval, TimeUnit.MILLISECONDS) scheduleAtFixedRate(Runnable { - tickNormal() + tickNormal(Starbound.TIMESTEP) }, Starbound.TIMESTEP_NANOS, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS) scheduleAtFixedRate(Runnable { @@ -232,7 +232,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread } protected abstract fun close0() - protected abstract fun tick0() + protected abstract fun tick0(delta: Double) private fun tickSystemWorlds() { systemWorlds.values.removeIf { @@ -246,7 +246,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread scope.launch { try { - it.get().tick() + it.get().tick(Starbound.SYSTEM_WORLD_TIMESTEP) } catch (err: Throwable) { LOGGER.fatal("Exception in system world $it event loop", err) } @@ -261,20 +261,20 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread } } - private fun tickNormal() { + private fun tickNormal(delta: Double) { try { // universeClock.nanos += Starbound.TIMESTEP_NANOS channels.connections.forEach { try { - it.tick() + it.tick(delta) } catch (err: Throwable) { LOGGER.error("Exception while ticking client connection", err) it.disconnect("Exception while ticking client connection: $err") } } - tick0() + tick0(delta) } catch (err: Throwable) { LOGGER.fatal("Exception in main server event loop", err) shutdown() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt index 9498e83b..6881a2ef 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt @@ -490,7 +490,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk val (x, y) = pos - val remove = !health.tick(cells.value[x, y].foreground.material.value.actualDamageTable) + val remove = !health.tick(cells.value[x, y].foreground.material.value.actualDamageTable, delta) onTileHealthUpdate(x, y, false, health) remove } backgroundHealth.entries.removeIf { (pos, health) -> val (x, y) = pos - val remove = !health.tick(cells.value[x, y].background.material.value.actualDamageTable) + val remove = !health.tick(cells.value[x, y].background.material.value.actualDamageTable, delta) onTileHealthUpdate(x, y, true, health) remove } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt index 50e7f0fa..1a49690a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerSystemWorld.kt @@ -150,7 +150,7 @@ class ServerSystemWorld : SystemWorld { private constructor(server: StarboundServer, location: Vector3i) : super(location, server.universeClock, server.universe) { this.server = server - this.lastSpawn = clock.seconds - Globals.systemWorld.objectSpawnCycle + this.lastSpawn = clock.time - Globals.systemWorld.objectSpawnCycle objectSpawnTime = random.nextRange(Globals.systemWorld.objectSpawnInterval) } @@ -173,27 +173,27 @@ class ServerSystemWorld : SystemWorld { val name = it.second.sample(random).orNull() ?: return@ifPresent val uuid = UUID(random.nextLong(), random.nextLong()) val prototype = Globals.systemObjects[name] ?: throw NullPointerException("Tried to create $name system world object, but there is no such object in /system_objects.config!") - val create = ServerEntity(prototype.create(uuid, name), uuid, randomObjectSpawnPosition(), clock.seconds) - create.enterOrbit(UniversePos(location), Vector2d.ZERO, clock.seconds) // orbit center of system + val create = ServerEntity(prototype.create(uuid, name), uuid, randomObjectSpawnPosition(), clock.time) + create.enterOrbit(UniversePos(location), Vector2d.ZERO, clock.time) // orbit center of system } } } private suspend fun spawnObjects() { - var diff = Globals.systemWorld.objectSpawnCycle.coerceAtMost(clock.seconds - lastSpawn) - lastSpawn = clock.seconds - diff + var diff = Globals.systemWorld.objectSpawnCycle.coerceAtMost(clock.time - lastSpawn) + lastSpawn = clock.time - diff while (diff > objectSpawnTime) { lastSpawn += objectSpawnTime objectSpawnTime = random.nextRange(Globals.systemWorld.objectSpawnInterval) - diff = clock.seconds - lastSpawn + diff = clock.time - lastSpawn Globals.systemWorld.objectSpawnPool.sample(random).ifPresent { val uuid = UUID(random.nextLong(), random.nextLong()) val config = Globals.systemObjects[it]?.create(uuid, it) ?: throw NullPointerException("Tried to create $it system world object, but there is no such object in /system_objects.config!") val pos = randomObjectSpawnPosition() - if (clock.seconds > lastSpawn + objectSpawnTime && config.moving) { + if (clock.time > lastSpawn + objectSpawnTime && config.moving) { // if this is not the last object we're spawning, and it's moving, immediately put it in orbit around a planet val targets = universe.children(systemLocation).filter { child -> entities.values.none { it.orbit.map { it.target == child }.orElse(false) } @@ -256,7 +256,7 @@ class ServerSystemWorld : SystemWorld { // in original engine, ticking happens at 20 updates per second // Since there is no Lua driven code, we can tick as fast as we want - suspend fun tick(delta: Double = Starbound.SYSTEM_WORLD_TIMESTEP) { + suspend fun tick(delta: Double) { var next = tasks.poll() while (next != null) { @@ -299,17 +299,17 @@ class ServerSystemWorld : SystemWorld { private val netVersions = Object2LongOpenHashMap() - suspend fun tick(delta: Double = Starbound.SYSTEM_WORLD_TIMESTEP) { + suspend fun tick(delta: Double) { val orbit = destination as? SystemWorldLocation.Orbit // if destination is an orbit we haven't started orbiting yet, update the time if (orbit != null) - destination = SystemWorldLocation.Orbit(orbit.position.copy(enterTime = clock.seconds)) + destination = SystemWorldLocation.Orbit(orbit.position.copy(enterTime = clock.time)) suspend fun nearPlanetOrbit(planet: UniversePos): Orbit { val toShip = planetPosition(planet) - position val angle = toShip.toAngle() - return Orbit(planet, 1, clock.seconds, Vector2d(cos(angle), sin(angle)) * (planetSize(planet) / 2.0 + Globals.systemWorld.clientShip.orbitDistance)) + return Orbit(planet, 1, clock.time, Vector2d(cos(angle), sin(angle)) * (planetSize(planet) / 2.0 + Globals.systemWorld.clientShip.orbitDistance)) } if (location is SystemWorldLocation.Celestial) { @@ -331,7 +331,7 @@ class ServerSystemWorld : SystemWorld { val destination: Vector2d if (this.orbit != null) { - this.orbit = this.orbit!!.copy(enterTime = clock.seconds) + this.orbit = this.orbit!!.copy(enterTime = clock.time) destination = orbitPosition(this.orbit!!) } else { destination = this.destination.resolve(this@ServerSystemWorld) ?: position @@ -446,8 +446,8 @@ class ServerSystemWorld : SystemWorld { var hasExpired = false private set - suspend fun tick(delta: Double = Starbound.SYSTEM_WORLD_TIMESTEP) { - if (!data.permanent && spawnTime > 0.0 && clock.seconds > spawnTime + data.lifeTime) + suspend fun tick(delta: Double) { + if (!data.permanent && spawnTime > 0.0 && clock.time > spawnTime + data.lifeTime) hasExpired = true val orbit = orbit.orNull() @@ -456,7 +456,7 @@ class ServerSystemWorld : SystemWorld { position = orbitPosition(orbit) } else if (data.permanent || !data.moving) { // permanent locations always have a solar orbit - enterOrbit(systemLocation, Vector2d.ZERO, clock.seconds) + enterOrbit(systemLocation, Vector2d.ZERO, clock.time) } else if (approach != null) { if (ships.values.any { it.location == location }) return @@ -468,9 +468,9 @@ class ServerSystemWorld : SystemWorld { position += toApproach.unitVector * data.speed * delta if ((approach - position).length < planetSize(this.approach!!) + data.orbitDistance) - enterOrbit(this.approach!!, approach, clock.seconds) + enterOrbit(this.approach!!, approach, clock.time) } else { - enterOrbit(approach!!, Vector2d.ZERO, clock.seconds) + enterOrbit(approach!!, Vector2d.ZERO, clock.time) } } else { val planets = universe.children(systemLocation).filter { child -> diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt index a12c2177..1851b292 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt @@ -141,7 +141,9 @@ class ServerWorld private constructor( } init { - eventLoop.scheduleAtFixedRate(::tick, 0L, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS) + eventLoop.scheduleAtFixedRate(Runnable { + tick(Starbound.TIMESTEP) + }, 0L, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS) } override fun toString(): String { @@ -243,7 +245,7 @@ class ServerWorld private constructor( return unapplied } - override fun tick() { + override fun tick(delta: Double) { try { if (clients.isEmpty() && isBusy <= 0) { idleTicks++ @@ -259,7 +261,7 @@ class ServerWorld private constructor( return } - super.tick() + super.tick(delta) val packet = StepUpdatePacket(ticks) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt index e11f2611..89e28006 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Clocks.kt @@ -1,17 +1,13 @@ package ru.dbotthepony.kstarbound.util -import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.io.readDouble import ru.dbotthepony.kstarbound.io.writeDouble -import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement import java.io.DataInputStream import java.io.DataOutputStream interface IClock { - val nanos: Long - val micros: Long get() = nanos / 1_000L - val millis: Long get() = nanos / 1_000_000L - val seconds: Double get() = (nanos / 1_000L) / 1_000_000.0 + val time: Double + val nanos: Long get() = (time * 1_000_000_000.0).toLong() } // this is stupid, but legacy protocol requires it @@ -28,62 +24,44 @@ class RelativeClock() : IClock { read(stream, isLegacy) } - private var pointOfReference = 0L + private var pointOfReference = 0.0 private var pointOfReferenceSet = false - override var nanos: Long = 0L + override var time: Double = 0.0 private set - fun set(age: Long) { + fun set(age: Double) { pointOfReferenceSet = false - nanos = age + time = age } - fun update(newPointOfReference: Long) { + fun update(newPointOfReference: Double) { if (pointOfReferenceSet) { val diff = newPointOfReference - pointOfReference if (diff > 0L) - nanos += diff + time += diff } pointOfReference = newPointOfReference } - fun update(newPointOfReference: Double) { - return update((newPointOfReference * 1_000_000_000.0).toLong()) - } - fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeBoolean(pointOfReferenceSet) - if (isLegacy) { - if (pointOfReferenceSet) - stream.writeDouble(pointOfReference / 1_000_000_000.0) + if (pointOfReferenceSet) + stream.writeDouble(pointOfReference) - stream.writeDouble(nanos / 1_000_000_000.0) - } else { - if (pointOfReferenceSet) - stream.writeLong(pointOfReference) - - stream.writeLong(nanos) - } + stream.writeDouble(time) } fun read(stream: DataInputStream, isLegacy: Boolean) { pointOfReferenceSet = stream.readBoolean() - if (isLegacy) { - if (pointOfReferenceSet) - pointOfReference = (stream.readDouble() * 1_000_000_000.0).toLong() + if (pointOfReferenceSet) + pointOfReference = stream.readDouble() - nanos = (stream.readDouble() * 1_000_000_000.0).toLong() - } else { - if (pointOfReferenceSet) - pointOfReference = stream.readLong() - - nanos = stream.readLong() - } + time = stream.readDouble() } } @@ -116,8 +94,8 @@ class JVMClock : IClock { } } - override val nanos: Long - get() = if (isPaused) baseline else (System.nanoTime() - origin) + baseline + override val time: Double + get() = if (isPaused) (baseline / 1_000_000_000.0) else ((System.nanoTime() - origin) + baseline) / 1_000_000_000.0 } class GameTimer(val time: Double = 0.0) { @@ -153,12 +131,12 @@ class GameTimer(val time: Double = 0.0) { timer = time - timer } - fun tick(delta: Double = Starbound.TIMESTEP): Boolean { + fun tick(delta: Double): Boolean { timer = (timer - delta).coerceAtLeast(0.0) return timer == 0.0 } - fun wrapTick(delta: Double = Starbound.TIMESTEP): Boolean { + fun wrapTick(delta: Double): Boolean { val result = tick(delta) if (result) reset() return result diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index c963ed55..e9d9e49d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -270,7 +270,7 @@ abstract class Chunk, This : Chunk 0.5f) 1 else -1 val angle = (start + direction * offset) * PI * 2.0 return parentPosition + Vector2d(cos(angle) * distance, sin(angle) * distance) @@ -159,7 +159,7 @@ abstract class SystemWorld(val location: Vector3i, val clock: JVMClock, val univ val targetPosition = if (orbit.target.isPlanet || orbit.target.isSatellite) planetPosition(orbit.target) else Vector2d.ZERO val distance = orbit.enterPosition.length val interval = orbitInterval(distance, false) - val timeOffset = ((clock.seconds - orbit.enterTime) % interval) / interval + val timeOffset = ((clock.time - orbit.enterTime) % interval) / interval val angle = (orbit.enterPosition * -1).toAngle() + orbit.direction * timeOffset * PI * 2.0 return targetPosition + Vector2d(cos(angle) * distance, sin(angle) * distance) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileHealth.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileHealth.kt index 0d2bf721..be3bb152 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileHealth.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileHealth.kt @@ -7,7 +7,6 @@ import ru.dbotthepony.kommons.io.writeStruct2f import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.tile.TileDamage import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig import ru.dbotthepony.kstarbound.defs.tile.TileDamageType @@ -98,7 +97,7 @@ sealed class TileHealth() { val isTicking: Boolean get() = !isHealthy && !isDead - fun tick(config: TileDamageConfig, delta: Double = Starbound.TIMESTEP): Boolean { + fun tick(config: TileDamageConfig, delta: Double): Boolean { if (isDead || isHealthy) return false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index 069bb8c6..e0cc2fd0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -275,19 +275,19 @@ abstract class World, ChunkType : Chunk, ChunkType : Chunk= (actorMovementParameters.minimumLiquidPercentage ?: 0.0) @@ -291,9 +290,9 @@ class ActorMovementController() : MovementController() { flyVelocity = flyVelocity.unitVector * (actorMovementParameters.flySpeed ?: 0.0) if (isLiquidMovement) - approachVelocity(flyVelocity * (1.0 - liquidImpedance) * movementModifiers.speedModifier, (movementParameters.liquidForce ?: 0.0) * movementModifiers.liquidMovementModifier) + approachVelocity(flyVelocity * (1.0 - liquidImpedance) * movementModifiers.speedModifier, (movementParameters.liquidForce ?: 0.0) * movementModifiers.liquidMovementModifier, delta) else - approachVelocity(flyVelocity * movementModifiers.speedModifier, movementParameters.airForce ?: 0.0) + approachVelocity(flyVelocity * movementModifiers.speedModifier, movementParameters.airForce ?: 0.0, delta) if (flyVelocity.x > 0.0) updatedMovingDirection = Direction.RIGHT @@ -325,7 +324,7 @@ class ActorMovementController() : MovementController() { val maxGroundSustain = movementParameters.groundMovementMaximumSustain ?: 0.0 val groundCheckDistance = movementParameters.groundMovementCheckDistance ?: 0.0 - groundMovementSustainTimer.tick() + groundMovementSustainTimer.tick(delta) if (isOnGround) { groundMovementSustainTimer = GameTimer(maxGroundSustain) @@ -365,13 +364,13 @@ class ActorMovementController() : MovementController() { velocity = velocity.copy(y = velocity.y + (jumpProfile.jumpSpeed ?: 0.0) * (jumpProfile.jumpInitialPercentage ?: 0.0) * jumpModifier) groundMovementSustainTimer = GameTimer(0.0) } else if (holdJump) { - reJumpTimer.tick() - jumpHoldTimer?.tick() + reJumpTimer.tick(delta) + jumpHoldTimer?.tick(delta) - approachYVelocity((jumpProfile.jumpSpeed ?: 0.0) * jumpModifier, (jumpProfile.jumpControlForce ?: 0.0) * jumpModifier) + approachYVelocity((jumpProfile.jumpSpeed ?: 0.0) * jumpModifier, (jumpProfile.jumpControlForce ?: 0.0) * jumpModifier, delta) } else { isJumping = false - reJumpTimer.tick() + reJumpTimer.tick(delta) } if (controlMove == Direction.LEFT) { @@ -397,7 +396,7 @@ class ActorMovementController() : MovementController() { else movementParameters.airForce ?: 0.0 - approachXVelocity(targetHorizontalAmbulatingVelocity + surfaceVelocity.x, ambulatingAccelerate) + approachXVelocity(targetHorizontalAmbulatingVelocity + surfaceVelocity.x, ambulatingAccelerate, delta) } } @@ -424,7 +423,7 @@ class ActorMovementController() : MovementController() { isFlying = controlFly != null isFalling = (velocity.y < (movementParameters.fallStatusSpeedMin ?: 0.0)) && !isGroundMovement - super.move() + super.move(delta) lastControlDown = controlDown lastControlJump = controlJump diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt index d8a63c59..7f4ad739 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt @@ -23,8 +23,8 @@ import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.math.PeriodicFunction import ru.dbotthepony.kstarbound.math.approachAngle import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec -import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement +import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup import ru.dbotthepony.kstarbound.network.syncher.NetworkedList import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap import ru.dbotthepony.kstarbound.network.syncher.NetworkedSignal @@ -34,18 +34,15 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedColor import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint import ru.dbotthepony.kstarbound.network.syncher.networkedFloat -import ru.dbotthepony.kstarbound.network.syncher.networkedList import ru.dbotthepony.kstarbound.network.syncher.networkedPointer import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt import ru.dbotthepony.kstarbound.network.syncher.networkedString import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt import ru.dbotthepony.kstarbound.util.random.random -import ru.dbotthepony.kstarbound.world.positiveModulo -import java.util.Collections +import java.util.* import java.util.function.Consumer import kotlin.math.atan2 import kotlin.math.cos -import kotlin.math.roundToInt import kotlin.math.sin import kotlin.math.sqrt @@ -590,7 +587,7 @@ class Animator() { // TODO: Dynamic target @Suppress("Name_Shadowing") - fun tick(delta: Double = Starbound.TIMESTEP) { + fun tick(delta: Double) { val delta = delta * animationRate for (state in stateTypes.values) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt index 53f4a0f0..e6426317 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt @@ -30,8 +30,8 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) { private var fixturesChangeset = -1 - override fun tick() { - super.tick() + override fun tick(delta: Double) { + super.tick(delta) if (isRemote && networkGroup.upstream.isInterpolating) { movement.updateFixtures() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt index 6888071c..0f42558a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt @@ -111,7 +111,7 @@ class ItemDropEntity() : DynamicEntity("/") { fun take(by: AbstractEntity): ItemStack { if (canTake) { state = State.TAKEN - age.set(0L) + age.set(0.0) owningEntity = by.entityID return item.copy() } @@ -124,8 +124,8 @@ class ItemDropEntity() : DynamicEntity("/") { override val metaBoundingBox: AABB get() = AABB(position - Vector2d(0.5, 0.5), position + Vector2d(0.5, 0.5)) - override fun tick() { - super.tick() + override fun tick(delta: Double) { + super.tick(delta) if (!isRemote) { if (item.isEmpty) { @@ -138,7 +138,7 @@ class ItemDropEntity() : DynamicEntity("/") { if (state != State.TAKEN) age.update(world.sky.time) else if (stayAliveFor > 0.0) { - stayAliveFor -= Starbound.TIMESTEP + stayAliveFor -= delta if (stayAliveFor <= 0.0) { state = State.DEAD @@ -157,10 +157,10 @@ class ItemDropEntity() : DynamicEntity("/") { remove(RemovalReason.REMOVED) } else if (stayAliveFor == -1.0) { val diff = world.geometry.diff(entity.position, position) - movement.approachVelocity(diff.unitVector * Globals.itemDrop.velocity, Globals.itemDrop.velocityApproach) + movement.approachVelocity(diff.unitVector * Globals.itemDrop.velocity, Globals.itemDrop.velocityApproach, delta) if (diff.length < Globals.itemDrop.pickupDistance) { - stayAliveFor = 0.05 // stay alive a little longer so pickup "animation" doesn't get cut off early + stayAliveFor = Starbound.TIMESTEP * 4.0 // stay alive a little longer so pickup "animation" doesn't get cut off early } } @@ -190,7 +190,7 @@ class ItemDropEntity() : DynamicEntity("/") { } entity.item.size -= diff - age.set(min(age.nanos, entity.age.nanos)) + age.set(min(age.time, entity.age.time)) // Average the position and velocity of the drop we merged with //movement.position += world.geometry.diff(movement.position, entity.movement.position) / 2.0 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt index bb1766e0..67fdf788 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt @@ -15,7 +15,6 @@ import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.times import ru.dbotthepony.kstarbound.Globals -import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.MovementParameters import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup @@ -29,7 +28,6 @@ import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.physics.CollisionPoly import ru.dbotthepony.kstarbound.world.physics.CollisionType import ru.dbotthepony.kstarbound.world.physics.Poly -import java.util.stream.Stream import kotlin.math.PI import kotlin.math.absoluteValue import kotlin.math.acos @@ -219,7 +217,7 @@ open class MovementController() { } - fun approachVelocity(targetVelocity: Vector2d, maxControlForce: Double) { + fun approachVelocity(targetVelocity: Vector2d, maxControlForce: Double, delta: Double) { // Instead of applying the force directly, work backwards and figure out the // maximum acceleration that could be achieved by the current control force, // and maximize the change in velocity based on that. @@ -229,13 +227,13 @@ open class MovementController() { if (mag == 0.0) return - val maximumAcceleration = maxControlForce / mass * Starbound.TIMESTEP + val maximumAcceleration = maxControlForce / mass * delta val clampedMag = mag.coerceIn(0.0, maximumAcceleration) velocity += diff * (clampedMag / mag) } - fun approachVelocityAlongAngle(angle: Double, targetVelocity: Double, maxControlForce: Double, positiveOnly: Boolean = false) { + fun approachVelocityAlongAngle(angle: Double, targetVelocity: Double, maxControlForce: Double, delta: Double, positiveOnly: Boolean = false) { // Same strategy as approachVelocity, work backwards to figure out the // maximum acceleration and apply that. @@ -249,26 +247,26 @@ open class MovementController() { if (diff == 0.0 || positiveOnly && diff < 0.0) return - val maximumAcceleration = maxControlForce / mass * Starbound.TIMESTEP + val maximumAcceleration = maxControlForce / mass * delta val diffMag = diff.absoluteValue val clampedMag = diffMag.coerceIn(0.0, maximumAcceleration) velocity += axis * diff * (clampedMag / diffMag) } - fun approachXVelocity(velocity: Double, maxControlForce: Double) { - approachVelocityAlongAngle(0.0, velocity, maxControlForce) + fun approachXVelocity(velocity: Double, maxControlForce: Double, delta: Double) { + approachVelocityAlongAngle(0.0, velocity, maxControlForce, delta) } - fun approachYVelocity(velocity: Double, maxControlForce: Double) { - approachVelocityAlongAngle(PI / 2.0, velocity, maxControlForce) + fun approachYVelocity(velocity: Double, maxControlForce: Double, delta: Double) { + approachVelocityAlongAngle(PI / 2.0, velocity, maxControlForce, delta) } /** * this function is executed in parallel */ // TODO: Ghost collisions occur, where objects trip on edges - open fun move() { + open fun move(delta: Double) { isZeroGravity = isGravityDisabled || gravityMultiplier == 0.0 || determineGravity().lengthSquared == 0.0 val movementParameters = movementParameters @@ -281,7 +279,7 @@ open class MovementController() { // TODO: Here: moving platforms sticky code if (movementParameters.collisionPoly == null || !movementParameters.collisionPoly.map({ true }, { it.isNotEmpty() }) || movementParameters.collisionEnabled != true) { - position += velocity * Starbound.TIMESTEP + position += velocity * delta surfaceSlope = Vector2d.POSITIVE_Y surfaceVelocity = Vector2d.ZERO isOnGround = false @@ -295,14 +293,14 @@ open class MovementController() { var steps = 1 movementParameters.maxMovementPerStep?.let { - steps = (velocity.length * Starbound.TIMESTEP / it).toInt() + 1 + steps = (velocity.length * delta / it).toInt() + 1 } var relativeVelocity = velocity surfaceSlope = Vector2d.POSITIVE_Y // TODO: Here: moving platforms sticky code - val dt = Starbound.TIMESTEP / steps + val dt = delta / steps for (step in 0 until steps) { val velocityMagnitude = relativeVelocity.length @@ -384,14 +382,14 @@ open class MovementController() { // independently). if (relativeVelocity.x < 0.0 && correction.x > 0.0) - relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TIMESTEP).coerceAtMost(0.0)) + relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / dt).coerceAtMost(0.0)) else if (relativeVelocity.x > 0.0 && correction.x < 0.0) - relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TIMESTEP).coerceAtLeast(0.0)) + relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / dt).coerceAtLeast(0.0)) if (relativeVelocity.y < 0.0 && correction.y > 0.0) - relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TIMESTEP).coerceAtMost(0.0)) + relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / dt).coerceAtMost(0.0)) else if (relativeVelocity.y > 0.0 && correction.y < 0.0) - relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TIMESTEP).coerceAtLeast(0.0)) + relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / dt).coerceAtLeast(0.0)) } } } @@ -410,7 +408,7 @@ open class MovementController() { if (!isZeroGravity && stickingDirection == null) { val buoyancy = (movementParameters.liquidBuoyancy ?: 0.0).coerceIn(0.0, 1.0) + liquidPercentage + (movementParameters.airBuoyancy ?: 0.0).coerceIn(0.0, 1.0) * (1.0 - liquidPercentage) val gravity = determineGravity() * (movementParameters.gravityMultiplier ?: 1.0) * (1.0 - buoyancy) - var environmentVelocity = gravity * Starbound.TIMESTEP + var environmentVelocity = gravity * delta if (isOnGround && (movementParameters.slopeSlidingFactor ?: 0.0) != 0.0 && surfaceSlope != Vector2d.ZERO) environmentVelocity += -surfaceSlope * (surfaceSlope.x * surfaceSlope.y) * (movementParameters.slopeSlidingFactor ?: 0.0) @@ -432,7 +430,7 @@ open class MovementController() { // but it is applied here as a multiplicative factor from [0, 1] so it does // not induce oscillation at very high friction and so it cannot be // negative. - val frictionFactor = (friction / mass * Starbound.TIMESTEP).coerceIn(0.0, 1.0) + val frictionFactor = (friction / mass * delta).coerceIn(0.0, 1.0) newVelocity = linearInterpolation(frictionFactor, newVelocity, refVel) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt index cfadbe8d..c4d6e97f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt @@ -216,8 +216,8 @@ abstract class TileEntity(path: String) : AbstractEntity(path) { } } - override fun tick() { - super.tick() + override fun tick(delta: Double) { + super.tick(delta) if (needToUpdateSpaces) { updateMaterialSpacesNow() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt index ca3a0911..950e0b62 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt @@ -394,19 +394,19 @@ open class WorldObject(val config: Registry.Entry) : TileEntit drawablesCache.invalidate() } - override fun tick() { - super.tick() + override fun tick(delta: Double) { + super.tick(delta) - flickerPeriod?.update(Starbound.TIMESTEP, world.random) + flickerPeriod?.update(delta, world.random) if (!isRemote) { - tileHealth.tick(config.value.damageConfig) - animator.tick() + tileHealth.tick(config.value.damageConfig, delta) + animator.tick(delta) val orientation = orientation if (orientation != null) { - frameTimer = (frameTimer + Starbound.TIMESTEP) % orientation.animationCycle + frameTimer = (frameTimer + delta) % orientation.animationCycle val oldFrame = frame frame = (frameTimer / orientation.animationCycle * orientation.frames).toInt().coerceIn(0, orientation.frames)