Entity message handling
This commit is contained in:
parent
0af6646212
commit
26c579f568
@ -22,6 +22,7 @@ import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
@ -339,6 +340,12 @@ class ClientWorld(
|
|||||||
client.activeConnection?.send(data)
|
client.activeConnection?.send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun remote(connectionID: Int): Connection? {
|
||||||
|
// connectionID might be of different client (and not of server's),
|
||||||
|
// but we always must send messages to server
|
||||||
|
return client.activeConnection
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ring = listOf(
|
val ring = listOf(
|
||||||
Vector2i(0, 0),
|
Vector2i(0, 0),
|
||||||
|
@ -45,6 +45,14 @@ class IdMap<T : Any>(val min: Int = 0, val max: Int = Int.MAX_VALUE, private val
|
|||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun get(key: Int): T? {
|
||||||
|
if (map is Int2ObjectMap) {
|
||||||
|
return (map as Int2ObjectMap).get(key)
|
||||||
|
} else {
|
||||||
|
return map[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
nextIndex = min - 1
|
nextIndex = min - 1
|
||||||
map.clear()
|
map.clear()
|
||||||
|
@ -180,13 +180,12 @@ data class ItemDescriptor(
|
|||||||
it.add("parameters", parameters.deepCopy())
|
it.add("parameters", parameters.deepCopy())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJson(): JsonObject? {
|
fun toJson(): JsonElement {
|
||||||
if (Starbound.IS_STORE_JSON) {
|
if (Starbound.IS_STORE_JSON) {
|
||||||
return VersionRegistry.make("Item", toJsonStruct()).toJson()
|
return VersionRegistry.make("Item", toJsonStruct()).toJson()
|
||||||
} else {
|
} else {
|
||||||
if (isEmpty) {
|
if (isEmpty)
|
||||||
return null
|
return JsonNull.INSTANCE
|
||||||
}
|
|
||||||
|
|
||||||
return toJsonStruct()
|
return toJsonStruct()
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@ package ru.dbotthepony.kstarbound.item
|
|||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterable
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterator
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterators
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntLists
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
@ -11,6 +15,7 @@ interface IContainer {
|
|||||||
var size: Int
|
var size: Int
|
||||||
operator fun get(index: Int): ItemStack
|
operator fun get(index: Int): ItemStack
|
||||||
operator fun set(index: Int, value: ItemStack)
|
operator fun set(index: Int, value: ItemStack)
|
||||||
|
fun clear()
|
||||||
|
|
||||||
fun ageItems(by: Double): Boolean {
|
fun ageItems(by: Double): Boolean {
|
||||||
var any = false
|
var any = false
|
||||||
@ -109,13 +114,29 @@ interface IContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// puts item into container, returns remaining not put items
|
// returns not inserted items
|
||||||
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
|
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
|
||||||
|
return put({ IntIterators.fromTo(0, size) }, item, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns not inserted items
|
||||||
|
fun put(slots: IntIterable, item: ItemStack, simulate: Boolean = false): ItemStack {
|
||||||
|
return put(slots::iterator, item, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns not inserted items
|
||||||
|
fun put(slots: () -> IntIterator, item: ItemStack, simulate: Boolean = false): ItemStack {
|
||||||
val copy = item.copy()
|
val copy = item.copy()
|
||||||
|
|
||||||
// first, try to put into not empty slots first
|
var itr = slots.invoke()
|
||||||
for (i in 0 until size) {
|
|
||||||
val itemThere = this[i]
|
while (itr.hasNext()) {
|
||||||
|
val slot = itr.nextInt()
|
||||||
|
|
||||||
|
if (slot !in 0 until size)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val itemThere = this[slot]
|
||||||
|
|
||||||
if (itemThere.isStackable(copy)) {
|
if (itemThere.isStackable(copy)) {
|
||||||
val newCount = (itemThere.size + copy.size).coerceAtMost(itemThere.maxStackSize)
|
val newCount = (itemThere.size + copy.size).coerceAtMost(itemThere.maxStackSize)
|
||||||
@ -131,21 +152,29 @@ interface IContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// then try to move into empty slots
|
itr = slots.invoke()
|
||||||
for (i in 0 until size) {
|
|
||||||
val itemThere = this[i]
|
while (itr.hasNext()) {
|
||||||
|
val slot = itr.nextInt()
|
||||||
|
|
||||||
|
if (slot !in 0 until size)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val itemThere = this[slot]
|
||||||
|
|
||||||
if (itemThere.isEmpty) {
|
if (itemThere.isEmpty) {
|
||||||
if (copy.size > copy.maxStackSize) {
|
if (itemThere.isEmpty) {
|
||||||
if (!simulate)
|
if (copy.size > copy.maxStackSize) {
|
||||||
this[i] = copy.copy(copy.maxStackSize)
|
if (!simulate)
|
||||||
|
this[slot] = copy.copy(copy.maxStackSize)
|
||||||
|
|
||||||
copy.size -= copy.maxStackSize
|
copy.size -= copy.maxStackSize
|
||||||
} else {
|
} else {
|
||||||
if (!simulate)
|
if (!simulate)
|
||||||
this[i] = copy
|
this[slot] = copy
|
||||||
|
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +182,153 @@ interface IContainer {
|
|||||||
return copy
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear()
|
fun take(slot: Int, amount: Long): ItemStack {
|
||||||
|
if (slot !in 0 until size || amount <= 0L)
|
||||||
|
return ItemStack.EMPTY
|
||||||
|
|
||||||
|
val item = this[slot]
|
||||||
|
|
||||||
|
if (item.isEmpty)
|
||||||
|
return item
|
||||||
|
|
||||||
|
if (item.size > amount) {
|
||||||
|
val copy = item.copy(amount)
|
||||||
|
item.shrink(amount)
|
||||||
|
return copy
|
||||||
|
} else {
|
||||||
|
this[slot] = ItemStack.EMPTY
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun takeExact(slot: Int, amount: Long): Boolean {
|
||||||
|
if (amount <= 0L)
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (slot !in 0 until size)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val item = this[slot]
|
||||||
|
|
||||||
|
if (item.isEmpty)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (item.size > amount) {
|
||||||
|
item.shrink(amount)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun swap(slot: Int, item: ItemStack, tryCombine: Boolean = true): ItemStack {
|
||||||
|
val existingItem = this[slot]
|
||||||
|
|
||||||
|
if (item.isEmpty && existingItem.isEmpty) {
|
||||||
|
return ItemStack.EMPTY
|
||||||
|
} else if (item.isEmpty && existingItem.isNotEmpty) {
|
||||||
|
// If we are passed in nothing, simply return what's there, if anything.
|
||||||
|
this[slot] = ItemStack.EMPTY
|
||||||
|
return existingItem
|
||||||
|
} else if (item.isNotEmpty && existingItem.isEmpty) {
|
||||||
|
// place into slot
|
||||||
|
// use put because item might be bigger than max stack size
|
||||||
|
return put(IntLists.singleton(slot), item)
|
||||||
|
} else {
|
||||||
|
// If something is there, try to stack with it first. If we can't stack,
|
||||||
|
// then swap.
|
||||||
|
|
||||||
|
if (tryCombine && existingItem.isStackable(item)) {
|
||||||
|
return put(IntLists.singleton(slot), item)
|
||||||
|
} else {
|
||||||
|
this[slot] = ItemStack.EMPTY
|
||||||
|
val slots = IntArrayList(IntIterators.fromTo(0, size))
|
||||||
|
slots.removeInt(slot)
|
||||||
|
slots.add(0, slot)
|
||||||
|
|
||||||
|
val remaining = put(slots, item)
|
||||||
|
|
||||||
|
if (remaining.isNotEmpty && this[slot].isStackable(remaining)) {
|
||||||
|
// damn
|
||||||
|
this[slot].grow(remaining.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun take(descriptor: ItemDescriptor, exactMatch: Boolean = false, simulate: Boolean = false): Boolean {
|
||||||
|
var toTake = descriptor.count
|
||||||
|
|
||||||
|
if (toTake <= 0L)
|
||||||
|
return true
|
||||||
|
|
||||||
|
val extractSlots = IntArrayList()
|
||||||
|
|
||||||
|
for (slot in 0 until size) {
|
||||||
|
val item = this[slot]
|
||||||
|
|
||||||
|
if (item.matches(descriptor, exactMatch)) {
|
||||||
|
toTake -= item.size
|
||||||
|
if (!simulate) extractSlots.add(slot)
|
||||||
|
if (toTake <= 0L) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simulate || toTake > 0L)
|
||||||
|
return toTake <= 0L
|
||||||
|
|
||||||
|
toTake = descriptor.count
|
||||||
|
|
||||||
|
for (slot in extractSlots) {
|
||||||
|
if (this[slot].size <= toTake) {
|
||||||
|
toTake -= this[slot].size
|
||||||
|
this[slot] = ItemStack.EMPTY
|
||||||
|
} else {
|
||||||
|
this[slot].shrink(toTake)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun take(descriptor: ItemStack, exactMatch: Boolean = false, simulate: Boolean = false): Boolean {
|
||||||
|
var toTake = descriptor.size
|
||||||
|
|
||||||
|
if (toTake <= 0L)
|
||||||
|
return true
|
||||||
|
|
||||||
|
val extractSlots = IntArrayList()
|
||||||
|
|
||||||
|
for (slot in 0 until size) {
|
||||||
|
val item = this[slot]
|
||||||
|
|
||||||
|
if (item.matches(descriptor, exactMatch)) {
|
||||||
|
toTake -= item.size
|
||||||
|
if (!simulate) extractSlots.add(slot)
|
||||||
|
if (toTake <= 0L) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simulate || toTake > 0L)
|
||||||
|
return toTake <= 0L
|
||||||
|
|
||||||
|
toTake = descriptor.size
|
||||||
|
|
||||||
|
for (slot in extractSlots) {
|
||||||
|
if (this[slot].size <= toTake) {
|
||||||
|
toTake -= this[slot].size
|
||||||
|
this[slot] = ItemStack.EMPTY
|
||||||
|
} else {
|
||||||
|
this[slot].shrink(toTake)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun toJson(store: Boolean = false): JsonArray {
|
fun toJson(store: Boolean = false): JsonArray {
|
||||||
val result = JsonArray(size)
|
val result = JsonArray(size)
|
||||||
|
@ -359,9 +359,9 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
|
|||||||
return ItemStack(entry, config, parameters.deepCopy(), size)
|
return ItemStack(entry, config, parameters.deepCopy(), size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJson(): JsonObject? {
|
fun toJson(): JsonElement {
|
||||||
if (isEmpty)
|
if (isEmpty)
|
||||||
return null
|
return JsonNull.INSTANCE
|
||||||
|
|
||||||
return createDescriptor().toJson()
|
return createDescriptor().toJson()
|
||||||
}
|
}
|
||||||
|
@ -245,6 +245,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val SERVER_CONNECTION_ID = 0
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
private val warpActionCodec = StreamCodec.Pair(WarpAction.CODEC, WarpMode.CODEC).koptional()
|
private val warpActionCodec = StreamCodec.Pair(WarpAction.CODEC, WarpMode.CODEC).koptional()
|
||||||
|
@ -24,6 +24,8 @@ import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
|||||||
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.EntityMessagePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.EntityMessageResponsePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.PingPacket
|
import ru.dbotthepony.kstarbound.network.packets.PingPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.PongPacket
|
import ru.dbotthepony.kstarbound.network.packets.PongPacket
|
||||||
@ -483,8 +485,8 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
LEGACY.add(HitRequestPacket::read)
|
LEGACY.add(HitRequestPacket::read)
|
||||||
LEGACY.add(DamageRequestPacket::read)
|
LEGACY.add(DamageRequestPacket::read)
|
||||||
LEGACY.add(::DamageNotificationPacket)
|
LEGACY.add(::DamageNotificationPacket)
|
||||||
LEGACY.skip("EntityMessage")
|
LEGACY.add(::EntityMessagePacket)
|
||||||
LEGACY.skip("EntityMessageResponse")
|
LEGACY.add(::EntityMessageResponsePacket)
|
||||||
LEGACY.add(::UpdateWorldPropertiesPacket)
|
LEGACY.add(::UpdateWorldPropertiesPacket)
|
||||||
LEGACY.add(::StepUpdatePacket)
|
LEGACY.add(::StepUpdatePacket)
|
||||||
|
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import ru.dbotthepony.kommons.io.readUUID
|
||||||
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
|
import ru.dbotthepony.kommons.io.writeUUID
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonArray
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonArray
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
class EntityMessagePacket(val entity: Either<Int, String>, val message: String, val arguments: JsonArray, val id: UUID, val sourceConnection: Int) : IClientPacket, IServerPacket {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||||
|
if (stream.readBoolean()) Either.right(stream.readInternedString()) else Either.left(stream.readInt()),
|
||||||
|
stream.readInternedString(),
|
||||||
|
stream.readJsonArray(),
|
||||||
|
stream.readUUID(),
|
||||||
|
stream.readUnsignedShort()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeBoolean(entity.isRight)
|
||||||
|
|
||||||
|
if (entity.isLeft) {
|
||||||
|
stream.writeInt(entity.left())
|
||||||
|
} else {
|
||||||
|
stream.writeBinaryString(entity.right())
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.writeBinaryString(message)
|
||||||
|
stream.writeJsonArray(arguments)
|
||||||
|
stream.writeUUID(id)
|
||||||
|
stream.writeShort(sourceConnection)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handle(connection: Connection, world: World<*, *>) {
|
||||||
|
if (entity.isLeft) {
|
||||||
|
val entity = world.entities[entity.left()]
|
||||||
|
|
||||||
|
if (entity == null) {
|
||||||
|
connection.send(EntityMessageResponsePacket(Either.left("No such entity ${this@EntityMessagePacket.entity}"), id))
|
||||||
|
} else {
|
||||||
|
entity.dispatchMessage(connection.connectionID, message, arguments)
|
||||||
|
.thenAccept(Consumer {
|
||||||
|
connection.send(EntityMessageResponsePacket(Either.right(it), id))
|
||||||
|
})
|
||||||
|
.exceptionally(Function {
|
||||||
|
connection.send(EntityMessageResponsePacket(Either.left(it.message ?: "Internal server error"), id))
|
||||||
|
null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TODO("messages to unique entities")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
handle(connection, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
val world = world
|
||||||
|
|
||||||
|
if (world != null) {
|
||||||
|
handle(connection, world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import ru.dbotthepony.kommons.io.readUUID
|
||||||
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
|
import ru.dbotthepony.kommons.io.writeUUID
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.io.readEither
|
||||||
|
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeEither
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class EntityMessageResponsePacket(val response: Either<String, JsonElement>, val id: UUID) : IServerPacket, IClientPacket {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||||
|
stream.readEither(isLegacy, { readInternedString() }, { readJsonElement() }),
|
||||||
|
stream.readUUID())
|
||||||
|
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
// ah yes, Variant<> in EntityMessagePacket, but Either<> in EntityMessageResponsePacket
|
||||||
|
stream.writeEither(response, isLegacy, { writeBinaryString(it) }, { writeJsonElement(it) })
|
||||||
|
stream.writeUUID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
val message = pendingEntityMessages.asMap().remove(id)
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
if (response.isLeft) {
|
||||||
|
message.completeExceptionally(AbstractEntity.MessageCallException(response.left()))
|
||||||
|
} else {
|
||||||
|
message.complete(response.right())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.enqueue {
|
||||||
|
val message = world?.pendingEntityMessages?.asMap()?.remove(this@EntityMessageResponsePacket.id)
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
if (response.isLeft) {
|
||||||
|
message.completeExceptionally(AbstractEntity.MessageCallException(response.left()))
|
||||||
|
} else {
|
||||||
|
message.complete(response.right())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
import ru.dbotthepony.kstarbound.network.IPacket
|
import ru.dbotthepony.kstarbound.network.IPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
@ -304,7 +305,7 @@ class ServerWorld private constructor(
|
|||||||
val broken = entity.damageTileEntity(occupySpaces, sourcePosition, actualDamage)
|
val broken = entity.damageTileEntity(occupySpaces, sourcePosition, actualDamage)
|
||||||
|
|
||||||
if (source != null && broken) {
|
if (source != null && broken) {
|
||||||
source.receiveMessage("tileEntityBroken", jsonArrayOf(
|
source.dispatchMessage(Connection.SERVER_CONNECTION_ID, "tileEntityBroken", jsonArrayOf(
|
||||||
damagePositions.firstOrNull { p -> actualPositions.any { it.position == p } } ?: entity.tilePosition,
|
damagePositions.firstOrNull { p -> actualPositions.any { it.position == p } } ?: entity.tilePosition,
|
||||||
entity.type.jsonName,
|
entity.type.jsonName,
|
||||||
(entity as? WorldObject)?.config?.key))
|
(entity as? WorldObject)?.config?.key))
|
||||||
@ -341,7 +342,7 @@ class ServerWorld private constructor(
|
|||||||
topMost = topMost.coerceAtLeast(result)
|
topMost = topMost.coerceAtLeast(result)
|
||||||
|
|
||||||
if (source != null && health?.isDead == true) {
|
if (source != null && health?.isDead == true) {
|
||||||
source.receiveMessage("tileBroken", jsonArrayOf(
|
source.dispatchMessage(Connection.SERVER_CONNECTION_ID, "tileBroken", jsonArrayOf(
|
||||||
pos, if (isBackground) "background" else "foreground",
|
pos, if (isBackground) "background" else "foreground",
|
||||||
tile!!.tile(isBackground).material.id ?: tile.tile(isBackground).material.key, // TODO: explicit string identifiers support
|
tile!!.tile(isBackground).material.id ?: tile.tile(isBackground).material.key, // TODO: explicit string identifiers support
|
||||||
tile.dungeonId,
|
tile.dungeonId,
|
||||||
@ -644,6 +645,10 @@ class ServerWorld private constructor(
|
|||||||
server.channels.connectionByID(data.destinationConnection)?.send(data)
|
server.channels.connectionByID(data.destinationConnection)?.send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun remote(connectionID: Int): Connection? {
|
||||||
|
return server.channels.connectionByID(connectionID)
|
||||||
|
}
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class MetadataJson(
|
data class MetadataJson(
|
||||||
val playerStart: Vector2d,
|
val playerStart: Vector2d,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
@ -26,6 +28,7 @@ import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
import ru.dbotthepony.kstarbound.network.IPacket
|
import ru.dbotthepony.kstarbound.network.IPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
@ -43,13 +46,17 @@ import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
|
|||||||
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.*
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val template: WorldTemplate) : ICellAccess {
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val template: WorldTemplate) : ICellAccess {
|
||||||
@ -573,6 +580,18 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
protected abstract fun networkHitRequest(data: HitRequestPacket)
|
protected abstract fun networkHitRequest(data: HitRequestPacket)
|
||||||
protected abstract fun networkDamageRequest(data: DamageRequestPacket)
|
protected abstract fun networkDamageRequest(data: DamageRequestPacket)
|
||||||
|
|
||||||
|
abstract fun remote(connectionID: Int): Connection?
|
||||||
|
|
||||||
|
// this *could* have been divided into per-entity map and beheaded world's map
|
||||||
|
// but we can't, because response packets contain only message UUID, and don't contain entity ID
|
||||||
|
val pendingEntityMessages: Cache<UUID, CompletableFuture<JsonElement>> = Caffeine.newBuilder()
|
||||||
|
.maximumSize(32000L) // some unreachable value unless there is a malicious actor
|
||||||
|
.expireAfterWrite(1L, TimeUnit.MINUTES)
|
||||||
|
.executor(Starbound.EXECUTOR)
|
||||||
|
.scheduler(Starbound)
|
||||||
|
.removalListener<UUID, CompletableFuture<JsonElement>> { key, value, cause -> if (!cause.wasEvicted()) value?.completeExceptionally(TimeoutException("Did not receive response from remote in time")) }
|
||||||
|
.build()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
@ -7,11 +7,9 @@ import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
|||||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.io.koptional
|
|
||||||
import ru.dbotthepony.kommons.io.nullable
|
import ru.dbotthepony.kommons.io.nullable
|
||||||
import ru.dbotthepony.kommons.util.Either
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
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
|
||||||
@ -30,6 +28,7 @@ import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
|||||||
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.EntityMessagePacket
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||||
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.NetworkedGroup
|
||||||
@ -43,6 +42,8 @@ import ru.dbotthepony.kstarbound.world.World
|
|||||||
import ru.dbotthepony.kstarbound.world.castRay
|
import ru.dbotthepony.kstarbound.world.castRay
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
||||||
@ -179,10 +180,6 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun receiveMessage(name: String, arguments: JsonArray): JsonElement? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
var removalReason: RemovalReason? = null
|
var removalReason: RemovalReason? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@ -459,6 +456,43 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entities can send other entities potentially remote messages and get
|
||||||
|
* responses back from them, and should implement this to receive and respond
|
||||||
|
* to messages. If the message is NOT handled, should return Nothing,
|
||||||
|
* otherwise should return some Json value.
|
||||||
|
* This will only ever be called on master entities.
|
||||||
|
*/
|
||||||
|
protected open fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// doesn't write stacktrace
|
||||||
|
class MessageCallException(message: String) : RuntimeException(message, null, true, false)
|
||||||
|
|
||||||
|
fun dispatchMessage(sourceConnection: Int, message: String, arguments: JsonArray): CompletableFuture<JsonElement> {
|
||||||
|
if (isRemote) {
|
||||||
|
val connection = world.remote(connectionID) ?: return CompletableFuture.failedFuture(NoSuchElementException("Can't dispatch entity message, no such connection $connectionID"))
|
||||||
|
val future = CompletableFuture<JsonElement>()
|
||||||
|
val uuid = UUID(world.random.nextLong(), world.random.nextLong())
|
||||||
|
world.pendingEntityMessages.put(uuid, future)
|
||||||
|
connection.send(EntityMessagePacket(Either.left(entityID), message, arguments, uuid, sourceConnection))
|
||||||
|
return future
|
||||||
|
} else {
|
||||||
|
val response = try {
|
||||||
|
handleMessage(sourceConnection, message, arguments)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
return CompletableFuture.failedFuture(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return CompletableFuture.failedFuture(MessageCallException("Message '$message' was not handled"))
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open fun render(client: StarboundClient, layers: LayeredRenderer) {
|
open fun render(client: StarboundClient, layers: LayeredRenderer) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities.tile
|
package ru.dbotthepony.kstarbound.world.entities.tile
|
||||||
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterators
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntLists
|
||||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
@ -23,8 +27,6 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
|||||||
import ru.dbotthepony.kstarbound.item.IContainer
|
import ru.dbotthepony.kstarbound.item.IContainer
|
||||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||||
@ -186,6 +188,49 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
|
||||||
|
return when (message.lowercase()) {
|
||||||
|
"startcrafting" -> startCrafting()
|
||||||
|
"stopcrafting" -> stopCrafting()
|
||||||
|
"burncontainercontents" -> burnContainerContents()
|
||||||
|
|
||||||
|
// returns not inserted items
|
||||||
|
"additems" -> items.add(ItemDescriptor(arguments[0]).build()).toJson()
|
||||||
|
"putitems" -> items.put(IntLists.singleton(arguments[0].asInt), ItemDescriptor(arguments[1]).build()).toJson()
|
||||||
|
"takeitems" -> items.take(arguments[0].asInt, arguments[1].asLong).toJson()
|
||||||
|
"swapitems" -> items.swap(arguments[0].asInt, ItemDescriptor(arguments[1]).build(), if (arguments.size() >= 3) arguments[2].asBoolean else true).toJson()
|
||||||
|
"applyaugment" -> TODO("applyaugment")
|
||||||
|
"consumeitems" -> JsonPrimitive(items.take(ItemDescriptor(arguments[0])))
|
||||||
|
"consumeitemsat" -> JsonPrimitive(items.takeExact(arguments[0].asInt, arguments[1].asLong))
|
||||||
|
"clearcontainer" -> {
|
||||||
|
val result = JsonArray()
|
||||||
|
|
||||||
|
for (slot in 0 until items.size) {
|
||||||
|
if (items[slot].isNotEmpty) {
|
||||||
|
result.add(items[slot].toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.clear()
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.handleMessage(connection, message, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCrafting(): JsonElement {
|
||||||
|
return JsonNull.INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopCrafting(): JsonElement {
|
||||||
|
return JsonNull.INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun burnContainerContents(): JsonElement {
|
||||||
|
return JsonNull.INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
// Networking of this container to legacy clients is incredibly stupid,
|
// Networking of this container to legacy clients is incredibly stupid,
|
||||||
// and networks entire state each time something has changed.
|
// and networks entire state each time something has changed.
|
||||||
inner class Container(size: Int) : NetworkedElement(), IContainer {
|
inner class Container(size: Int) : NetworkedElement(), IContainer {
|
||||||
|
Loading…
Reference in New Issue
Block a user