Update all methods to not reference global timestep constant
this sets up ground for variable tick rate
This commit is contained in:
parent
7cd0f5e173
commit
8fe7a6f951
@ -703,7 +703,7 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
|||||||
|
|
||||||
private fun renderWorld(world: ClientWorld) {
|
private fun renderWorld(world: ClientWorld) {
|
||||||
updateViewportParams()
|
updateViewportParams()
|
||||||
world.tick()
|
world.tick(Starbound.TIMESTEP)
|
||||||
|
|
||||||
stack.clear(Matrix3f.identity())
|
stack.clear(Matrix3f.identity())
|
||||||
|
|
||||||
@ -772,7 +772,7 @@ class StarboundClient private constructor(val clientID: Int) : BlockableEventLoo
|
|||||||
GLFW.glfwPollEvents()
|
GLFW.glfwPollEvents()
|
||||||
|
|
||||||
if (world != null && Starbound.initialized)
|
if (world != null && Starbound.initialized)
|
||||||
world.tick()
|
world.tick(Starbound.TIMESTEP)
|
||||||
|
|
||||||
activeConnection?.flush()
|
activeConnection?.flush()
|
||||||
return
|
return
|
||||||
|
@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound.math
|
|||||||
|
|
||||||
import ru.dbotthepony.kommons.math.linearInterpolation
|
import ru.dbotthepony.kommons.math.linearInterpolation
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.math.sin
|
|
||||||
|
|
||||||
data class PeriodicFunction(
|
data class PeriodicFunction(
|
||||||
val period: Double = 1.0,
|
val period: Double = 1.0,
|
||||||
|
@ -72,7 +72,7 @@ data class ClientConnectPacket(
|
|||||||
|
|
||||||
connection.receiveShipChunks(shipChunks)
|
connection.receiveShipChunks(shipChunks)
|
||||||
connection.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation))
|
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.channel.flush()
|
||||||
connection.inGame()
|
connection.inGame()
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import java.util.function.Consumer
|
|||||||
|
|
||||||
open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protected val codec: StreamCodec<TYPE>, protected val legacyCodec: StreamCodec<LEGACY>, protected val toLegacy: (TYPE) -> LEGACY, protected val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate<TYPE> {
|
open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protected val codec: StreamCodec<TYPE>, protected val legacyCodec: StreamCodec<LEGACY>, protected val toLegacy: (TYPE) -> LEGACY, protected val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate<TYPE> {
|
||||||
protected val valueListeners = Listenable.Impl<TYPE>()
|
protected val valueListeners = Listenable.Impl<TYPE>()
|
||||||
protected val queue = LinkedList<Pair<Double, TYPE>>()
|
protected val queue = InterpolationQueue<TYPE> { this.value = it; valueListeners.accept(value) }
|
||||||
protected var isInterpolating = false
|
protected var isInterpolating = false
|
||||||
protected var currentTime = 0.0
|
protected var currentTime = 0.0
|
||||||
|
|
||||||
@ -48,17 +48,9 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
|
|||||||
|
|
||||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
if (isLegacy) {
|
if (isLegacy) {
|
||||||
if (queue.isNotEmpty()) {
|
legacyCodec.write(data, toLegacy(queue.last { value }))
|
||||||
legacyCodec.write(data, toLegacy(queue.last.second))
|
|
||||||
} else {
|
|
||||||
legacyCodec.write(data, toLegacy(value))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (queue.isNotEmpty()) {
|
codec.write(data, queue.last { value })
|
||||||
codec.write(data, queue.last.second)
|
|
||||||
} else {
|
|
||||||
codec.write(data, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,19 +59,7 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
|
|||||||
bumpVersion()
|
bumpVersion()
|
||||||
|
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
// Only append an incoming delta to our pending value list if the incoming
|
queue.push(read, interpolationDelay)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
value = read
|
value = read
|
||||||
valueListeners.accept(read)
|
valueListeners.accept(read)
|
||||||
@ -90,38 +70,22 @@ open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protecte
|
|||||||
writeInitial(data, isLegacy)
|
writeInitial(data, isLegacy)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readBlankDelta(interpolationDelay: Double) {
|
override fun readBlankDelta(interpolationDelay: Double) {}
|
||||||
// TODO: original engine doesn't override this, is this intentional?
|
|
||||||
// tickInterpolation(interpolationDelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun enableInterpolation(extrapolation: Double) {
|
override fun enableInterpolation(extrapolation: Double) {
|
||||||
if (!isInterpolating) {
|
if (!isInterpolating) {
|
||||||
isInterpolating = true
|
isInterpolating = true
|
||||||
queue.clear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disableInterpolation() {
|
override fun disableInterpolation() {
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
isInterpolating = false
|
isInterpolating = false
|
||||||
|
|
||||||
if (queue.isNotEmpty()) {
|
|
||||||
value = queue.last.second
|
|
||||||
valueListeners.accept(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.clear()
|
queue.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tickInterpolation(delta: Double) {
|
override fun tickInterpolation(delta: Double) {
|
||||||
require(delta >= 0.0) { "Negative interpolation delta: $delta" }
|
queue.tick(delta)
|
||||||
currentTime += delta
|
|
||||||
|
|
||||||
while (queue.isNotEmpty() && queue.first.first <= currentTime) {
|
|
||||||
value = queue.removeFirst().second
|
|
||||||
valueListeners.accept(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,8 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val queue = ArrayDeque<Pair<Double, Double>>()
|
private data class QueueEntry(val time: Double, val value: Double)
|
||||||
|
private val queue = ArrayDeque<QueueEntry>()
|
||||||
|
|
||||||
var currentTime = 0.0
|
var currentTime = 0.0
|
||||||
private set
|
private set
|
||||||
@ -95,7 +96,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
|
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
queue.clear()
|
queue.clear()
|
||||||
queue.add(currentTime to t)
|
queue.add(QueueEntry(currentTime, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
valueListeners.accept(t)
|
valueListeners.accept(t)
|
||||||
@ -130,13 +131,13 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
queue.clear()
|
queue.clear()
|
||||||
|
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
queue.add(currentTime to value)
|
queue.add(QueueEntry(currentTime, value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
if (queue.isNotEmpty())
|
if (queue.isNotEmpty())
|
||||||
(if (isLegacy) legacyOps else ops).write(data, queue.last().second)
|
(if (isLegacy) legacyOps else ops).write(data, queue.last().value)
|
||||||
else
|
else
|
||||||
(if (isLegacy) legacyOps else ops).write(data, value)
|
(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) {
|
if (isInterpolating) {
|
||||||
val realDelay = interpolationDelay + currentTime
|
val realDelay = interpolationDelay + currentTime
|
||||||
if (queue.last().first > realDelay)
|
if (queue.last().time > realDelay)
|
||||||
queue.clear()
|
queue.clear()
|
||||||
|
|
||||||
queue.add(realDelay to read)
|
queue.add(QueueEntry(realDelay, read))
|
||||||
value = interpolated()
|
value = interpolated()
|
||||||
valueListeners.accept(value)
|
valueListeners.accept(value)
|
||||||
} else {
|
} else {
|
||||||
@ -172,7 +173,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
if (actual < last)
|
if (actual < last)
|
||||||
queue.clear()
|
queue.clear()
|
||||||
|
|
||||||
queue.add(actual to lastPoint)
|
queue.add(QueueEntry(actual, lastPoint))
|
||||||
|
|
||||||
val old = value
|
val old = value
|
||||||
value = interpolated()
|
value = interpolated()
|
||||||
@ -187,7 +188,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
if (!isInterpolating) {
|
if (!isInterpolating) {
|
||||||
isInterpolating = true
|
isInterpolating = true
|
||||||
queue.clear()
|
queue.clear()
|
||||||
queue.add(currentTime to value)
|
queue.add(QueueEntry(currentTime, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.extrapolation = extrapolation
|
this.extrapolation = extrapolation
|
||||||
@ -198,7 +199,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
isInterpolating = false
|
isInterpolating = false
|
||||||
|
|
||||||
if (queue.isNotEmpty()) {
|
if (queue.isNotEmpty()) {
|
||||||
value = queue.last().second
|
value = queue.last().value
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.clear()
|
queue.clear()
|
||||||
@ -210,7 +211,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
currentTime += delta
|
currentTime += delta
|
||||||
|
|
||||||
if (isInterpolating && queue.size >= 2) {
|
if (isInterpolating && queue.size >= 2) {
|
||||||
while (queue.size > 2 && queue[1].first <= currentTime) {
|
while (queue.size > 2 && queue[1].time <= currentTime) {
|
||||||
queue.removeFirst()
|
queue.removeFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +230,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
|
|
||||||
val actualTime = time + currentTime
|
val actualTime = time + currentTime
|
||||||
|
|
||||||
if (actualTime < queue.first().first) {
|
if (actualTime < queue.first().time) {
|
||||||
// extrapolate into past?
|
// extrapolate into past?
|
||||||
val (time0, value0) = queue[0]
|
val (time0, value0) = queue[0]
|
||||||
val (time1, value1) = queue[1]
|
val (time1, value1) = queue[1]
|
||||||
@ -240,7 +241,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
|||||||
diff = 0.0
|
diff = 0.0
|
||||||
|
|
||||||
return interpolator.interpolate(diff, value0, value1)
|
return interpolator.interpolate(diff, value0, value1)
|
||||||
} else if (actualTime > queue.last().first) {
|
} else if (actualTime > queue.last().time) {
|
||||||
// extrapolate into future
|
// extrapolate into future
|
||||||
val (time0, value0) = queue[queue.size - 2]
|
val (time0, value0) = queue[queue.size - 2]
|
||||||
val (time1, value1) = queue[queue.size - 1]
|
val (time1, value1) = queue[queue.size - 1]
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.syncher
|
||||||
|
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
class InterpolationQueue<T>(private val visitor: (T) -> Unit) : Iterable<T> {
|
||||||
|
private data class Entry<T>(val time: Double, val value: T) : Comparable<Entry<*>> {
|
||||||
|
override fun compareTo(other: Entry<*>): Int {
|
||||||
|
return time.compareTo(other.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentTime = 0.0
|
||||||
|
private val queue = LinkedList<Entry<T>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<T> {
|
||||||
|
return object : Iterator<T> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -19,8 +19,8 @@ class NetworkedList<E>(
|
|||||||
private val elementsFactory: (Int) -> MutableList<E> = ::ArrayList
|
private val elementsFactory: (Int) -> MutableList<E> = ::ArrayList
|
||||||
) : NetworkedElement(), MutableList<E> {
|
) : NetworkedElement(), MutableList<E> {
|
||||||
private val backlog = ArrayDeque<Pair<Long, Entry<E>>>()
|
private val backlog = ArrayDeque<Pair<Long, Entry<E>>>()
|
||||||
private val queue = ArrayDeque<Pair<Double, Entry<E>>>()
|
|
||||||
private val elements = elementsFactory(10)
|
private val elements = elementsFactory(10)
|
||||||
|
private val queue = InterpolationQueue<Entry<E>> { it.apply(elements) }
|
||||||
|
|
||||||
private enum class Type {
|
private enum class Type {
|
||||||
ADD, REMOVE, CLEAR;
|
ADD, REMOVE, CLEAR;
|
||||||
@ -41,7 +41,6 @@ class NetworkedList<E>(
|
|||||||
|
|
||||||
private val clearEntry = Entry<E>(Type.CLEAR, 0, KOptional())
|
private val clearEntry = Entry<E>(Type.CLEAR, 0, KOptional())
|
||||||
private var isInterpolating = false
|
private var isInterpolating = false
|
||||||
private var currentTime = 0.0
|
|
||||||
private var isRemote = false
|
private var isRemote = false
|
||||||
private val listeners = CopyOnWriteArrayList<Runnable>()
|
private val listeners = CopyOnWriteArrayList<Runnable>()
|
||||||
|
|
||||||
@ -56,7 +55,7 @@ class NetworkedList<E>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun latestState(): List<E> {
|
private fun latestState(): List<E> {
|
||||||
if (queue.isEmpty()) {
|
if (queue.isEmpty) {
|
||||||
return elements
|
return elements
|
||||||
} else {
|
} else {
|
||||||
val copy = elementsFactory(elements.size)
|
val copy = elementsFactory(elements.size)
|
||||||
@ -64,9 +63,7 @@ class NetworkedList<E>(
|
|||||||
for (v in elements)
|
for (v in elements)
|
||||||
copy.add(v)
|
copy.add(v)
|
||||||
|
|
||||||
for ((_, e) in queue)
|
queue.forEach { it.apply(copy) }
|
||||||
e.apply(copy)
|
|
||||||
|
|
||||||
return copy
|
return copy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,17 +123,7 @@ class NetworkedList<E>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
val actualTime = interpolationDelay + currentTime
|
queue.push(entry, interpolationDelay)
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
entry.apply(elements)
|
entry.apply(elements)
|
||||||
}
|
}
|
||||||
@ -186,16 +173,11 @@ class NetworkedList<E>(
|
|||||||
|
|
||||||
override fun disableInterpolation() {
|
override fun disableInterpolation() {
|
||||||
isInterpolating = false
|
isInterpolating = false
|
||||||
queue.forEach { it.second.apply(elements) }
|
|
||||||
queue.clear()
|
queue.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tickInterpolation(delta: Double) {
|
override fun tickInterpolation(delta: Double) {
|
||||||
currentTime += delta
|
queue.tick(delta)
|
||||||
|
|
||||||
while (queue.isNotEmpty() && queue.first().first <= currentTime) {
|
|
||||||
queue.removeFirst().second.apply(elements)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasChangedSince(version: Long): Boolean {
|
override fun hasChangedSince(version: Long): Boolean {
|
||||||
|
@ -50,12 +50,12 @@ class NetworkedMap<K, V>(
|
|||||||
init {
|
init {
|
||||||
map.addListener(object : ListenableMap.MapListener<K, V> {
|
map.addListener(object : ListenableMap.MapListener<K, V> {
|
||||||
override fun onClear() {
|
override fun onClear() {
|
||||||
if (isReading) return
|
if (isReading > 0) return
|
||||||
check(!isRemote) { "This map is not owned by this side" }
|
check(!isRemote) { "This map is not owned by this side" }
|
||||||
|
|
||||||
// this is fragile (due to interpolation fuckery, we remove everything before applying delayed changes),
|
// this is fragile (due to interpolation fuckery, we remove everything before applying delayed changes),
|
||||||
// but let's hope it doesn't break
|
// but let's hope it doesn't break
|
||||||
delayed.clear()
|
queue.clear()
|
||||||
backlog.add(currentVersion() to clearEntry)
|
backlog.add(currentVersion() to clearEntry)
|
||||||
|
|
||||||
purgeBacklog()
|
purgeBacklog()
|
||||||
@ -63,7 +63,7 @@ class NetworkedMap<K, V>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onValueAdded(key: K, value: V) {
|
override fun onValueAdded(key: K, value: V) {
|
||||||
if (isReading) return
|
if (isReading > 0) return
|
||||||
check(!isRemote) { "This map is not owned by this side" }
|
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))))
|
backlog.add(currentVersion() to Entry(Action.ADD, KOptional(nativeKey.copy(key)), KOptional(nativeValue.copy(value))))
|
||||||
purgeBacklog()
|
purgeBacklog()
|
||||||
@ -71,7 +71,7 @@ class NetworkedMap<K, V>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onValueRemoved(key: K, value: V) {
|
override fun onValueRemoved(key: K, value: V) {
|
||||||
if (isReading) return
|
if (isReading > 0) return
|
||||||
check(!isRemote) { "This map is not owned by this side" }
|
check(!isRemote) { "This map is not owned by this side" }
|
||||||
backlog.add(currentVersion() to Entry(Action.REMOVE, KOptional(nativeKey.copy(key)), KOptional()))
|
backlog.add(currentVersion() to Entry(Action.REMOVE, KOptional(nativeKey.copy(key)), KOptional()))
|
||||||
purgeBacklog()
|
purgeBacklog()
|
||||||
@ -168,9 +168,14 @@ class NetworkedMap<K, V>(
|
|||||||
|
|
||||||
private val clearEntry = Entry<K, V>(Action.CLEAR, KOptional(), KOptional())
|
private val clearEntry = Entry<K, V>(Action.CLEAR, KOptional(), KOptional())
|
||||||
private val backlog = ArrayDeque<Pair<Long, Entry<K, V>>>()
|
private val backlog = ArrayDeque<Pair<Long, Entry<K, V>>>()
|
||||||
private val delayed = ArrayDeque<Pair<Double, Entry<K, V>>>()
|
|
||||||
private var currentTime = 0.0
|
private val queue = InterpolationQueue<Entry<K, V>> {
|
||||||
private var isReading = false
|
isReading++
|
||||||
|
it.apply(this)
|
||||||
|
isReading--
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isReading = 0
|
||||||
private var isRemote = false
|
private var isRemote = false
|
||||||
private var isInterpolating = false
|
private var isInterpolating = false
|
||||||
|
|
||||||
@ -192,10 +197,10 @@ class NetworkedMap<K, V>(
|
|||||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||||
try {
|
try {
|
||||||
isRemote = true
|
isRemote = true
|
||||||
isReading = true
|
isReading++
|
||||||
|
|
||||||
backlog.clear()
|
backlog.clear()
|
||||||
delayed.clear()
|
queue.clear()
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
backlog.add(currentVersion() to clearEntry)
|
backlog.add(currentVersion() to clearEntry)
|
||||||
@ -228,31 +233,31 @@ class NetworkedMap<K, V>(
|
|||||||
|
|
||||||
purgeBacklog()
|
purgeBacklog()
|
||||||
} finally {
|
} finally {
|
||||||
isReading = false
|
isReading--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||||
if (isDumb && isLegacy) {
|
if (isDumb && isLegacy) {
|
||||||
val construct = HashMap<K, V>(size + delayed.size)
|
val construct = HashMap<K, V>(size + queue.size)
|
||||||
|
|
||||||
for ((k, v) in entries) {
|
for ((k, v) in entries) {
|
||||||
construct[k] = v
|
construct[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((_, v) in delayed) {
|
for (v in queue) {
|
||||||
v.apply(construct)
|
v.apply(construct)
|
||||||
}
|
}
|
||||||
|
|
||||||
dumbCodec.write(data, construct)
|
dumbCodec.write(data, construct)
|
||||||
} else {
|
} else {
|
||||||
data.writeVarInt(size + delayed.size)
|
data.writeVarInt(size + queue.size)
|
||||||
|
|
||||||
for ((k, v) in entries) {
|
for ((k, v) in entries) {
|
||||||
Entry(Action.ADD, KOptional(k), KOptional(v)).write(data, isLegacy, this)
|
Entry(Action.ADD, KOptional(k), KOptional(v)).write(data, isLegacy, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((_, v) in delayed) {
|
for (v in queue) {
|
||||||
v.write(data, isLegacy, this)
|
v.write(data, isLegacy, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,31 +270,18 @@ class NetworkedMap<K, V>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isReading = true
|
isReading++
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
when (val action = data.readUnsignedByte()) {
|
when (val action = data.readUnsignedByte()) {
|
||||||
0 -> break
|
0 -> break
|
||||||
1 -> {
|
1 -> readInitial(data, isLegacy)
|
||||||
readInitial(data, isLegacy)
|
|
||||||
isReading = true
|
|
||||||
}
|
|
||||||
2 -> {
|
2 -> {
|
||||||
val change = if (isLegacy) readLegacyEntry(data) else readNativeEntry(data)
|
val change = if (isLegacy) readLegacyEntry(data) else readNativeEntry(data)
|
||||||
backlog.add(currentVersion() to change)
|
backlog.add(currentVersion() to change)
|
||||||
|
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
val actualDelay = interpolationDelay + currentTime
|
queue.push(change, interpolationDelay)
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
change.apply(this)
|
change.apply(this)
|
||||||
}
|
}
|
||||||
@ -301,7 +293,7 @@ class NetworkedMap<K, V>(
|
|||||||
|
|
||||||
purgeBacklog()
|
purgeBacklog()
|
||||||
} finally {
|
} finally {
|
||||||
isReading = false
|
isReading--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,30 +333,12 @@ class NetworkedMap<K, V>(
|
|||||||
override fun disableInterpolation() {
|
override fun disableInterpolation() {
|
||||||
if (isInterpolating) {
|
if (isInterpolating) {
|
||||||
isInterpolating = false
|
isInterpolating = false
|
||||||
|
queue.clear()
|
||||||
try {
|
|
||||||
isReading = true
|
|
||||||
delayed.forEach { it.second.apply(this) }
|
|
||||||
delayed.clear()
|
|
||||||
} finally {
|
|
||||||
isReading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
purgeBacklog()
|
purgeBacklog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tickInterpolation(delta: Double) {
|
override fun tickInterpolation(delta: Double) {
|
||||||
currentTime += delta
|
queue.tick(delta)
|
||||||
|
|
||||||
try {
|
|
||||||
isReading = true
|
|
||||||
|
|
||||||
while (delayed.isNotEmpty() && delayed.first().first <= currentTime) {
|
|
||||||
delayed.removeFirst().second.apply(this)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
isReading = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,9 @@ import java.io.DataOutputStream
|
|||||||
class NetworkedSignal<S>(private val codec: StreamCodec<S>, private val maxSize: Int = 100) : NetworkedElement(), Iterable<S>, Iterator<S> {
|
class NetworkedSignal<S>(private val codec: StreamCodec<S>, private val maxSize: Int = 100) : NetworkedElement(), Iterable<S>, Iterator<S> {
|
||||||
private data class Signal<S>(val version: Long, val signal: S)
|
private data class Signal<S>(val version: Long, val signal: S)
|
||||||
|
|
||||||
private var currentTime = 0.0
|
|
||||||
private val visibleSignals = ArrayDeque<Signal<S>>()
|
private val visibleSignals = ArrayDeque<Signal<S>>()
|
||||||
private val internalSignals = ArrayDeque<Signal<S>>()
|
private val internalSignals = ArrayDeque<Signal<S>>()
|
||||||
private val delayedSignals = ArrayDeque<Pair<Double, S>>()
|
private val delayedSignals = InterpolationQueue<S> { push(it) }
|
||||||
private var isInterpolating = false
|
private var isInterpolating = false
|
||||||
|
|
||||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {}
|
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {}
|
||||||
@ -34,14 +33,7 @@ class NetworkedSignal<S>(private val codec: StreamCodec<S>, private val maxSize:
|
|||||||
val signal = codec.read(data)
|
val signal = codec.read(data)
|
||||||
|
|
||||||
if (isInterpolating && interpolationDelay > 0.0) {
|
if (isInterpolating && interpolationDelay > 0.0) {
|
||||||
val actualDelay = interpolationDelay + currentTime
|
delayedSignals.push(signal, interpolationDelay)
|
||||||
|
|
||||||
if (delayedSignals.isNotEmpty() && delayedSignals.last().first > actualDelay) {
|
|
||||||
delayedSignals.forEach { push(it.second) }
|
|
||||||
delayedSignals.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
delayedSignals.add(actualDelay to signal)
|
|
||||||
} else {
|
} else {
|
||||||
push(signal)
|
push(signal)
|
||||||
}
|
}
|
||||||
@ -68,20 +60,11 @@ class NetworkedSignal<S>(private val codec: StreamCodec<S>, private val maxSize:
|
|||||||
|
|
||||||
override fun disableInterpolation() {
|
override fun disableInterpolation() {
|
||||||
isInterpolating = false
|
isInterpolating = false
|
||||||
|
|
||||||
for ((_, v) in delayedSignals) {
|
|
||||||
push(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
delayedSignals.clear()
|
delayedSignals.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tickInterpolation(delta: Double) {
|
override fun tickInterpolation(delta: Double) {
|
||||||
currentTime += delta
|
delayedSignals.tick(delta)
|
||||||
|
|
||||||
while (delayedSignals.isNotEmpty() && delayedSignals.first().first <= currentTime) {
|
|
||||||
push(delayedSignals.removeFirst().second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
|
@ -9,7 +9,7 @@ class IntegratedStarboundServer(val client: StarboundClient, root: File) : Starb
|
|||||||
channels.createLocalChannel()
|
channels.createLocalChannel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick0() {
|
override fun tick0(delta: Double) {
|
||||||
if (client.isShutdown) {
|
if (client.isShutdown) {
|
||||||
shutdown()
|
shutdown()
|
||||||
}
|
}
|
||||||
|
@ -423,7 +423,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
warpQueue.trySend(destination to deploy)
|
warpQueue.trySend(destination to deploy)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tick() {
|
fun tick(delta: Double) {
|
||||||
if (!isConnected || !channel.isOpen)
|
if (!isConnected || !channel.isOpen)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -187,11 +187,11 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
scheduleAtFixedRate(Runnable {
|
scheduleAtFixedRate(Runnable {
|
||||||
channels.broadcast(UniverseTimeUpdatePacket(universeClock.seconds))
|
channels.broadcast(UniverseTimeUpdatePacket(universeClock.time))
|
||||||
}, Globals.universeServer.clockUpdatePacketInterval, Globals.universeServer.clockUpdatePacketInterval, TimeUnit.MILLISECONDS)
|
}, Globals.universeServer.clockUpdatePacketInterval, Globals.universeServer.clockUpdatePacketInterval, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
scheduleAtFixedRate(Runnable {
|
scheduleAtFixedRate(Runnable {
|
||||||
tickNormal()
|
tickNormal(Starbound.TIMESTEP)
|
||||||
}, Starbound.TIMESTEP_NANOS, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
|
}, Starbound.TIMESTEP_NANOS, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
|
||||||
|
|
||||||
scheduleAtFixedRate(Runnable {
|
scheduleAtFixedRate(Runnable {
|
||||||
@ -232,7 +232,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun close0()
|
protected abstract fun close0()
|
||||||
protected abstract fun tick0()
|
protected abstract fun tick0(delta: Double)
|
||||||
|
|
||||||
private fun tickSystemWorlds() {
|
private fun tickSystemWorlds() {
|
||||||
systemWorlds.values.removeIf {
|
systemWorlds.values.removeIf {
|
||||||
@ -246,7 +246,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
it.get().tick()
|
it.get().tick(Starbound.SYSTEM_WORLD_TIMESTEP)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.fatal("Exception in system world $it event loop", err)
|
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 {
|
try {
|
||||||
// universeClock.nanos += Starbound.TIMESTEP_NANOS
|
// universeClock.nanos += Starbound.TIMESTEP_NANOS
|
||||||
|
|
||||||
channels.connections.forEach {
|
channels.connections.forEach {
|
||||||
try {
|
try {
|
||||||
it.tick()
|
it.tick(delta)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.error("Exception while ticking client connection", err)
|
LOGGER.error("Exception while ticking client connection", err)
|
||||||
it.disconnect("Exception while ticking client connection: $err")
|
it.disconnect("Exception while ticking client connection: $err")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tick0()
|
tick0(delta)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.fatal("Exception in main server event loop", err)
|
LOGGER.fatal("Exception in main server event loop", err)
|
||||||
shutdown()
|
shutdown()
|
||||||
|
@ -490,7 +490,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick(delta: Double) {
|
||||||
ticks++
|
ticks++
|
||||||
|
|
||||||
ticketsLock.withLock {
|
ticketsLock.withLock {
|
||||||
@ -521,18 +521,18 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
if (state != ChunkState.FULL)
|
if (state != ChunkState.FULL)
|
||||||
return
|
return
|
||||||
|
|
||||||
super.tick()
|
super.tick(delta)
|
||||||
|
|
||||||
foregroundHealth.entries.removeIf { (pos, health) ->
|
foregroundHealth.entries.removeIf { (pos, health) ->
|
||||||
val (x, y) = pos
|
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)
|
onTileHealthUpdate(x, y, false, health)
|
||||||
remove
|
remove
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundHealth.entries.removeIf { (pos, health) ->
|
backgroundHealth.entries.removeIf { (pos, health) ->
|
||||||
val (x, y) = pos
|
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)
|
onTileHealthUpdate(x, y, true, health)
|
||||||
remove
|
remove
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
|
|
||||||
private constructor(server: StarboundServer, location: Vector3i) : super(location, server.universeClock, server.universe) {
|
private constructor(server: StarboundServer, location: Vector3i) : super(location, server.universeClock, server.universe) {
|
||||||
this.server = server
|
this.server = server
|
||||||
this.lastSpawn = clock.seconds - Globals.systemWorld.objectSpawnCycle
|
this.lastSpawn = clock.time - Globals.systemWorld.objectSpawnCycle
|
||||||
objectSpawnTime = random.nextRange(Globals.systemWorld.objectSpawnInterval)
|
objectSpawnTime = random.nextRange(Globals.systemWorld.objectSpawnInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,27 +173,27 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
val name = it.second.sample(random).orNull() ?: return@ifPresent
|
val name = it.second.sample(random).orNull() ?: return@ifPresent
|
||||||
val uuid = UUID(random.nextLong(), random.nextLong())
|
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 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)
|
val create = ServerEntity(prototype.create(uuid, name), uuid, randomObjectSpawnPosition(), clock.time)
|
||||||
create.enterOrbit(UniversePos(location), Vector2d.ZERO, clock.seconds) // orbit center of system
|
create.enterOrbit(UniversePos(location), Vector2d.ZERO, clock.time) // orbit center of system
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun spawnObjects() {
|
private suspend fun spawnObjects() {
|
||||||
var diff = Globals.systemWorld.objectSpawnCycle.coerceAtMost(clock.seconds - lastSpawn)
|
var diff = Globals.systemWorld.objectSpawnCycle.coerceAtMost(clock.time - lastSpawn)
|
||||||
lastSpawn = clock.seconds - diff
|
lastSpawn = clock.time - diff
|
||||||
|
|
||||||
while (diff > objectSpawnTime) {
|
while (diff > objectSpawnTime) {
|
||||||
lastSpawn += objectSpawnTime
|
lastSpawn += objectSpawnTime
|
||||||
objectSpawnTime = random.nextRange(Globals.systemWorld.objectSpawnInterval)
|
objectSpawnTime = random.nextRange(Globals.systemWorld.objectSpawnInterval)
|
||||||
diff = clock.seconds - lastSpawn
|
diff = clock.time - lastSpawn
|
||||||
|
|
||||||
Globals.systemWorld.objectSpawnPool.sample(random).ifPresent {
|
Globals.systemWorld.objectSpawnPool.sample(random).ifPresent {
|
||||||
val uuid = UUID(random.nextLong(), random.nextLong())
|
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 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()
|
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
|
// 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 ->
|
val targets = universe.children(systemLocation).filter { child ->
|
||||||
entities.values.none { it.orbit.map { it.target == child }.orElse(false) }
|
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
|
// 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
|
// 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()
|
var next = tasks.poll()
|
||||||
|
|
||||||
while (next != null) {
|
while (next != null) {
|
||||||
@ -299,17 +299,17 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
|
|
||||||
private val netVersions = Object2LongOpenHashMap<UUID>()
|
private val netVersions = Object2LongOpenHashMap<UUID>()
|
||||||
|
|
||||||
suspend fun tick(delta: Double = Starbound.SYSTEM_WORLD_TIMESTEP) {
|
suspend fun tick(delta: Double) {
|
||||||
val orbit = destination as? SystemWorldLocation.Orbit
|
val orbit = destination as? SystemWorldLocation.Orbit
|
||||||
|
|
||||||
// if destination is an orbit we haven't started orbiting yet, update the time
|
// if destination is an orbit we haven't started orbiting yet, update the time
|
||||||
if (orbit != null)
|
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 {
|
suspend fun nearPlanetOrbit(planet: UniversePos): Orbit {
|
||||||
val toShip = planetPosition(planet) - position
|
val toShip = planetPosition(planet) - position
|
||||||
val angle = toShip.toAngle()
|
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) {
|
if (location is SystemWorldLocation.Celestial) {
|
||||||
@ -331,7 +331,7 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
val destination: Vector2d
|
val destination: Vector2d
|
||||||
|
|
||||||
if (this.orbit != null) {
|
if (this.orbit != null) {
|
||||||
this.orbit = this.orbit!!.copy(enterTime = clock.seconds)
|
this.orbit = this.orbit!!.copy(enterTime = clock.time)
|
||||||
destination = orbitPosition(this.orbit!!)
|
destination = orbitPosition(this.orbit!!)
|
||||||
} else {
|
} else {
|
||||||
destination = this.destination.resolve(this@ServerSystemWorld) ?: position
|
destination = this.destination.resolve(this@ServerSystemWorld) ?: position
|
||||||
@ -446,8 +446,8 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
var hasExpired = false
|
var hasExpired = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
suspend fun tick(delta: Double = Starbound.SYSTEM_WORLD_TIMESTEP) {
|
suspend fun tick(delta: Double) {
|
||||||
if (!data.permanent && spawnTime > 0.0 && clock.seconds > spawnTime + data.lifeTime)
|
if (!data.permanent && spawnTime > 0.0 && clock.time > spawnTime + data.lifeTime)
|
||||||
hasExpired = true
|
hasExpired = true
|
||||||
|
|
||||||
val orbit = orbit.orNull()
|
val orbit = orbit.orNull()
|
||||||
@ -456,7 +456,7 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
position = orbitPosition(orbit)
|
position = orbitPosition(orbit)
|
||||||
} else if (data.permanent || !data.moving) {
|
} else if (data.permanent || !data.moving) {
|
||||||
// permanent locations always have a solar orbit
|
// permanent locations always have a solar orbit
|
||||||
enterOrbit(systemLocation, Vector2d.ZERO, clock.seconds)
|
enterOrbit(systemLocation, Vector2d.ZERO, clock.time)
|
||||||
} else if (approach != null) {
|
} else if (approach != null) {
|
||||||
if (ships.values.any { it.location == location })
|
if (ships.values.any { it.location == location })
|
||||||
return
|
return
|
||||||
@ -468,9 +468,9 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
position += toApproach.unitVector * data.speed * delta
|
position += toApproach.unitVector * data.speed * delta
|
||||||
|
|
||||||
if ((approach - position).length < planetSize(this.approach!!) + data.orbitDistance)
|
if ((approach - position).length < planetSize(this.approach!!) + data.orbitDistance)
|
||||||
enterOrbit(this.approach!!, approach, clock.seconds)
|
enterOrbit(this.approach!!, approach, clock.time)
|
||||||
} else {
|
} else {
|
||||||
enterOrbit(approach!!, Vector2d.ZERO, clock.seconds)
|
enterOrbit(approach!!, Vector2d.ZERO, clock.time)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val planets = universe.children(systemLocation).filter { child ->
|
val planets = universe.children(systemLocation).filter { child ->
|
||||||
|
@ -141,7 +141,9 @@ class ServerWorld private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
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 {
|
override fun toString(): String {
|
||||||
@ -243,7 +245,7 @@ class ServerWorld private constructor(
|
|||||||
return unapplied
|
return unapplied
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick(delta: Double) {
|
||||||
try {
|
try {
|
||||||
if (clients.isEmpty() && isBusy <= 0) {
|
if (clients.isEmpty() && isBusy <= 0) {
|
||||||
idleTicks++
|
idleTicks++
|
||||||
@ -259,7 +261,7 @@ class ServerWorld private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
super.tick()
|
super.tick(delta)
|
||||||
|
|
||||||
val packet = StepUpdatePacket(ticks)
|
val packet = StepUpdatePacket(ticks)
|
||||||
|
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.io.readDouble
|
import ru.dbotthepony.kstarbound.io.readDouble
|
||||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
interface IClock {
|
interface IClock {
|
||||||
val nanos: Long
|
val time: Double
|
||||||
val micros: Long get() = nanos / 1_000L
|
val nanos: Long get() = (time * 1_000_000_000.0).toLong()
|
||||||
val millis: Long get() = nanos / 1_000_000L
|
|
||||||
val seconds: Double get() = (nanos / 1_000L) / 1_000_000.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is stupid, but legacy protocol requires it
|
// this is stupid, but legacy protocol requires it
|
||||||
@ -28,62 +24,44 @@ class RelativeClock() : IClock {
|
|||||||
read(stream, isLegacy)
|
read(stream, isLegacy)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pointOfReference = 0L
|
private var pointOfReference = 0.0
|
||||||
private var pointOfReferenceSet = false
|
private var pointOfReferenceSet = false
|
||||||
|
|
||||||
override var nanos: Long = 0L
|
override var time: Double = 0.0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun set(age: Long) {
|
fun set(age: Double) {
|
||||||
pointOfReferenceSet = false
|
pointOfReferenceSet = false
|
||||||
nanos = age
|
time = age
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(newPointOfReference: Long) {
|
fun update(newPointOfReference: Double) {
|
||||||
if (pointOfReferenceSet) {
|
if (pointOfReferenceSet) {
|
||||||
val diff = newPointOfReference - pointOfReference
|
val diff = newPointOfReference - pointOfReference
|
||||||
|
|
||||||
if (diff > 0L)
|
if (diff > 0L)
|
||||||
nanos += diff
|
time += diff
|
||||||
}
|
}
|
||||||
|
|
||||||
pointOfReference = newPointOfReference
|
pointOfReference = newPointOfReference
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(newPointOfReference: Double) {
|
|
||||||
return update((newPointOfReference * 1_000_000_000.0).toLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeBoolean(pointOfReferenceSet)
|
stream.writeBoolean(pointOfReferenceSet)
|
||||||
|
|
||||||
if (isLegacy) {
|
if (pointOfReferenceSet)
|
||||||
if (pointOfReferenceSet)
|
stream.writeDouble(pointOfReference)
|
||||||
stream.writeDouble(pointOfReference / 1_000_000_000.0)
|
|
||||||
|
|
||||||
stream.writeDouble(nanos / 1_000_000_000.0)
|
stream.writeDouble(time)
|
||||||
} else {
|
|
||||||
if (pointOfReferenceSet)
|
|
||||||
stream.writeLong(pointOfReference)
|
|
||||||
|
|
||||||
stream.writeLong(nanos)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun read(stream: DataInputStream, isLegacy: Boolean) {
|
fun read(stream: DataInputStream, isLegacy: Boolean) {
|
||||||
pointOfReferenceSet = stream.readBoolean()
|
pointOfReferenceSet = stream.readBoolean()
|
||||||
|
|
||||||
if (isLegacy) {
|
if (pointOfReferenceSet)
|
||||||
if (pointOfReferenceSet)
|
pointOfReference = stream.readDouble()
|
||||||
pointOfReference = (stream.readDouble() * 1_000_000_000.0).toLong()
|
|
||||||
|
|
||||||
nanos = (stream.readDouble() * 1_000_000_000.0).toLong()
|
time = stream.readDouble()
|
||||||
} else {
|
|
||||||
if (pointOfReferenceSet)
|
|
||||||
pointOfReference = stream.readLong()
|
|
||||||
|
|
||||||
nanos = stream.readLong()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,8 +94,8 @@ class JVMClock : IClock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val nanos: Long
|
override val time: Double
|
||||||
get() = if (isPaused) baseline else (System.nanoTime() - origin) + baseline
|
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) {
|
class GameTimer(val time: Double = 0.0) {
|
||||||
@ -153,12 +131,12 @@ class GameTimer(val time: Double = 0.0) {
|
|||||||
timer = time - timer
|
timer = time - timer
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tick(delta: Double = Starbound.TIMESTEP): Boolean {
|
fun tick(delta: Double): Boolean {
|
||||||
timer = (timer - delta).coerceAtLeast(0.0)
|
timer = (timer - delta).coerceAtLeast(0.0)
|
||||||
return timer == 0.0
|
return timer == 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun wrapTick(delta: Double = Starbound.TIMESTEP): Boolean {
|
fun wrapTick(delta: Double): Boolean {
|
||||||
val result = tick(delta)
|
val result = tick(delta)
|
||||||
if (result) reset()
|
if (result) reset()
|
||||||
return result
|
return result
|
||||||
|
@ -270,7 +270,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun tick() {
|
open fun tick(delta: Double) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,13 @@ import ru.dbotthepony.kommons.util.getValue
|
|||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.defs.world.FlyingType
|
import ru.dbotthepony.kstarbound.defs.world.FlyingType
|
||||||
import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig
|
import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WarpPhase
|
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.MasterElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
||||||
@ -74,7 +73,7 @@ class Sky() {
|
|||||||
var referenceClock: IClock? = null
|
var referenceClock: IClock? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
time = value?.seconds ?: time
|
time = value?.time ?: time
|
||||||
}
|
}
|
||||||
|
|
||||||
val speedupTime: Double get() {
|
val speedupTime: Double get() {
|
||||||
@ -192,8 +191,8 @@ class Sky() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun tick(delta: Double = Starbound.TIMESTEP) {
|
fun tick(delta: Double) {
|
||||||
time = referenceClock?.seconds ?: (time + delta)
|
time = referenceClock?.time ?: (time + delta)
|
||||||
flashTimer = (flashTimer - delta).coerceAtLeast(0.0)
|
flashTimer = (flashTimer - delta).coerceAtLeast(0.0)
|
||||||
|
|
||||||
if (flyingType != FlyingType.NONE) {
|
if (flyingType != FlyingType.NONE) {
|
||||||
|
@ -149,7 +149,7 @@ abstract class SystemWorld(val location: Vector3i, val clock: JVMClock, val univ
|
|||||||
val interval = orbitInterval(distance, coordinate.isSatellite)
|
val interval = orbitInterval(distance, coordinate.isSatellite)
|
||||||
|
|
||||||
val start = random.nextFloat().toDouble()
|
val start = random.nextFloat().toDouble()
|
||||||
val offset = (clock.seconds % interval) / interval
|
val offset = (clock.time % interval) / interval
|
||||||
val direction = if (random.nextFloat() > 0.5f) 1 else -1
|
val direction = if (random.nextFloat() > 0.5f) 1 else -1
|
||||||
val angle = (start + direction * offset) * PI * 2.0
|
val angle = (start + direction * offset) * PI * 2.0
|
||||||
return parentPosition + Vector2d(cos(angle) * distance, sin(angle) * distance)
|
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 targetPosition = if (orbit.target.isPlanet || orbit.target.isSatellite) planetPosition(orbit.target) else Vector2d.ZERO
|
||||||
val distance = orbit.enterPosition.length
|
val distance = orbit.enterPosition.length
|
||||||
val interval = orbitInterval(distance, false)
|
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
|
val angle = (orbit.enterPosition * -1).toAngle() + orbit.direction * timeOffset * PI * 2.0
|
||||||
return targetPosition + Vector2d(cos(angle) * distance, sin(angle) * distance)
|
return targetPosition + Vector2d(cos(angle) * distance, sin(angle) * distance)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import ru.dbotthepony.kommons.io.writeStruct2f
|
|||||||
import ru.dbotthepony.kommons.util.getValue
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||||
@ -98,7 +97,7 @@ sealed class TileHealth() {
|
|||||||
val isTicking: Boolean
|
val isTicking: Boolean
|
||||||
get() = !isHealthy && !isDead
|
get() = !isHealthy && !isDead
|
||||||
|
|
||||||
fun tick(config: TileDamageConfig, delta: Double = Starbound.TIMESTEP): Boolean {
|
fun tick(config: TileDamageConfig, delta: Double): Boolean {
|
||||||
if (isDead || isHealthy)
|
if (isDead || isHealthy)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
@ -275,19 +275,19 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
broadcast(SetPlayerStartPacket(position, respawnInWorld))
|
broadcast(SetPlayerStartPacket(position, respawnInWorld))
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun tick() {
|
open fun tick(delta: Double) {
|
||||||
ticks++
|
ticks++
|
||||||
|
|
||||||
Starbound.EXECUTOR.submit(ParallelPerform(dynamicEntities.spliterator(), {
|
Starbound.EXECUTOR.submit(ParallelPerform(dynamicEntities.spliterator(), {
|
||||||
if (!it.isRemote) {
|
if (!it.isRemote) {
|
||||||
it.movement.move()
|
it.movement.move(delta)
|
||||||
}
|
}
|
||||||
})).join()
|
})).join()
|
||||||
|
|
||||||
entityList.forEach {
|
entityList.forEach {
|
||||||
try {
|
try {
|
||||||
if (it.isInWorld) // entities might remove other entities during tick
|
if (it.isInWorld) // entities might remove other entities during tick
|
||||||
it.tick()
|
it.tick(delta)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
if (it.isRemote && isServer) {
|
if (it.isRemote && isServer) {
|
||||||
LOGGER.error("Exception ticking client spawned entity $it, removing", err)
|
LOGGER.error("Exception ticking client spawned entity $it, removing", err)
|
||||||
@ -299,9 +299,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (chunk in chunkMap.chunks())
|
for (chunk in chunkMap.chunks())
|
||||||
chunk.tick()
|
chunk.tick(delta)
|
||||||
|
|
||||||
sky.tick()
|
sky.tick(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||||
|
@ -172,11 +172,11 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
|||||||
|
|
||||||
var isRemote: Boolean = false
|
var isRemote: Boolean = false
|
||||||
|
|
||||||
open fun tick() {
|
open fun tick(delta: Double) {
|
||||||
mailbox.executeQueuedTasks()
|
mailbox.executeQueuedTasks()
|
||||||
|
|
||||||
if (networkGroup.upstream.isInterpolating) {
|
if (networkGroup.upstream.isInterpolating) {
|
||||||
networkGroup.upstream.tickInterpolation(Starbound.TIMESTEP)
|
networkGroup.upstream.tickInterpolation(delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import ru.dbotthepony.kommons.util.KOptional
|
|||||||
import ru.dbotthepony.kommons.util.getValue
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.JumpProfile
|
import ru.dbotthepony.kstarbound.defs.JumpProfile
|
||||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
@ -204,7 +203,7 @@ class ActorMovementController() : MovementController() {
|
|||||||
controlMovementModifiers = ActorMovementModifiers.EMPTY
|
controlMovementModifiers = ActorMovementModifiers.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun move() {
|
override fun move(delta: Double) {
|
||||||
// TODO: anchor entity
|
// TODO: anchor entity
|
||||||
|
|
||||||
if (anchorEntity?.isInWorld != true)
|
if (anchorEntity?.isInWorld != true)
|
||||||
@ -222,8 +221,8 @@ class ActorMovementController() : MovementController() {
|
|||||||
isGroundMovement = false
|
isGroundMovement = false
|
||||||
isLiquidMovement = false
|
isLiquidMovement = false
|
||||||
|
|
||||||
velocity = (anchorEntity.position - position) / Starbound.TIMESTEP
|
velocity = (anchorEntity.position - position) / delta
|
||||||
super.move()
|
super.move(delta)
|
||||||
position = anchorEntity.position
|
position = anchorEntity.position
|
||||||
} else {
|
} else {
|
||||||
val movementParameters = actorMovementParameters.merge(controlActorMovementParameters)
|
val movementParameters = actorMovementParameters.merge(controlActorMovementParameters)
|
||||||
@ -267,15 +266,15 @@ class ActorMovementController() : MovementController() {
|
|||||||
|
|
||||||
targetHorizontalAmbulatingVelocity = 0.0
|
targetHorizontalAmbulatingVelocity = 0.0
|
||||||
|
|
||||||
rotation = (rotation + controlRotationRate * Starbound.TIMESTEP) % (PI * 2.0)
|
rotation = (rotation + controlRotationRate * delta) % (PI * 2.0)
|
||||||
velocity += controlAcceleration * Starbound.TIMESTEP + controlForce / mass * Starbound.TIMESTEP
|
velocity += controlAcceleration * delta + controlForce / mass * delta
|
||||||
|
|
||||||
approachVelocities.forEach {
|
approachVelocities.forEach {
|
||||||
approachVelocity(it.target, it.maxControlForce)
|
approachVelocity(it.target, it.maxControlForce, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
approachVelocityAngles.forEach {
|
approachVelocityAngles.forEach {
|
||||||
approachVelocityAlongAngle(it.alongAngle, it.targetVelocity, it.maxControlForce, it.positiveOnly)
|
approachVelocityAlongAngle(it.alongAngle, it.targetVelocity, it.maxControlForce, delta, it.positiveOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLiquidMovement = liquidPercentage >= (actorMovementParameters.minimumLiquidPercentage ?: 0.0)
|
isLiquidMovement = liquidPercentage >= (actorMovementParameters.minimumLiquidPercentage ?: 0.0)
|
||||||
@ -291,9 +290,9 @@ class ActorMovementController() : MovementController() {
|
|||||||
flyVelocity = flyVelocity.unitVector * (actorMovementParameters.flySpeed ?: 0.0)
|
flyVelocity = flyVelocity.unitVector * (actorMovementParameters.flySpeed ?: 0.0)
|
||||||
|
|
||||||
if (isLiquidMovement)
|
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
|
else
|
||||||
approachVelocity(flyVelocity * movementModifiers.speedModifier, movementParameters.airForce ?: 0.0)
|
approachVelocity(flyVelocity * movementModifiers.speedModifier, movementParameters.airForce ?: 0.0, delta)
|
||||||
|
|
||||||
if (flyVelocity.x > 0.0)
|
if (flyVelocity.x > 0.0)
|
||||||
updatedMovingDirection = Direction.RIGHT
|
updatedMovingDirection = Direction.RIGHT
|
||||||
@ -325,7 +324,7 @@ class ActorMovementController() : MovementController() {
|
|||||||
val maxGroundSustain = movementParameters.groundMovementMaximumSustain ?: 0.0
|
val maxGroundSustain = movementParameters.groundMovementMaximumSustain ?: 0.0
|
||||||
val groundCheckDistance = movementParameters.groundMovementCheckDistance ?: 0.0
|
val groundCheckDistance = movementParameters.groundMovementCheckDistance ?: 0.0
|
||||||
|
|
||||||
groundMovementSustainTimer.tick()
|
groundMovementSustainTimer.tick(delta)
|
||||||
|
|
||||||
if (isOnGround) {
|
if (isOnGround) {
|
||||||
groundMovementSustainTimer = GameTimer(maxGroundSustain)
|
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)
|
velocity = velocity.copy(y = velocity.y + (jumpProfile.jumpSpeed ?: 0.0) * (jumpProfile.jumpInitialPercentage ?: 0.0) * jumpModifier)
|
||||||
groundMovementSustainTimer = GameTimer(0.0)
|
groundMovementSustainTimer = GameTimer(0.0)
|
||||||
} else if (holdJump) {
|
} else if (holdJump) {
|
||||||
reJumpTimer.tick()
|
reJumpTimer.tick(delta)
|
||||||
jumpHoldTimer?.tick()
|
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 {
|
} else {
|
||||||
isJumping = false
|
isJumping = false
|
||||||
reJumpTimer.tick()
|
reJumpTimer.tick(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controlMove == Direction.LEFT) {
|
if (controlMove == Direction.LEFT) {
|
||||||
@ -397,7 +396,7 @@ class ActorMovementController() : MovementController() {
|
|||||||
else
|
else
|
||||||
movementParameters.airForce ?: 0.0
|
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
|
isFlying = controlFly != null
|
||||||
isFalling = (velocity.y < (movementParameters.fallStatusSpeedMin ?: 0.0)) && !isGroundMovement
|
isFalling = (velocity.y < (movementParameters.fallStatusSpeedMin ?: 0.0)) && !isGroundMovement
|
||||||
|
|
||||||
super.move()
|
super.move(delta)
|
||||||
|
|
||||||
lastControlDown = controlDown
|
lastControlDown = controlDown
|
||||||
lastControlJump = controlJump
|
lastControlJump = controlJump
|
||||||
|
@ -23,8 +23,8 @@ import ru.dbotthepony.kstarbound.math.Interpolator
|
|||||||
import ru.dbotthepony.kstarbound.math.PeriodicFunction
|
import ru.dbotthepony.kstarbound.math.PeriodicFunction
|
||||||
import ru.dbotthepony.kstarbound.math.approachAngle
|
import ru.dbotthepony.kstarbound.math.approachAngle
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
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.NetworkedElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedList
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedList
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedSignal
|
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.networkedEventCounter
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
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.networkedPointer
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.world.positiveModulo
|
import java.util.*
|
||||||
import java.util.Collections
|
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
@ -590,7 +587,7 @@ class Animator() {
|
|||||||
|
|
||||||
// TODO: Dynamic target
|
// TODO: Dynamic target
|
||||||
@Suppress("Name_Shadowing")
|
@Suppress("Name_Shadowing")
|
||||||
fun tick(delta: Double = Starbound.TIMESTEP) {
|
fun tick(delta: Double) {
|
||||||
val delta = delta * animationRate
|
val delta = delta * animationRate
|
||||||
|
|
||||||
for (state in stateTypes.values) {
|
for (state in stateTypes.values) {
|
||||||
|
@ -30,8 +30,8 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) {
|
|||||||
|
|
||||||
private var fixturesChangeset = -1
|
private var fixturesChangeset = -1
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick(delta: Double) {
|
||||||
super.tick()
|
super.tick(delta)
|
||||||
|
|
||||||
if (isRemote && networkGroup.upstream.isInterpolating) {
|
if (isRemote && networkGroup.upstream.isInterpolating) {
|
||||||
movement.updateFixtures()
|
movement.updateFixtures()
|
||||||
|
@ -111,7 +111,7 @@ class ItemDropEntity() : DynamicEntity("/") {
|
|||||||
fun take(by: AbstractEntity): ItemStack {
|
fun take(by: AbstractEntity): ItemStack {
|
||||||
if (canTake) {
|
if (canTake) {
|
||||||
state = State.TAKEN
|
state = State.TAKEN
|
||||||
age.set(0L)
|
age.set(0.0)
|
||||||
owningEntity = by.entityID
|
owningEntity = by.entityID
|
||||||
return item.copy()
|
return item.copy()
|
||||||
}
|
}
|
||||||
@ -124,8 +124,8 @@ class ItemDropEntity() : DynamicEntity("/") {
|
|||||||
override val metaBoundingBox: AABB
|
override val metaBoundingBox: AABB
|
||||||
get() = AABB(position - Vector2d(0.5, 0.5), position + Vector2d(0.5, 0.5))
|
get() = AABB(position - Vector2d(0.5, 0.5), position + Vector2d(0.5, 0.5))
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick(delta: Double) {
|
||||||
super.tick()
|
super.tick(delta)
|
||||||
|
|
||||||
if (!isRemote) {
|
if (!isRemote) {
|
||||||
if (item.isEmpty) {
|
if (item.isEmpty) {
|
||||||
@ -138,7 +138,7 @@ class ItemDropEntity() : DynamicEntity("/") {
|
|||||||
if (state != State.TAKEN)
|
if (state != State.TAKEN)
|
||||||
age.update(world.sky.time)
|
age.update(world.sky.time)
|
||||||
else if (stayAliveFor > 0.0) {
|
else if (stayAliveFor > 0.0) {
|
||||||
stayAliveFor -= Starbound.TIMESTEP
|
stayAliveFor -= delta
|
||||||
|
|
||||||
if (stayAliveFor <= 0.0) {
|
if (stayAliveFor <= 0.0) {
|
||||||
state = State.DEAD
|
state = State.DEAD
|
||||||
@ -157,10 +157,10 @@ class ItemDropEntity() : DynamicEntity("/") {
|
|||||||
remove(RemovalReason.REMOVED)
|
remove(RemovalReason.REMOVED)
|
||||||
} else if (stayAliveFor == -1.0) {
|
} else if (stayAliveFor == -1.0) {
|
||||||
val diff = world.geometry.diff(entity.position, position)
|
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) {
|
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
|
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
|
// Average the position and velocity of the drop we merged with
|
||||||
//movement.position += world.geometry.diff(movement.position, entity.movement.position) / 2.0
|
//movement.position += world.geometry.diff(movement.position, entity.movement.position) / 2.0
|
||||||
|
@ -15,7 +15,6 @@ import ru.dbotthepony.kommons.util.setValue
|
|||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kommons.vector.times
|
import ru.dbotthepony.kommons.vector.times
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
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.CollisionPoly
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.util.stream.Stream
|
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.acos
|
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
|
// Instead of applying the force directly, work backwards and figure out the
|
||||||
// maximum acceleration that could be achieved by the current control force,
|
// maximum acceleration that could be achieved by the current control force,
|
||||||
// and maximize the change in velocity based on that.
|
// and maximize the change in velocity based on that.
|
||||||
@ -229,13 +227,13 @@ open class MovementController() {
|
|||||||
|
|
||||||
if (mag == 0.0) return
|
if (mag == 0.0) return
|
||||||
|
|
||||||
val maximumAcceleration = maxControlForce / mass * Starbound.TIMESTEP
|
val maximumAcceleration = maxControlForce / mass * delta
|
||||||
val clampedMag = mag.coerceIn(0.0, maximumAcceleration)
|
val clampedMag = mag.coerceIn(0.0, maximumAcceleration)
|
||||||
|
|
||||||
velocity += diff * (clampedMag / mag)
|
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
|
// Same strategy as approachVelocity, work backwards to figure out the
|
||||||
// maximum acceleration and apply that.
|
// maximum acceleration and apply that.
|
||||||
|
|
||||||
@ -249,26 +247,26 @@ open class MovementController() {
|
|||||||
|
|
||||||
if (diff == 0.0 || positiveOnly && diff < 0.0) return
|
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 diffMag = diff.absoluteValue
|
||||||
val clampedMag = diffMag.coerceIn(0.0, maximumAcceleration)
|
val clampedMag = diffMag.coerceIn(0.0, maximumAcceleration)
|
||||||
|
|
||||||
velocity += axis * diff * (clampedMag / diffMag)
|
velocity += axis * diff * (clampedMag / diffMag)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun approachXVelocity(velocity: Double, maxControlForce: Double) {
|
fun approachXVelocity(velocity: Double, maxControlForce: Double, delta: Double) {
|
||||||
approachVelocityAlongAngle(0.0, velocity, maxControlForce)
|
approachVelocityAlongAngle(0.0, velocity, maxControlForce, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun approachYVelocity(velocity: Double, maxControlForce: Double) {
|
fun approachYVelocity(velocity: Double, maxControlForce: Double, delta: Double) {
|
||||||
approachVelocityAlongAngle(PI / 2.0, velocity, maxControlForce)
|
approachVelocityAlongAngle(PI / 2.0, velocity, maxControlForce, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this function is executed in parallel
|
* this function is executed in parallel
|
||||||
*/
|
*/
|
||||||
// TODO: Ghost collisions occur, where objects trip on edges
|
// 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
|
isZeroGravity = isGravityDisabled || gravityMultiplier == 0.0 || determineGravity().lengthSquared == 0.0
|
||||||
|
|
||||||
val movementParameters = movementParameters
|
val movementParameters = movementParameters
|
||||||
@ -281,7 +279,7 @@ open class MovementController() {
|
|||||||
// TODO: Here: moving platforms sticky code
|
// TODO: Here: moving platforms sticky code
|
||||||
|
|
||||||
if (movementParameters.collisionPoly == null || !movementParameters.collisionPoly.map({ true }, { it.isNotEmpty() }) || movementParameters.collisionEnabled != true) {
|
if (movementParameters.collisionPoly == null || !movementParameters.collisionPoly.map({ true }, { it.isNotEmpty() }) || movementParameters.collisionEnabled != true) {
|
||||||
position += velocity * Starbound.TIMESTEP
|
position += velocity * delta
|
||||||
surfaceSlope = Vector2d.POSITIVE_Y
|
surfaceSlope = Vector2d.POSITIVE_Y
|
||||||
surfaceVelocity = Vector2d.ZERO
|
surfaceVelocity = Vector2d.ZERO
|
||||||
isOnGround = false
|
isOnGround = false
|
||||||
@ -295,14 +293,14 @@ open class MovementController() {
|
|||||||
var steps = 1
|
var steps = 1
|
||||||
|
|
||||||
movementParameters.maxMovementPerStep?.let {
|
movementParameters.maxMovementPerStep?.let {
|
||||||
steps = (velocity.length * Starbound.TIMESTEP / it).toInt() + 1
|
steps = (velocity.length * delta / it).toInt() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var relativeVelocity = velocity
|
var relativeVelocity = velocity
|
||||||
surfaceSlope = Vector2d.POSITIVE_Y
|
surfaceSlope = Vector2d.POSITIVE_Y
|
||||||
// TODO: Here: moving platforms sticky code
|
// TODO: Here: moving platforms sticky code
|
||||||
|
|
||||||
val dt = Starbound.TIMESTEP / steps
|
val dt = delta / steps
|
||||||
|
|
||||||
for (step in 0 until steps) {
|
for (step in 0 until steps) {
|
||||||
val velocityMagnitude = relativeVelocity.length
|
val velocityMagnitude = relativeVelocity.length
|
||||||
@ -384,14 +382,14 @@ open class MovementController() {
|
|||||||
// independently).
|
// independently).
|
||||||
|
|
||||||
if (relativeVelocity.x < 0.0 && correction.x > 0.0)
|
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)
|
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)
|
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)
|
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) {
|
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 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)
|
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)
|
if (isOnGround && (movementParameters.slopeSlidingFactor ?: 0.0) != 0.0 && surfaceSlope != Vector2d.ZERO)
|
||||||
environmentVelocity += -surfaceSlope * (surfaceSlope.x * surfaceSlope.y) * (movementParameters.slopeSlidingFactor ?: 0.0)
|
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
|
// 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
|
// not induce oscillation at very high friction and so it cannot be
|
||||||
// negative.
|
// 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)
|
newVelocity = linearInterpolation(frictionFactor, newVelocity, refVel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,8 +216,8 @@ abstract class TileEntity(path: String) : AbstractEntity(path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick(delta: Double) {
|
||||||
super.tick()
|
super.tick(delta)
|
||||||
|
|
||||||
if (needToUpdateSpaces) {
|
if (needToUpdateSpaces) {
|
||||||
updateMaterialSpacesNow()
|
updateMaterialSpacesNow()
|
||||||
|
@ -394,19 +394,19 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
drawablesCache.invalidate()
|
drawablesCache.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick(delta: Double) {
|
||||||
super.tick()
|
super.tick(delta)
|
||||||
|
|
||||||
flickerPeriod?.update(Starbound.TIMESTEP, world.random)
|
flickerPeriod?.update(delta, world.random)
|
||||||
|
|
||||||
if (!isRemote) {
|
if (!isRemote) {
|
||||||
tileHealth.tick(config.value.damageConfig)
|
tileHealth.tick(config.value.damageConfig, delta)
|
||||||
animator.tick()
|
animator.tick(delta)
|
||||||
|
|
||||||
val orientation = orientation
|
val orientation = orientation
|
||||||
|
|
||||||
if (orientation != null) {
|
if (orientation != null) {
|
||||||
frameTimer = (frameTimer + Starbound.TIMESTEP) % orientation.animationCycle
|
frameTimer = (frameTimer + delta) % orientation.animationCycle
|
||||||
val oldFrame = frame
|
val oldFrame = frame
|
||||||
frame = (frameTimer / orientation.animationCycle * orientation.frames).toInt().coerceIn(0, orientation.frames)
|
frame = (frameTimer / orientation.animationCycle * orientation.frames).toInt().coerceIn(0, orientation.frames)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user