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.math.roundTowardsNegativeInfinity
|
||||
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.DamageRequestPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||
@ -339,6 +340,12 @@ class ClientWorld(
|
||||
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 {
|
||||
val ring = listOf(
|
||||
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
|
||||
}
|
||||
|
||||
override fun get(key: Int): T? {
|
||||
if (map is Int2ObjectMap) {
|
||||
return (map as Int2ObjectMap).get(key)
|
||||
} else {
|
||||
return map[key]
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
nextIndex = min - 1
|
||||
map.clear()
|
||||
|
@ -180,13 +180,12 @@ data class ItemDescriptor(
|
||||
it.add("parameters", parameters.deepCopy())
|
||||
}
|
||||
|
||||
fun toJson(): JsonObject? {
|
||||
fun toJson(): JsonElement {
|
||||
if (Starbound.IS_STORE_JSON) {
|
||||
return VersionRegistry.make("Item", toJsonStruct()).toJson()
|
||||
} else {
|
||||
if (isEmpty) {
|
||||
return null
|
||||
}
|
||||
if (isEmpty)
|
||||
return JsonNull.INSTANCE
|
||||
|
||||
return toJsonStruct()
|
||||
}
|
||||
|
@ -3,6 +3,10 @@ package ru.dbotthepony.kstarbound.item
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
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.defs.item.ItemDescriptor
|
||||
import java.util.random.RandomGenerator
|
||||
@ -11,6 +15,7 @@ interface IContainer {
|
||||
var size: Int
|
||||
operator fun get(index: Int): ItemStack
|
||||
operator fun set(index: Int, value: ItemStack)
|
||||
fun clear()
|
||||
|
||||
fun ageItems(by: Double): Boolean {
|
||||
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 {
|
||||
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()
|
||||
|
||||
// first, try to put into not empty slots first
|
||||
for (i in 0 until size) {
|
||||
val itemThere = this[i]
|
||||
var itr = slots.invoke()
|
||||
|
||||
while (itr.hasNext()) {
|
||||
val slot = itr.nextInt()
|
||||
|
||||
if (slot !in 0 until size)
|
||||
continue
|
||||
|
||||
val itemThere = this[slot]
|
||||
|
||||
if (itemThere.isStackable(copy)) {
|
||||
val newCount = (itemThere.size + copy.size).coerceAtMost(itemThere.maxStackSize)
|
||||
@ -131,21 +152,29 @@ interface IContainer {
|
||||
}
|
||||
}
|
||||
|
||||
// then try to move into empty slots
|
||||
for (i in 0 until size) {
|
||||
val itemThere = this[i]
|
||||
itr = slots.invoke()
|
||||
|
||||
while (itr.hasNext()) {
|
||||
val slot = itr.nextInt()
|
||||
|
||||
if (slot !in 0 until size)
|
||||
continue
|
||||
|
||||
val itemThere = this[slot]
|
||||
|
||||
if (itemThere.isEmpty) {
|
||||
if (copy.size > copy.maxStackSize) {
|
||||
if (!simulate)
|
||||
this[i] = copy.copy(copy.maxStackSize)
|
||||
if (itemThere.isEmpty) {
|
||||
if (copy.size > copy.maxStackSize) {
|
||||
if (!simulate)
|
||||
this[slot] = copy.copy(copy.maxStackSize)
|
||||
|
||||
copy.size -= copy.maxStackSize
|
||||
} else {
|
||||
if (!simulate)
|
||||
this[i] = copy
|
||||
copy.size -= copy.maxStackSize
|
||||
} else {
|
||||
if (!simulate)
|
||||
this[slot] = copy
|
||||
|
||||
return ItemStack.EMPTY
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,7 +182,153 @@ interface IContainer {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
fun toJson(): JsonObject? {
|
||||
fun toJson(): JsonElement {
|
||||
if (isEmpty)
|
||||
return null
|
||||
return JsonNull.INSTANCE
|
||||
|
||||
return createDescriptor().toJson()
|
||||
}
|
||||
|
@ -245,6 +245,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SERVER_CONNECTION_ID = 0
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
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.EntityCreatePacket
|
||||
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.PingPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.PongPacket
|
||||
@ -483,8 +485,8 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(HitRequestPacket::read)
|
||||
LEGACY.add(DamageRequestPacket::read)
|
||||
LEGACY.add(::DamageNotificationPacket)
|
||||
LEGACY.skip("EntityMessage")
|
||||
LEGACY.skip("EntityMessageResponse")
|
||||
LEGACY.add(::EntityMessagePacket)
|
||||
LEGACY.add(::EntityMessageResponsePacket)
|
||||
LEGACY.add(::UpdateWorldPropertiesPacket)
|
||||
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.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||
import ru.dbotthepony.kstarbound.network.Connection
|
||||
import ru.dbotthepony.kstarbound.network.IPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||
@ -304,7 +305,7 @@ class ServerWorld private constructor(
|
||||
val broken = entity.damageTileEntity(occupySpaces, sourcePosition, actualDamage)
|
||||
|
||||
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,
|
||||
entity.type.jsonName,
|
||||
(entity as? WorldObject)?.config?.key))
|
||||
@ -341,7 +342,7 @@ class ServerWorld private constructor(
|
||||
topMost = topMost.coerceAtLeast(result)
|
||||
|
||||
if (source != null && health?.isDead == true) {
|
||||
source.receiveMessage("tileBroken", jsonArrayOf(
|
||||
source.dispatchMessage(Connection.SERVER_CONNECTION_ID, "tileBroken", jsonArrayOf(
|
||||
pos, if (isBackground) "background" else "foreground",
|
||||
tile!!.tile(isBackground).material.id ?: tile.tile(isBackground).material.key, // TODO: explicit string identifiers support
|
||||
tile.dungeonId,
|
||||
@ -644,6 +645,10 @@ class ServerWorld private constructor(
|
||||
server.channels.connectionByID(data.destinationConnection)?.send(data)
|
||||
}
|
||||
|
||||
override fun remote(connectionID: Int): Connection? {
|
||||
return server.channels.connectionByID(connectionID)
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class MetadataJson(
|
||||
val playerStart: Vector2d,
|
||||
|
@ -1,5 +1,7 @@
|
||||
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.JsonNull
|
||||
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.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kstarbound.network.Connection
|
||||
import ru.dbotthepony.kstarbound.network.IPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
|
||||
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.CollisionType
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
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.locks.ReentrantLock
|
||||
import java.util.function.Predicate
|
||||
import java.util.random.RandomGenerator
|
||||
import java.util.stream.Stream
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
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 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 {
|
||||
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.objects.ObjectArrayList
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
import ru.dbotthepony.kommons.io.nullable
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
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.HitRequestPacket
|
||||
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.MasterElement
|
||||
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.physics.Poly
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Consumer
|
||||
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
|
||||
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) {
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.tile
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
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.FastByteArrayOutputStream
|
||||
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.ItemStack
|
||||
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.networkedBoolean
|
||||
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,
|
||||
// and networks entire state each time something has changed.
|
||||
inner class Container(size: Int) : NetworkedElement(), IContainer {
|
||||
|
Loading…
Reference in New Issue
Block a user