Add more world bindings
This commit is contained in:
parent
5c840eaf77
commit
7e26f0d3b8
28
ADDITIONS.md
28
ADDITIONS.md
@ -73,12 +73,27 @@ val color: TileColor = TileColor.DEFAULT
|
|||||||
* `centered` (defaults to `true`)
|
* `centered` (defaults to `true`)
|
||||||
* `fullbright` (defaults to `false`)
|
* `fullbright` (defaults to `false`)
|
||||||
|
|
||||||
|
#### .liquid
|
||||||
|
* `liquidId` is no longer essential and can be skipped; engine **will not** assign it to anything, but liquid will still be fully functional from engine's point of view
|
||||||
|
* However, this has serious implications:
|
||||||
|
* Liquid will become "invisible" to legacy clients (this is not guaranteed, and if it ever "bleeds" into structures sent to legacy clients due to missed workarounds in code, legacy client will blow up.)
|
||||||
|
* Lua scripts written solely for original engine won't see this liquid too (this includes base game assets!), unless they use new improved functions
|
||||||
|
* `liquidId` can be specified as any number in 1 -> 2^31 - 1 range (0 is reserved for "empty" meta-liquid)
|
||||||
|
* This will make liquid "invisible" to original clients only, Lua code should continue to function normally
|
||||||
|
* This is not guaranteed, and if it ever "bleeds" into structures sent to legacy clients due to missed workarounds in code, legacy client will blow up.
|
||||||
|
|
||||||
#### .matierial
|
#### .matierial
|
||||||
|
* Meta-materials are no longer treated uniquely, and are defined as "real" materials, just like every other material, but still preserve unique interactions.
|
||||||
|
* `materialId` is no longer essential and can be skipped, with same notes as described in `liquidId`.
|
||||||
|
* `materialId` can be specified as any number in 1 -> 2^31 - 1 (softly excluding reserved "meta materials" ID range, since this range is not actually reserved, but is expected to be used solely by meta materials), with legacy client implications only.
|
||||||
* Implemented `isConnectable`, which was planned by original developers, but scrapped in process (defaults to `true`, by default only next meta-materials have it set to false: `empty`, `null` and `boundary`)
|
* Implemented `isConnectable`, which was planned by original developers, but scrapped in process (defaults to `true`, by default only next meta-materials have it set to false: `empty`, `null` and `boundary`)
|
||||||
* Used by object and plant anchoring code to determine valid placement
|
* Used by object and plant anchoring code to determine valid placement
|
||||||
* Used by world tile rendering code (render piece rule `Connects`)
|
* Used by world tile rendering code (render piece rule `Connects`)
|
||||||
* And finally, used by `canPlaceMaterial` to determine whenever player can place blocks next to it (at least one such tile should be present for player to be able to place blocks next to it)
|
* And finally, used by `canPlaceMaterial` to determine whenever player can place blocks next to it (at least one such tile should be present for player to be able to place blocks next to it)
|
||||||
|
|
||||||
|
#### .matmod
|
||||||
|
* `modId` is no longer essential and can be skipped, or specified as any number in 1 -> 2^31 range, with notes of `materialId` and `liquidId` apply.
|
||||||
|
|
||||||
## Scripting
|
## Scripting
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
@ -102,6 +117,19 @@ val color: TileColor = TileColor.DEFAULT
|
|||||||
* Added `animator.hasEffect(effect: string): boolean`
|
* Added `animator.hasEffect(effect: string): boolean`
|
||||||
* Added `animator.parts(): List<string>`
|
* Added `animator.parts(): List<string>`
|
||||||
|
|
||||||
|
#### world
|
||||||
|
|
||||||
|
* Added `world.liquidNamesAlongLine(start: Vector2d, end: Vector2d): List<LiquidState>`, will return Liquid' name instead of its ID
|
||||||
|
* Added `world.liquidNameAt(at: Vector2i): LiquidState?`, will return Liquid' name instead of its ID
|
||||||
|
* Added `world.biomeBlockNamesAt(at: Vector2i): List<String>?`, will return Block names instead of their IDs
|
||||||
|
* Added `world.destroyNamedLiquid(at: Vector2i): LiquidState?`, will return Liquid' name instead of its ID
|
||||||
|
* Added `world.gravityVector(at: Vector2d): Vector2d`. **Attention:** directional gravity is WIP.
|
||||||
|
* Added `world.itemDropLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.playerLineQuery(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>`
|
||||||
|
* `world.entityCanDamage(source: EntityID, target: EntityID): Boolean` now properly accounts for case when `source == target`
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
@ -7,6 +7,7 @@ import io.netty.channel.ChannelInitializer
|
|||||||
import io.netty.channel.local.LocalAddress
|
import io.netty.channel.local.LocalAddress
|
||||||
import io.netty.channel.local.LocalChannel
|
import io.netty.channel.local.LocalChannel
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel
|
import io.netty.channel.socket.nio.NioSocketChannel
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -34,8 +35,40 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType) : Conn
|
|||||||
channel.flush()
|
channel.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val occupiedEntityIDs = IntAVLTreeSet()
|
||||||
|
private var nextEntityID = 0
|
||||||
|
|
||||||
|
fun nextEntityID(): Int {
|
||||||
|
if (nextEntityID !in entityIDRange) {
|
||||||
|
nextEntityID = entityIDRange.first
|
||||||
|
}
|
||||||
|
|
||||||
|
var itrs = 0
|
||||||
|
|
||||||
|
while (occupiedEntityIDs.contains(nextEntityID)) {
|
||||||
|
if (++nextEntityID !in entityIDRange) {
|
||||||
|
nextEntityID = entityIDRange.first
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itrs++ > 66000) {
|
||||||
|
throw RuntimeException("Ran out of entity IDs to allocate!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
occupiedEntityIDs.add(nextEntityID)
|
||||||
|
return nextEntityID
|
||||||
|
}
|
||||||
|
|
||||||
|
fun freeEntityID(id: Int) {
|
||||||
|
occupiedEntityIDs.remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetOccupiedEntityIDs() {
|
||||||
|
occupiedEntityIDs.clear()
|
||||||
|
}
|
||||||
|
|
||||||
fun enqueue(task: StarboundClient.() -> Unit) {
|
fun enqueue(task: StarboundClient.() -> Unit) {
|
||||||
client.mailbox.execute { task.invoke(client) }
|
client.execute { task.invoke(client) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
@ -71,9 +71,9 @@ enum class RenderLayer {
|
|||||||
|
|
||||||
fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: AbstractTileState): Point {
|
fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: AbstractTileState): Point {
|
||||||
if (isModifier) {
|
if (isModifier) {
|
||||||
return tileLayer(isBackground, true, tile.modifier?.value?.renderParameters?.zLevel ?: 0L, tile.modifier?.value?.modId?.toLong() ?: 0L, tile.modifierHueShift)
|
return tileLayer(isBackground, true, tile.modifier.value.renderParameters.zLevel, tile.modifier.value.modId?.toLong() ?: 0L, tile.modifierHueShift)
|
||||||
} else {
|
} else {
|
||||||
return tileLayer(isBackground, false, tile.material.value.renderParameters.zLevel, tile.material.value.materialId.toLong(), tile.hueShift)
|
return tileLayer(isBackground, false, tile.material.value.renderParameters.zLevel, tile.material.value.materialId?.toLong() ?: 0L, tile.hueShift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.world
|
package ru.dbotthepony.kstarbound.client.world
|
||||||
|
|
||||||
import com.google.common.base.Supplier
|
import com.google.common.base.Supplier
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.longs.LongArraySet
|
import it.unimi.dsi.fastutil.longs.LongArraySet
|
||||||
@ -20,9 +22,11 @@ import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.UpdateWorldPropertiesPacket
|
||||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
|
import ru.dbotthepony.kstarbound.world.TileModification
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
|
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
|
||||||
@ -66,6 +70,10 @@ class ClientWorld(
|
|||||||
override val eventLoop: BlockableEventLoop
|
override val eventLoop: BlockableEventLoop
|
||||||
get() = client
|
get() = client
|
||||||
|
|
||||||
|
override fun setProperty0(key: String, value: JsonElement) {
|
||||||
|
client.activeConnection?.send(UpdateWorldPropertiesPacket(JsonObject().apply { add(key, value) }))
|
||||||
|
}
|
||||||
|
|
||||||
inner class RenderRegion(val x: Int, val y: Int) {
|
inner class RenderRegion(val x: Int, val y: Int) {
|
||||||
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
||||||
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, RenderLayer.Point>>()
|
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, RenderLayer.Point>>()
|
||||||
@ -305,6 +313,17 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun applyTileModifications(
|
||||||
|
modifications: Collection<Pair<Vector2i, TileModification>>,
|
||||||
|
allowEntityOverlap: Boolean,
|
||||||
|
ignoreTileProtection: Boolean
|
||||||
|
): List<Pair<Vector2i, TileModification>> {
|
||||||
|
// send packets to server here
|
||||||
|
// this is required because Lua scripts call this method
|
||||||
|
// and Lua scripts want these changes to be applied serverside (good game security, i approve)
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ring = listOf(
|
val ring = listOf(
|
||||||
Vector2i(0, 0),
|
Vector2i(0, 0),
|
||||||
|
@ -32,6 +32,7 @@ import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
|||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
// uint8_t
|
// uint8_t
|
||||||
enum class TeamType(override val jsonName: String) : IStringSerializable {
|
enum class TeamType(override val jsonName: String) : IStringSerializable {
|
||||||
@ -83,7 +84,44 @@ data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int =
|
|||||||
stream.writeShort(team)
|
stream.writeShort(team)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canDamage(victim: EntityDamageTeam, victimIsSelf: Boolean): Boolean {
|
||||||
|
if (victimIsSelf) {
|
||||||
|
return type == TeamType.INDISCRIMINATE
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (type) {
|
||||||
|
TeamType.NULL -> false
|
||||||
|
TeamType.FRIENDLY -> victim.type in damageableByFriendly
|
||||||
|
TeamType.ENEMY -> victim.type in damageableByEnemy || victim.type == TeamType.ENEMY && team != victim.team
|
||||||
|
TeamType.PVP -> victim.type in damageableByFriendly || victim.type == TeamType.PVP && (team == 0 || team != victim.team)
|
||||||
|
TeamType.PASSIVE -> false // never deal damage
|
||||||
|
TeamType.GHOSTLY -> false // never deal damage
|
||||||
|
TeamType.ENVIRONMENT -> victim.type in damageableByEnvironment
|
||||||
|
TeamType.INDISCRIMINATE -> victim.type != TeamType.GHOSTLY
|
||||||
|
TeamType.ASSISTANT -> victim.type in damageableByFriendly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val damageableByFriendly = EnumSet.noneOf(TeamType::class.java)
|
||||||
|
private val damageableByEnemy = EnumSet.noneOf(TeamType::class.java)
|
||||||
|
private val damageableByEnvironment = EnumSet.noneOf(TeamType::class.java)
|
||||||
|
|
||||||
|
init {
|
||||||
|
damageableByFriendly.add(TeamType.ENEMY)
|
||||||
|
damageableByFriendly.add(TeamType.PASSIVE)
|
||||||
|
damageableByFriendly.add(TeamType.ENVIRONMENT)
|
||||||
|
damageableByFriendly.add(TeamType.INDISCRIMINATE)
|
||||||
|
|
||||||
|
damageableByEnemy.add(TeamType.FRIENDLY)
|
||||||
|
damageableByEnemy.add(TeamType.PVP)
|
||||||
|
damageableByEnemy.add(TeamType.INDISCRIMINATE)
|
||||||
|
|
||||||
|
damageableByEnvironment.add(TeamType.FRIENDLY)
|
||||||
|
damageableByEnvironment.add(TeamType.PVP)
|
||||||
|
damageableByEnvironment.add(TeamType.INDISCRIMINATE)
|
||||||
|
}
|
||||||
|
|
||||||
val NULL = EntityDamageTeam()
|
val NULL = EntityDamageTeam()
|
||||||
val PASSIVE = EntityDamageTeam(TeamType.PASSIVE)
|
val PASSIVE = EntityDamageTeam(TeamType.PASSIVE)
|
||||||
val CODEC = nativeCodec(::EntityDamageTeam, EntityDamageTeam::write)
|
val CODEC = nativeCodec(::EntityDamageTeam, EntityDamageTeam::write)
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.annotations.JsonAdapter
|
import com.google.gson.annotations.JsonAdapter
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
@ -21,6 +22,7 @@ import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kommons.gson.consumeNull
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
import ru.dbotthepony.kommons.gson.contains
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
|
import ru.dbotthepony.kommons.gson.value
|
||||||
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.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -230,6 +232,10 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
*/
|
*/
|
||||||
abstract fun flop(): Drawable
|
abstract fun flop(): Drawable
|
||||||
|
|
||||||
|
open fun toJson(): JsonObject {
|
||||||
|
return JsonObject()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Empty()
|
val EMPTY = Empty()
|
||||||
val CENTERED = Transformations(true)
|
val CENTERED = Transformations(true)
|
||||||
@ -245,8 +251,8 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
|
|||||||
private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter<ImmutableList<Vector2f>>
|
private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter<ImmutableList<Vector2f>>
|
||||||
private val transformations = gson.getAdapter(Transformations::class.java)
|
private val transformations = gson.getAdapter(Transformations::class.java)
|
||||||
|
|
||||||
override fun write(out: JsonWriter?, value: Drawable?) {
|
override fun write(out: JsonWriter, value: Drawable?) {
|
||||||
TODO("Not yet implemented")
|
out.value(value?.toJson())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): Drawable {
|
override fun read(`in`: JsonReader): Drawable {
|
||||||
|
@ -3,15 +3,15 @@ package ru.dbotthepony.kstarbound.defs
|
|||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
|
||||||
enum class EntityType(override val jsonName: String) : IStringSerializable {
|
enum class EntityType(override val jsonName: String, val storeName: String) : IStringSerializable {
|
||||||
PLANT("PlantEntity"),
|
PLANT("plant", "PlantEntity"),
|
||||||
OBJECT("ObjectEntity"),
|
OBJECT("object", "ObjectEntity"),
|
||||||
VEHICLE("VehicleEntity"),
|
VEHICLE("vehicle", "VehicleEntity"),
|
||||||
ITEM_DROP("ItemDropEntity"),
|
ITEM_DROP("itemDrop", "ItemDropEntity"),
|
||||||
PLANT_DROP("PlantDropEntity"), // wat
|
PLANT_DROP("plantDrop", "PlantDropEntity"), // wat
|
||||||
PROJECTILE("ProjectileEntity"),
|
PROJECTILE("projectile", "ProjectileEntity"),
|
||||||
STAGEHAND("StagehandEntity"),
|
STAGEHAND("stagehand", "StagehandEntity"),
|
||||||
MONSTER("MonsterEntity"),
|
MONSTER("monster", "MonsterEntity"),
|
||||||
NPC("NpcEntity"),
|
NPC("npc", "NpcEntity"),
|
||||||
PLAYER("PlayerEntity");
|
PLAYER("player", "PlayerEntity");
|
||||||
}
|
}
|
||||||
|
@ -502,7 +502,7 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar
|
|||||||
obj.orientationIndex = orientation.toLong()
|
obj.orientationIndex = orientation.toLong()
|
||||||
obj.joinWorld(parent)
|
obj.joinWorld(parent)
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("Tried to place object ${obj.config.key} at ${obj.tilePosition}, but it can't be placed there!")
|
LOGGER.error("Dungeon generator tried to place object ${obj.config.key} at ${obj.tilePosition}, but it can't be placed there!")
|
||||||
}
|
}
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.error("Exception while putting dungeon object $obj at ${obj!!.tilePosition}", err)
|
LOGGER.error("Exception while putting dungeon object $obj at ${obj!!.tilePosition}", err)
|
||||||
|
@ -98,6 +98,9 @@ fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier<ItemDescri
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor {
|
fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor {
|
||||||
|
if (data.metatable?.rawget("__nils") != null)
|
||||||
|
return ItemDescriptor(toJsonFromLua(data)) // assume it is json
|
||||||
|
|
||||||
val name = indexNoYield(data, 1L) ?: indexNoYield(data, "name") ?: indexNoYield(data, "item")
|
val name = indexNoYield(data, 1L) ?: indexNoYield(data, "name") ?: indexNoYield(data, "item")
|
||||||
val count = indexNoYield(data, 2L) ?: indexNoYield(data, "count") ?: 1L
|
val count = indexNoYield(data, 2L) ?: indexNoYield(data, "count") ?: 1L
|
||||||
val parameters = indexNoYield(data, 3L) ?: indexNoYield(data, "parameters") ?: indexNoYield(data, "data")
|
val parameters = indexNoYield(data, 3L) ?: indexNoYield(data, "parameters") ?: indexNoYield(data, "data")
|
||||||
@ -114,7 +117,17 @@ fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Does not obey meta methods, need to find replacement where possible")
|
fun ExecutionContext.ItemDescriptor(data: Any?): ItemDescriptor {
|
||||||
|
if (data is ByteString) {
|
||||||
|
return ItemDescriptor(data.decode(), 1L, JsonObject())
|
||||||
|
} else if (data is Table) {
|
||||||
|
return ItemDescriptor(data)
|
||||||
|
} else {
|
||||||
|
return ItemDescriptor.EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Does not obey meta methods, find replacement where possible")
|
||||||
fun ItemDescriptor(data: Table): ItemDescriptor {
|
fun ItemDescriptor(data: Table): ItemDescriptor {
|
||||||
val name = data[1L] ?: data["name"] ?: data["item"]
|
val name = data[1L] ?: data["name"] ?: data["item"]
|
||||||
val count = data[2L] ?: data["count"] ?: 1L
|
val count = data[2L] ?: data["count"] ?: 1L
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
@ -10,7 +11,7 @@ import ru.dbotthepony.kstarbound.json.builder.JsonIgnore
|
|||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class LiquidDefinition(
|
data class LiquidDefinition(
|
||||||
val name: String,
|
val name: String,
|
||||||
val liquidId: Int,
|
val liquidId: Int?,
|
||||||
val description: String = "...",
|
val description: String = "...",
|
||||||
val tickDelta: Int = 1,
|
val tickDelta: Int = 1,
|
||||||
val color: RGBAColor,
|
val color: RGBAColor,
|
||||||
@ -24,7 +25,7 @@ data class LiquidDefinition(
|
|||||||
val isMeta: Boolean = false,
|
val isMeta: Boolean = false,
|
||||||
) {
|
) {
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class Interaction(val liquid: Int, val liquidResult: Int? = null, val materialResult: String? = null) {
|
data class Interaction(val liquid: Either<Int, String>, val liquidResult: Either<Int, String>? = null, val materialResult: String? = null) {
|
||||||
init {
|
init {
|
||||||
require(liquidResult != null || materialResult != null) { "Both liquidResult and materialResult are missing" }
|
require(liquidResult != null || materialResult != null) { "Both liquidResult and materialResult are missing" }
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import ru.dbotthepony.kstarbound.json.builder.JsonIgnore
|
|||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class TileDefinition(
|
data class TileDefinition(
|
||||||
val materialId: Int,
|
val materialId: Int?,
|
||||||
val materialName: String,
|
val materialName: String,
|
||||||
val particleColor: RGBAColor? = null,
|
val particleColor: RGBAColor? = null,
|
||||||
val itemDrop: String? = null,
|
val itemDrop: String? = null,
|
||||||
@ -54,7 +54,7 @@ data class TileDefinition(
|
|||||||
val blocksLiquidFlow: Boolean = collisionKind.isSolidCollision,
|
val blocksLiquidFlow: Boolean = collisionKind.isSolidCollision,
|
||||||
) : IRenderableTile, IThingWithDescription by descriptionData {
|
) : IRenderableTile, IThingWithDescription by descriptionData {
|
||||||
init {
|
init {
|
||||||
require(materialId > 0) { "Invalid tile ID $materialId" }
|
require(materialId == null || materialId > 0) { "Invalid tile ID $materialId" }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun supportsModifier(modifier: Registry.Entry<TileModifierDefinition>): Boolean {
|
fun supportsModifier(modifier: Registry.Entry<TileModifierDefinition>): Boolean {
|
||||||
|
@ -11,7 +11,7 @@ import ru.dbotthepony.kstarbound.json.builder.JsonIgnore
|
|||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class TileModifierDefinition(
|
data class TileModifierDefinition(
|
||||||
val modId: Int,
|
val modId: Int?,
|
||||||
val modName: String,
|
val modName: String,
|
||||||
val itemDrop: String? = null,
|
val itemDrop: String? = null,
|
||||||
val health: Double? = null,
|
val health: Double? = null,
|
||||||
@ -39,7 +39,7 @@ data class TileModifierDefinition(
|
|||||||
override val renderParameters: RenderParameters
|
override val renderParameters: RenderParameters
|
||||||
) : IRenderableTile, IThingWithDescription by descriptionData {
|
) : IRenderableTile, IThingWithDescription by descriptionData {
|
||||||
init {
|
init {
|
||||||
require(modId > 0) { "Invalid tile modifier ID $modId" }
|
require(modId == null || modId > 0) { "Invalid tile modifier ID $modId" }
|
||||||
}
|
}
|
||||||
|
|
||||||
val actualDamageTable: TileDamageConfig by lazy {
|
val actualDamageTable: TileDamageConfig by lazy {
|
||||||
|
@ -536,6 +536,9 @@ class WorldLayout {
|
|||||||
fun getWeighting(x: Int, y: Int): List<RegionWeighting> {
|
fun getWeighting(x: Int, y: Int): List<RegionWeighting> {
|
||||||
val weighting = ArrayList<RegionWeighting>()
|
val weighting = ArrayList<RegionWeighting>()
|
||||||
|
|
||||||
|
if (layers.isEmpty())
|
||||||
|
return weighting
|
||||||
|
|
||||||
fun addLayerWeighting(layer: Layer, x: Int, weightFactor: Double) {
|
fun addLayerWeighting(layer: Layer, x: Int, weightFactor: Double) {
|
||||||
if (layer.cells.isEmpty())
|
if (layer.cells.isEmpty())
|
||||||
return
|
return
|
||||||
@ -610,6 +613,28 @@ class WorldLayout {
|
|||||||
return weighting
|
return weighting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findLayer(y: Int): Layer? {
|
||||||
|
if (layers.isEmpty())
|
||||||
|
return null
|
||||||
|
|
||||||
|
if (y == layers.first().yStart) {
|
||||||
|
return layers.first()
|
||||||
|
} else if (y < layers.first().yStart) {
|
||||||
|
return null
|
||||||
|
} else if (y >= layers.last().yStart) {
|
||||||
|
return layers.last()
|
||||||
|
} else {
|
||||||
|
return layers[layers.indexOfFirst { it.yStart >= y } - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findLayerAndCell(x: Int, y: Int): Pair<Layer, Region>? {
|
||||||
|
// find the target layer
|
||||||
|
val layer = findLayer(y) ?: return null
|
||||||
|
val cell = layer.findContainingCell(x)
|
||||||
|
return layer to layer.cells[cell.first]
|
||||||
|
}
|
||||||
|
|
||||||
companion object : TypeAdapter<WorldLayout>() {
|
companion object : TypeAdapter<WorldLayout>() {
|
||||||
override fun write(out: JsonWriter, value: WorldLayout?) {
|
override fun write(out: JsonWriter, value: WorldLayout?) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
|
@ -513,6 +513,16 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isSurfaceLayer(x: Int, y: Int): Boolean {
|
||||||
|
val parameters = worldParameters as? TerrestrialWorldParameters ?: return false
|
||||||
|
val layout = worldLayout ?: return false
|
||||||
|
return layout.findLayerAndCell(x, y)?.first == layout.findLayerAndCell(x, parameters.surfaceLayer.layerBaseHeight)?.first
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSurfaceLayer(pos: Vector2i): Boolean {
|
||||||
|
return isSurfaceLayer(pos.x, pos.y)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
@ -25,6 +25,30 @@ interface IContainer {
|
|||||||
return any
|
return any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasCountOfItem(item: ItemDescriptor, exactMatch: Boolean = false): Long {
|
||||||
|
var count = 0L
|
||||||
|
|
||||||
|
for (i in 0 until size) {
|
||||||
|
if (this[i].matches(item, exactMatch)) {
|
||||||
|
count += this[i].size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasCountOfItem(item: ItemStack, exactMatch: Boolean = false): Long {
|
||||||
|
var count = 0L
|
||||||
|
|
||||||
|
for (i in 0 until size) {
|
||||||
|
if (this[i].matches(item, exactMatch)) {
|
||||||
|
count += this[i].size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
// puts item into container, returns remaining not put items
|
// puts item into container, returns remaining not put items
|
||||||
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
|
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
|
||||||
val copy = item.copy()
|
val copy = item.copy()
|
||||||
|
@ -304,7 +304,27 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
|
|||||||
if (isEmpty || other.isEmpty)
|
if (isEmpty || other.isEmpty)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return size != 0L && other.size != 0L && maxStackSize > size && other.config == config && other.parameters == parameters
|
return size != 0L && other.size != 0L && maxStackSize > size && entry == other.entry && other.config == config && other.parameters == parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whenever items match, ignoring their sizes
|
||||||
|
*/
|
||||||
|
fun matches(other: ItemStack, exact: Boolean = false): Boolean {
|
||||||
|
if (isEmpty && other.isEmpty)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return entry == other.entry && (!exact || parameters == other.parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whenever items match, ignoring their sizes
|
||||||
|
*/
|
||||||
|
fun matches(other: ItemDescriptor, exact: Boolean = false): Boolean {
|
||||||
|
if (isEmpty && other.isEmpty)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return entry.name == other.name && (!exact || parameters == other.parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -314,7 +334,7 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
|
|||||||
if (isEmpty)
|
if (isEmpty)
|
||||||
return other.isEmpty
|
return other.isEmpty
|
||||||
|
|
||||||
return other.size == size && other.config == config && other.parameters == parameters
|
return matches(other) && size == other.size
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@ -346,18 +366,6 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toTable(allocator: TableFactory): Table? {
|
|
||||||
if (isEmpty) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return allocator.newTable(0, 3).also {
|
|
||||||
it.rawset("name", entry.name)
|
|
||||||
it.rawset("count", size)
|
|
||||||
it.rawset("parameters", allocator.from(parameters))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
|
@ -8,6 +8,7 @@ import com.google.gson.JsonPrimitive
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
||||||
import org.classdump.luna.ByteString
|
import org.classdump.luna.ByteString
|
||||||
import org.classdump.luna.Conversions
|
import org.classdump.luna.Conversions
|
||||||
|
import org.classdump.luna.LuaRuntimeException
|
||||||
import org.classdump.luna.Table
|
import org.classdump.luna.Table
|
||||||
import org.classdump.luna.TableFactory
|
import org.classdump.luna.TableFactory
|
||||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||||
@ -24,6 +25,7 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.math.Line2d
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
fun ExecutionContext.toVector2i(table: Any): Vector2i {
|
fun ExecutionContext.toVector2i(table: Any): Vector2i {
|
||||||
@ -46,6 +48,12 @@ fun ExecutionContext.toVector2d(table: Any): Vector2d {
|
|||||||
return Vector2d(x.toDouble(), y.toDouble())
|
return Vector2d(x.toDouble(), y.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ExecutionContext.toLine2d(table: Any): Line2d {
|
||||||
|
val p0 = toVector2d(indexNoYield(table, 1L) ?: throw LuaRuntimeException("Invalid line: $table"))
|
||||||
|
val p1 = toVector2d(indexNoYield(table, 2L) ?: throw LuaRuntimeException("Invalid line: $table"))
|
||||||
|
return Line2d(p0, p1)
|
||||||
|
}
|
||||||
|
|
||||||
fun ExecutionContext.toPoly(table: Table): Poly {
|
fun ExecutionContext.toPoly(table: Table): Poly {
|
||||||
val vertices = ArrayList<Vector2d>()
|
val vertices = ArrayList<Vector2d>()
|
||||||
|
|
||||||
@ -303,14 +311,18 @@ fun TableFactory.createJsonArray(): Table {
|
|||||||
return createJsonTable(LUA_HINT_ARRAY, 0, 0).data
|
return createJsonTable(LUA_HINT_ARRAY, 0, 0).data
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: IStruct2d): Table {
|
fun TableFactory.from(value: IStruct2d?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(2, 0).apply {
|
return newTable(2, 0).apply {
|
||||||
this[1L] = value.component1()
|
this[1L] = value.component1()
|
||||||
this[2L] = value.component2()
|
this[2L] = value.component2()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: Poly): Table {
|
fun TableFactory.from(value: Poly?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(value.vertices.size, 0).apply {
|
return newTable(value.vertices.size, 0).apply {
|
||||||
value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) }
|
value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) }
|
||||||
}
|
}
|
||||||
@ -326,14 +338,18 @@ fun TableFactory.fromCollection(value: Collection<JsonElement?>): Table {
|
|||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: IStruct2i): Table {
|
fun TableFactory.from(value: IStruct2i?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(2, 0).also {
|
return newTable(2, 0).also {
|
||||||
it.rawset(1L, value.component1())
|
it.rawset(1L, value.component1())
|
||||||
it.rawset(2L, value.component2())
|
it.rawset(2L, value.component2())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: IStruct3i): Table {
|
fun TableFactory.from(value: IStruct3i?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(3, 0).also {
|
return newTable(3, 0).also {
|
||||||
it.rawset(1L, value.component1())
|
it.rawset(1L, value.component1())
|
||||||
it.rawset(2L, value.component2())
|
it.rawset(2L, value.component2())
|
||||||
@ -341,7 +357,9 @@ fun TableFactory.from(value: IStruct3i): Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: IStruct4i): Table {
|
fun TableFactory.from(value: IStruct4i?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(3, 0).also {
|
return newTable(3, 0).also {
|
||||||
it.rawset(1L, value.component1())
|
it.rawset(1L, value.component1())
|
||||||
it.rawset(2L, value.component2())
|
it.rawset(2L, value.component2())
|
||||||
@ -350,7 +368,9 @@ fun TableFactory.from(value: IStruct4i): Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: RGBAColor): Table {
|
fun TableFactory.from(value: RGBAColor?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(3, 0).also {
|
return newTable(3, 0).also {
|
||||||
it.rawset(1L, value.redInt.toLong())
|
it.rawset(1L, value.redInt.toLong())
|
||||||
it.rawset(2L, value.greenInt.toLong())
|
it.rawset(2L, value.greenInt.toLong())
|
||||||
@ -367,7 +387,9 @@ fun TableFactory.from(value: Collection<Any>): Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TableFactory.from(value: AABB): Table {
|
fun TableFactory.from(value: AABB?): Table? {
|
||||||
|
value ?: return null
|
||||||
|
|
||||||
return newTable(3, 0).also {
|
return newTable(3, 0).also {
|
||||||
it.rawset(1L, value.mins.x)
|
it.rawset(1L, value.mins.x)
|
||||||
it.rawset(2L, value.mins.y)
|
it.rawset(2L, value.mins.y)
|
||||||
|
@ -7,6 +7,7 @@ import org.classdump.luna.Table
|
|||||||
import org.classdump.luna.TableFactory
|
import org.classdump.luna.TableFactory
|
||||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||||
import org.classdump.luna.lib.ArgumentIterator
|
import org.classdump.luna.lib.ArgumentIterator
|
||||||
|
import org.classdump.luna.lib.TableLib
|
||||||
import org.classdump.luna.runtime.AbstractFunction0
|
import org.classdump.luna.runtime.AbstractFunction0
|
||||||
import org.classdump.luna.runtime.AbstractFunction1
|
import org.classdump.luna.runtime.AbstractFunction1
|
||||||
import org.classdump.luna.runtime.AbstractFunction2
|
import org.classdump.luna.runtime.AbstractFunction2
|
||||||
@ -17,6 +18,8 @@ import org.classdump.luna.runtime.Dispatch
|
|||||||
import org.classdump.luna.runtime.ExecutionContext
|
import org.classdump.luna.runtime.ExecutionContext
|
||||||
import org.classdump.luna.runtime.LuaFunction
|
import org.classdump.luna.runtime.LuaFunction
|
||||||
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
fun ExecutionContext.indexNoYield(table: Any, key: Any): Any? {
|
fun ExecutionContext.indexNoYield(table: Any, key: Any): Any? {
|
||||||
return try {
|
return try {
|
||||||
@ -82,6 +85,14 @@ operator fun Table.get(index: Any): Any? = rawget(index)
|
|||||||
operator fun Table.get(index: Long): Any? = rawget(index)
|
operator fun Table.get(index: Long): Any? = rawget(index)
|
||||||
operator fun Table.get(index: Int): Any? = rawget(index.toLong())
|
operator fun Table.get(index: Int): Any? = rawget(index.toLong())
|
||||||
|
|
||||||
|
operator fun Table.contains(index: Any): Boolean {
|
||||||
|
return rawget(index) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun Table.contains(index: Long): Boolean {
|
||||||
|
return rawget(index) != null
|
||||||
|
}
|
||||||
|
|
||||||
operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
||||||
var key: Any? = initialKey() ?: return ObjectIterators.emptyIterator()
|
var key: Any? = initialKey() ?: return ObjectIterators.emptyIterator()
|
||||||
data class Pair(override val key: Any, override val value: Any) : Map.Entry<Any, Any>
|
data class Pair(override val key: Any, override val value: Any) : Map.Entry<Any, Any>
|
||||||
@ -100,6 +111,41 @@ operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to be used in places where we need to "unpack" table, like this:
|
||||||
|
*
|
||||||
|
* ```lua
|
||||||
|
* local array = unpack(tab)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* except this function unpacks using rawget
|
||||||
|
*/
|
||||||
|
fun Table.unpackAsArray(): Array<Any?> {
|
||||||
|
var min = Long.MAX_VALUE
|
||||||
|
var max = 0L
|
||||||
|
|
||||||
|
for ((k, v) in this) {
|
||||||
|
if (k is Long) {
|
||||||
|
max = max(k, max)
|
||||||
|
min = min(k, min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val length = max - min
|
||||||
|
|
||||||
|
if (length <= 0L)
|
||||||
|
return arrayOf()
|
||||||
|
|
||||||
|
val array = arrayOfNulls<Any>(length.toInt())
|
||||||
|
var i2 = 0
|
||||||
|
|
||||||
|
for (i in min .. max) {
|
||||||
|
array[i2++] = this[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
fun TableFactory.tableOf(vararg values: Any?): Table {
|
fun TableFactory.tableOf(vararg values: Any?): Table {
|
||||||
val table = newTable(values.size, 0)
|
val table = newTable(values.size, 0)
|
||||||
|
|
||||||
@ -110,6 +156,10 @@ fun TableFactory.tableOf(vararg values: Any?): Table {
|
|||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun TableFactory.tableOf(): Table {
|
||||||
|
return newTable()
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated("Function is a stub")
|
@Deprecated("Function is a stub")
|
||||||
fun luaStub(message: String = "not yet implemented"): LuaFunction<Any?, Any?, Any?, Any?, Any?> {
|
fun luaStub(message: String = "not yet implemented"): LuaFunction<Any?, Any?, Any?, Any?, Any?> {
|
||||||
return object : LuaFunction<Any?, Any?, Any?, Any?, Any?>() {
|
return object : LuaFunction<Any?, Any?, Any?, Any?, Any?>() {
|
||||||
|
@ -191,10 +191,7 @@ private fun createTreasure(context: ExecutionContext, arguments: ArgumentIterato
|
|||||||
|
|
||||||
val get = Registries.treasurePools[pool] ?: throw LuaRuntimeException("No such treasure pool $pool")
|
val get = Registries.treasurePools[pool] ?: throw LuaRuntimeException("No such treasure pool $pool")
|
||||||
|
|
||||||
val result = get.value.evaluate(seed ?: System.nanoTime(), level)
|
context.returnBuffer.setTo(context.tableOf(*get.value.evaluate(seed ?: System.nanoTime(), level).filter { it.isNotEmpty }.map { context.from(it.toJson()) }.toTypedArray()))
|
||||||
.stream().filter { it.isNotEmpty }.map { it.toTable(context)!! }.toList()
|
|
||||||
|
|
||||||
context.returnBuffer.setTo(context.from(result))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIterator) {
|
private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||||
|
@ -1,24 +1,164 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.bindings
|
package ru.dbotthepony.kstarbound.lua.bindings
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.ByteString
|
import org.classdump.luna.ByteString
|
||||||
|
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 org.classdump.luna.runtime.LuaFunction
|
||||||
import ru.dbotthepony.kommons.collect.map
|
import ru.dbotthepony.kommons.collect.map
|
||||||
import ru.dbotthepony.kommons.collect.toList
|
import ru.dbotthepony.kommons.collect.toList
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||||
|
import ru.dbotthepony.kstarbound.lua.contains
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
import ru.dbotthepony.kstarbound.lua.from
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
import ru.dbotthepony.kstarbound.lua.get
|
||||||
|
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.nextOptionalInteger
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toJson
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||||
|
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.unpackAsArray
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.Line2d
|
||||||
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
|
import ru.dbotthepony.kstarbound.util.GameTimer
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.shuffle
|
||||||
import ru.dbotthepony.kstarbound.util.valueOf
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
|
import ru.dbotthepony.kstarbound.world.RayFilterResult
|
||||||
|
import ru.dbotthepony.kstarbound.world.TileModification
|
||||||
|
import ru.dbotthepony.kstarbound.world.TileRayFilter
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
|
||||||
|
import ru.dbotthepony.kstarbound.world.castRay
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import java.util.Collections
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
private val directionalAngles = intArrayOf(4, 8, 12, 0, 2, 6, 10, 14, 1, 3, 7, 5, 15, 13, 9, 11).let { arr ->
|
||||||
|
Array(arr.size) { Vector2d.angle(arr[it] * PI / 8.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExecutionContext.resolvePolyCollision(self: World<*, *>, originalPoly: Poly, position: Vector2d, maximumCorrection: Double, collisions: Set<CollisionType>) {
|
||||||
|
val poly = originalPoly + position
|
||||||
|
|
||||||
|
data class Entry(val poly: Poly, val center: Vector2d, var distance: Double = 0.0) : Comparable<Entry> {
|
||||||
|
override fun compareTo(other: Entry): Int {
|
||||||
|
return distance.compareTo(other.distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val tiles = ObjectArrayList<Entry>()
|
||||||
|
|
||||||
|
for (tile in self.queryTileCollisions(poly.aabb.enlarge(maximumCorrection + 1.0, maximumCorrection + 1.0))) {
|
||||||
|
if (tile.type in collisions) {
|
||||||
|
tiles.add(Entry(tile.poly, tile.poly.centre))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun separate(loops: Int, axis: Vector2d?): Vector2d? {
|
||||||
|
var body = poly
|
||||||
|
var correction = Vector2d.ZERO
|
||||||
|
|
||||||
|
for (i in 0 until loops) {
|
||||||
|
val center = body.centre
|
||||||
|
|
||||||
|
for (tile in tiles)
|
||||||
|
tile.distance = tile.center.distanceSquared(center)
|
||||||
|
|
||||||
|
tiles.sort()
|
||||||
|
|
||||||
|
var anyIntersects = false
|
||||||
|
|
||||||
|
for (tile in tiles) {
|
||||||
|
val intersection = tile.poly.intersect(body, axis)
|
||||||
|
|
||||||
|
if (intersection != null) {
|
||||||
|
anyIntersects = true
|
||||||
|
body += intersection.vector
|
||||||
|
correction += intersection.vector
|
||||||
|
|
||||||
|
if (correction.length >= maximumCorrection)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyIntersects)
|
||||||
|
return correction
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tile in tiles) {
|
||||||
|
if (tile.poly.intersect(body) != null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return correction
|
||||||
|
}
|
||||||
|
|
||||||
|
var trySeparate = separate(10, null)
|
||||||
|
|
||||||
|
// First try any-directional SAT separation for two loops
|
||||||
|
if (trySeparate != null) {
|
||||||
|
returnBuffer.setTo(from(position + trySeparate))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, try direction-limiting SAT in cardinals, then 45 degs, then in
|
||||||
|
// between, for 16 total angles in a circle.
|
||||||
|
// TODO: if "any direction" separation fails, then this will surely fail too?
|
||||||
|
for (axis in directionalAngles) {
|
||||||
|
trySeparate = separate(4, axis)
|
||||||
|
|
||||||
|
if (trySeparate != null) {
|
||||||
|
returnBuffer.setTo(from(position + trySeparate))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExecutionContext.returnLiquid(liquid: AbstractLiquidState, returnNames: Boolean?) {
|
||||||
|
if (returnNames == true) {
|
||||||
|
returnBuffer.setTo(tableOf(liquid.state.key, liquid.level))
|
||||||
|
} else if (liquid.state.id != null) {
|
||||||
|
returnBuffer.setTo(tableOf(liquid.state.id, liquid.level))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||||
val callbacks = lua.newTable()
|
val callbacks = lua.newTable()
|
||||||
@ -74,4 +214,379 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
|||||||
returnBuffer.setTo(self.collide(toPoly(rect), Predicate { it.type in actualCollisions }).findAny().isPresent)
|
returnBuffer.setTo(self.collide(toPoly(rect), Predicate { it.type in actualCollisions }).findAny().isPresent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbacks["pointCollision"] = luaFunction { rect: Table, collisions: Table? ->
|
||||||
|
if (collisions == null) {
|
||||||
|
returnBuffer.setTo(self.collide(toVector2d(rect), Predicate { it.type.isSolidCollision }))
|
||||||
|
} else {
|
||||||
|
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
returnBuffer.setTo(self.collide(toVector2d(rect), Predicate { it.type in actualCollisions }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["pointTileCollision"] = luaFunction { rect: Table, collisions: Table? ->
|
||||||
|
val cell = self.getCell(toVector2i(rect))
|
||||||
|
|
||||||
|
if (collisions == null) {
|
||||||
|
returnBuffer.setTo(cell.foreground.material.value.collisionKind.isSolidCollision)
|
||||||
|
} else {
|
||||||
|
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
returnBuffer.setTo(cell.foreground.material.value.collisionKind in actualCollisions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["lineTileCollision"] = luaFunction { pos0: Table, pos1: Table, collisions: Table? ->
|
||||||
|
if (collisions == null) {
|
||||||
|
returnBuffer.setTo(self.castRay(toVector2d(pos0), toVector2d(pos1), TileRayFilter { cell, fraction, x, y, normal, borderX, borderY -> if (cell.foreground.material.value.collisionKind.isSolidCollision) RayFilterResult.HIT else RayFilterResult.SKIP }).traversedTiles.isNotEmpty())
|
||||||
|
} else {
|
||||||
|
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
returnBuffer.setTo(self.castRay(toVector2d(pos0), toVector2d(pos1), TileRayFilter { cell, fraction, x, y, normal, borderX, borderY -> if (cell.foreground.material.value.collisionKind in actualCollisions) RayFilterResult.HIT else RayFilterResult.SKIP }).traversedTiles.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["lineTileCollisionPoint"] = luaFunction { pos0: Table, pos1: Table, collisions: Table? ->
|
||||||
|
val result = if (collisions == null) {
|
||||||
|
self.castRay(toVector2d(pos0), toVector2d(pos1), TileRayFilter { cell, fraction, x, y, normal, borderX, borderY -> if (cell.foreground.material.value.collisionKind.isSolidCollision) RayFilterResult.HIT else RayFilterResult.SKIP })
|
||||||
|
} else {
|
||||||
|
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
self.castRay(toVector2d(pos0), toVector2d(pos1), TileRayFilter { cell, fraction, x, y, normal, borderX, borderY -> if (cell.foreground.material.value.collisionKind in actualCollisions) RayFilterResult.HIT else RayFilterResult.SKIP })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.hitTile == null) {
|
||||||
|
returnBuffer.setTo()
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(tableOf(from(result.hitTile.borderCross), from(result.hitTile.normal.normal)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["rectTileCollision"] = luaFunction { rect: Table, collisions: Table? ->
|
||||||
|
if (collisions == null) {
|
||||||
|
returnBuffer.setTo(self.anyCellSatisfies(toAABB(rect), World.CellPredicate { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision }))
|
||||||
|
} else {
|
||||||
|
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
returnBuffer.setTo(self.anyCellSatisfies(toAABB(rect), World.CellPredicate { x, y, cell -> cell.foreground.material.value.collisionKind in actualCollisions }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["lineCollision"] = luaFunction { pos0: Table, pos1: Table, collisions: Table? ->
|
||||||
|
val actualCollisions = if (collisions == null) CollisionType.SOLID else EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
|
||||||
|
val result = self.collide(Line2d(toVector2d(pos0), toVector2d(pos1))) { it.type in actualCollisions }
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
returnBuffer.setTo()
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(from(result.border), from(result.normal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["polyCollision"] = luaFunction { rect: Table, translate: Table?, collisions: Table? ->
|
||||||
|
if (collisions == null) {
|
||||||
|
returnBuffer.setTo(self.collide(toPoly(rect).let { if (translate != null) it + toVector2d(translate) else it }, Predicate { it.type.isSolidCollision }).findAny().isPresent)
|
||||||
|
} else {
|
||||||
|
val actualCollisions = EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
returnBuffer.setTo(self.collide(toPoly(rect).let { if (translate != null) it + toVector2d(translate) else it }, Predicate { it.type in actualCollisions }).findAny().isPresent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["collisionBlocksAlongLine"] = luaFunction { pos0: Table, pos1: Table, collisions: Table?, limit: Number? ->
|
||||||
|
val actualCollisions = if (collisions == null) CollisionType.SOLID else EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
var actualLimit = limit?.toInt() ?: Int.MAX_VALUE
|
||||||
|
|
||||||
|
if (actualLimit < 0)
|
||||||
|
actualLimit = Int.MAX_VALUE
|
||||||
|
|
||||||
|
val result = self.castRay(toVector2d(pos0), toVector2d(pos1)) { cell, fraction, x, y, normal, borderX, borderY ->
|
||||||
|
if (cell.foreground.material.value.collisionKind in actualCollisions) {
|
||||||
|
val tlimit = --actualLimit
|
||||||
|
|
||||||
|
if (tlimit > 0) {
|
||||||
|
RayFilterResult.CONTINUE
|
||||||
|
} else if (tlimit == 0) {
|
||||||
|
RayFilterResult.HIT
|
||||||
|
} else {
|
||||||
|
RayFilterResult.BREAK
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RayFilterResult.SKIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(tableOf(*result.traversedTiles.map { from(it.pos) }.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["liquidAlongLine"] = luaFunction { pos0: Table, pos1: Table ->
|
||||||
|
val liquid = newTable()
|
||||||
|
var i = 1L
|
||||||
|
|
||||||
|
self.castRay(toVector2d(pos0), toVector2d(pos1)) { cell, fraction, x, y, normal, borderX, borderY ->
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.level > 0f && cell.liquid.state.id != null) {
|
||||||
|
liquid[i++] = tableOf(tableOf(x, y), tableOf(cell.liquid.state.id, cell.liquid.level.toDouble()))
|
||||||
|
}
|
||||||
|
|
||||||
|
RayFilterResult.SKIP
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(liquid)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["liquidNamesAlongLine"] = luaFunction { pos0: Table, pos1: Table ->
|
||||||
|
val liquid = newTable()
|
||||||
|
var i = 1L
|
||||||
|
|
||||||
|
self.castRay(toVector2d(pos0), toVector2d(pos1)) { cell, fraction, x, y, normal, borderX, borderY ->
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.level > 0f) {
|
||||||
|
liquid[i++] = tableOf(tableOf(x, y), tableOf(cell.liquid.state.key, cell.liquid.level.toDouble()))
|
||||||
|
}
|
||||||
|
|
||||||
|
RayFilterResult.SKIP
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(liquid)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["resolvePolyCollision"] = luaFunction { poly: Table, position: Table, maximumCorrection: Number, collisions: Table? ->
|
||||||
|
val actualCollisions = if (collisions == null) CollisionType.SOLID else EnumSet.copyOf(collisions.iterator().map { CollisionType.entries.valueOf((it.value as ByteString).decode()) }.toList())
|
||||||
|
resolvePolyCollision(self, toPoly(poly), toVector2d(position), maximumCorrection.toDouble(), actualCollisions)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["tileIsOccupied"] = luaFunction { pos: Table, isForeground: Boolean?, includeEmphemeral: Boolean? ->
|
||||||
|
val cell = self.getCell(toVector2i(pos))
|
||||||
|
|
||||||
|
if (cell.tile(isForeground == false).material.isNotEmptyTile) {
|
||||||
|
returnBuffer.setTo(true)
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(self.entityIndex.tileEntitiesAt(toVector2i(pos)).any { !it.isEphemeral || includeEmphemeral == true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["placeObject"] = luaFunction { type: ByteString, pos0: Table, objectDirection: Number?, parameters: Table? ->
|
||||||
|
val pos = toVector2i(pos0)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val prototype = Registries.worldObjects[type.decode()] ?: throw LuaRuntimeException("No such object $type")
|
||||||
|
var direction = Direction.RIGHT
|
||||||
|
|
||||||
|
if (objectDirection != null && objectDirection.toLong() < 0L)
|
||||||
|
direction = Direction.LEFT
|
||||||
|
|
||||||
|
val json = if (parameters == null) JsonObject() else parameters.toJson(true) as JsonObject
|
||||||
|
val orientation = prototype.value.findValidOrientation(self, pos, direction)
|
||||||
|
|
||||||
|
if (orientation == -1) {
|
||||||
|
LOGGER.debug("Lua script tried to place object {} at {}, but it can't be placed there!", prototype.key, pos)
|
||||||
|
returnBuffer.setTo(false)
|
||||||
|
} else {
|
||||||
|
val create = WorldObject.create(prototype, pos, json)
|
||||||
|
create?.orientationIndex = orientation.toLong()
|
||||||
|
create?.joinWorld(self)
|
||||||
|
returnBuffer.setTo(create != null)
|
||||||
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Exception while placing world object $type at $pos", err)
|
||||||
|
returnBuffer.setTo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["spawnItem"] = luaFunctionN("spawnItem") {
|
||||||
|
val itemType = toJsonFromLua(it.nextAny())
|
||||||
|
val pos = toVector2d(it.nextTable())
|
||||||
|
val inputCount = it.nextOptionalInteger() ?: 1L
|
||||||
|
val inputParameters = toJsonFromLua(it.nextOptionalAny(null))
|
||||||
|
val initialVelocity = toVector2d(it.nextOptionalAny(tableOf(0L, 0L)))
|
||||||
|
val intangibleTime = it.nextOptionalAny(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val descriptor: ItemDescriptor
|
||||||
|
|
||||||
|
if (itemType is JsonObject) {
|
||||||
|
descriptor = ItemDescriptor(itemType)
|
||||||
|
} else {
|
||||||
|
descriptor = ItemDescriptor(itemType.asString, inputCount, if (inputParameters.isJsonNull) JsonObject() else inputParameters.asJsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descriptor.isEmpty) {
|
||||||
|
LOGGER.debug("Lua script tried to create non existing item {} at {}", itemType, pos)
|
||||||
|
returnBuffer.setTo()
|
||||||
|
} else {
|
||||||
|
val create = ItemDropEntity(descriptor)
|
||||||
|
create.movement.velocity = initialVelocity
|
||||||
|
|
||||||
|
if (intangibleTime is Number) {
|
||||||
|
create.intangibleTimer = GameTimer(intangibleTime.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
create.joinWorld(self)
|
||||||
|
returnBuffer.setTo(create.entityID)
|
||||||
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Exception while creating item $itemType at $pos", err)
|
||||||
|
returnBuffer.setTo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["spawnTreasure"] = luaFunction { position: Table, pool: ByteString, level: Number, seed: Number? ->
|
||||||
|
val entities = IntArrayList()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val items = Registries.treasurePools
|
||||||
|
.getOrThrow(pool.decode())
|
||||||
|
.value
|
||||||
|
// not using lua.random because we are, well, world's bindings
|
||||||
|
.evaluate(if (seed != null) random(seed.toLong()) else self.random, level.toDouble())
|
||||||
|
|
||||||
|
val pos = toVector2d(position)
|
||||||
|
|
||||||
|
for (item in items) {
|
||||||
|
val entity = ItemDropEntity(item)
|
||||||
|
entity.position = pos
|
||||||
|
entity.joinWorld(self)
|
||||||
|
entities.add(entity.entityID)
|
||||||
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Exception while spawning treasure from $pool at $position", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(tableOf(*entities.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["spawnMonster"] = luaStub("spawnMonster")
|
||||||
|
callbacks["spawnNpc"] = luaStub("spawnNpc")
|
||||||
|
callbacks["spawnStagehand"] = luaStub("spawnStagehand")
|
||||||
|
callbacks["spawnProjectile"] = luaStub("spawnProjectile")
|
||||||
|
callbacks["spawnVehicle"] = luaStub("spawnVehicle")
|
||||||
|
|
||||||
|
callbacks["threatLevel"] = luaFunction { returnBuffer.setTo(self.template.threatLevel) }
|
||||||
|
callbacks["time"] = luaFunction { returnBuffer.setTo(self.sky.time) }
|
||||||
|
callbacks["day"] = luaFunction { returnBuffer.setTo(self.sky.day) }
|
||||||
|
callbacks["timeOfDay"] = luaFunction { returnBuffer.setTo(self.sky.timeOfDay) }
|
||||||
|
callbacks["dayLength"] = luaFunction { returnBuffer.setTo(self.sky.dayLength) }
|
||||||
|
|
||||||
|
callbacks["getProperty"] = luaFunction { name: ByteString, orElse: Any? ->
|
||||||
|
returnBuffer.setTo(from(self.getProperty(name.decode()) { toJsonFromLua(orElse) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["setProperty"] = luaFunction { name: ByteString, value: Any? ->
|
||||||
|
self.setProperty(name.decode(), toJsonFromLua(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["liquidAt"] = luaFunction { posOrRect: Table ->
|
||||||
|
if (posOrRect[1L] is Number) {
|
||||||
|
val cell = self.getCell(toVector2i(posOrRect))
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid) {
|
||||||
|
returnLiquid(cell.liquid, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val level = self.averageLiquidLevel(toAABB(posOrRect))
|
||||||
|
|
||||||
|
if (level != null && level.type.id != null) {
|
||||||
|
returnBuffer.setTo(tableOf(level.type.id, level.average))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["liquidNameAt"] = luaFunction { posOrRect: Table ->
|
||||||
|
if (posOrRect[1L] is Number) {
|
||||||
|
val cell = self.getCell(toVector2i(posOrRect))
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid) {
|
||||||
|
returnLiquid(cell.liquid, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val level = self.averageLiquidLevel(toAABB(posOrRect))
|
||||||
|
|
||||||
|
if (level != null) {
|
||||||
|
returnBuffer.setTo(tableOf(level.type.key, level.average))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["gravity"] = luaFunction { pos: Table ->
|
||||||
|
returnBuffer.setTo(self.gravityAt(toVector2d(pos)).y)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["gravityVector"] = luaFunction { pos: Table ->
|
||||||
|
returnBuffer.setTo(from(self.gravityAt(toVector2d(pos))))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["spawnLiquid"] = luaFunction { pos: Table, liquid: Any, quantity: Number ->
|
||||||
|
val action = TileModification.Pour(if (liquid is ByteString) Registries.liquid.ref(liquid.decode()) else Registries.liquid.ref((liquid as Number).toInt()), quantity.toFloat())
|
||||||
|
returnBuffer.setTo(self.applyTileModifications(listOf(toVector2i(pos) to action), false).isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["destroyLiquid"] = luaFunction { pos: Table ->
|
||||||
|
val action = TileModification.Pour(BuiltinMetaMaterials.NO_LIQUID.ref, 0f)
|
||||||
|
val cell = self.getCell(toVector2i(pos))
|
||||||
|
self.applyTileModifications(listOf(toVector2i(pos) to action), false)
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid)
|
||||||
|
returnLiquid(cell.liquid, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["destroyNamedLiquid"] = luaFunction { pos: Table ->
|
||||||
|
val action = TileModification.Pour(BuiltinMetaMaterials.NO_LIQUID.ref, 0f)
|
||||||
|
val cell = self.getCell(toVector2i(pos))
|
||||||
|
self.applyTileModifications(listOf(toVector2i(pos) to action), false)
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid)
|
||||||
|
returnLiquid(cell.liquid, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["isTileProtected"] = luaFunction { pos: Table -> returnBuffer.setTo(self.getCell(toVector2i(pos)).dungeonId in self.protectedDungeonIDs) }
|
||||||
|
|
||||||
|
callbacks["findPlatformerPath"] = luaStub("findPlatformerPath")
|
||||||
|
callbacks["platformerPathStart"] = luaStub("platformerPathStart")
|
||||||
|
|
||||||
|
callbacks["type"] = luaFunction { returnBuffer.setTo(self.template.worldParameters?.typeName ?: "unknown") }
|
||||||
|
callbacks["size"] = luaFunction { returnBuffer.setTo(from(self.geometry.size)) }
|
||||||
|
callbacks["inSurfaceLayer"] = luaFunction { pos: Table -> returnBuffer.setTo(self.template.isSurfaceLayer(toVector2i(pos))) }
|
||||||
|
callbacks["surfaceLevel"] = luaFunction { returnBuffer.setTo(self.template.surfaceLevel()) }
|
||||||
|
callbacks["terrestrial"] = luaFunction { returnBuffer.setTo(self.template.worldParameters is TerrestrialWorldParameters) }
|
||||||
|
|
||||||
|
callbacks["itemDropItem"] = luaFunction { id: Number ->
|
||||||
|
returnBuffer.setTo(from((self.entities[id.toInt()] as? ItemDropEntity)?.item?.toJson()))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["biomeBlocksAt"] = luaFunction { pos: Table, returnNames: Boolean? ->
|
||||||
|
val info = self.template.cellInfo(toVector2i(pos))
|
||||||
|
val blocks = tableOf()
|
||||||
|
var i = 1L
|
||||||
|
|
||||||
|
val biome = info.blockBiome
|
||||||
|
|
||||||
|
if (biome != null) {
|
||||||
|
biome.mainBlock.native.entry?.id?.let { blocks[i++] = it }
|
||||||
|
biome.subBlocks.forEach { it.native.entry?.id?.let { blocks[i++] = it } }
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["biomeBlockNamesAt"] = luaFunction { pos: Table, returnNames: Boolean? ->
|
||||||
|
val info = self.template.cellInfo(toVector2i(pos))
|
||||||
|
val blocks = tableOf()
|
||||||
|
var i = 1L
|
||||||
|
|
||||||
|
val biome = info.blockBiome
|
||||||
|
|
||||||
|
if (biome != null) {
|
||||||
|
biome.mainBlock.native.entry?.key?.let { blocks[i++] = it }
|
||||||
|
biome.subBlocks.forEach { it.native.entry?.key?.let { blocks[i++] = it } }
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["dungeonId"] = luaFunction { pos: Table -> returnBuffer.setTo(self.getCell(toVector2i(pos)).dungeonId) }
|
||||||
|
|
||||||
|
provideWorldEntitiesBindings(self, callbacks, lua)
|
||||||
|
|
||||||
|
if (self is ServerWorld) {
|
||||||
|
provideServerWorldBindings(self, callbacks, lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvironment) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,359 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua.bindings
|
||||||
|
|
||||||
|
import org.classdump.luna.ByteString
|
||||||
|
import org.classdump.luna.LuaRuntimeException
|
||||||
|
import org.classdump.luna.Table
|
||||||
|
import org.classdump.luna.runtime.ExecutionContext
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
|
import ru.dbotthepony.kstarbound.defs.`object`.LoungeOrientation
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||||
|
import ru.dbotthepony.kstarbound.lua.from
|
||||||
|
import ru.dbotthepony.kstarbound.lua.get
|
||||||
|
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
||||||
|
import ru.dbotthepony.kstarbound.lua.iterator
|
||||||
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
|
import ru.dbotthepony.kstarbound.lua.luaStub
|
||||||
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toLine2d
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toPoly
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||||
|
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||||
|
import ru.dbotthepony.kstarbound.lua.unpackAsArray
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.shuffle
|
||||||
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.ActorEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.InspectableEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.tile.LoungeableObject
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Predicate
|
||||||
|
|
||||||
|
private enum class EntityBoundMode(override val jsonName: String) : IStringSerializable {
|
||||||
|
META_BOUNDING_BOX("MetaBoundBox"),
|
||||||
|
COLLISION_AREA("CollisionArea"),
|
||||||
|
POSITION("Position")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExecutionContext.entityQueryImpl(self: World<*, *>, options: Table, predicate: Predicate<AbstractEntity> = Predicate { true }): Table {
|
||||||
|
val withoutEntityId = (indexNoYield(options, "withoutEntityId") as Number?)?.toInt()
|
||||||
|
|
||||||
|
val includedTypes = EnumSet.allOf(EntityType::class.java)
|
||||||
|
val getIncludedTypes = indexNoYield(options, "includedTypes") as Table?
|
||||||
|
|
||||||
|
if (getIncludedTypes != null) {
|
||||||
|
includedTypes.clear()
|
||||||
|
|
||||||
|
for ((_, v) in getIncludedTypes) {
|
||||||
|
when (val t = (v as ByteString).decode()) {
|
||||||
|
"mobile" -> {
|
||||||
|
includedTypes.add(EntityType.PLAYER)
|
||||||
|
includedTypes.add(EntityType.MONSTER)
|
||||||
|
includedTypes.add(EntityType.NPC)
|
||||||
|
includedTypes.add(EntityType.PROJECTILE)
|
||||||
|
includedTypes.add(EntityType.ITEM_DROP)
|
||||||
|
includedTypes.add(EntityType.VEHICLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
"creature" -> {
|
||||||
|
includedTypes.add(EntityType.PLAYER)
|
||||||
|
includedTypes.add(EntityType.MONSTER)
|
||||||
|
includedTypes.add(EntityType.NPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
includedTypes.add(EntityType.entries.valueOf(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val callScript = (indexNoYield(options, "callScript") as ByteString?)?.decode()
|
||||||
|
val callScriptArgs = (indexNoYield(options, "callScriptArgs") as Table?)?.unpackAsArray() ?: arrayOf()
|
||||||
|
val callScriptResult = indexNoYield(options, "callScriptResult") ?: true
|
||||||
|
|
||||||
|
val lineQuery = (indexNoYield(options, "line") as Table?)?.let { toLine2d(it) }
|
||||||
|
val polyQuery = (indexNoYield(options, "poly") as Table?)?.let { toPoly(it) }
|
||||||
|
val rectQuery = (indexNoYield(options, "rect") as Table?)?.let { toAABB(it) }
|
||||||
|
|
||||||
|
val radius = indexNoYield(options, "radius")
|
||||||
|
|
||||||
|
val radiusQuery = if (radius is Number) {
|
||||||
|
val center = toVector2d(indexNoYield(options, "center") ?: throw LuaRuntimeException("Specified 'radius', but not 'center'"))
|
||||||
|
center to radius.toDouble()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val boundMode = EntityBoundMode.entries.valueOf((indexNoYield(options, "boundMode") as ByteString?)?.decode() ?: "CollisionArea")
|
||||||
|
|
||||||
|
val innerPredicate = Predicate<AbstractEntity> {
|
||||||
|
if (!predicate.test(it)) return@Predicate false
|
||||||
|
if (it.type !in includedTypes) return@Predicate false
|
||||||
|
if (it.entityID == withoutEntityId) return@Predicate false
|
||||||
|
|
||||||
|
if (callScript != null) {
|
||||||
|
if (it !is ScriptedEntity || it.isRemote) return@Predicate false
|
||||||
|
val call = it.callScript(callScript, *callScriptArgs)
|
||||||
|
if (call.isEmpty() || call[0] != callScriptResult) return@Predicate false
|
||||||
|
}
|
||||||
|
|
||||||
|
when (boundMode) {
|
||||||
|
EntityBoundMode.META_BOUNDING_BOX -> {
|
||||||
|
// If using MetaBoundBox, the regular line / box query methods already
|
||||||
|
// enforce collision with MetaBoundBox
|
||||||
|
|
||||||
|
if (radiusQuery != null) {
|
||||||
|
return@Predicate self.geometry.rectIntersectsCircle(it.metaBoundingBox, radiusQuery.first, radiusQuery.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityBoundMode.COLLISION_AREA -> {
|
||||||
|
// Collision area queries either query based on the collision area if
|
||||||
|
// that's given, or as a fallback the regular bound box.
|
||||||
|
|
||||||
|
var collisionArea = it.collisionArea
|
||||||
|
|
||||||
|
if (collisionArea.isEmpty)
|
||||||
|
collisionArea = it.metaBoundingBox
|
||||||
|
|
||||||
|
if (lineQuery != null)
|
||||||
|
return@Predicate self.geometry.lineIntersectsRect(lineQuery, collisionArea)
|
||||||
|
|
||||||
|
if (polyQuery != null)
|
||||||
|
return@Predicate self.geometry.polyIntersectsPoly(polyQuery, Poly(collisionArea))
|
||||||
|
|
||||||
|
if (rectQuery != null)
|
||||||
|
return@Predicate self.geometry.rectIntersectsRect(rectQuery, collisionArea)
|
||||||
|
|
||||||
|
if (radiusQuery != null)
|
||||||
|
return@Predicate self.geometry.rectIntersectsCircle(collisionArea, radiusQuery.first, radiusQuery.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityBoundMode.POSITION -> {
|
||||||
|
if (lineQuery != null)
|
||||||
|
return@Predicate self.geometry.lineIntersectsRect(lineQuery, AABB.rectangle(it.position, 0.0))
|
||||||
|
|
||||||
|
if (polyQuery != null)
|
||||||
|
return@Predicate self.geometry.polyContains(polyQuery, it.position)
|
||||||
|
|
||||||
|
if (rectQuery != null)
|
||||||
|
return@Predicate self.geometry.rectContains(rectQuery, it.position)
|
||||||
|
|
||||||
|
if (radiusQuery != null)
|
||||||
|
return@Predicate self.geometry.diff(radiusQuery.first, it.position).length <= radiusQuery.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val entitites = if (lineQuery != null) {
|
||||||
|
// TODO: this is wildly inefficient
|
||||||
|
self.entityIndex.query(AABB(lineQuery), innerPredicate)
|
||||||
|
} else if (polyQuery != null) {
|
||||||
|
self.entityIndex.query(polyQuery.aabb, innerPredicate)
|
||||||
|
} else if (rectQuery != null) {
|
||||||
|
self.entityIndex.query(rectQuery, innerPredicate)
|
||||||
|
} else if (radiusQuery != null) {
|
||||||
|
self.entityIndex.query(AABB.withSide(radiusQuery.first, radiusQuery.second), innerPredicate)
|
||||||
|
} else {
|
||||||
|
mutableListOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val order = (indexNoYield(options, "order") as ByteString?)?.decode()?.lowercase()) {
|
||||||
|
null -> {} // do nothing
|
||||||
|
"random" -> entitites.shuffle(self.random)
|
||||||
|
"nearest" -> {
|
||||||
|
val nearestPosition = lineQuery?.p0 ?: polyQuery?.centre ?: rectQuery?.centre ?: radiusQuery?.first ?: Vector2d.ZERO
|
||||||
|
|
||||||
|
entitites.sortWith { o1, o2 ->
|
||||||
|
self.geometry.diff(o1.position, nearestPosition).lengthSquared.compareTo(self.geometry.diff(o2.position, nearestPosition).lengthSquared) }
|
||||||
|
}
|
||||||
|
else -> throw LuaRuntimeException("Unknown entity sort order $order!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableOf(*entitites.map { it.entityID.toLong() }.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExecutionContext.intermediateQueryFunction(self: World<*, *>, pos1: Table, pos2OrRadius: Any, options: Table?, predicate: Predicate<AbstractEntity>) {
|
||||||
|
val actualOptions = options ?: tableOf()
|
||||||
|
|
||||||
|
if (pos2OrRadius is Number) {
|
||||||
|
actualOptions["center"] = pos1
|
||||||
|
actualOptions["radius"] = pos2OrRadius
|
||||||
|
} else {
|
||||||
|
pos2OrRadius as Table
|
||||||
|
actualOptions["rect"] = tableOf(pos1[1L], pos1[2L], pos2OrRadius[1L], pos2OrRadius[2L])
|
||||||
|
}
|
||||||
|
|
||||||
|
returnBuffer.setTo(entityQueryImpl(self, actualOptions, predicate))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExecutionContext.intermediateLineQueryFunction(self: World<*, *>, pos1: Table, pos2: Table, options: Table?, predicate: Predicate<AbstractEntity>) {
|
||||||
|
val actualOptions = options ?: tableOf()
|
||||||
|
actualOptions["line"] = tableOf(pos1, pos2)
|
||||||
|
returnBuffer.setTo(entityQueryImpl(self, actualOptions, predicate))
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : AbstractEntity> createQueryFunction(self: World<*, *>) = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
|
||||||
|
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate { it is T })
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : AbstractEntity> createLineQueryFunction(self: World<*, *>) = luaFunction { pos1: Table, pos2: Table, options: Table? ->
|
||||||
|
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate { it is T })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) {
|
||||||
|
callbacks["entityQuery"] = createQueryFunction<AbstractEntity>(self)
|
||||||
|
callbacks["monsterQuery"] = createQueryFunction<AbstractEntity>(self) // TODO
|
||||||
|
callbacks["npcQuery"] = createQueryFunction<AbstractEntity>(self) // TODO
|
||||||
|
callbacks["itemDropQuery"] = createQueryFunction<ItemDropEntity>(self)
|
||||||
|
callbacks["playerQuery"] = createQueryFunction<PlayerEntity>(self)
|
||||||
|
|
||||||
|
callbacks["entityLineQuery"] = createLineQueryFunction<AbstractEntity>(self)
|
||||||
|
callbacks["monsterLineQuery"] = createLineQueryFunction<AbstractEntity>(self) // TODO
|
||||||
|
callbacks["npcLineQuery"] = createLineQueryFunction<AbstractEntity>(self) // TODO
|
||||||
|
callbacks["itemDropLineQuery"] = createLineQueryFunction<ItemDropEntity>(self)
|
||||||
|
callbacks["playerLineQuery"] = createLineQueryFunction<PlayerEntity>(self)
|
||||||
|
|
||||||
|
callbacks["objectQuery"] = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
|
||||||
|
var objectName: String? = null
|
||||||
|
|
||||||
|
if (options != null)
|
||||||
|
objectName = (indexNoYield(options, "name") as ByteString?)?.decode()
|
||||||
|
|
||||||
|
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate {
|
||||||
|
it is WorldObject && (objectName == null || it.config.key == objectName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["objectLineQuery"] = luaFunction { pos1: Table, pos2: Table, options: Table? ->
|
||||||
|
var objectName: String? = null
|
||||||
|
|
||||||
|
if (options != null)
|
||||||
|
objectName = (indexNoYield(options, "name") as ByteString?)?.decode()
|
||||||
|
|
||||||
|
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate {
|
||||||
|
it is WorldObject && (objectName == null || it.config.key == objectName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["loungeableQuery"] = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
|
||||||
|
var orientationName: String? = null
|
||||||
|
|
||||||
|
if (options != null)
|
||||||
|
orientationName = (indexNoYield(options, "orientation") as ByteString?)?.decode()
|
||||||
|
|
||||||
|
val orientation = when (orientationName) {
|
||||||
|
null -> LoungeOrientation.NONE
|
||||||
|
else -> LoungeOrientation.entries.valueOf(orientationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate {
|
||||||
|
it is LoungeableObject && (orientation == LoungeOrientation.NONE || it.sitOrientation == orientation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["loungeableLineQuery"] = luaFunction { pos1: Table, pos2: Table, options: Table? ->
|
||||||
|
var orientationName: String? = null
|
||||||
|
|
||||||
|
if (options != null)
|
||||||
|
orientationName = (indexNoYield(options, "orientation") as ByteString?)?.decode()
|
||||||
|
|
||||||
|
val orientation = when (orientationName) {
|
||||||
|
null -> LoungeOrientation.NONE
|
||||||
|
else -> LoungeOrientation.entries.valueOf(orientationName!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate {
|
||||||
|
it is LoungeableObject && (orientation == LoungeOrientation.NONE || it.sitOrientation == orientation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["objectAt"] = luaFunction { pos: Table ->
|
||||||
|
returnBuffer.setTo(self.entityIndex.tileEntityAt(toVector2i(pos))?.entityID)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityExists"] = luaFunction { id: Number ->
|
||||||
|
returnBuffer.setTo(id.toInt() in self.entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityCanDamage"] = luaFunction { source: Number, target: Number ->
|
||||||
|
val a = self.entities[source.toInt()]
|
||||||
|
val b = self.entities[target.toInt()]
|
||||||
|
|
||||||
|
returnBuffer.setTo(a != null && b != null && a.team.get().canDamage(b.team.get(), a == b))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityDamageTeam"] = luaFunction { id: Number ->
|
||||||
|
returnBuffer.setTo(from(Starbound.gson.toJsonTree(self.entities[id]?.team?.get())))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityAggressive"] = luaStub("entityAggressive")
|
||||||
|
|
||||||
|
callbacks["entityType"] = luaFunction { id: Number -> returnBuffer.setTo(self.entities[id.toInt()]?.type?.jsonName) }
|
||||||
|
callbacks["entityPosition"] = luaFunction { id: Number -> returnBuffer.setTo(from(self.entities[id.toInt()]?.position)) }
|
||||||
|
callbacks["entityVelocity"] = luaFunction { id: Number -> returnBuffer.setTo(from((self.entities[id.toInt()] as? DynamicEntity)?.movement?.velocity)) }
|
||||||
|
callbacks["entityMetaBoundBox"] = luaFunction { id: Number -> returnBuffer.setTo(from(self.entities[id.toInt()]?.metaBoundingBox)) }
|
||||||
|
callbacks["entityCurrency"] = luaFunction { id: Number, currency: ByteString -> returnBuffer.setTo((self.entities[id.toInt()] as? PlayerEntity)?.inventory?.currencies?.get(currency.decode())) }
|
||||||
|
callbacks["entityHasCountOfItem"] = luaFunction { id: Number, descriptor: Any, exact: Boolean? ->
|
||||||
|
val player = self.entities[id.toInt()] as? PlayerEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(player.inventory.hasCountOfItem(ItemDescriptor(descriptor), exact ?: false))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityHealth"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(tableOf(entity.health, entity.maxHealth))
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entitySpecies"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(entity.species)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityGender"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] as? HumanoidActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
returnBuffer.setTo(entity.gender.jsonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityName"] = luaFunction { id: Number ->
|
||||||
|
val entity = self.entities[id.toInt()] ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
when (entity) {
|
||||||
|
is ActorEntity -> returnBuffer.setTo(entity.name)
|
||||||
|
is WorldObject -> returnBuffer.setTo(entity.config.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityDescription"] = luaFunction { id: Number, species: ByteString? ->
|
||||||
|
val entity = self.entities[id.toInt()] ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
if (entity is InspectableEntity) {
|
||||||
|
returnBuffer.setTo(entity.inspectionDescription(species?.decode()))
|
||||||
|
} else {
|
||||||
|
returnBuffer.setTo(entity.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks["entityPortrait"] = luaFunction { id: Number, mode: ByteString ->
|
||||||
|
val entity = self.entities[id.toInt()] as? ActorEntity ?: return@luaFunction returnBuffer.setTo()
|
||||||
|
|
||||||
|
returnBuffer.setTo(tableOf(*entity.portrait(ActorEntity.PortraitMode.entries.valueOf(mode.decode())).map { from(it.toJson()) }.toTypedArray()))
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ 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.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
import ru.dbotthepony.kstarbound.lua.toColor
|
import ru.dbotthepony.kstarbound.lua.toColor
|
||||||
import ru.dbotthepony.kstarbound.lua.toJson
|
import ru.dbotthepony.kstarbound.lua.toJson
|
||||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||||
@ -55,7 +56,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
|||||||
table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) }
|
table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) }
|
||||||
|
|
||||||
// original engine parity, it returns occupied spaces in local coordinates
|
// original engine parity, it returns occupied spaces in local coordinates
|
||||||
table["spaces"] = luaFunction { returnBuffer.setTo(from(self.occupySpaces.map { from(it - self.tilePosition) })) }
|
table["spaces"] = luaFunction { returnBuffer.setTo(tableOf(*self.occupySpaces.map { from(it - self.tilePosition) }.toTypedArray())) }
|
||||||
|
|
||||||
table["setProcessingDirectives"] = luaFunction { directives: ByteString -> self.animator.processingDirectives = directives.decode() }
|
table["setProcessingDirectives"] = luaFunction { directives: ByteString -> self.animator.processingDirectives = directives.decode() }
|
||||||
table["setSoundEffectEnabled"] = luaFunction { state: Boolean -> self.soundEffectEnabled = state }
|
table["setSoundEffectEnabled"] = luaFunction { state: Boolean -> self.soundEffectEnabled = state }
|
||||||
|
@ -3,23 +3,40 @@
|
|||||||
|
|
||||||
package ru.dbotthepony.kstarbound.math
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.guava.immutableList
|
||||||
import ru.dbotthepony.kommons.math.intersectRectangles
|
import ru.dbotthepony.kommons.math.intersectRectangles
|
||||||
import ru.dbotthepony.kommons.math.rectangleContainsRectangle
|
import ru.dbotthepony.kommons.math.rectangleContainsRectangle
|
||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Axis Aligned Bounding Box, represented by two points, [mins] as lowermost corner of BB,
|
* Axis Aligned Bounding Box, represented by two points, [mins] as lowermost corner of BB,
|
||||||
* and [maxs] as uppermost corner of BB
|
* and [maxs] as uppermost corner of BB
|
||||||
*/
|
*/
|
||||||
data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
||||||
|
constructor(line: Line2d) : this(
|
||||||
|
Vector2d(min(line.p0.x, line.p1.x), min(line.p0.y, line.p1.y)),
|
||||||
|
Vector2d(max(line.p0.x, line.p1.x), max(line.p0.y, line.p1.y)),
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
|
// require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
|
||||||
require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
|
// require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isEmpty: Boolean
|
||||||
|
get() = mins.x > maxs.x || mins.y > maxs.y
|
||||||
|
|
||||||
|
val isZero: Boolean
|
||||||
|
get() = mins == maxs
|
||||||
|
|
||||||
|
val isEmptyOrZero: Boolean
|
||||||
|
get() = isEmpty || isZero
|
||||||
|
|
||||||
operator fun plus(other: AABB) = AABB(mins + other.mins, maxs + other.maxs)
|
operator fun plus(other: AABB) = AABB(mins + other.mins, maxs + other.maxs)
|
||||||
operator fun minus(other: AABB) = AABB(mins - other.mins, maxs - other.maxs)
|
operator fun minus(other: AABB) = AABB(mins - other.mins, maxs - other.maxs)
|
||||||
operator fun times(other: AABB) = AABB(mins * other.mins, maxs * other.maxs)
|
operator fun times(other: AABB) = AABB(mins * other.mins, maxs * other.maxs)
|
||||||
@ -50,6 +67,8 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
val width get() = maxs.x - mins.x
|
val width get() = maxs.x - mins.x
|
||||||
val height get() = maxs.y - mins.y
|
val height get() = maxs.y - mins.y
|
||||||
|
|
||||||
|
val volume get() = max(width * height, 0.0)
|
||||||
|
|
||||||
val extents get() = Vector2d(width * 0.5, height * 0.5)
|
val extents get() = Vector2d(width * 0.5, height * 0.5)
|
||||||
|
|
||||||
val diameter get() = mins.distance(maxs)
|
val diameter get() = mins.distance(maxs)
|
||||||
@ -57,6 +76,15 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
|
|
||||||
val perimeter get() = (xSpan + ySpan) * 2.0
|
val perimeter get() = (xSpan + ySpan) * 2.0
|
||||||
|
|
||||||
|
val edges: List<Line2d> by lazy {
|
||||||
|
immutableList {
|
||||||
|
accept(Line2d(Vector2d(mins.x, mins.y), Vector2d(maxs.x, mins.y)))
|
||||||
|
accept(Line2d(Vector2d(maxs.x, mins.y), Vector2d(maxs.x, maxs.y)))
|
||||||
|
accept(Line2d(Vector2d(maxs.x, maxs.y), Vector2d(mins.x, maxs.y)))
|
||||||
|
accept(Line2d(Vector2d(mins.x, maxs.y), Vector2d(mins.x, mins.y)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isInside(point: IStruct2d): Boolean {
|
fun isInside(point: IStruct2d): Boolean {
|
||||||
return point.component1() in mins.x .. maxs.x && point.component2() in mins.y .. maxs.y
|
return point.component1() in mins.x .. maxs.x && point.component2() in mins.y .. maxs.y
|
||||||
}
|
}
|
||||||
@ -76,6 +104,10 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
return intersectRectangles(mins, maxs, other.mins, other.maxs)
|
return intersectRectangles(mins, maxs, other.mins, other.maxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun intersect(line: Line2d): Boolean {
|
||||||
|
return line.p0 in this || line.p1 in this || edges.any { it.intersect(line).intersects }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whenever [other] is contained (encased) inside this AABB
|
* Returns whenever [other] is contained (encased) inside this AABB
|
||||||
*/
|
*/
|
||||||
@ -159,6 +191,13 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun overlap(other: AABB): AABB {
|
||||||
|
return AABB(
|
||||||
|
Vector2d(max(mins.x, other.mins.x), max(mins.y, other.mins.y)),
|
||||||
|
Vector2d(min(maxs.x, other.maxs.x), min(maxs.y, other.maxs.y)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns AABB which contains both AABBs
|
* Returns AABB which contains both AABBs
|
||||||
*/
|
*/
|
||||||
@ -234,6 +273,11 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun leftCorner(pos: Vector2d, width: Double, height: Double): AABB {
|
||||||
|
return AABB(pos, pos + Vector2d(width, height))
|
||||||
|
}
|
||||||
|
|
||||||
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
||||||
|
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,9 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
normal = Vector2d(-diff.y, diff.x)
|
normal = Vector2d(-diff.y, diff.x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val center: Vector2d
|
||||||
|
get() = p0 + difference * 0.5
|
||||||
|
|
||||||
operator fun plus(other: IStruct2d): Line2d {
|
operator fun plus(other: IStruct2d): Line2d {
|
||||||
return Line2d(p0 + other, p1 + other)
|
return Line2d(p0 + other, p1 + other)
|
||||||
}
|
}
|
||||||
@ -48,15 +51,14 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
return Line2d(p0 * other, p1 * other)
|
return Line2d(p0 * other, p1 * other)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Intersection(val intersects: Boolean, val point: KOptional<Vector2d>, val t: KOptional<Double>, val coincides: Boolean, val glances: Boolean) {
|
data class Intersection(val intersects: Boolean, val point: Vector2d?, val t: Double?, val coincides: Boolean, val glances: Boolean) {
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Intersection(false, KOptional(), KOptional(), false, false)
|
val EMPTY = Intersection(false, null, null, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun difference(): Vector2d {
|
val difference: Vector2d
|
||||||
return p1 - p0
|
get() = p1 - p0
|
||||||
}
|
|
||||||
|
|
||||||
fun reverse(): Line2d {
|
fun reverse(): Line2d {
|
||||||
return Line2d(p1, p0)
|
return Line2d(p1, p0)
|
||||||
@ -82,8 +84,8 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
fun intersect(other: Line2d, infinite: Boolean = false): Intersection {
|
fun intersect(other: Line2d, infinite: Boolean = false): Intersection {
|
||||||
val (c, d) = other
|
val (c, d) = other
|
||||||
|
|
||||||
val ab = difference()
|
val ab = difference
|
||||||
val cd = other.difference()
|
val cd = other.difference
|
||||||
|
|
||||||
val abCross = p0.cross(p1)
|
val abCross = p0.cross(p1)
|
||||||
val cdCross = c.cross(d)
|
val cdCross = c.cross(d)
|
||||||
@ -120,7 +122,7 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Intersection(intersects, KOptional.ofNullable(point), KOptional(t), true, intersects)
|
return Intersection(intersects, point, t, true, intersects)
|
||||||
} else {
|
} else {
|
||||||
return Intersection.EMPTY
|
return Intersection.EMPTY
|
||||||
}
|
}
|
||||||
@ -132,8 +134,8 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
|
|
||||||
return Intersection(
|
return Intersection(
|
||||||
intersects = intersects,
|
intersects = intersects,
|
||||||
t = KOptional(ta),
|
t = ta,
|
||||||
point = KOptional((p1 - p0) * ta + p0),
|
point = (p1 - p0) * ta + p0,
|
||||||
coincides = false,
|
coincides = false,
|
||||||
glances = !infinite && intersects && (ta <= NEAR_ZERO || ta >= NEAR_ONE || tb <= NEAR_ZERO || tb >= NEAR_ONE)
|
glances = !infinite && intersects && (ta <= NEAR_ZERO || ta >= NEAR_ONE || tb <= NEAR_ZERO || tb >= NEAR_ONE)
|
||||||
)
|
)
|
||||||
@ -141,7 +143,7 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun project(axis: IStruct2d): Double {
|
fun project(axis: IStruct2d): Double {
|
||||||
val diff = difference()
|
val diff = difference
|
||||||
val (x, y) = axis
|
val (x, y) = axis
|
||||||
return ((x - p0.x) * diff.x + (y - p0.y) * diff.y) / diff.lengthSquared
|
return ((x - p0.x) * diff.x + (y - p0.y) * diff.y) / diff.lengthSquared
|
||||||
}
|
}
|
||||||
@ -152,7 +154,7 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
if (!infinite)
|
if (!infinite)
|
||||||
proj = proj.coerceIn(0.0, 1.0)
|
proj = proj.coerceIn(0.0, 1.0)
|
||||||
|
|
||||||
return (Vector2d(other) - p0 + difference() * proj).length
|
return (Vector2d(other) - p0 + difference * proj).length
|
||||||
}
|
}
|
||||||
|
|
||||||
fun distanceTo(other: Vector2d, infinite: Boolean = false): Double {
|
fun distanceTo(other: Vector2d, infinite: Boolean = false): Double {
|
||||||
@ -161,7 +163,7 @@ data class Line2d(val p0: Vector2d, val p1: Vector2d) {
|
|||||||
if (!infinite)
|
if (!infinite)
|
||||||
proj = proj.coerceIn(0.0, 1.0)
|
proj = proj.coerceIn(0.0, 1.0)
|
||||||
|
|
||||||
return (other - p0 + difference() * proj).length
|
return (other - p0 + difference * proj).length
|
||||||
}
|
}
|
||||||
|
|
||||||
class Adapter(gson: Gson) : TypeAdapter<Line2d>() {
|
class Adapter(gson: Gson) : TypeAdapter<Line2d>() {
|
||||||
|
@ -245,5 +245,15 @@ data class Vector2d(
|
|||||||
|
|
||||||
@JvmField val POSITIVE_XY = Vector2d(1.0, 1.0)
|
@JvmField val POSITIVE_XY = Vector2d(1.0, 1.0)
|
||||||
@JvmField val NEGATIVE_XY = Vector2d(-1.0, -1.0)
|
@JvmField val NEGATIVE_XY = Vector2d(-1.0, -1.0)
|
||||||
|
|
||||||
|
fun angle(angle: Double, amplitude: Double = 1.0): Vector2d {
|
||||||
|
if (amplitude == 0.0)
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
val sin = sin(angle)
|
||||||
|
val cos = cos(angle)
|
||||||
|
|
||||||
|
return Vector2d(cos * amplitude, sin * amplitude)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ class WorldStopPacket(val reason: String = "") : IClientPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun play(connection: ClientConnection) {
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.resetOccupiedEntityIDs()
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -209,7 +209,7 @@ class ServerWorld private constructor(
|
|||||||
if (source != null && health?.isDead == true) {
|
if (source != null && health?.isDead == true) {
|
||||||
source.receiveMessage("tileBroken", jsonArrayOf(
|
source.receiveMessage("tileBroken", jsonArrayOf(
|
||||||
pos, if (isBackground) "background" else "foreground",
|
pos, if (isBackground) "background" else "foreground",
|
||||||
tile!!.tile(isBackground).material.id ?: 0, // TODO: string identifiers support
|
tile!!.tile(isBackground).material.id ?: tile.tile(isBackground).material.key, // TODO: explicit string identifiers support
|
||||||
tile.dungeonId,
|
tile.dungeonId,
|
||||||
health.isHarvested
|
health.isHarvested
|
||||||
))
|
))
|
||||||
@ -222,7 +222,7 @@ class ServerWorld private constructor(
|
|||||||
return topMost
|
return topMost
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List<Pair<Vector2i, TileModification>> {
|
override fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean): List<Pair<Vector2i, TileModification>> {
|
||||||
val unapplied = ArrayList(modifications)
|
val unapplied = ArrayList(modifications)
|
||||||
var size: Int
|
var size: Int
|
||||||
|
|
||||||
@ -438,7 +438,6 @@ class ServerWorld private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun setProperty0(key: String, value: JsonElement) {
|
override fun setProperty0(key: String, value: JsonElement) {
|
||||||
super.setProperty0(key, value)
|
|
||||||
broadcast(UpdateWorldPropertiesPacket(JsonObject().apply { add(key, value) }))
|
broadcast(UpdateWorldPropertiesPacket(JsonObject().apply { add(key, value) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,8 +312,8 @@ class NativeUniverseSource(private val db: BTreeDB6?, private val universe: Serv
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
intersection.intersects &&
|
intersection.intersects &&
|
||||||
intersection.point.get() != proposed.p0 &&
|
intersection.point != proposed.p0 &&
|
||||||
intersection.point.get() != proposed.p1
|
intersection.point != proposed.p1
|
||||||
) {
|
) {
|
||||||
valid = false
|
valid = false
|
||||||
break
|
break
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -70,5 +71,5 @@ fun <C : Comparable<C>, T : Any> Stream<Pair<C, T>>.binnedChoice(value: C): Opti
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <E : IStringSerializable> Collection<E>.valueOf(value: String): E {
|
fun <E : IStringSerializable> Collection<E>.valueOf(value: String): E {
|
||||||
return firstOrNull { it.match(value) } ?: throw NoSuchElementException("$value is not a valid element")
|
return firstOrNull { it.match(value) } ?: throw NoSuchElementException("'$value' is not a valid ${first()::class.qualifiedName}")
|
||||||
}
|
}
|
||||||
|
@ -238,3 +238,14 @@ fun RandomGenerator.nextRange(range: IStruct2i): Int {
|
|||||||
fun RandomGenerator.nextRange(range: IStruct2d): Double {
|
fun RandomGenerator.nextRange(range: IStruct2d): Double {
|
||||||
return if (range.component1() == range.component2()) return range.component1() else nextDouble(range.component1(), range.component2())
|
return if (range.component1() == range.component2()) return range.component1() else nextDouble(range.component1(), range.component2())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> MutableList<T>.shuffle(random: RandomGenerator) {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
val rand = random.nextInt(size)
|
||||||
|
val a = this[i]
|
||||||
|
val b = this[rand]
|
||||||
|
|
||||||
|
this[i] = b
|
||||||
|
this[rand] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
|||||||
import it.unimi.dsi.fastutil.longs.LongArrayList
|
import it.unimi.dsi.fastutil.longs.LongArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap
|
import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
@ -250,8 +251,8 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun query(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): List<AbstractEntity> {
|
fun query(rect: AABB, filter: Predicate<in AbstractEntity> = Predicate { true }, withEdges: Boolean = true): MutableList<AbstractEntity> {
|
||||||
val entriesDirect = ArrayList<AbstractEntity>()
|
val entriesDirect = ObjectArrayList<AbstractEntity>()
|
||||||
|
|
||||||
iterate(rect, withEdges = withEdges, visitor = {
|
iterate(rect, withEdges = withEdges, visitor = {
|
||||||
if (filter.test(it)) entriesDirect.add(it)
|
if (filter.test(it)) entriesDirect.add(it)
|
||||||
@ -282,6 +283,10 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
return first(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as TileEntity?
|
return first(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as TileEntity?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tileEntitiesAt(pos: Vector2i): MutableList<TileEntity> {
|
||||||
|
return query(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as MutableList<TileEntity>
|
||||||
|
}
|
||||||
|
|
||||||
fun iterate(rect: AABB, visitor: (AbstractEntity) -> Unit, withEdges: Boolean = true) {
|
fun iterate(rect: AABB, visitor: (AbstractEntity) -> Unit, withEdges: Boolean = true) {
|
||||||
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
||||||
}
|
}
|
||||||
@ -301,7 +306,7 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
val sector = map[index(x, y)] ?: continue
|
val sector = map[index(x, y)] ?: continue
|
||||||
|
|
||||||
for (entry in sector.entries) {
|
for (entry in sector.entries) {
|
||||||
if (entry.intersects(actualRegion, withEdges) && seen.add(entry.id)) {
|
if (seen.add(entry.id) && entry.intersects(actualRegion, withEdges)) {
|
||||||
val visit = visitor(entry.value)
|
val visit = visitor(entry.value)
|
||||||
|
|
||||||
if (visit.isPresent)
|
if (visit.isPresent)
|
||||||
|
@ -23,13 +23,24 @@ data class RayCastResult(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class RayFilterResult(val hit: Boolean, val write: Boolean) {
|
enum class RayFilterResult(val hit: Boolean, val write: Boolean) {
|
||||||
// stop tracing, write hit tile into traversed tiles list
|
/**
|
||||||
|
* stop tracing, write hit tile into traversed tiles list
|
||||||
|
*/
|
||||||
HIT(true, true),
|
HIT(true, true),
|
||||||
// stop tracing, don't write hit tile into traversed tiles list
|
|
||||||
HIT_SKIP(true, false),
|
/**
|
||||||
// continue tracing, don't write hit tile into traversed tiles list
|
* stop tracing, don't write hit tile into traversed tiles list
|
||||||
|
*/
|
||||||
|
BREAK(true, false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* continue tracing, don't write hit tile into traversed tiles list
|
||||||
|
*/
|
||||||
SKIP(false, false),
|
SKIP(false, false),
|
||||||
// continue tracing, write hit tile into traversed tiles list
|
|
||||||
|
/**
|
||||||
|
* continue tracing, write hit tile into traversed tiles list
|
||||||
|
*/
|
||||||
CONTINUE(false, true);
|
CONTINUE(false, true);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -120,7 +131,7 @@ fun ICellAccess.castRay(
|
|||||||
normal = yNormal
|
normal = yNormal
|
||||||
}
|
}
|
||||||
|
|
||||||
cell = getCell(cellPosX, cellPosY) ?: return RayCastResult(hitTiles, null, travelled / distance, start, start + direction * travelled, direction)
|
cell = getCell(cellPosX, cellPosY)
|
||||||
result = filter.test(cell, 0.0, cellPosX, cellPosY, normal, start.x + direction.x * travelled, start.y + direction.y * travelled)
|
result = filter.test(cell, 0.0, cellPosX, cellPosY, normal, start.x + direction.x * travelled, start.y + direction.y * travelled)
|
||||||
|
|
||||||
val c = if (result.write || result.hit) {
|
val c = if (result.write || result.hit) {
|
||||||
|
@ -32,9 +32,8 @@ class Sky() {
|
|||||||
|
|
||||||
var skyParameters by networkedGroup.upstream.add(networkedJson(SkyParameters()))
|
var skyParameters by networkedGroup.upstream.add(networkedJson(SkyParameters()))
|
||||||
var skyType by networkedGroup.upstream.add(networkedEnumStupid(SkyType.ORBITAL))
|
var skyType by networkedGroup.upstream.add(networkedEnumStupid(SkyType.ORBITAL))
|
||||||
|
|
||||||
var time by networkedGroup.upstream.add(networkedDouble())
|
var time by networkedGroup.upstream.add(networkedDouble())
|
||||||
private set
|
|
||||||
var flyingType by networkedGroup.upstream.add(networkedEnum(FlyingType.NONE))
|
var flyingType by networkedGroup.upstream.add(networkedEnum(FlyingType.NONE))
|
||||||
private set
|
private set
|
||||||
var enterHyperspace by networkedGroup.upstream.add(networkedBoolean())
|
var enterHyperspace by networkedGroup.upstream.add(networkedBoolean())
|
||||||
@ -67,6 +66,15 @@ class Sky() {
|
|||||||
var pathRotation: Double = 0.0
|
var pathRotation: Double = 0.0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
val dayLength: Double
|
||||||
|
get() = skyParameters.dayLength ?: 1000.0
|
||||||
|
|
||||||
|
val day: Int
|
||||||
|
get() = if (dayLength <= 1.0) 0 else (time / dayLength).toInt()
|
||||||
|
|
||||||
|
val timeOfDay: Double
|
||||||
|
get() = if (dayLength <= 1.0) 0.0 else time % dayLength
|
||||||
|
|
||||||
var destination: SkyParameters? = null
|
var destination: SkyParameters? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.isEmptyLiquid
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyModifier
|
import ru.dbotthepony.kstarbound.defs.tile.isEmptyModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||||
@ -269,8 +270,9 @@ sealed class TileModification {
|
|||||||
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.isInfinite)
|
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.isInfinite)
|
||||||
return false // it makes no sense to try to pour liquid into infinite source
|
return false // it makes no sense to try to pour liquid into infinite source
|
||||||
|
|
||||||
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.state != state.entry)
|
if (state.isNotEmptyLiquid && cell.liquid.state.isNotEmptyLiquid && cell.liquid.state != state.entry)
|
||||||
return false // it makes also makes no sense to magically replace liquid what is already there
|
return false // it makes also makes no sense to magically replace liquid what is already there
|
||||||
|
// (unless we are removing existing liquid)
|
||||||
|
|
||||||
// while checks above makes vanilla client look stupid when it tries to pour liquids into other
|
// while checks above makes vanilla client look stupid when it tries to pour liquids into other
|
||||||
// liquids, we must think better than vanilla client.
|
// liquids, we must think better than vanilla client.
|
||||||
@ -290,8 +292,11 @@ sealed class TileModification {
|
|||||||
} else {
|
} else {
|
||||||
cell.liquid.reset()
|
cell.liquid.reset()
|
||||||
cell.liquid.state = state
|
cell.liquid.state = state
|
||||||
cell.liquid.level = level
|
|
||||||
cell.liquid.pressure = 1f
|
if (state.isNotEmptyLiquid) {
|
||||||
|
cell.liquid.level = level
|
||||||
|
cell.liquid.pressure = 1f
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
world.setCell(position, cell)
|
world.setCell(position, cell)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||||
@ -12,11 +15,14 @@ import ru.dbotthepony.kommons.collect.filterNotNull
|
|||||||
import ru.dbotthepony.kommons.gson.set
|
import ru.dbotthepony.kommons.gson.set
|
||||||
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.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.math.AABBi
|
import ru.dbotthepony.kstarbound.math.AABBi
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||||
@ -24,10 +30,10 @@ import ru.dbotthepony.kstarbound.math.*
|
|||||||
import ru.dbotthepony.kstarbound.network.IPacket
|
import ru.dbotthepony.kstarbound.network.IPacket
|
||||||
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
|
||||||
import ru.dbotthepony.kstarbound.util.ParallelPerform
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileView
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||||||
@ -265,6 +271,18 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
mergeJson(worldProperties, properties)
|
mergeJson(worldProperties, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getProperty(name: String): JsonElement {
|
||||||
|
return worldProperties[name] ?: JsonNull.INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProperty(name: String, orElse: JsonElement): JsonElement {
|
||||||
|
return worldProperties[name] ?: orElse
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProperty(name: String, orElse: () -> JsonElement): JsonElement {
|
||||||
|
return worldProperties[name] ?: orElse()
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun setProperty0(key: String, value: JsonElement) {
|
protected open fun setProperty0(key: String, value: JsonElement) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -417,6 +435,39 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collide(point: Vector2d, filter: Predicate<CollisionPoly>): Boolean {
|
||||||
|
return queryTileCollisions(AABB.withSide(point, 2.0)).any { filter.test(it) && point in it.poly }
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LineCollisionResult(val poly: CollisionPoly, val border: Vector2d, val normal: Vector2d)
|
||||||
|
|
||||||
|
// ugly.
|
||||||
|
// but original code is way worse than this, because it considers A FUCKING RECTANGLE
|
||||||
|
// holy shiet
|
||||||
|
// we, on other hand, consider only tiles along line
|
||||||
|
fun collide(line: Line2d, filter: Predicate<CollisionPoly>): LineCollisionResult? {
|
||||||
|
var found: LineCollisionResult? = null
|
||||||
|
|
||||||
|
castRay(line.p0, line.p1) { cell, fraction, x, y, normal, borderX, borderY ->
|
||||||
|
val query = queryTileCollisions(AABB.withSide(Vector2d(borderX, borderY), 2.0))
|
||||||
|
|
||||||
|
for (poly in query) {
|
||||||
|
if (filter.test(poly)) {
|
||||||
|
val intersect = poly.poly.intersect(line)
|
||||||
|
|
||||||
|
if (intersect != null) {
|
||||||
|
found = LineCollisionResult(poly, intersect.second.point!!, intersect.first)
|
||||||
|
return@castRay RayFilterResult.BREAK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RayFilterResult.SKIP
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
fun polyIntersects(with: Poly, filter: Predicate<CollisionPoly> = Predicate { true }, tolerance: Double = 0.0): Boolean {
|
fun polyIntersects(with: Poly, filter: Predicate<CollisionPoly> = Predicate { true }, tolerance: Double = 0.0): Boolean {
|
||||||
return collide(with, filter).anyMatch { it.penetration >= tolerance }
|
return collide(with, filter).anyMatch { it.penetration >= tolerance }
|
||||||
}
|
}
|
||||||
@ -433,6 +484,36 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
return template.worldParameters?.gravity ?: Vector2d.ZERO
|
return template.worldParameters?.gravity ?: Vector2d.ZERO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class LiquidLevel(val type: Registry.Entry<LiquidDefinition>, val average: Double)
|
||||||
|
|
||||||
|
fun averageLiquidLevel(rect: AABB): LiquidLevel? {
|
||||||
|
val liquidLevels = Object2DoubleOpenHashMap<Registry.Entry<LiquidDefinition>>()
|
||||||
|
var area = 0.0
|
||||||
|
|
||||||
|
anyCellSatisfies(rect) { x, y, cell ->
|
||||||
|
val blockIncidence = AABB.leftCorner(Vector2d(x.toDouble(), y.toDouble()), 1.0, 1.0).overlap(rect).volume
|
||||||
|
area += blockIncidence
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.level > 0f) {
|
||||||
|
liquidLevels.put(cell.liquid.state, liquidLevels.getDouble(cell.liquid.state) + cell.liquid.level.coerceAtMost(1f) * blockIncidence)
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (liquidLevels.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val max = liquidLevels.object2DoubleEntrySet().maxBy { it.doubleValue }!!
|
||||||
|
return LiquidLevel(max.key, max.doubleValue / area)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns unapplied tile modifications
|
||||||
|
*/
|
||||||
|
abstract fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List<Pair<Vector2i, TileModification>>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import ru.dbotthepony.kstarbound.io.readVector2i
|
import ru.dbotthepony.kstarbound.io.readVector2i
|
||||||
@ -242,7 +243,7 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean = true, val loop
|
|||||||
return poly.distance(nearestTo(poly.centre, point))
|
return poly.distance(nearestTo(poly.centre, point))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun split(poly: Poly): List<Poly> {
|
val splitLines: ImmutableList<Pair<Line2d, Pair<Vector2d, Vector2d>>> by lazy {
|
||||||
val lines = ObjectArrayList<Pair<Line2d, Pair<Vector2d, Vector2d>>>(4)
|
val lines = ObjectArrayList<Pair<Line2d, Pair<Vector2d, Vector2d>>>(4)
|
||||||
|
|
||||||
if (x.isSplitting) {
|
if (x.isSplitting) {
|
||||||
@ -255,14 +256,18 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean = true, val loop
|
|||||||
lines.add(Line2d(Vector2d(0.0, y.cellsD), Vector2d(1.0, y.cellsD)) to (Vector2d(0.0, -y.cellsD) to Vector2d.ZERO))
|
lines.add(Line2d(Vector2d(0.0, y.cellsD), Vector2d(1.0, y.cellsD)) to (Vector2d(0.0, -y.cellsD) to Vector2d.ZERO))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lines.isEmpty) {
|
ImmutableList.copyOf(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun split(poly: Poly): List<Poly> {
|
||||||
|
if (splitLines.isEmpty()) {
|
||||||
return listOf(poly)
|
return listOf(poly)
|
||||||
}
|
}
|
||||||
|
|
||||||
val split = ObjectArrayList<Poly>()
|
val split = ObjectArrayList<Poly>()
|
||||||
split.add(poly)
|
split.add(poly)
|
||||||
|
|
||||||
for ((line, corrections) in lines) {
|
for ((line, corrections) in splitLines) {
|
||||||
val (correctionIfLeft, correctionIfRight) = corrections
|
val (correctionIfLeft, correctionIfRight) = corrections
|
||||||
val itr = split.listIterator()
|
val itr = split.listIterator()
|
||||||
|
|
||||||
@ -285,8 +290,8 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean = true, val loop
|
|||||||
val result = line.intersect(vedge, true)
|
val result = line.intersect(vedge, true)
|
||||||
|
|
||||||
// check if it is an actual split, if poly points just rest on that line consider they rest on left side
|
// check if it is an actual split, if poly points just rest on that line consider they rest on left side
|
||||||
if (result.intersects) {
|
if (result.intersects && result.point != null) {
|
||||||
val point = line.intersect(vedge, true).point.orThrow { RuntimeException() }
|
val point = result.point
|
||||||
|
|
||||||
if (left0 < 0.0 && left1 >= 0.0) {
|
if (left0 < 0.0 && left1 >= 0.0) {
|
||||||
leftPoints.add(vedge.p1 + correctionIfLeft)
|
leftPoints.add(vedge.p1 + correctionIfLeft)
|
||||||
@ -326,4 +331,67 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean = true, val loop
|
|||||||
val wrap = wrap(point)
|
val wrap = wrap(point)
|
||||||
return split(poly).any { it.contains(wrap) }
|
return split(poly).any { it.contains(wrap) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun split(lineToSplit: Line2d): List<Line2d> {
|
||||||
|
val result = ObjectArrayList<Line2d>()
|
||||||
|
result.add(lineToSplit)
|
||||||
|
|
||||||
|
val itr = result.listIterator()
|
||||||
|
|
||||||
|
for ((splitLine, corrections) in splitLines) {
|
||||||
|
val (correctionIfLeft, correctionIfRight) = corrections
|
||||||
|
|
||||||
|
for (line in itr) {
|
||||||
|
val intersect = splitLine.intersect(line, true)
|
||||||
|
|
||||||
|
if (intersect.intersects && intersect.point != null) {
|
||||||
|
val left0 = splitLine.isLeft(line.p0)
|
||||||
|
val left1 = splitLine.isLeft(line.p1)
|
||||||
|
|
||||||
|
itr.remove()
|
||||||
|
|
||||||
|
if (left0 < 0.0 && left1 >= 0.0) {
|
||||||
|
// crossing from right to left
|
||||||
|
itr.add(Line2d(line.p1 + correctionIfLeft, intersect.point + correctionIfLeft))
|
||||||
|
itr.add(Line2d(intersect.point + correctionIfRight, line.p0 + correctionIfRight))
|
||||||
|
} else {
|
||||||
|
// crossing from left to right OR starting right at left border?
|
||||||
|
itr.add(Line2d(line.p0 + correctionIfLeft, intersect.point + correctionIfLeft))
|
||||||
|
itr.add(Line2d(intersect.point + correctionIfRight, line.p1 + correctionIfRight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rectIntersectsCircle(rect: AABB, center: Vector2d, radius: Double): Boolean {
|
||||||
|
if (center in rect)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return rect.edges.any { lineIntersectsCircle(it, center, radius) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lineIntersectsCircle(line: Line2d, center: Vector2d, radius: Double): Boolean {
|
||||||
|
return split(line).any { it.distanceTo(nearestTo(it.center, center)) <= radius }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lineIntersectsRect(line: Line2d, rect: AABB): Boolean {
|
||||||
|
val lines = split(line)
|
||||||
|
val rects = split(rect).first
|
||||||
|
return lines.any { l -> rects.any { r -> r.intersect(l) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun polyIntersectsPoly(a: Poly, b: Poly): Boolean {
|
||||||
|
val sa = split(a)
|
||||||
|
val sb = split(b)
|
||||||
|
return sa.any { p -> sb.any { p.intersect(it) != null } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rectIntersectsRect(a: AABB, b: AABB): Boolean {
|
||||||
|
val sa = split(a).first
|
||||||
|
val sb = split(b).first
|
||||||
|
return sa.any { p -> sb.any { p.intersect(it) } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package ru.dbotthepony.kstarbound.world.entities
|
|||||||
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.io.koptional
|
import ru.dbotthepony.kommons.io.koptional
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
@ -14,6 +16,8 @@ import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
|
|||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
@ -68,6 +72,9 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
abstract val type: EntityType
|
abstract val type: EntityType
|
||||||
|
|
||||||
|
open val isEphemeral: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, then the entity will be discoverable by its unique id and will be
|
* If set, then the entity will be discoverable by its unique id and will be
|
||||||
* indexed in the stored world. Unique ids must be different across all
|
* indexed in the stored world. Unique ids must be different across all
|
||||||
@ -105,16 +112,22 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
val networkGroup = MasterElement(NetworkedGroup())
|
val networkGroup = MasterElement(NetworkedGroup())
|
||||||
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
|
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
|
||||||
|
|
||||||
|
fun writeNetwork(isLegacy: Boolean): ByteArrayList {
|
||||||
|
val stream = FastByteArrayOutputStream()
|
||||||
|
writeNetwork(DataOutputStream(stream), isLegacy)
|
||||||
|
return ByteArrayList.wrap(stream.array, stream.length)
|
||||||
|
}
|
||||||
|
|
||||||
protected var spatialEntry: EntityIndex.Entry? = null
|
protected var spatialEntry: EntityIndex.Entry? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for spatial index
|
* Used for spatial index, AABB in world coordinates
|
||||||
*/
|
*/
|
||||||
abstract val metaBoundingBox: AABB
|
abstract val metaBoundingBox: AABB
|
||||||
|
|
||||||
open val collisionArea: AABB
|
open val collisionArea: AABB
|
||||||
get() = NEVER
|
get() = AABB.NEVER
|
||||||
|
|
||||||
open fun onNetworkUpdate() {
|
open fun onNetworkUpdate() {
|
||||||
|
|
||||||
@ -128,8 +141,13 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
if (innerWorld != null)
|
if (innerWorld != null)
|
||||||
throw IllegalStateException("Already spawned (in world $innerWorld)")
|
throw IllegalStateException("Already spawned (in world $innerWorld)")
|
||||||
|
|
||||||
if (entityID == 0)
|
if (entityID == 0) {
|
||||||
entityID = world.nextEntityID.incrementAndGet()
|
if (world is ClientWorld) {
|
||||||
|
entityID = world.client.activeConnection?.nextEntityID() ?: world.nextEntityID.incrementAndGet()
|
||||||
|
} else {
|
||||||
|
entityID = world.nextEntityID.incrementAndGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
world.eventLoop.ensureSameThread()
|
world.eventLoop.ensureSameThread()
|
||||||
|
|
||||||
@ -143,6 +161,12 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
world.entityList.add(this)
|
world.entityList.add(this)
|
||||||
spatialEntry = world.entityIndex.Entry(this)
|
spatialEntry = world.entityIndex.Entry(this)
|
||||||
onJoinWorld(world)
|
onJoinWorld(world)
|
||||||
|
|
||||||
|
if (world is ClientWorld && !isRemote) {
|
||||||
|
val connection = world.client.activeConnection
|
||||||
|
// TODO: incomplete
|
||||||
|
connection?.send(EntityCreatePacket(type, writeNetwork(connection.isLegacy), networkGroup.write(0L, connection.isLegacy).first, entityID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(reason: RemovalReason) {
|
fun remove(reason: RemovalReason) {
|
||||||
@ -167,6 +191,13 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
world.clients.forEach {
|
world.clients.forEach {
|
||||||
it.forget(this, reason)
|
it.forget(this, reason)
|
||||||
}
|
}
|
||||||
|
} else if (world is ClientWorld && !isRemote) {
|
||||||
|
val connection = world.client.activeConnection
|
||||||
|
|
||||||
|
if (connection != null) {
|
||||||
|
connection.send(EntityDestroyPacket(entityID, writeNetwork(connection.isLegacy), reason.dying))
|
||||||
|
connection.freeEntityID(entityID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +225,5 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
private val NEVER = AABB(Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,35 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.Drawable
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monsters, NPCs, Players
|
* Monsters, NPCs, Players
|
||||||
*/
|
*/
|
||||||
abstract class ActorEntity() : DynamicEntity() {
|
abstract class ActorEntity() : DynamicEntity() {
|
||||||
final override val movement: ActorMovementController = ActorMovementController()
|
final override val movement: ActorMovementController = ActorMovementController()
|
||||||
abstract val statusController: StatusController
|
abstract val statusController: StatusController
|
||||||
|
|
||||||
|
enum class DamageBarType {
|
||||||
|
DEFAULT, NONE, SPECIAL
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val health: Double
|
||||||
|
abstract val maxHealth: Double
|
||||||
|
abstract val damageBarType: DamageBarType
|
||||||
|
|
||||||
|
abstract val name: String
|
||||||
|
|
||||||
|
enum class PortraitMode(override val jsonName: String) : IStringSerializable {
|
||||||
|
HEAD("head"),
|
||||||
|
BUST("bust"),
|
||||||
|
FULL("full"),
|
||||||
|
FULL_NEUTRAL("fullneutral"),
|
||||||
|
FULL_NUDE("fullnude"),
|
||||||
|
FULL_NEUTRAL_NUDE("fullneutralnude");
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun portrait(mode: PortraitMode): List<Drawable> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import ru.dbotthepony.kommons.io.koptional
|
|||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.util.getValue
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.JumpProfile
|
import ru.dbotthepony.kstarbound.defs.JumpProfile
|
||||||
@ -82,7 +83,7 @@ class ActorMovementController() : MovementController() {
|
|||||||
|
|
||||||
var controlMove: Direction? = null
|
var controlMove: Direction? = null
|
||||||
|
|
||||||
var actorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY
|
var actorMovementParameters: ActorMovementParameters = Globals.actorMovementParameters
|
||||||
var movementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY
|
var movementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY
|
||||||
|
|
||||||
var controlActorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY
|
var controlActorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY
|
||||||
@ -189,6 +190,11 @@ class ActorMovementController() : MovementController() {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resetBaseParameters(base: ActorMovementParameters) {
|
||||||
|
actorMovementParameters = Globals.actorMovementParameters.merge(base)
|
||||||
|
movementParameters = calculateMovementParameters(actorMovementParameters)
|
||||||
|
}
|
||||||
|
|
||||||
fun clearControls() {
|
fun clearControls() {
|
||||||
controlRotationRate = 0.0
|
controlRotationRate = 0.0
|
||||||
controlAcceleration = Vector2d.ZERO
|
controlAcceleration = Vector2d.ZERO
|
||||||
|
@ -4,6 +4,7 @@ import ru.dbotthepony.kommons.util.getValue
|
|||||||
import ru.dbotthepony.kommons.util.setValue
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.actor.Gender
|
||||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
@ -17,6 +18,9 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedStatefulItem
|
|||||||
abstract class HumanoidActorEntity() : ActorEntity() {
|
abstract class HumanoidActorEntity() : ActorEntity() {
|
||||||
abstract val aimPosition: Vector2d
|
abstract val aimPosition: Vector2d
|
||||||
|
|
||||||
|
abstract val species: String
|
||||||
|
abstract val gender: Gender
|
||||||
|
|
||||||
val effects = EffectEmitter(this)
|
val effects = EffectEmitter(this)
|
||||||
|
|
||||||
// it makes no sense to split ToolUser' logic into separate class
|
// it makes no sense to split ToolUser' logic into separate class
|
||||||
|
@ -48,7 +48,6 @@ class ItemDropEntity() : DynamicEntity() {
|
|||||||
var shouldNotExpire = false
|
var shouldNotExpire = false
|
||||||
val age = RelativeClock()
|
val age = RelativeClock()
|
||||||
var intangibleTimer = GameTimer(0.0)
|
var intangibleTimer = GameTimer(0.0)
|
||||||
private set
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
movement.applyParameters(Globals.itemDrop.movementSettings)
|
movement.applyParameters(Globals.itemDrop.movementSettings)
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities.api
|
||||||
|
|
||||||
|
interface InspectableEntity {
|
||||||
|
/**
|
||||||
|
* Default implementation returns true
|
||||||
|
*/
|
||||||
|
val isInspectable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this entity can be entered into the player log, will return the log identifier.
|
||||||
|
*/
|
||||||
|
val inspectionLogName: String?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long description to display when inspected, if any
|
||||||
|
*/
|
||||||
|
fun inspectionDescription(species: String?): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities.api
|
||||||
|
|
||||||
|
interface ScriptedEntity {
|
||||||
|
// Call a script function directly with the given arguments, should return
|
||||||
|
// nothing only on failure.
|
||||||
|
fun callScript(fnName: String, vararg arguments: Any?): Array<Any?>
|
||||||
|
|
||||||
|
// Execute the given code directly in the underlying context, return nothing
|
||||||
|
// on failure.
|
||||||
|
fun evalScript(code: String): Array<Any?>
|
||||||
|
}
|
@ -8,6 +8,7 @@ import ru.dbotthepony.kommons.util.setValue
|
|||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.actor.Gender
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.HumanoidData
|
import ru.dbotthepony.kstarbound.defs.actor.HumanoidData
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
|
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode
|
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode
|
||||||
@ -99,8 +100,23 @@ class PlayerEntity() : HumanoidActorEntity() {
|
|||||||
networkGroup.upstream.add(effectAnimator.networkGroup)
|
networkGroup.upstream.add(effectAnimator.networkGroup)
|
||||||
networkGroup.upstream.add(statusController)
|
networkGroup.upstream.add(statusController)
|
||||||
networkGroup.upstream.add(techController.networkGroup)
|
networkGroup.upstream.add(techController.networkGroup)
|
||||||
|
|
||||||
|
movement.resetBaseParameters(Globals.player.movementParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val health: Double
|
||||||
|
get() = statusController.resources["health"]!!.value
|
||||||
|
override val maxHealth: Double
|
||||||
|
get() = statusController.resources["health"]!!.maxValue!!
|
||||||
|
override val damageBarType: DamageBarType
|
||||||
|
get() = DamageBarType.DEFAULT
|
||||||
|
override val name: String
|
||||||
|
get() = humanoidData.name
|
||||||
|
override val species: String
|
||||||
|
get() = humanoidData.species
|
||||||
|
override val gender: Gender
|
||||||
|
get() = humanoidData.gender
|
||||||
|
|
||||||
override val metaBoundingBox: AABB
|
override val metaBoundingBox: AABB
|
||||||
get() = Globals.player.metaBoundBox + position
|
get() = Globals.player.metaBoundBox + position
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import ru.dbotthepony.kommons.util.setValue
|
|||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.EquipmentSlot
|
import ru.dbotthepony.kstarbound.defs.actor.EquipmentSlot
|
||||||
import ru.dbotthepony.kstarbound.defs.actor.EssentialSlot
|
import ru.dbotthepony.kstarbound.defs.actor.EssentialSlot
|
||||||
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||||
@ -84,7 +85,7 @@ class PlayerInventory {
|
|||||||
.map { it to networkedItem() }
|
.map { it to networkedItem() }
|
||||||
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||||
|
|
||||||
private val currencies = NetworkedMap(keyCodec = InternedStringCodec, valueCodec = UnsignedVarLongCodec to LongValueCodec, isDumb = true)
|
val currencies = NetworkedMap(keyCodec = InternedStringCodec, valueCodec = UnsignedVarLongCodec to LongValueCodec, isDumb = true)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// this is required for original engine
|
// this is required for original engine
|
||||||
@ -124,6 +125,27 @@ class PlayerInventory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasCountOfItem(item: ItemDescriptor, exactMatch: Boolean = false): Long {
|
||||||
|
var count = 0L
|
||||||
|
|
||||||
|
if (handSlot.matches(item, exactMatch)) {
|
||||||
|
count += handSlot.size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trashSlot.matches(item, exactMatch)) {
|
||||||
|
count += trashSlot.size
|
||||||
|
}
|
||||||
|
|
||||||
|
for (pitem in equipment.values) {
|
||||||
|
if (pitem.get().matches(item, exactMatch)) {
|
||||||
|
count += trashSlot.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bags.values.forEach { count += it.hasCountOfItem(item, exactMatch) }
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
// "swap slot" in original sources
|
// "swap slot" in original sources
|
||||||
var handSlot by networkGroup.add(networkedItem())
|
var handSlot by networkGroup.add(networkedItem())
|
||||||
var trashSlot by networkGroup.add(networkedItem())
|
var trashSlot by networkGroup.add(networkedItem())
|
||||||
|
@ -27,18 +27,30 @@ class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(c
|
|||||||
isInteractive = true
|
isInteractive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val sitPositions = ArrayList<Vector2d>()
|
val sitPositions = ArrayList<Vector2d>()
|
||||||
private var sitFlipDirection = false
|
|
||||||
private var sitOrientation = LoungeOrientation.NONE
|
var sitFlipDirection = false
|
||||||
private var sitAngle = 0.0
|
private set
|
||||||
private var sitCoverImage = ""
|
var sitOrientation = LoungeOrientation.NONE
|
||||||
private var sitFlipImages = false
|
private set
|
||||||
private val sitStatusEffects = ObjectArraySet<Either<StatModifier, String>>()
|
var sitAngle = 0.0
|
||||||
private val sitEffectEmitters = ObjectArraySet<String>()
|
private set
|
||||||
private var sitEmote: String? = null
|
var sitCoverImage = ""
|
||||||
private var sitDance: String? = null
|
private set
|
||||||
private var sitArmorCosmeticOverrides: JsonObject = JsonObject()
|
var sitFlipImages = false
|
||||||
private var sitCursorOverride: String? = null
|
private set
|
||||||
|
|
||||||
|
val sitStatusEffects = ObjectArraySet<Either<StatModifier, String>>()
|
||||||
|
val sitEffectEmitters = ObjectArraySet<String>()
|
||||||
|
|
||||||
|
var sitEmote: String? = null
|
||||||
|
private set
|
||||||
|
var sitDance: String? = null
|
||||||
|
private set
|
||||||
|
var sitArmorCosmeticOverrides: JsonObject = JsonObject()
|
||||||
|
private set
|
||||||
|
var sitCursorOverride: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
private fun updateSitParams() {
|
private fun updateSitParams() {
|
||||||
orientation ?: return
|
orientation ?: return
|
||||||
|
@ -13,6 +13,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.ByteString
|
import org.classdump.luna.ByteString
|
||||||
import org.classdump.luna.Table
|
import org.classdump.luna.Table
|
||||||
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.Registries
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
@ -103,6 +104,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun loadParameters(parameters: JsonObject) {
|
open fun loadParameters(parameters: JsonObject) {
|
||||||
|
if ("uniqueId" in parameters) {
|
||||||
|
uniqueID.accept(KOptional(parameters["uniqueId"]!!.asString))
|
||||||
|
}
|
||||||
|
|
||||||
for ((k, v) in parameters.entrySet()) {
|
for ((k, v) in parameters.entrySet()) {
|
||||||
this.parameters[k] = v
|
this.parameters[k] = v
|
||||||
}
|
}
|
||||||
@ -397,6 +402,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
provideEntityBindings(this, lua)
|
provideEntityBindings(this, lua)
|
||||||
provideAnimatorBindings(animator, lua)
|
provideAnimatorBindings(animator, lua)
|
||||||
lua.attach(config.value.scripts)
|
lua.attach(config.value.scripts)
|
||||||
|
lua.random = world.random
|
||||||
luaUpdate.stepCount = lookupProperty(JsonPath("scriptDelta")) { JsonPrimitive(5) }.asDouble
|
luaUpdate.stepCount = lookupProperty(JsonPath("scriptDelta")) { JsonPrimitive(5) }.asDouble
|
||||||
lua.init()
|
lua.init()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.physics
|
package ru.dbotthepony.kstarbound.world.physics
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
enum class CollisionType(override val jsonName: String, val isEmpty: Boolean, val isSolidCollision: Boolean, val isTileCollision: Boolean) : IStringSerializable {
|
enum class CollisionType(override val jsonName: String, val isEmpty: Boolean, val isSolidCollision: Boolean, val isTileCollision: Boolean) : IStringSerializable {
|
||||||
// not loaded, block collisions by default
|
// not loaded, block collisions by default
|
||||||
@ -21,4 +22,9 @@ enum class CollisionType(override val jsonName: String, val isEmpty: Boolean, va
|
|||||||
else
|
else
|
||||||
return other
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val SOLID: Set<CollisionType> = Collections.unmodifiableSet(EnumSet.copyOf(entries.filter { it.isSolidCollision }.toSet()))
|
||||||
|
val TILE: Set<CollisionType> = Collections.unmodifiableSet(EnumSet.copyOf(entries.filter { it.isTileCollision }.toSet()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import com.google.gson.TypeAdapterFactory
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.lwjgl.opengl.GL11.GL_LINES
|
import org.lwjgl.opengl.GL11.GL_LINES
|
||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
@ -273,7 +274,7 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
|
|||||||
if (isEmpty || !aabb.intersectWeak(other.aabb))
|
if (isEmpty || !aabb.intersectWeak(other.aabb))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val normals = ObjectOpenHashSet<Vector2d>()
|
val normals = HashSet<Vector2d>(edges.size + other.edges.size)
|
||||||
edges.forEach { normals.add(it.normal) }
|
edges.forEach { normals.add(it.normal) }
|
||||||
other.edges.forEach { normals.add(it.normal) }
|
other.edges.forEach { normals.add(it.normal) }
|
||||||
|
|
||||||
@ -281,7 +282,7 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
|
|||||||
normals.removeIf { it.dot(axis) == 0.0 }
|
normals.removeIf { it.dot(axis) == 0.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
val intersections = ArrayList<Penetration>()
|
val intersections = ObjectArrayList<Penetration>()
|
||||||
|
|
||||||
for (normal in normals) {
|
for (normal in normals) {
|
||||||
val projectThis = project(normal)
|
val projectThis = project(normal)
|
||||||
@ -349,12 +350,27 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersections.isEmpty())
|
if (intersections.isEmpty)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return intersections.min()
|
return intersections.min()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun intersect(line: Line2d): Pair<Vector2d, Line2d.Intersection>? {
|
||||||
|
val intersections = ObjectArrayList<Pair<Vector2d, Line2d.Intersection>>()
|
||||||
|
|
||||||
|
for (edge in edges) {
|
||||||
|
val intersect = line.intersect(edge)
|
||||||
|
|
||||||
|
if (intersect.intersects && !intersect.coincides) {
|
||||||
|
intersections.add(edge.normal to intersect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intersections.sortWith { o1, o2 -> o1.second.t!!.compareTo(o2.second.t!!) }
|
||||||
|
return intersections.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
fun render(client: StarboundClient = StarboundClient.current(), color: RGBAColor = RGBAColor.LIGHT_GREEN) {
|
fun render(client: StarboundClient = StarboundClient.current(), color: RGBAColor = RGBAColor.LIGHT_GREEN) {
|
||||||
if (isEmpty) return
|
if (isEmpty) return
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
|||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import ru.dbotthepony.kstarbound.io.Vector2fCodec
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
import ru.dbotthepony.kstarbound.math.vector.Vector2f
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2fCodec
|
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.EventCounterElement
|
import ru.dbotthepony.kstarbound.network.syncher.EventCounterElement
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||||
|
Loading…
Reference in New Issue
Block a user