2 Players in same place test

This commit is contained in:
DBotThePony 2024-03-27 17:11:38 +07:00
parent 9bbef92ea9
commit 3579f46209
Signed by: DBot
GPG Key ID: DCC23B5715498507
22 changed files with 161 additions and 60 deletions

View File

@ -90,7 +90,7 @@ object Starbound : ISBFileLocator {
const val NATIVE_PROTOCOL_VERSION = 748 const val NATIVE_PROTOCOL_VERSION = 748
const val LEGACY_PROTOCOL_VERSION = 747 const val LEGACY_PROTOCOL_VERSION = 747
const val TIMESTEP = 1.0 / 60.0 const val TIMESTEP = 1.0 / 60.0
const val TICK_TIME_ADVANCE_NANOS = (TIMESTEP * 1_000_000_000L).toLong() const val TIMESTEP_NANOS = (TIMESTEP * 1_000_000_000L).toLong()
// compile flags. uuuugh // compile flags. uuuugh
const val DEDUP_CELL_STATES = true const val DEDUP_CELL_STATES = true

View File

@ -681,7 +681,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
} }
val spinner = ExecutionSpinner(::executeQueuedTasks, ::renderFrame, Starbound.TICK_TIME_ADVANCE_NANOS) val spinner = ExecutionSpinner(::executeQueuedTasks, ::renderFrame, Starbound.TIMESTEP_NANOS)
val settings = ClientSettings() val settings = ClientSettings()
val viewportCells: ICellAccess = object : ICellAccess { val viewportCells: ICellAccess = object : ICellAccess {

View File

@ -70,9 +70,19 @@ data class HumanoidData(
stream.writeBinaryString(facialMaskDirectives) stream.writeBinaryString(facialMaskDirectives)
stream.writeBinaryString(personalityIdle) stream.writeBinaryString(personalityIdle)
stream.writeBinaryString(personalityArmIdle) stream.writeBinaryString(personalityArmIdle)
if (isLegacy) stream.writeStruct2f(personalityHeadOffset.toFloatVector()) else stream.writeStruct2d(personalityHeadOffset)
if (isLegacy) stream.writeStruct2f(personalityArmOffset.toFloatVector()) else stream.writeStruct2d(personalityArmOffset) if (isLegacy) {
stream.writeColor(color) stream.writeStruct2f(personalityHeadOffset.toFloatVector())
stream.writeStruct2f(personalityArmOffset.toFloatVector())
stream.writeByte(color.redInt)
stream.writeByte(color.greenInt)
stream.writeByte(color.blueInt)
stream.writeByte(color.alphaInt)
} else {
stream.writeStruct2d(personalityHeadOffset)
stream.writeStruct2d(personalityArmOffset)
stream.writeColor(color)
}
stream.writeBoolean(imagePath != null) stream.writeBoolean(imagePath != null)
@ -101,13 +111,12 @@ data class HumanoidData(
val facialMaskType = stream.readInternedString() val facialMaskType = stream.readInternedString()
val facialMaskDirectives = stream.readInternedString() val facialMaskDirectives = stream.readInternedString()
val color: RGBAColor = stream.readColor()
val personalityIdle: String = stream.readInternedString() val personalityIdle: String = stream.readInternedString()
val personalityArmIdle: String = stream.readInternedString() val personalityArmIdle: String = stream.readInternedString()
val personalityHeadOffset: Vector2d = if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d() val personalityHeadOffset: Vector2d = if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d()
val personalityArmOffset: Vector2d = if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d() val personalityArmOffset: Vector2d = if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d()
val color: RGBAColor = if (isLegacy) RGBAColor(stream.readUnsignedByte(), stream.readUnsignedByte(), stream.readUnsignedByte(), stream.readUnsignedByte()) else stream.readColor()
val imagePath: String? = if (stream.readBoolean()) stream.readInternedString() else null val imagePath: String? = if (stream.readBoolean()) stream.readInternedString() else null
return HumanoidData( return HumanoidData(

View File

@ -103,7 +103,7 @@ data class ItemDescriptor(
constructor(ref: Registry.Ref<IItemDefinition>, count: Long, parameters: JsonObject) : this(ref.key.left(), count, parameters) constructor(ref: Registry.Ref<IItemDefinition>, count: Long, parameters: JsonObject) : this(ref.key.left(), count, parameters)
val isEmpty get() = count <= 0L || name == "" || ref.isEmpty val isEmpty get() = count <= 0L || name == "" || ref.isEmpty
val ref by lazy { Registries.items.ref(name) } val ref by lazy { if (name == "") Registries.items.emptyRef else Registries.items.ref(name) }
override fun toString(): String { override fun toString(): String {
return "ItemDescriptor[$name, $count, $parameters]" return "ItemDescriptor[$name, $count, $parameters]"

View File

@ -102,8 +102,8 @@ fun OutputStream.writeAABBLegacyOptional(value: KOptional<AABB>) {
}.ifNotPresent { }.ifNotPresent {
writeFloat(Float.MAX_VALUE) writeFloat(Float.MAX_VALUE)
writeFloat(Float.MAX_VALUE) writeFloat(Float.MAX_VALUE)
writeFloat(Float.MIN_VALUE) writeFloat(-Float.MAX_VALUE)
writeFloat(Float.MIN_VALUE) writeFloat(-Float.MAX_VALUE)
} }
} }

View File

@ -32,7 +32,7 @@ open class ItemStack {
} }
constructor(descriptor: ItemDescriptor) { constructor(descriptor: ItemDescriptor) {
this.config = Registries.items.ref(descriptor.name) this.config = descriptor.ref
this.count = descriptor.count this.count = descriptor.count
this.parameters = descriptor.parameters.deepCopy() this.parameters = descriptor.parameters.deepCopy()
} }
@ -211,7 +211,7 @@ open class ItemStack {
val EMPTY = ItemStack() val EMPTY = ItemStack()
fun create(descriptor: ItemDescriptor): ItemStack { fun create(descriptor: ItemDescriptor): ItemStack {
return EMPTY return ItemStack(descriptor)
} }
} }
} }

View File

@ -236,6 +236,7 @@ class PacketRegistry(val isLegacy: Boolean) {
networkReadBuffer.removeElements(0, reader.position().toInt()) networkReadBuffer.removeElements(0, reader.position().toInt())
} catch (err: EOFException) { } catch (err: EOFException) {
// Ignore EOF, since it is caused by segmented nature of TCP // Ignore EOF, since it is caused by segmented nature of TCP
break
} }
} }
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.network.packets package ru.dbotthepony.kstarbound.network.packets
import it.unimi.dsi.fastutil.bytes.ByteArrayList
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.io.readByteArray import ru.dbotthepony.kommons.io.readByteArray
@ -16,18 +17,18 @@ import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.io.File import java.io.File
class EntityCreatePacket(val entityType: EntityType, val storeData: ByteArray, val firstNetState: ByteArray, val entityID: Int) : IServerPacket, IClientPacket { class EntityCreatePacket(val entityType: EntityType, val storeData: ByteArrayList, val firstNetState: ByteArrayList, val entityID: Int) : IServerPacket, IClientPacket {
constructor(stream: DataInputStream, isLegacy: Boolean) : this( constructor(stream: DataInputStream, isLegacy: Boolean) : this(
EntityType.entries[stream.readUnsignedByte()], EntityType.entries[stream.readUnsignedByte()],
stream.readByteArray(), ByteArrayList.wrap(stream.readByteArray()),
stream.readByteArray(), ByteArrayList.wrap(stream.readByteArray()),
stream.readSignedVarInt() stream.readSignedVarInt()
) )
override fun write(stream: DataOutputStream, isLegacy: Boolean) { override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(entityType.ordinal) stream.writeByte(entityType.ordinal)
stream.writeByteArray(storeData) stream.writeByteArray(storeData.elements(), 0, storeData.size)
stream.writeByteArray(firstNetState) stream.writeByteArray(firstNetState.elements(), 0, firstNetState.size)
stream.writeSignedVarInt(entityID) stream.writeSignedVarInt(entityID)
} }
@ -37,15 +38,25 @@ class EntityCreatePacket(val entityType: EntityType, val storeData: ByteArray, v
} else { } else {
val entity = when (entityType) { val entity = when (entityType) {
EntityType.PLAYER -> { EntityType.PLAYER -> {
val player = PlayerEntity(DataInputStream(FastByteArrayInputStream(storeData)), connection.isLegacy) try {
player.networkGroup.read(firstNetState, isLegacy = connection.isLegacy) val player = PlayerEntity(DataInputStream(FastByteArrayInputStream(storeData.elements(), 0, storeData.size)), connection.isLegacy)
player player.networkGroup.read(firstNetState, isLegacy = connection.isLegacy)
val (data) = player.networkGroup.write(isLegacy = true)
player.networkGroup.read(data, isLegacy = true)
player
} catch (err: Throwable) {
LOGGER.error("", err)
null
}
} }
else -> null else -> null
} }
entity?.entityID = entityID entity?.entityID = entityID
entity?.isRemote = true
entity?.networkGroup?.upstream?.enableInterpolation(0.0)
connection.enqueue { connection.enqueue {
entity?.joinWorld(this) entity?.joinWorld(this)

View File

@ -10,6 +10,7 @@ import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.io.writeByteArray import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeSignedVarInt import ru.dbotthepony.kommons.io.writeSignedVarInt
import ru.dbotthepony.kommons.io.writeVarInt import ru.dbotthepony.kommons.io.writeVarInt
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.ClientConnection import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.network.IServerPacket import ru.dbotthepony.kstarbound.network.IServerPacket
@ -17,14 +18,14 @@ import ru.dbotthepony.kstarbound.server.ServerConnection
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<ByteArray>) : IServerPacket, IClientPacket { class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<ByteArrayList>) : IServerPacket, IClientPacket {
override fun write(stream: DataOutputStream, isLegacy: Boolean) { override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeVarInt(forConnection) stream.writeVarInt(forConnection)
stream.writeVarInt(deltas.size) stream.writeVarInt(deltas.size)
for ((k, v) in deltas.entries) { for ((k, v) in deltas.entries) {
stream.writeSignedVarInt(k) stream.writeSignedVarInt(k)
stream.writeByteArray(v) stream.writeByteArray(v.elements(), 0, v.size)
} }
} }
@ -34,7 +35,7 @@ class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<By
if (id !in connection.entityIDRange) { if (id !in connection.entityIDRange) {
LOGGER.error("Player $connection tried to update entity with ID $id, but that's outside of allowed range ${connection.entityIDRange}!") LOGGER.error("Player $connection tried to update entity with ID $id, but that's outside of allowed range ${connection.entityIDRange}!")
} else { } else {
entities[id]?.readDelta(ByteArrayList.wrap(delta), 0.0, connection.isLegacy) entities[id]?.networkGroup?.read(delta, Starbound.TIMESTEP, connection.isLegacy)
} }
} }
} }
@ -49,12 +50,12 @@ class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<By
val forConnection = stream.readVarInt() val forConnection = stream.readVarInt()
val size = stream.readVarInt() val size = stream.readVarInt()
val deltas = Int2ObjectAVLTreeMap<ByteArray>() val deltas = Int2ObjectAVLTreeMap<ByteArrayList>()
for (i in 0 until size) { for (i in 0 until size) {
val k = stream.readSignedVarInt() val k = stream.readSignedVarInt()
val v = stream.readByteArray() val v = stream.readByteArray()
deltas[k] = v deltas[k] = ByteArrayList.wrap(v)
} }
return EntityUpdateSetPacket(forConnection, deltas) return EntityUpdateSetPacket(forConnection, deltas)

View File

@ -145,6 +145,8 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
value = read value = read
valueListeners.accept(read) valueListeners.accept(read)
} }
bumpVersion()
} }
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) { override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
@ -193,9 +195,10 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
} }
override fun tickInterpolation(delta: Double) { override fun tickInterpolation(delta: Double) {
require(delta >= 0.0) { "Negative interpolation delta: $delta" }
currentTime += delta currentTime += delta
if (isInterpolating) { if (isInterpolating && queue.size >= 2) {
while (queue.size > 2 && queue[1].first <= currentTime) { while (queue.size > 2 && queue[1].first <= currentTime) {
queue.removeFirst() queue.removeFirst()
} }
@ -220,13 +223,23 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
val (time0, value0) = queue[0] val (time0, value0) = queue[0]
val (time1, value1) = queue[1] val (time1, value1) = queue[1]
return interpolator.interpolate(((actualTime - time0) / (time1 - time0)).coerceAtLeast(-extrapolation), value0, value1) var diff = ((actualTime - time0) / (time1 - time0)).coerceAtLeast(-extrapolation)
if (diff.isNaN() || !diff.isFinite())
diff = 0.0
return interpolator.interpolate(diff, value0, value1)
} else if (actualTime > queue.last().first) { } else if (actualTime > queue.last().first) {
// 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]
return interpolator.interpolate(((actualTime - time1) / (time1 - time0)).coerceAtMost(extrapolation + 1.0), value0, value1) var diff = ((actualTime - time1) / (time1 - time0)).coerceAtMost(extrapolation + 1.0)
if (diff.isNaN() || !diff.isFinite())
diff = 0.0
return interpolator.interpolate(diff, value0, value1)
} else { } else {
// normal interpolation // normal interpolation
for (i in 0 until queue.size - 1) { for (i in 0 until queue.size - 1) {
@ -234,7 +247,12 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
val (time1, value1) = queue[i + 1] val (time1, value1) = queue[i + 1]
if (actualTime in time0 .. time1) { if (actualTime in time0 .. time1) {
return interpolator.interpolate((actualTime - time0) / (time1 - time0), value0, value1) var diff = (actualTime - time0) / (time1 - time0)
if (diff.isNaN() || !diff.isFinite())
diff = 0.0
return interpolator.interpolate(diff, value0, value1)
} }
} }

View File

@ -93,6 +93,8 @@ class NetworkedMap<K, V>(
} }
fun write(data: DataOutputStream, isLegacy: Boolean, self: NetworkedMap<K, V>) { fun write(data: DataOutputStream, isLegacy: Boolean, self: NetworkedMap<K, V>) {
data.writeByte(action.ordinal)
if (isLegacy) { if (isLegacy) {
when (action) { when (action) {
Action.ADD -> { Action.ADD -> {

View File

@ -3,6 +3,10 @@ package ru.dbotthepony.kstarbound.server
import com.google.gson.JsonObject import com.google.gson.JsonObject
import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelHandlerContext
import it.unimi.dsi.fastutil.bytes.ByteArrayList import it.unimi.dsi.fastutil.bytes.ByteArrayList
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
@ -21,6 +25,8 @@ import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.ConnectionType import ru.dbotthepony.kstarbound.network.ConnectionType
import ru.dbotthepony.kstarbound.network.IServerPacket import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
@ -32,6 +38,8 @@ import ru.dbotthepony.kstarbound.world.IChunkListener
import ru.dbotthepony.kstarbound.world.api.ImmutableCell import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.WorldObject import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import java.io.DataOutputStream
import java.util.HashMap import java.util.HashMap
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -250,6 +258,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
} }
} }
private val entityVersions = Int2LongOpenHashMap()
init {
entityVersions.defaultReturnValue(-1L)
}
fun tickWorld() { fun tickWorld() {
val world = world val world = world
@ -284,6 +298,33 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
itr.remove() itr.remove()
} }
for ((id, entity) in world.entities) {
if (entity.connectionID != connectionID && entity is PlayerEntity) {
if (entityVersions.get(id) == -1L) {
// never networked
val initial = FastByteArrayOutputStream()
entity.writeNetwork(DataOutputStream(initial), isLegacy)
val (data, version) = entity.networkGroup.write(isLegacy = isLegacy)
entityVersions.put(id, version)
send(EntityCreatePacket(
entity.type,
ByteArrayList.wrap(initial.array, initial.length),
data,
entity.entityID
))
} else {
val (data, version) = entity.networkGroup.write(remoteVersion = entityVersions.get(id), isLegacy = isLegacy)
entityVersions.put(id, version)
if (data.isNotEmpty()) {
send(EntityUpdateSetPacket(entity.connectionID, Int2ObjectMaps.singleton(entity.entityID, data)))
}
}
}
}
} }
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
@ -316,9 +357,17 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
shipWorld = it shipWorld = it
shipWorld.thread.start() shipWorld.thread.start()
send(PlayerWarpResultPacket(true, WarpAlias.OwnShip, false)) send(PlayerWarpResultPacket(true, WarpAlias.OwnShip, false))
shipWorld.acceptPlayer(this).exceptionally { shipWorld.acceptPlayer(this).thenAccept {
for (conn in server.channels.connections) {
if (conn.isLegacy && conn !== this) {
conn.shipWorld.acceptPlayer(this)
break
}
}
}.exceptionally {
LOGGER.error("Shipworld of $this rejected to accept its owner", it) LOGGER.error("Shipworld of $this rejected to accept its owner", it)
disconnect("Shipworld rejected player warp request: $it") disconnect("Shipworld rejected player warp request: $it")
null
} }
}.exceptionally { }.exceptionally {
LOGGER.error("Error while initializing shipworld for $this", it) LOGGER.error("Error while initializing shipworld for $this", it)

View File

@ -31,7 +31,7 @@ sealed class StarboundServer(val root: File) : Closeable {
val serverID = threadCounter.getAndIncrement() val serverID = threadCounter.getAndIncrement()
val mailbox = MailboxExecutorService() val mailbox = MailboxExecutorService()
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS) val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TIMESTEP_NANOS)
val thread = Thread(spinner, "Starbound Server $serverID") val thread = Thread(spinner, "Starbound Server $serverID")
val universe = ServerUniverse() val universe = ServerUniverse()
val chat = ChatHandler(this) val chat = ChatHandler(this)

View File

@ -113,7 +113,7 @@ class ServerWorld private constructor(
} }
} }
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS) val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TIMESTEP_NANOS)
val thread = Thread(spinner, "Starbound Server World Thread") val thread = Thread(spinner, "Starbound Server World Thread")
val ticketListLock = ReentrantLock() val ticketListLock = ReentrantLock()

View File

@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.util
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
import ru.dbotthepony.kommons.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.WindowsBindings import ru.dbotthepony.kstarbound.WindowsBindings
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
@ -47,7 +46,7 @@ class ExecutionSpinner(private val waiter: Runnable, private val spinner: Boolea
} }
private fun timeUntilNextFrame(): Long { private fun timeUntilNextFrame(): Long {
return Starbound.TICK_TIME_ADVANCE_NANOS - (System.nanoTime() - lastRender) - frameRenderTime return Starbound.TIMESTEP_NANOS - (System.nanoTime() - lastRender) - frameRenderTime
} }
private var carrier: Thread? = null private var carrier: Thread? = null

View File

@ -269,7 +269,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
ticks++ ticks++
mailbox.executeQueuedTasks() mailbox.executeQueuedTasks()
ForkJoinPool.commonPool().submit(ParallelPerform(dynamicEntities.spliterator(), { it.movement.move() })).join() ForkJoinPool.commonPool().submit(ParallelPerform(dynamicEntities.spliterator(), { if (!it.isRemote) it.movement.move() })).join()
mailbox.executeQueuedTasks() mailbox.executeQueuedTasks()
entities.values.forEach { it.think() } entities.values.forEach { it.think() }

View File

@ -3,16 +3,19 @@ package ru.dbotthepony.kstarbound.world.entities
import it.unimi.dsi.fastutil.bytes.ByteArrayList import it.unimi.dsi.fastutil.bytes.ByteArrayList
import ru.dbotthepony.kommons.util.MailboxExecutorService import ru.dbotthepony.kommons.util.MailboxExecutorService
import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.JsonDriven import ru.dbotthepony.kstarbound.defs.JsonDriven
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.World
import java.io.DataInputStream import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.UUID
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
abstract class AbstractEntity(path: String) : JsonDriven(path) { abstract class AbstractEntity(path: String) : JsonDriven(path) {
@ -54,8 +57,15 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
throw IllegalStateException("Already has Entity ID set (to $field)") throw IllegalStateException("Already has Entity ID set (to $field)")
field = value field = value
if (value < 0) {
connectionID = (-value - 1) / 65536 + 1
}
} }
var connectionID: Int = 0
private set
var mailbox = MailboxExecutorService() var mailbox = MailboxExecutorService()
private set private set
@ -67,6 +77,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
val isSpawned: Boolean val isSpawned: Boolean
get() = innerWorld != null get() = innerWorld != null
abstract val type: EntityType
/** /**
* If set, then the entity will be discoverable by its unique id and will be * If set, then the entity will be discoverable by its unique id and will be
@ -88,9 +99,8 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
protected open fun onJoinWorld(world: World<*, *>) { } protected open fun onJoinWorld(world: World<*, *>) { }
protected open fun onRemove(world: World<*, *>) { } protected open fun onRemove(world: World<*, *>) { }
abstract fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) val networkGroup = MasterElement(NetworkedGroup())
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
abstract fun readDelta(stream: ByteArrayList, interpolationTime: Double = 0.0, isLegacy: Boolean)
fun joinWorld(world: World<*, *>) { fun joinWorld(world: World<*, *>) {
if (innerWorld != null) if (innerWorld != null)
@ -124,8 +134,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
innerWorld = null innerWorld = null
} }
open val isRemote: Boolean var isRemote: Boolean = false
get() = innerWorld?.isRemote ?: false
fun think() { fun think() {
thinkShared() thinkShared()
@ -142,7 +151,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
} }
protected open fun thinkRemote() { protected open fun thinkRemote() {
networkGroup.upstream.tickInterpolation(Starbound.TIMESTEP)
} }
protected open fun thinkLocal() { protected open fun thinkLocal() {

View File

@ -133,8 +133,8 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
val networkGroup = NetworkedGroup() val networkGroup = NetworkedGroup()
var duration by networkedFixedPoint(0.01).also { networkGroup.add(it); it.interpolator = Interpolator.Linear } var duration by networkedFixedPoint(0.01).also { networkGroup.add(it); it.interpolator = Interpolator.Linear }
var maxDuration by networkedFixedPoint(0.01).also { networkGroup.add(it) } var maxDuration by networkedFloat().also { networkGroup.add(it) }
var sourceEntity by networkedData(KOptional(), KOptionalIntValueCodec) var sourceEntity by networkedData(KOptional(), KOptionalIntValueCodec).also { networkGroup.add(it) }
} }
// stats // stats

View File

@ -21,6 +21,7 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.server.world.ServerWorld
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.world.Side import ru.dbotthepony.kstarbound.world.Side
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
@ -46,11 +47,10 @@ open class WorldObject(
} }
} }
override fun readDelta(stream: ByteArrayList, interpolationTime: Double, isLegacy: Boolean) { override val type: EntityType
TODO("Not yet implemented") get() = EntityType.OBJECT
}
override fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) { override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -2,11 +2,13 @@ package ru.dbotthepony.kstarbound.world.entities.player
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.bytes.ByteArrayList import it.unimi.dsi.fastutil.bytes.ByteArrayList
import ru.dbotthepony.kommons.io.writeBinaryString
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.GlobalDefaults import ru.dbotthepony.kstarbound.GlobalDefaults
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.actor.HumanoidData import ru.dbotthepony.kstarbound.defs.actor.HumanoidData
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode
@ -53,10 +55,16 @@ class PlayerEntity() : HumanoidActorEntity("/") {
println(humanoidData) println(humanoidData)
} }
override val type: EntityType
get() = EntityType.PLAYER
var gamemode = PlayerGamemode.CASUAL var gamemode = PlayerGamemode.CASUAL
override fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) { override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
TODO("Not yet implemented") stream.writeBinaryString(uniqueID!!)
stream.writeBinaryString("")
if (isLegacy) stream.writeInt(gamemode.ordinal) else stream.writeByte(gamemode.ordinal)
humanoidData.write(stream, isLegacy)
} }
val inventory = PlayerInventory() val inventory = PlayerInventory()
@ -65,12 +73,6 @@ class PlayerEntity() : HumanoidActorEntity("/") {
override val statusController = StatusController(this, GlobalDefaults.player.statusControllerSettings) override val statusController = StatusController(this, GlobalDefaults.player.statusControllerSettings)
val techController = TechController(this) val techController = TechController(this)
val networkGroup = MasterElement(NetworkedGroup())
override fun readDelta(stream: ByteArrayList, interpolationTime: Double, isLegacy: Boolean) {
networkGroup.read(stream, interpolationTime, isLegacy)
}
var state by networkGroup.upstream.add(networkedEnum(State.IDLE)) var state by networkGroup.upstream.add(networkedEnum(State.IDLE))
var shifting by networkGroup.upstream.add(networkedBoolean()) var shifting by networkGroup.upstream.add(networkedBoolean())
private var xAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125)) private var xAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125))

View File

@ -58,6 +58,8 @@ class PlayerInventory {
} }
} }
val networkGroup = NetworkedGroup()
// here it gets interesting, original code is using List#sorted, which itself uses Star::sort, // here it gets interesting, original code is using List#sorted, which itself uses Star::sort,
// which is just an alias for std::sort, and std::sort is ***not*** stable sort, meaning // which is just an alias for std::sort, and std::sort is ***not*** stable sort, meaning
// if bags have same priority, PlayerInventory behavior becomes undefined // if bags have same priority, PlayerInventory behavior becomes undefined
@ -69,8 +71,6 @@ class PlayerInventory {
.map { it.key to Bag(it.value.size) } .map { it.key to Bag(it.value.size) }
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second })) .collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
val networkGroup = NetworkedGroup()
val equipment: ImmutableMap<EquipmentSlot, NetworkedItemStack> = EquipmentSlot.entries val equipment: ImmutableMap<EquipmentSlot, NetworkedItemStack> = EquipmentSlot.entries
.stream() .stream()
.map { it to networkedItem() } .map { it to networkedItem() }

View File

@ -108,7 +108,7 @@ object NetworkedElementTests {
val result = master.write().first val result = master.write().first
slave.read(FastByteArrayInputStream(result.array, 0, result.length)) slave.read(result)
assertEquals(567, slaveField1.get()) assertEquals(567, slaveField1.get())
assertEquals(17000, slaveField2.get()) assertEquals(17000, slaveField2.get())