More Lua bindings
This commit is contained in:
parent
151571e8d0
commit
782fb29dbf
@ -129,6 +129,15 @@ val color: TileColor = TileColor.DEFAULT
|
|||||||
* Added `world.objectLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
* Added `world.objectLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
||||||
* Added `world.loungeableLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
* Added `world.loungeableLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
||||||
* `world.entityCanDamage(source: EntityID, target: EntityID): Boolean` now properly accounts for case when `source == target`
|
* `world.entityCanDamage(source: EntityID, target: EntityID): Boolean` now properly accounts for case when `source == target`
|
||||||
|
* `world.entityHandItem(id: EntityID, hand: String): String` now accepts `"secondary"` as `hand` argument (in addition to `"primary"`/`"alt"`)
|
||||||
|
* `world.containerConsume(id: EntityID, item: ItemDescriptor, exact: Boolean?): Boolean?` now accepts `exact` which forces exact match on item descriptor (default `false`)
|
||||||
|
* `world.containerStackItems(id: EntityID, items: ItemDescriptor): ItemDescriptor` now actually does what it says on tin, instead of being equal to `world.containerAddItems`
|
||||||
|
* **ONLY** for local entities, or when using native protocol (but why would you ever mutate containers over network in first place)
|
||||||
|
* Remote entities on legacy protocol will behave like `world.containerAddItems` has been called
|
||||||
|
* `world.containerItemApply(id: EntityID, items: ItemDescriptor, slot: Int): ItemDescriptor` is no longer equal to `world.containerSwapItemsNoCombine` and does what its docs say, but im not sure if it is ever makes sense
|
||||||
|
* Clarification - Original docs are not very clear, but what it does is it tries to put provided item into target slot _only_ if it contains item of same type (contains stackable). If slot is empty or item in slot can not be stacked with provided item, this function does nothing (and returns stack initially passed to the function)
|
||||||
|
* **ONLY** for local entities, or when using native protocol (but why would you ever mutate containers over network in first place)
|
||||||
|
* Remote entities on legacy protocol will try to simulate new behavior locally using item checks and remote `putItems` message
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.item
|
||||||
|
|
||||||
|
class ContainerIterator(private val container: IContainer) : Iterator<ItemStack> {
|
||||||
|
private var index = 0
|
||||||
|
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return index < container.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): ItemStack {
|
||||||
|
return container[index++]
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,13 @@ import it.unimi.dsi.fastutil.ints.IntArrayList
|
|||||||
import it.unimi.dsi.fastutil.ints.IntIterable
|
import it.unimi.dsi.fastutil.ints.IntIterable
|
||||||
import it.unimi.dsi.fastutil.ints.IntIterator
|
import it.unimi.dsi.fastutil.ints.IntIterator
|
||||||
import it.unimi.dsi.fastutil.ints.IntIterators
|
import it.unimi.dsi.fastutil.ints.IntIterators
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntList
|
||||||
import it.unimi.dsi.fastutil.ints.IntLists
|
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
|
||||||
|
|
||||||
interface IContainer {
|
interface IContainer : Iterable<ItemStack> {
|
||||||
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)
|
||||||
@ -114,20 +115,41 @@ interface IContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns not inserted items
|
/**
|
||||||
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
|
* not inserted items -> slots touched
|
||||||
|
*/
|
||||||
|
fun add(item: ItemStack, simulate: Boolean = false): Pair<ItemStack, IntList> {
|
||||||
return put({ IntIterators.fromTo(0, size) }, item, simulate)
|
return put({ IntIterators.fromTo(0, size) }, item, simulate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns not inserted items
|
/**
|
||||||
fun put(slots: IntIterable, item: ItemStack, simulate: Boolean = false): ItemStack {
|
* not inserted items -> slots touched
|
||||||
|
*/
|
||||||
|
fun stackWithExisting(item: ItemStack, simulate: Boolean = false): Pair<ItemStack, IntList> {
|
||||||
|
val validSlots = IntArrayList(size)
|
||||||
|
|
||||||
|
for (slot in 0 until size) {
|
||||||
|
if (this[slot].isNotEmpty) {
|
||||||
|
validSlots.add(slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return put(validSlots, item, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* not inserted items -> slots touched
|
||||||
|
*/
|
||||||
|
fun put(slots: IntIterable, item: ItemStack, simulate: Boolean = false): Pair<ItemStack, IntList> {
|
||||||
return put(slots::iterator, item, simulate)
|
return put(slots::iterator, item, simulate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns not inserted items
|
/**
|
||||||
fun put(slots: () -> IntIterator, item: ItemStack, simulate: Boolean = false): ItemStack {
|
* not inserted items -> slots touched
|
||||||
|
*/
|
||||||
|
fun put(slots: () -> IntIterator, item: ItemStack, simulate: Boolean = false): Pair<ItemStack, IntList> {
|
||||||
val copy = item.copy()
|
val copy = item.copy()
|
||||||
|
val touched = IntArrayList()
|
||||||
var itr = slots.invoke()
|
var itr = slots.invoke()
|
||||||
|
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
@ -139,6 +161,7 @@ interface IContainer {
|
|||||||
val itemThere = this[slot]
|
val itemThere = this[slot]
|
||||||
|
|
||||||
if (itemThere.isStackable(copy)) {
|
if (itemThere.isStackable(copy)) {
|
||||||
|
touched.add(slot)
|
||||||
val newCount = (itemThere.size + copy.size).coerceAtMost(itemThere.maxStackSize)
|
val newCount = (itemThere.size + copy.size).coerceAtMost(itemThere.maxStackSize)
|
||||||
val diff = newCount - itemThere.size
|
val diff = newCount - itemThere.size
|
||||||
|
|
||||||
@ -148,7 +171,7 @@ interface IContainer {
|
|||||||
itemThere.size += diff
|
itemThere.size += diff
|
||||||
|
|
||||||
if (copy.isEmpty)
|
if (copy.isEmpty)
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY to touched
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,23 +186,23 @@ interface IContainer {
|
|||||||
val itemThere = this[slot]
|
val itemThere = this[slot]
|
||||||
|
|
||||||
if (itemThere.isEmpty) {
|
if (itemThere.isEmpty) {
|
||||||
if (itemThere.isEmpty) {
|
touched.add(slot)
|
||||||
if (copy.size > copy.maxStackSize) {
|
|
||||||
if (!simulate)
|
|
||||||
this[slot] = copy.copy(copy.maxStackSize)
|
|
||||||
|
|
||||||
copy.size -= copy.maxStackSize
|
if (copy.size > copy.maxStackSize) {
|
||||||
} else {
|
if (!simulate)
|
||||||
if (!simulate)
|
this[slot] = copy.copy(copy.maxStackSize)
|
||||||
this[slot] = copy
|
|
||||||
|
|
||||||
return ItemStack.EMPTY
|
copy.size -= copy.maxStackSize
|
||||||
}
|
} else {
|
||||||
|
if (!simulate)
|
||||||
|
this[slot] = copy
|
||||||
|
|
||||||
|
return ItemStack.EMPTY to touched
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy
|
return copy to touched
|
||||||
}
|
}
|
||||||
|
|
||||||
fun take(slot: Int, amount: Long): ItemStack {
|
fun take(slot: Int, amount: Long): ItemStack {
|
||||||
@ -233,20 +256,20 @@ interface IContainer {
|
|||||||
} else if (item.isNotEmpty && existingItem.isEmpty) {
|
} else if (item.isNotEmpty && existingItem.isEmpty) {
|
||||||
// place into slot
|
// place into slot
|
||||||
// use put because item might be bigger than max stack size
|
// use put because item might be bigger than max stack size
|
||||||
return put(IntLists.singleton(slot), item)
|
return put(IntLists.singleton(slot), item).first
|
||||||
} else {
|
} else {
|
||||||
// If something is there, try to stack with it first. If we can't stack,
|
// If something is there, try to stack with it first. If we can't stack,
|
||||||
// then swap.
|
// then swap.
|
||||||
|
|
||||||
if (tryCombine && existingItem.isStackable(item)) {
|
if (tryCombine && existingItem.isStackable(item)) {
|
||||||
return put(IntLists.singleton(slot), item)
|
return put(IntLists.singleton(slot), item).first
|
||||||
} else {
|
} else {
|
||||||
this[slot] = ItemStack.EMPTY
|
this[slot] = ItemStack.EMPTY
|
||||||
val slots = IntArrayList(IntIterators.fromTo(0, size))
|
val slots = IntArrayList(IntIterators.fromTo(0, size))
|
||||||
slots.removeInt(slot)
|
slots.removeInt(slot)
|
||||||
slots.add(0, slot)
|
slots.add(0, slot)
|
||||||
|
|
||||||
val remaining = put(slots, item)
|
val remaining = put(slots, item).first
|
||||||
|
|
||||||
if (remaining.isNotEmpty && this[slot].isStackable(remaining)) {
|
if (remaining.isNotEmpty && this[slot].isStackable(remaining)) {
|
||||||
// damn
|
// damn
|
||||||
@ -258,6 +281,14 @@ interface IContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun combineAt(slot: Int, item: ItemStack): ItemStack {
|
||||||
|
if (this[slot].isEmpty || !this[slot].isStackable(item)) {
|
||||||
|
return item
|
||||||
|
} else {
|
||||||
|
return put(IntLists.singleton(slot), item).first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun take(descriptor: ItemDescriptor, exactMatch: Boolean = false, simulate: Boolean = false): Boolean {
|
fun take(descriptor: ItemDescriptor, exactMatch: Boolean = false, simulate: Boolean = false): Boolean {
|
||||||
var toTake = descriptor.count
|
var toTake = descriptor.count
|
||||||
|
|
||||||
@ -387,7 +418,7 @@ interface IContainer {
|
|||||||
val lost = ArrayList<ItemStack>()
|
val lost = ArrayList<ItemStack>()
|
||||||
|
|
||||||
for (i in size until read.size) {
|
for (i in size until read.size) {
|
||||||
val remaining = add(read[i])
|
val remaining = add(read[i]).first
|
||||||
|
|
||||||
if (remaining.isNotEmpty) {
|
if (remaining.isNotEmpty) {
|
||||||
lost.add(remaining)
|
lost.add(remaining)
|
||||||
@ -408,4 +439,8 @@ interface IContainer {
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<ItemStack> {
|
||||||
|
return ContainerIterator(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,10 @@ object ItemRegistry {
|
|||||||
val isNotEmpty: Boolean
|
val isNotEmpty: Boolean
|
||||||
get() = !isEmpty
|
get() = !isEmpty
|
||||||
|
|
||||||
|
// for Lua scripts
|
||||||
|
val nameOrNull: String?
|
||||||
|
get() = if (isEmpty) null else name
|
||||||
|
|
||||||
val directory = file?.computeDirectory() ?: "/"
|
val directory = file?.computeDirectory() ?: "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,6 +366,14 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
|
|||||||
return createDescriptor().toJson()
|
return createDescriptor().toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toTable(allocator: TableFactory): Table? {
|
||||||
|
if (isEmpty) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return createDescriptor().toTable(allocator)
|
||||||
|
}
|
||||||
|
|
||||||
class Adapter(gson: Gson) : TypeAdapter<ItemStack>() {
|
class Adapter(gson: Gson) : TypeAdapter<ItemStack>() {
|
||||||
override fun write(out: JsonWriter, value: ItemStack?) {
|
override fun write(out: JsonWriter, value: ItemStack?) {
|
||||||
val json = value?.toJson()
|
val json = value?.toJson()
|
||||||
|
@ -156,6 +156,26 @@ fun TableFactory.tableOf(vararg values: Any?): Table {
|
|||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun TableFactory.tableOf(vararg values: Int): Table {
|
||||||
|
val table = newTable(values.size, 0)
|
||||||
|
|
||||||
|
for ((i, v) in values.withIndex()) {
|
||||||
|
table[i + 1L] = v.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TableFactory.tableOf(vararg values: Long): Table {
|
||||||
|
val table = newTable(values.size, 0)
|
||||||
|
|
||||||
|
for ((i, v) in values.withIndex()) {
|
||||||
|
table[i + 1L] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
fun TableFactory.tableMapOf(vararg values: Pair<Any, Any?>): Table {
|
fun TableFactory.tableMapOf(vararg values: Pair<Any, Any?>): Table {
|
||||||
val table = newTable(0, values.size)
|
val table = newTable(0, values.size)
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import org.classdump.luna.ByteString
|
|||||||
import org.classdump.luna.LuaRuntimeException
|
import org.classdump.luna.LuaRuntimeException
|
||||||
import org.classdump.luna.Table
|
import org.classdump.luna.Table
|
||||||
import org.classdump.luna.runtime.ExecutionContext
|
import org.classdump.luna.runtime.ExecutionContext
|
||||||
|
import ru.dbotthepony.kommons.gson.JsonArrayCollector
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
@ -15,17 +16,23 @@ import ru.dbotthepony.kstarbound.lua.get
|
|||||||
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
||||||
import ru.dbotthepony.kstarbound.lua.iterator
|
import ru.dbotthepony.kstarbound.lua.iterator
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
|
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||||
import ru.dbotthepony.kstarbound.lua.luaStub
|
import ru.dbotthepony.kstarbound.lua.luaStub
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
|
import ru.dbotthepony.kstarbound.lua.tableMapOf
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||||
import ru.dbotthepony.kstarbound.lua.toLine2d
|
import ru.dbotthepony.kstarbound.lua.toLine2d
|
||||||
import ru.dbotthepony.kstarbound.lua.toPoly
|
import ru.dbotthepony.kstarbound.lua.toPoly
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||||
import ru.dbotthepony.kstarbound.lua.unpackAsArray
|
import ru.dbotthepony.kstarbound.lua.unpackAsArray
|
||||||
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
|
import ru.dbotthepony.kstarbound.stream
|
||||||
import ru.dbotthepony.kstarbound.util.random.shuffle
|
import ru.dbotthepony.kstarbound.util.random.shuffle
|
||||||
import ru.dbotthepony.kstarbound.util.valueOf
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
@ -35,8 +42,11 @@ import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
|
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.api.InspectableEntity
|
import ru.dbotthepony.kstarbound.world.entities.api.InspectableEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.tile.ContainerObject
|
||||||
import ru.dbotthepony.kstarbound.world.entities.tile.LoungeableObject
|
import ru.dbotthepony.kstarbound.world.entities.tile.LoungeableObject
|
||||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
@ -356,4 +366,241 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
|
|||||||
|
|
||||||
returnBuffer.setTo(tableOf(*entity.portrait(ActorEntity.PortraitMode.entries.valueOf(mode.decode())).map { from(it.toJson()) }.toTypedArray()))
|
returnBuffer.setTo(tableOf(*entity.portrait(ActorEntity.PortraitMode.entries.valueOf(mode.decode())).map { from(it.toJson()) }.toTypedArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbacks["entityHandItem"] = luaFunction { id: Number, hand: ByteString ->
|
||||||
|
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
when (val gethand = hand.decode().lowercase()) {
|
||||||
|
"primary" -> returnBuffer.setTo(entity.primaryHandItem.entry.nameOrNull)
|
||||||
|
"alt", "secondary" -> returnBuffer.setTo(entity.secondaryHandItem.entry.nameOrNull)
|
||||||
|
else -> throw LuaRuntimeException("Unknown tool hand $gethand")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityHandItemDescriptor"] = luaFunction { id: Number, hand: ByteString ->
|
||||||
|
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
when (val gethand = hand.decode().lowercase()) {
|
||||||
|
"primary" -> returnBuffer.setTo(entity.primaryHandItem.toTable(this))
|
||||||
|
"alt", "secondary" -> returnBuffer.setTo(entity.secondaryHandItem.toTable(this))
|
||||||
|
else -> throw LuaRuntimeException("Unknown tool hand $gethand")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityUniqueId"] = luaFunction { id: Number ->
|
||||||
|
returnBuffer.setTo(self.entities[id.toInt()]?.uniqueID?.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["getObjectParameter"] = luaFunction { id: Number, parameter: ByteString, defaultValue: Any? ->
|
||||||
|
// FIXME: this is stupid (defaultValue is ignored when we lookup parameter on non existing entity),
|
||||||
|
// but we must retain original behavior
|
||||||
|
val entity = self.entities[id.toInt()] as? WorldObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
val result = entity.lookupProperty(parameter.decode())
|
||||||
|
|
||||||
|
if (result.isJsonNull) {
|
||||||
|
returnBuffer.setTo(defaultValue)
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(from(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["getNpcScriptParameter"] = luaStub("getNpcScriptParameter")
|
||||||
|
|
||||||
|
callbacks["objectSpaces"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? WorldObject ?: return@luaFunction returnBuffer.setTo(tableOf())
|
||||||
|
returnBuffer.setTo(tableOf(*entity.occupySpaces.map { from(it - entity.tilePosition) }.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["farmableStage"] = luaStub("farmableStage")
|
||||||
|
|
||||||
|
callbacks["containerSize"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(entity.items.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerClose"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(false)
|
||||||
|
// FIXME: this doesn't get networked if called on client
|
||||||
|
// (AND this is the reason why in multiplayer player can't see chest/container open animations when
|
||||||
|
// other players open them)
|
||||||
|
entity.closeContainer()
|
||||||
|
returnBuffer.setTo(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerOpen"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(false)
|
||||||
|
// FIXME: this doesn't get networked if called on client
|
||||||
|
// (AND this is the reason why in multiplayer player can't see chest/container open animations when
|
||||||
|
// other players open them)
|
||||||
|
entity.openContainer()
|
||||||
|
returnBuffer.setTo(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerItems"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(tableOf(*entity.items.filter { it.isNotEmpty }.map { it.toTable(this) }.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerItemAt"] = luaFunction { id: Number, index: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
if (index.toInt() < entity.items.size)
|
||||||
|
returnBuffer.setTo(entity.items[index.toInt()].toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerConsume"] = luaFunction { id: Number, desc: Any, exactMatch: Boolean? ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(entity.takeItem(ItemDescriptor(desc), exact = exactMatch ?: false).getNow(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerConsumeAt"] = luaFunction { id: Number, slot: Number, amount: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(entity.takeItemAt(slot.toInt(), amount.toLong()).getNow(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerAvailable"] = luaFunction { id: Number, desc: Any ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(entity.items.take(ItemDescriptor(desc), simulate = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// why we have containerItems + containerTakeAll, when we could have containerItems + containerClear?????????
|
||||||
|
callbacks["containerTakeAll"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(tableOf(*entity.clearContainer().getNow(listOf()).map { it.toTable(this) }.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerTakeAt"] = luaFunction { id: Number, slot: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
if (slot.toInt() < entity.items.size)
|
||||||
|
returnBuffer.setTo(entity.takeItemAt(slot.toInt()).getNow(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerTakeNumItemsAt"] = luaFunction { id: Number, slot: Number, amount: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
if (slot.toInt() < entity.items.size)
|
||||||
|
returnBuffer.setTo(entity.takeItemAt(slot.toInt(), amount.toLong()).getNow(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerItemsCanFit"] = luaFunction { id: Number, desc: Any ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
val item = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(item.size - entity.items.add(item, simulate = true).first.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerItemsFitWhere"] = luaFunction { id: Number, desc: Any ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
val item = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
val (leftover, touched) = entity.items.add(item, simulate = true)
|
||||||
|
|
||||||
|
returnBuffer.setTo(tableMapOf(
|
||||||
|
"leftover" to leftover.size,
|
||||||
|
"slots" to tableOf(*touched.toIntArray())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerAddItems"] = luaFunction { id: Number, desc: Any ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(desc)
|
||||||
|
val build = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(entity.addItems(build).getNow(build)?.toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerStackItems"] = luaFunction { id: Number, desc: Any ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(desc)
|
||||||
|
val build = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(entity.stackWithExisting(build).getNow(build)?.toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerPutItemsAt"] = luaFunction { id: Number, desc: Any, slot: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(desc)
|
||||||
|
val build = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(entity.putItems(slot.toInt(), build).getNow(build)?.toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerSwapItems"] = luaFunction { id: Number, desc: Any, slot: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(desc)
|
||||||
|
val build = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(entity.swapItems(slot.toInt(), build, tryCombine = true).getNow(build)?.toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerSwapItemsNoCombine"] = luaFunction { id: Number, desc: Any, slot: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(desc)
|
||||||
|
val build = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(entity.swapItems(slot.toInt(), build, tryCombine = false).getNow(build)?.toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["containerItemApply"] = luaFunction { id: Number, desc: Any, slot: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ContainerObject ?: return@luaFunction returnBuffer.setTo(desc)
|
||||||
|
val build = ItemDescriptor(desc).build(random = lua.random)
|
||||||
|
returnBuffer.setTo(entity.combineItems(slot.toInt(), build).getNow(build)?.toTable(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["callScriptedEntity"] = luaFunctionN("callScriptedEntity") {
|
||||||
|
val id = it.nextInteger()
|
||||||
|
val function = it.nextString().decode()
|
||||||
|
val entity = self.entities[id.toInt()] ?: throw LuaRuntimeException("Entity with ID $id does not exist")
|
||||||
|
|
||||||
|
if (entity !is ScriptedEntity)
|
||||||
|
throw LuaRuntimeException("$entity is not scripted entity")
|
||||||
|
|
||||||
|
if (entity.isRemote)
|
||||||
|
throw LuaRuntimeException("$entity is not owned by this side")
|
||||||
|
|
||||||
|
returnBuffer.setToContentsOf(entity.callScript(function, *it.copyRemaining()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["findUniqueEntity"] = luaStub("findUniqueEntity")
|
||||||
|
|
||||||
|
callbacks["sendEntityMessage"] = luaFunctionN("sendEntityMessage") {
|
||||||
|
val id = it.nextAny()
|
||||||
|
val func = it.nextString().decode()
|
||||||
|
|
||||||
|
if (id is Number) {
|
||||||
|
val entityID = id.toInt()
|
||||||
|
|
||||||
|
returnBuffer.setTo(LuaFuture(
|
||||||
|
future = self.dispatchEntityMessage(self.connectionID, entityID, func, it.copyRemaining()
|
||||||
|
.stream().map { toJsonFromLua(it) }.collect(JsonArrayCollector)).thenApply { from(it) },
|
||||||
|
isLocal = Connection.connectionForEntityID(entityID) == self.connectionID
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
id as ByteString
|
||||||
|
val entityID = id.decode()
|
||||||
|
val findAlreadyLoaded = self.entities.values.find { it.uniqueID.get() == entityID }
|
||||||
|
|
||||||
|
val isLocal = if (findAlreadyLoaded == null)
|
||||||
|
self.isServer
|
||||||
|
else
|
||||||
|
!findAlreadyLoaded.isRemote
|
||||||
|
|
||||||
|
returnBuffer.setTo(LuaFuture(
|
||||||
|
future = self.dispatchEntityMessage(self.connectionID, entityID, func, it.copyRemaining()
|
||||||
|
.stream().map { toJsonFromLua(it) }.collect(JsonArrayCollector)).thenApply { from(it) },
|
||||||
|
isLocal = isLocal
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["loungeableOccupied"] = luaStub("loungeableOccupied")
|
||||||
|
callbacks["isMonster"] = luaStub("isMonster")
|
||||||
|
callbacks["monsterType"] = luaStub("monsterType")
|
||||||
|
callbacks["npcType"] = luaStub("npcType")
|
||||||
|
callbacks["stagehandType"] = luaStub("stagehandType")
|
||||||
|
callbacks["isNpc"] = luaStub("isNpc")
|
||||||
|
|
||||||
|
callbacks["isEntityInteractive"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()]
|
||||||
|
|
||||||
|
if (entity is InteractiveEntity)
|
||||||
|
returnBuffer.setTo(entity.isInteractive)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityMouthPosition"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
// original entine returns non nil only for "Chatty entity"
|
||||||
|
returnBuffer.setTo(from(entity.mouthPosition))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityTypeName"] = luaStub("entityTypeName")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua.userdata
|
||||||
|
|
||||||
|
import org.classdump.luna.Table
|
||||||
|
import org.classdump.luna.Userdata
|
||||||
|
import org.classdump.luna.impl.ImmutableTable
|
||||||
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.CompletionException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [isLocal] tells us whenever result() should block current thread if [future] is not yet fulfilled
|
||||||
|
* (to retain original behavior for "locally async" calls)
|
||||||
|
*
|
||||||
|
* god damn it.
|
||||||
|
*/
|
||||||
|
class LuaFuture(val future: CompletableFuture<out Any?>, val isLocal: Boolean) : Userdata<CompletableFuture<out Any?>>() {
|
||||||
|
override fun getMetatable(): Table {
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setMetatable(mt: Table?): Table {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserValue(): CompletableFuture<out Any?> {
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserValue(value: CompletableFuture<out Any?>?): CompletableFuture<out Any?> {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val metadata = ImmutableTable.Builder()
|
||||||
|
.add("finished", luaFunction { self: LuaFuture ->
|
||||||
|
returnBuffer.setTo(self.future.isDone)
|
||||||
|
})
|
||||||
|
.add("succeeded", luaFunction { self: LuaFuture ->
|
||||||
|
returnBuffer.setTo(!self.future.isCompletedExceptionally)
|
||||||
|
})
|
||||||
|
.add("failed", luaFunction { self: LuaFuture ->
|
||||||
|
returnBuffer.setTo(self.future.isCompletedExceptionally)
|
||||||
|
})
|
||||||
|
.add("result", luaFunction { self: LuaFuture ->
|
||||||
|
try {
|
||||||
|
if (self.future.isCompletedExceptionally) {
|
||||||
|
returnBuffer.setTo()
|
||||||
|
} else if (self.isLocal) {
|
||||||
|
returnBuffer.setTo(self.future.join())
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(self.future.getNow(null))
|
||||||
|
}
|
||||||
|
} catch (err: CompletionException) {
|
||||||
|
returnBuffer.setTo()
|
||||||
|
} catch (err: CancellationException) {
|
||||||
|
returnBuffer.setTo()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add("error", luaFunction { self: LuaFuture ->
|
||||||
|
// this is slow, but we can't get Exception out of CompletableFuture without latter throwing former
|
||||||
|
try {
|
||||||
|
if (self.isLocal) {
|
||||||
|
returnBuffer.setTo(self.future.join())
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(self.future.getNow(null))
|
||||||
|
}
|
||||||
|
} catch (err: CompletionException) {
|
||||||
|
returnBuffer.setTo(err.message ?: "internal error")
|
||||||
|
} catch (err: CancellationException) {
|
||||||
|
returnBuffer.setTo(err.message ?: "internal error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
@ -45,24 +45,20 @@ class EntityMessagePacket(val entity: Either<Int, String>, val message: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handle(connection: Connection, world: World<*, *>) {
|
private fun handle(connection: Connection, world: World<*, *>) {
|
||||||
val entity = if (entity.isLeft) {
|
val future = if (entity.isLeft) {
|
||||||
world.entities[entity.left()]
|
world.dispatchEntityMessage(connection.connectionID, entity.left(), message, arguments)
|
||||||
} else {
|
} else {
|
||||||
world.entities.values.firstOrNull { it.uniqueID.get() == entity.right() }
|
world.dispatchEntityMessage(connection.connectionID, entity.right(), message, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity == null) {
|
future
|
||||||
connection.send(EntityMessageResponsePacket(Either.left("No such entity ${this@EntityMessagePacket.entity}"), id))
|
.thenAccept(Consumer {
|
||||||
} else {
|
connection.send(EntityMessageResponsePacket(Either.right(it), id))
|
||||||
entity.dispatchMessage(connection.connectionID, message, arguments)
|
})
|
||||||
.thenAccept(Consumer {
|
.exceptionally(Function {
|
||||||
connection.send(EntityMessageResponsePacket(Either.right(it), id))
|
connection.send(EntityMessageResponsePacket(Either.left(it.message ?: "Internal server error"), id))
|
||||||
})
|
null
|
||||||
.exceptionally(Function {
|
})
|
||||||
connection.send(EntityMessageResponsePacket(Either.left(it.message ?: "Internal server error"), id))
|
|
||||||
null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun play(connection: ServerConnection) {
|
override fun play(connection: ServerConnection) {
|
||||||
|
@ -14,6 +14,7 @@ import ru.dbotthepony.kstarbound.json.writeJsonElement
|
|||||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
@ -36,7 +37,7 @@ class EntityMessageResponsePacket(val response: Either<String, JsonElement>, val
|
|||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
if (response.isLeft) {
|
if (response.isLeft) {
|
||||||
message.completeExceptionally(AbstractEntity.MessageCallException(response.left()))
|
message.completeExceptionally(World.MessageCallException(response.left()))
|
||||||
} else {
|
} else {
|
||||||
message.complete(response.right())
|
message.complete(response.right())
|
||||||
}
|
}
|
||||||
@ -50,7 +51,7 @@ class EntityMessageResponsePacket(val response: Either<String, JsonElement>, val
|
|||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
if (response.isLeft) {
|
if (response.isLeft) {
|
||||||
message.completeExceptionally(AbstractEntity.MessageCallException(response.left()))
|
message.completeExceptionally(World.MessageCallException(response.left()))
|
||||||
} else {
|
} else {
|
||||||
message.complete(response.right())
|
message.complete(response.right())
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.network.IClientPacket
|
|||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.EntityInteractResultPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.EntityInteractResultPacket
|
||||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@ -28,14 +29,13 @@ class EntityInteractPacket(val request: InteractRequest, val id: UUID) : IServer
|
|||||||
override fun play(connection: ServerConnection) {
|
override fun play(connection: ServerConnection) {
|
||||||
if (request.target >= 0) {
|
if (request.target >= 0) {
|
||||||
connection.enqueue {
|
connection.enqueue {
|
||||||
connection.send(EntityInteractResultPacket(entities[request.target]?.interact(request) ?: InteractAction.NONE, id, request.source))
|
connection.send(EntityInteractResultPacket((entities[request.target] as? InteractiveEntity)?.interact(request) ?: InteractAction.NONE, id, request.source))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val other = connection.server.channels.connectionByID(Connection.connectionForEntityID(request.target)) ?: throw IllegalArgumentException("No such connection ID ${Connection.connectionForEntityID(request.target)} for EntityInteractPacket")
|
val other = connection.server.channels.connectionByID(Connection.connectionForEntityID(request.target)) ?: throw IllegalArgumentException("No such connection ID ${Connection.connectionForEntityID(request.target)} for EntityInteractPacket")
|
||||||
|
|
||||||
if (other == connection) {
|
if (other == connection)
|
||||||
throw IllegalStateException("Attempt to interact with own entity through server?")
|
throw IllegalArgumentException("Attempt to interact with own entity through server?")
|
||||||
}
|
|
||||||
|
|
||||||
other.send(this)
|
other.send(this)
|
||||||
}
|
}
|
||||||
|
@ -1,442 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Callable
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
import java.util.concurrent.Delayed
|
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
import java.util.concurrent.Future
|
|
||||||
import java.util.concurrent.FutureTask
|
|
||||||
import java.util.concurrent.RejectedExecutionException
|
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
|
||||||
import java.util.concurrent.ScheduledFuture
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.locks.LockSupport
|
|
||||||
import java.util.function.Consumer
|
|
||||||
import kotlin.NoSuchElementException
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
private fun <E : Comparable<E>> LinkedList<E>.enqueue(value: E) {
|
|
||||||
if (isEmpty()) {
|
|
||||||
add(value)
|
|
||||||
} else if (first >= value) {
|
|
||||||
addFirst(value)
|
|
||||||
} else if (last <= value) {
|
|
||||||
addLast(value)
|
|
||||||
} else {
|
|
||||||
val iterator = listIterator()
|
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
val i = iterator.next()
|
|
||||||
|
|
||||||
if (i >= value) {
|
|
||||||
iterator.previous()
|
|
||||||
iterator.add(value)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ScheduledExecutorService] which act as a mailbox, [executeQueuedTasks] must be called from main thread.
|
|
||||||
*
|
|
||||||
* [submit], [execute], etc can be called on any thread. If any of enqueueing methods are called on the same thread
|
|
||||||
* as where [executeQueuedTasks] was called, executes provided lambda immediately and returns completed future.
|
|
||||||
*/
|
|
||||||
class MailboxExecutorService(@Volatile var thread: Thread = Thread.currentThread()) : ScheduledExecutorService {
|
|
||||||
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
|
|
||||||
|
|
||||||
private val timers = LinkedList<Timer<*>>()
|
|
||||||
private val repeatableTimers = LinkedList<RepeatableTimer>()
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var isShutdown = false
|
|
||||||
@Volatile
|
|
||||||
private var isTerminated = false
|
|
||||||
|
|
||||||
private val timeOrigin = JVMClock()
|
|
||||||
|
|
||||||
var exceptionHandler: Consumer<Throwable>? = null
|
|
||||||
|
|
||||||
private inner class Timer<T>(task: Callable<T>, val executeAt: Long) : FutureTask<T>(task), ScheduledFuture<T> {
|
|
||||||
override fun compareTo(other: Delayed): Int {
|
|
||||||
return getDelay(TimeUnit.NANOSECONDS).compareTo(other.getDelay(TimeUnit.NANOSECONDS))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDelay(unit: TimeUnit): Long {
|
|
||||||
return unit.convert(executeAt, TimeUnit.NANOSECONDS) - timeOrigin.nanos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class CompletedFuture<T>(private val value: T) : Future<T> {
|
|
||||||
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isCancelled(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isDone(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun get(): T {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun get(timeout: Long, unit: TimeUnit): T {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val VOID = CompletedFuture(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class RepeatableTimer(
|
|
||||||
task: Runnable,
|
|
||||||
initialDelay: Long,
|
|
||||||
val period: Long,
|
|
||||||
val fixedDelay: Boolean,
|
|
||||||
): FutureTask<Unit>({ task.run() }), ScheduledFuture<Unit> {
|
|
||||||
var next = initialDelay
|
|
||||||
private set
|
|
||||||
|
|
||||||
public override fun runAndReset(): Boolean {
|
|
||||||
if (fixedDelay) {
|
|
||||||
next += period
|
|
||||||
return super.runAndReset()
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return super.runAndReset()
|
|
||||||
} finally {
|
|
||||||
next += period
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun compareTo(other: Delayed): Int {
|
|
||||||
return getDelay(TimeUnit.NANOSECONDS).compareTo(other.getDelay(TimeUnit.NANOSECONDS))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDelay(unit: TimeUnit): Long {
|
|
||||||
return unit.convert(next, TimeUnit.NANOSECONDS) - timeOrigin.nanos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSameThread(): Boolean {
|
|
||||||
return Thread.currentThread() === thread
|
|
||||||
}
|
|
||||||
|
|
||||||
fun executeQueuedTasks() {
|
|
||||||
thread = Thread.currentThread()
|
|
||||||
|
|
||||||
if (isShutdown) {
|
|
||||||
if (!isTerminated) {
|
|
||||||
isTerminated = true
|
|
||||||
|
|
||||||
futureQueue.forEach {
|
|
||||||
it.cancel(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
futureQueue.clear()
|
|
||||||
timers.clear()
|
|
||||||
repeatableTimers.clear()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var next = futureQueue.poll()
|
|
||||||
|
|
||||||
while (next != null) {
|
|
||||||
if (isTerminated) return
|
|
||||||
next.run()
|
|
||||||
Thread.interrupted()
|
|
||||||
|
|
||||||
try {
|
|
||||||
next.get()
|
|
||||||
} catch (err: ExecutionException) {
|
|
||||||
exceptionHandler?.accept(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
next = futureQueue.poll()
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!timers.isEmpty()) {
|
|
||||||
if (isTerminated) return
|
|
||||||
val first = timers.first
|
|
||||||
|
|
||||||
if (first.isCancelled) {
|
|
||||||
timers.removeFirst()
|
|
||||||
} else if (first.executeAt <= timeOrigin.nanos) {
|
|
||||||
first.run()
|
|
||||||
Thread.interrupted()
|
|
||||||
|
|
||||||
try {
|
|
||||||
first.get()
|
|
||||||
} catch (err: ExecutionException) {
|
|
||||||
exceptionHandler?.accept(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
timers.removeFirst()
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repeatableTimers.isNotEmpty()) {
|
|
||||||
val executed = LinkedList<RepeatableTimer>()
|
|
||||||
|
|
||||||
while (repeatableTimers.isNotEmpty()) {
|
|
||||||
if (isTerminated) return
|
|
||||||
val first = repeatableTimers.first
|
|
||||||
|
|
||||||
if (first.isDone) {
|
|
||||||
repeatableTimers.removeFirst()
|
|
||||||
} else if (first.next <= timeOrigin.nanos) {
|
|
||||||
if (first.runAndReset()) {
|
|
||||||
executed.add(first)
|
|
||||||
}
|
|
||||||
|
|
||||||
repeatableTimers.removeFirst()
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executed.forEach { repeatableTimers.enqueue(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute(command: Runnable) {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
if (isSameThread()) {
|
|
||||||
command.run()
|
|
||||||
} else {
|
|
||||||
futureQueue.add(FutureTask(command, Unit))
|
|
||||||
LockSupport.unpark(thread)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shutdown() {
|
|
||||||
isShutdown = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shutdownNow(): List<Runnable> {
|
|
||||||
if (isTerminated) return listOf()
|
|
||||||
isShutdown = true
|
|
||||||
isTerminated = true
|
|
||||||
|
|
||||||
val result = ArrayList<Runnable>()
|
|
||||||
|
|
||||||
futureQueue.forEach {
|
|
||||||
it.cancel(false)
|
|
||||||
result.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
futureQueue.clear()
|
|
||||||
|
|
||||||
timers.forEach { it.cancel(false) }
|
|
||||||
repeatableTimers.forEach { it.cancel(false) }
|
|
||||||
|
|
||||||
timers.clear()
|
|
||||||
repeatableTimers.clear()
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isShutdown(): Boolean {
|
|
||||||
return isShutdown
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isTerminated(): Boolean {
|
|
||||||
return isTerminated
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> submit(task: Callable<T>): Future<T> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
if (isSameThread()) return CompletedFuture(task.call())
|
|
||||||
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
if (isSameThread()) { task.run(); return CompletedFuture(result) }
|
|
||||||
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun submit(task: Runnable): Future<*> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
if (isSameThread()) { task.run(); return CompletedFuture.VOID }
|
|
||||||
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
if (isSameThread()) {
|
|
||||||
return tasks.map { CompletedFuture(it.call()) }
|
|
||||||
} else {
|
|
||||||
return tasks.map { submit(it) }.onEach { it.get() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> invokeAll(
|
|
||||||
tasks: Collection<Callable<T>>,
|
|
||||||
timeout: Long,
|
|
||||||
unit: TimeUnit
|
|
||||||
): List<Future<T>> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
if (isSameThread()) {
|
|
||||||
return tasks.map { CompletedFuture(it.call()) }
|
|
||||||
} else {
|
|
||||||
return tasks.map { submit(it) }.onEach { it.get(timeout, unit) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>): T {
|
|
||||||
if (tasks.isEmpty())
|
|
||||||
throw NoSuchElementException("Provided task list is empty")
|
|
||||||
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
if (isSameThread()) {
|
|
||||||
return tasks.first().call()
|
|
||||||
} else {
|
|
||||||
return submit(tasks.first()).get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>, timeout: Long, unit: TimeUnit): T {
|
|
||||||
if (tasks.isEmpty())
|
|
||||||
throw NoSuchElementException("Provided task list is empty")
|
|
||||||
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
if (isSameThread()) {
|
|
||||||
return tasks.first().call()
|
|
||||||
} else {
|
|
||||||
return submit(tasks.first()).get(timeout, unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <V> join(future: Future<V>): V {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
if (!isSameThread())
|
|
||||||
return future.get()
|
|
||||||
|
|
||||||
while (!future.isDone) {
|
|
||||||
executeQueuedTasks()
|
|
||||||
LockSupport.parkNanos(1_000_000L)
|
|
||||||
}
|
|
||||||
|
|
||||||
return future.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun schedule(command: Runnable, delay: Long, unit: TimeUnit): ScheduledFuture<*> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
val timer = Timer({ command.run() }, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
|
||||||
|
|
||||||
if (isSameThread() && delay <= 0L) {
|
|
||||||
timer.run()
|
|
||||||
Thread.interrupted()
|
|
||||||
} else if (isSameThread()) {
|
|
||||||
timers.enqueue(timer)
|
|
||||||
} else {
|
|
||||||
execute {
|
|
||||||
if (timer.isCancelled) {
|
|
||||||
// do nothing
|
|
||||||
} else if (timer.executeAt <= timeOrigin.nanos) {
|
|
||||||
timer.run()
|
|
||||||
Thread.interrupted()
|
|
||||||
} else {
|
|
||||||
timers.enqueue(timer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return timer
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <V : Any?> schedule(callable: Callable<V>, delay: Long, unit: TimeUnit): ScheduledFuture<V> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
val timer = Timer(callable, timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(delay, unit))
|
|
||||||
|
|
||||||
if (isSameThread() && delay <= 0L) {
|
|
||||||
timer.run()
|
|
||||||
Thread.interrupted()
|
|
||||||
} else if (isSameThread()) {
|
|
||||||
timers.enqueue(timer)
|
|
||||||
} else {
|
|
||||||
execute {
|
|
||||||
if (timer.isCancelled) {
|
|
||||||
// do nothing
|
|
||||||
} else if (timer.executeAt <= timeOrigin.nanos) {
|
|
||||||
timer.run()
|
|
||||||
Thread.interrupted()
|
|
||||||
} else {
|
|
||||||
timers.enqueue(timer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return timer
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scheduleAtFixedRate(
|
|
||||||
command: Runnable,
|
|
||||||
initialDelay: Long,
|
|
||||||
period: Long,
|
|
||||||
unit: TimeUnit
|
|
||||||
): ScheduledFuture<*> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
return RepeatableTimer(
|
|
||||||
command,
|
|
||||||
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
|
||||||
TimeUnit.NANOSECONDS.convert(period, unit), true)
|
|
||||||
.also {
|
|
||||||
execute {
|
|
||||||
if (it.isCancelled) {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
repeatableTimers.enqueue(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scheduleWithFixedDelay(
|
|
||||||
command: Runnable,
|
|
||||||
initialDelay: Long,
|
|
||||||
delay: Long,
|
|
||||||
unit: TimeUnit
|
|
||||||
): ScheduledFuture<*> {
|
|
||||||
if (isShutdown) throw RejectedExecutionException("This mailbox is shutting down")
|
|
||||||
|
|
||||||
return RepeatableTimer(
|
|
||||||
command,
|
|
||||||
timeOrigin.nanos + TimeUnit.NANOSECONDS.convert(initialDelay, unit),
|
|
||||||
TimeUnit.NANOSECONDS.convert(delay, unit), false)
|
|
||||||
.also {
|
|
||||||
execute {
|
|
||||||
if (it.isCancelled) {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
repeatableTimers.enqueue(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world
|
|||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
|
import com.google.gson.JsonArray
|
||||||
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
|
||||||
@ -14,6 +15,7 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||||
import ru.dbotthepony.kommons.gson.set
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
import ru.dbotthepony.kommons.util.IStruct2i
|
import ru.dbotthepony.kommons.util.IStruct2i
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
@ -32,6 +34,7 @@ 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
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.EntityMessagePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||||
@ -582,6 +585,29 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
abstract fun remote(connectionID: Int): Connection?
|
abstract fun remote(connectionID: Int): Connection?
|
||||||
|
|
||||||
|
// doesn't write stacktrace
|
||||||
|
class MessageCallException(message: String) : RuntimeException(message, null, true, false)
|
||||||
|
|
||||||
|
fun dispatchEntityMessage(sourceConnection: Int, entityID: Int, message: String, arguments: JsonArray): CompletableFuture<JsonElement> {
|
||||||
|
val connectionID = Connection.connectionForEntityID(entityID)
|
||||||
|
|
||||||
|
if (connectionID == this.connectionID) {
|
||||||
|
val entity = entities[entityID] ?: return CompletableFuture.failedFuture(MessageCallException("No such entity $entityID"))
|
||||||
|
return entity.tryHandleMessage(sourceConnection, message, arguments)
|
||||||
|
} else {
|
||||||
|
val connection = remote(connectionID) ?: return CompletableFuture.failedFuture(NoSuchElementException("Can't dispatch entity message, no such connection $connectionID"))
|
||||||
|
val future = CompletableFuture<JsonElement>()
|
||||||
|
val uuid = UUID(random.nextLong(), random.nextLong())
|
||||||
|
pendingEntityMessages.put(uuid, future)
|
||||||
|
connection.send(EntityMessagePacket(Either.left(entityID), message, arguments, uuid, sourceConnection))
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispatchEntityMessage(sourceConnection: Int, entityID: String, message: String, arguments: JsonArray): CompletableFuture<JsonElement> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
// this *could* have been divided into per-entity map and beheaded world's map
|
// 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
|
// 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()
|
val pendingEntityMessages: Cache<UUID, CompletableFuture<JsonElement>> = Caffeine.newBuilder()
|
||||||
|
@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
|||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
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.Starbound
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
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
|
||||||
@ -34,7 +35,6 @@ import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
|||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
|
||||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||||
import ru.dbotthepony.kstarbound.world.EntityIndex
|
import ru.dbotthepony.kstarbound.world.EntityIndex
|
||||||
import ru.dbotthepony.kstarbound.world.TileRayFilter
|
import ru.dbotthepony.kstarbound.world.TileRayFilter
|
||||||
@ -42,8 +42,11 @@ 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.PriorityQueue
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.ScheduledFuture
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
||||||
@ -81,9 +84,6 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
LOGGER.error("Error while executing queued task on $this", it)
|
LOGGER.error("Error while executing queued task on $this", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
var mailbox = MailboxExecutorService().also { it.exceptionHandler = exceptionLogger }
|
|
||||||
private set
|
|
||||||
|
|
||||||
private var innerWorld: World<*, *>? = null
|
private var innerWorld: World<*, *>? = null
|
||||||
|
|
||||||
val world: World<*, *>
|
val world: World<*, *>
|
||||||
@ -201,9 +201,6 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
check(!world.entities.containsKey(entityID)) { "Duplicate entity ID: $entityID" }
|
check(!world.entities.containsKey(entityID)) { "Duplicate entity ID: $entityID" }
|
||||||
|
|
||||||
if (mailbox.isShutdown)
|
|
||||||
mailbox = MailboxExecutorService().also { it.exceptionHandler = exceptionLogger }
|
|
||||||
|
|
||||||
innerWorld = world
|
innerWorld = world
|
||||||
world.entities[entityID] = this
|
world.entities[entityID] = this
|
||||||
world.entityList.add(this)
|
world.entityList.add(this)
|
||||||
@ -227,7 +224,9 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
removalReason = reason
|
removalReason = reason
|
||||||
|
|
||||||
mailbox.shutdownNow()
|
scheduledTasks.forEach { it.cancel(false) }
|
||||||
|
scheduledTasks.clear()
|
||||||
|
|
||||||
check(world.entities.remove(entityID) == this) { "Tried to remove $this from $world, but removed something else!" }
|
check(world.entities.remove(entityID) == this) { "Tried to remove $this from $world, but removed something else!" }
|
||||||
world.entityList.remove(this)
|
world.entityList.remove(this)
|
||||||
|
|
||||||
@ -255,12 +254,24 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun interact(request: InteractRequest): InteractAction {
|
// for fast check on completed tasks
|
||||||
return InteractAction.NONE
|
// This is necessary to cancel tasks when we are removed, so we don't reference ourselves
|
||||||
|
// in event loop after we have been removed
|
||||||
|
private val scheduledTasks = PriorityQueue<ScheduledFuture<*>>()
|
||||||
|
|
||||||
|
protected fun scheduleInTicks(ticks: Int, action: Runnable) {
|
||||||
|
scheduledTasks.add(world.eventLoop.schedule(action, ticks * Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun schedule(time: Long, unit: TimeUnit, action: Runnable) {
|
||||||
|
scheduledTasks.add(world.eventLoop.schedule(action, time, unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
var isRemote: Boolean = false
|
var isRemote: Boolean = false
|
||||||
|
|
||||||
|
open val mouthPosition: Vector2d
|
||||||
|
get() = position
|
||||||
|
|
||||||
private fun isDamageAuthoritative(target: AbstractEntity): Boolean {
|
private fun isDamageAuthoritative(target: AbstractEntity): Boolean {
|
||||||
// Damage manager is authoritative if either one of the entities is
|
// Damage manager is authoritative if either one of the entities is
|
||||||
// masterOnly, OR the manager is server-side and both entities are
|
// masterOnly, OR the manager is server-side and both entities are
|
||||||
@ -371,7 +382,9 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
open fun damagedOther(notification: DamageNotificationPacket) {}
|
open fun damagedOther(notification: DamageNotificationPacket) {}
|
||||||
|
|
||||||
open fun tick(delta: Double) {
|
open fun tick(delta: Double) {
|
||||||
mailbox.executeQueuedTasks()
|
while (scheduledTasks.isNotEmpty() && scheduledTasks.peek().isDone) {
|
||||||
|
scheduledTasks.poll()
|
||||||
|
}
|
||||||
|
|
||||||
if (networkGroup.upstream.isInterpolating) {
|
if (networkGroup.upstream.isInterpolating) {
|
||||||
networkGroup.upstream.tickInterpolation(delta)
|
networkGroup.upstream.tickInterpolation(delta)
|
||||||
@ -467,30 +480,26 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// doesn't write stacktrace
|
fun tryHandleMessage(sourceConnection: Int, message: String, arguments: JsonArray): CompletableFuture<JsonElement> {
|
||||||
class MessageCallException(message: String) : RuntimeException(message, null, true, false)
|
val response = try {
|
||||||
|
handleMessage(sourceConnection, message, arguments)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
return CompletableFuture.failedFuture(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return CompletableFuture.failedFuture(World.MessageCallException("Message '$message' was not handled"))
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun dispatchMessage(sourceConnection: Int, message: String, arguments: JsonArray): CompletableFuture<JsonElement> {
|
fun dispatchMessage(sourceConnection: Int, message: String, arguments: JsonArray): CompletableFuture<JsonElement> {
|
||||||
if (isRemote) {
|
if (!isRemote) {
|
||||||
val connection = world.remote(connectionID) ?: return CompletableFuture.failedFuture(NoSuchElementException("Can't dispatch entity message, no such connection $connectionID"))
|
return tryHandleMessage(sourceConnection, message, arguments)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return world.dispatchEntityMessage(sourceConnection, entityID, message, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun render(client: StarboundClient, layers: LayeredRenderer) {
|
open fun render(client: StarboundClient, layers: LayeredRenderer) {
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities.api
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||||
|
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||||
|
|
||||||
|
// sigh
|
||||||
|
interface InteractiveEntity {
|
||||||
|
val isInteractive: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
fun interact(request: InteractRequest): InteractAction {
|
||||||
|
return InteractAction.NONE
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities.api
|
||||||
|
|
||||||
|
interface LoungeableEntity {
|
||||||
|
}
|
@ -5,7 +5,6 @@ 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
|
||||||
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.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
|
||||||
@ -26,6 +25,8 @@ import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
|||||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
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.json.jsonArrayOf
|
||||||
|
import ru.dbotthepony.kstarbound.json.stream
|
||||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||||
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
|
||||||
@ -38,10 +39,11 @@ import ru.dbotthepony.kstarbound.world.World
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(config) {
|
class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(config) {
|
||||||
var opened by networkedSignedInt().also { networkGroup.upstream.add(it) }
|
var openFrameIndex by networkedSignedInt().also { networkGroup.upstream.add(it) }
|
||||||
var isCrafting by networkedBoolean().also { networkGroup.upstream.add(it) }
|
var isCrafting by networkedBoolean().also { networkGroup.upstream.add(it) }
|
||||||
var craftingProgress by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
|
var craftingProgress by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
|
||||||
val items = Container(lookupProperty("slotCount").asInt).also { networkGroup.upstream.add(it) }
|
val items = Container(lookupProperty("slotCount").asInt).also { networkGroup.upstream.add(it) }
|
||||||
@ -65,6 +67,25 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
lookupProperty("itemAgeMultiplier") { JsonPrimitive(1.0) }.asDouble
|
lookupProperty("itemAgeMultiplier") { JsonPrimitive(1.0) }.asDouble
|
||||||
}.also { parametersLazies.add(it) }
|
}.also { parametersLazies.add(it) }
|
||||||
|
|
||||||
|
private var openCount = 0
|
||||||
|
|
||||||
|
fun openContainer() {
|
||||||
|
if (openCount++ == 0) {
|
||||||
|
openFrameIndex = lookupProperty("openFrameIndex") { JsonPrimitive(2) }.asInt
|
||||||
|
|
||||||
|
scheduleInTicks(lookupProperty("autoCloseCooldown").asInt) {
|
||||||
|
closeContainer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeContainer() {
|
||||||
|
if (--openCount <= 0) {
|
||||||
|
openCount = 0
|
||||||
|
openFrameIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun tick(delta: Double) {
|
override fun tick(delta: Double) {
|
||||||
super.tick(delta)
|
super.tick(delta)
|
||||||
|
|
||||||
@ -106,7 +127,7 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
override fun deserialize(data: JsonObject) {
|
override fun deserialize(data: JsonObject) {
|
||||||
super.deserialize(data)
|
super.deserialize(data)
|
||||||
|
|
||||||
opened = data.get("opened", 0)
|
openFrameIndex = data.get("opened", 0)
|
||||||
isCrafting = data.get("crafting", false)
|
isCrafting = data.get("crafting", false)
|
||||||
craftingProgress = data.get("craftingProgress", 0.0)
|
craftingProgress = data.get("craftingProgress", 0.0)
|
||||||
isInitialized = data.get("initialized", true)
|
isInitialized = data.get("initialized", true)
|
||||||
@ -118,7 +139,7 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
|
|
||||||
// required by original engine
|
// required by original engine
|
||||||
data["currentState"] = 0
|
data["currentState"] = 0
|
||||||
data["opened"] = opened
|
data["opened"] = openFrameIndex
|
||||||
data["crafting"] = isCrafting
|
data["crafting"] = isCrafting
|
||||||
data["craftingProgress"] = craftingProgress
|
data["craftingProgress"] = craftingProgress
|
||||||
data["initialized"] = isInitialized
|
data["initialized"] = isInitialized
|
||||||
@ -150,7 +171,7 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
LOGGER.error("Unknown treasure pool $get! Can't generate container contents at $tilePosition.")
|
LOGGER.error("Unknown treasure pool $get! Can't generate container contents at $tilePosition.")
|
||||||
} else {
|
} else {
|
||||||
for (item in treasurePool.value.evaluate(random, level)) {
|
for (item in treasurePool.value.evaluate(random, level)) {
|
||||||
val leftover = items.add(item)
|
val leftover = items.add(item).first
|
||||||
|
|
||||||
if (leftover.isNotEmpty) {
|
if (leftover.isNotEmpty) {
|
||||||
LOGGER.warn("Tried to overfill container at $tilePosition")
|
LOGGER.warn("Tried to overfill container at $tilePosition")
|
||||||
@ -189,7 +210,7 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
|
override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
|
||||||
return when (message.lowercase()) {
|
return when (message.lowercase()) { // because legacy protocol allows it
|
||||||
"startcrafting" -> {
|
"startcrafting" -> {
|
||||||
startCrafting()
|
startCrafting()
|
||||||
JsonNull.INSTANCE
|
JsonNull.INSTANCE
|
||||||
@ -206,12 +227,14 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns not inserted items
|
// returns not inserted items
|
||||||
"additems" -> items.add(ItemDescriptor(arguments[0]).build()).toJson()
|
"additems" -> items.add(ItemDescriptor(arguments[0]).build()).first.toJson()
|
||||||
"putitems" -> items.put(IntLists.singleton(arguments[0].asInt), ItemDescriptor(arguments[1]).build()).toJson()
|
"stackitems" -> items.stackWithExisting(ItemDescriptor(arguments[0]).build()).first.toJson()
|
||||||
|
"putitems" -> items.put(IntLists.singleton(arguments[0].asInt), ItemDescriptor(arguments[1]).build()).first.toJson()
|
||||||
"takeitems" -> items.take(arguments[0].asInt, arguments[1].asLong).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()
|
"swapitems" -> items.swap(arguments[0].asInt, ItemDescriptor(arguments[1]).build(), if (arguments.size() >= 3) arguments[2].asBoolean else true).toJson()
|
||||||
|
"combineitems" -> items.combineAt(arguments[0].asInt, ItemDescriptor(arguments[1]).build()).toJson()
|
||||||
"applyaugment" -> TODO("applyaugment")
|
"applyaugment" -> TODO("applyaugment")
|
||||||
"consumeitems" -> JsonPrimitive(items.take(ItemDescriptor(arguments[0])))
|
"consumeitems" -> JsonPrimitive(items.take(ItemDescriptor(arguments[0]), exactMatch = arguments.get(1, false)))
|
||||||
"consumeitemsat" -> JsonPrimitive(items.takeExact(arguments[0].asInt, arguments[1].asLong))
|
"consumeitemsat" -> JsonPrimitive(items.takeExact(arguments[0].asInt, arguments[1].asLong))
|
||||||
"clearcontainer" -> {
|
"clearcontainer" -> {
|
||||||
val result = JsonArray()
|
val result = JsonArray()
|
||||||
@ -230,6 +253,88 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addItems(item: ItemStack): CompletableFuture<ItemStack> {
|
||||||
|
if (isRemote) {
|
||||||
|
return dispatchMessage(world.connectionID, "addItems", jsonArrayOf(item.toJson())).thenApply { ItemDescriptor(it).build() }
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.add(item).first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putItems(slot: Int, item: ItemStack): CompletableFuture<ItemStack> {
|
||||||
|
if (isRemote) {
|
||||||
|
return dispatchMessage(world.connectionID, "putItems", jsonArrayOf(item.toJson(), slot)).thenApply { ItemDescriptor(it).build() }
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.put(IntLists.singleton(slot), item).first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stackWithExisting(item: ItemStack): CompletableFuture<ItemStack> {
|
||||||
|
if (isRemote) {
|
||||||
|
if (world.remote(connectionID)!!.isLegacy) {
|
||||||
|
return dispatchMessage(world.connectionID, "addItems", jsonArrayOf(item.toJson())).thenApply { ItemDescriptor(it).build() }
|
||||||
|
} else {
|
||||||
|
return dispatchMessage(world.connectionID, "stackItems", jsonArrayOf(item.toJson())).thenApply { ItemDescriptor(it).build() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.stackWithExisting(item).first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun swapItems(slot: Int, item: ItemStack, tryCombine: Boolean = true): CompletableFuture<ItemStack> {
|
||||||
|
if (isRemote) {
|
||||||
|
return dispatchMessage(world.connectionID, "swapItems", jsonArrayOf(slot, item.toJson(), tryCombine)).thenApply { ItemDescriptor(it).build() }
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.swap(slot, item, tryCombine))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun combineItems(slot: Int, item: ItemStack): CompletableFuture<ItemStack> {
|
||||||
|
if (isRemote) {
|
||||||
|
if (world.remote(connectionID)!!.isLegacy) {
|
||||||
|
if (items[slot].isEmpty || !items[slot].isStackable(item)) {
|
||||||
|
return CompletableFuture.completedFuture(item)
|
||||||
|
} else {
|
||||||
|
return dispatchMessage(world.connectionID, "putItems", jsonArrayOf(slot, item.toJson()))
|
||||||
|
.thenApply { ItemDescriptor(it).build() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return dispatchMessage(world.connectionID, "combineItems", jsonArrayOf(slot, item.toJson()))
|
||||||
|
.thenApply { ItemDescriptor(it).build() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.combineAt(slot, item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun takeItem(item: ItemDescriptor, exact: Boolean = false): CompletableFuture<Boolean> {
|
||||||
|
if (isRemote) {
|
||||||
|
return dispatchMessage(world.connectionID, "consumeItems", jsonArrayOf(item.toJson(), exact)).thenApply { it.asBoolean }
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.take(item, exactMatch = exact))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun takeItemAt(index: Int, amount: Long = Long.MAX_VALUE): CompletableFuture<Boolean> {
|
||||||
|
if (isRemote) {
|
||||||
|
return dispatchMessage(world.connectionID, "consumeItemsAt", jsonArrayOf(index, amount)).thenApply { it.asBoolean }
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(items.takeExact(index, amount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearContainer(): CompletableFuture<List<ItemStack>> {
|
||||||
|
if (isRemote) {
|
||||||
|
return dispatchMessage(world.connectionID, "clearContainer", JsonArray()).thenApply {
|
||||||
|
(it as JsonArray).stream().map { ItemDescriptor(it).build() }.filter { it.isNotEmpty }.toList()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val future = CompletableFuture.completedFuture(items.filter { it.isNotEmpty })
|
||||||
|
items.clear()
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startCrafting() {
|
private fun startCrafting() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +20,10 @@ import ru.dbotthepony.kstarbound.util.coalesceNull
|
|||||||
import ru.dbotthepony.kstarbound.util.valueOf
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity
|
||||||
import java.lang.Math.toRadians
|
import java.lang.Math.toRadians
|
||||||
|
|
||||||
class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(config) {
|
class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(config), LoungeableEntity {
|
||||||
init {
|
init {
|
||||||
isInteractive = true
|
isInteractive = true
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ import ru.dbotthepony.kstarbound.world.World
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
@ -98,7 +99,7 @@ import java.util.HashMap
|
|||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity(), ScriptedEntity {
|
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity(), ScriptedEntity, InteractiveEntity {
|
||||||
override fun deserialize(data: JsonObject) {
|
override fun deserialize(data: JsonObject) {
|
||||||
super.deserialize(data)
|
super.deserialize(data)
|
||||||
direction = data.get("direction", directions) { Direction.LEFT }
|
direction = data.get("direction", directions) { Direction.LEFT }
|
||||||
@ -142,7 +143,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
|
|
||||||
data["parameters"] = JsonObject().also {
|
data["parameters"] = JsonObject().also {
|
||||||
for ((k, v) in parameters) {
|
for ((k, v) in parameters) {
|
||||||
it[k] = v.deepCopy()
|
it[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,7 +204,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
networkGroup.upstream.add(uniqueID)
|
networkGroup.upstream.add(uniqueID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isInteractive by networkedBoolean().also { networkGroup.upstream.add(it) }
|
final override var isInteractive by networkedBoolean().also { networkGroup.upstream.add(it) }
|
||||||
val declaredMaterialSpaces = NetworkedList(materialSpacesCodec, materialSpacesCodecLegacy).also { networkGroup.upstream.add(it) }
|
val declaredMaterialSpaces = NetworkedList(materialSpacesCodec, materialSpacesCodecLegacy).also { networkGroup.upstream.add(it) }
|
||||||
|
|
||||||
private val materialSpaces0 = ManualLazy {
|
private val materialSpaces0 = ManualLazy {
|
||||||
|
Loading…
Reference in New Issue
Block a user